1 #include "CMilitary.h"
2 
3 #include <limits>
4 
5 #include "headers/HEngine.h"
6 
7 #include "CRNG.h"
8 #include "CAI.h"
9 #include "CUnit.h"
10 #include "CUnitTable.h"
11 #include "CGroup.h"
12 #include "CTaskHandler.h"
13 #include "CThreatMap.h"
14 #include "CIntel.h"
15 #include "CWishList.h"
16 #include "CConfigParser.h"
17 #include "CDefenseMatrix.h"
18 #include "ReusableObjectFactory.hpp"
19 #include "GameMap.hpp"
20 #include "Util.hpp"
21 #include "CCataloguer.h"
22 
23 
CMilitary(AIClasses * ai)24 CMilitary::CMilitary(AIClasses *ai): ARegistrar(200) {
25 	this->ai = ai;
26 
27 	groups[SCOUT] = &activeScoutGroups;
28 	groups[ENGAGE] = &activeAttackGroups;
29 	groups[BOMBER] = &activeBomberGroups;
30 	groups[AIRFIGHTER] = &activeAirFighterGroups;
31 
32 	drawTasks = false;
33 
34 	unitCategory forbiddenCats;
35 
36 	if (ai->gamemap->IsWaterMap())
37 		forbiddenCats |= LAND;
38 	else if (!ai->gamemap->IsHooverMap())
39 		forbiddenCats |= (SEA|SUB);
40 
41 	allowedEnvCats = CATS_ENV & ~forbiddenCats;
42 }
43 
remove(ARegistrar & object)44 void CMilitary::remove(ARegistrar &object) {
45 	CGroup *group = dynamic_cast<CGroup*>(&object);
46 
47 	LOG_II("CMilitary::remove " << (*group))
48 
49 	activeScoutGroups.erase(group->key);
50 	activeAttackGroups.erase(group->key);
51 	activeBomberGroups.erase(group->key);
52 	activeAirFighterGroups.erase(group->key);
53 	mergeGroups.erase(group->key);
54 
55 	for (std::map<int,CGroup*>::iterator i = assemblingGroups.begin(); i != assemblingGroups.end(); ++i) {
56 		if (i->second->key == group->key) {
57 			assemblingGroups.erase(i->first);
58 			break;
59 		}
60 	}
61 
62 	group->unreg(*this);
63 
64 	ReusableObjectFactory<CGroup>::Release(group);
65 }
66 
addUnit(CUnit & unit)67 bool CMilitary::addUnit(CUnit& unit) {
68 	LOG_II("CMilitary::addUnit " << unit)
69 
70 	assert(unit.group == NULL);
71 
72 	unitCategory c = unit.type->cats;
73 
74 	if ((c&ATTACKER).any() && (c&MOBILE).any() && (c&DEFENSE).none()) {
75 		unitCategory wishedCats = ai->unittable->unitsUnderConstruction[unit.key];
76 		CGroup* group;
77 
78 		if ((c&SCOUTER).any() && wishedCats.any() && (wishedCats&SCOUTER).none())
79 			c &= ~SCOUTER; // scout was not requested
80 
81 		if ((c&SCOUTER).any()) {
82 			group = requestGroup(SCOUT);
83 		}
84 		else if((c&AIR).any() && (c&ARTILLERY).any()) {
85 			group = requestGroup(BOMBER);
86 		}
87 		else if((c&AIR).any() && (c&ASSAULT).none()) {
88 			group = requestGroup(AIRFIGHTER);
89 		}
90 		else {
91 			/* If there is a new factory, or the current group is busy, request
92 			   a new group */
93 			std::map<int,CGroup*>::iterator i = assemblingGroups.find(unit.builtBy);
94 			if (i == assemblingGroups.end()	|| i->second->busy || !i->second->canAdd(&unit)) {
95 				group = requestGroup(ENGAGE);
96 				assemblingGroups[unit.builtBy] = group;
97 			} else {
98 				group = i->second;
99 			}
100 		}
101 		group->addUnit(unit);
102 
103 		return true;
104 	}
105 
106 	return false;
107 }
108 
requestGroup(MilitaryGroupBehaviour type)109 CGroup* CMilitary::requestGroup(MilitaryGroupBehaviour type) {
110 	CGroup *group = ReusableObjectFactory<CGroup>::Instance();
111 	group->ai = ai;
112 	group->reset();
113 	group->reg(*this);
114 
115 	LOG_II("CMilitary::requestGroup " << (*group))
116 
117 	switch(type) {
118 		case SCOUT:
119 			activeScoutGroups[group->key] = group;
120 			break;
121 		case BOMBER:
122 			activeBomberGroups[group->key] = group;
123 			break;
124 		case ENGAGE:
125 			activeAttackGroups[group->key] = group;
126 			break;
127 		case AIRFIGHTER:
128 			activeAirFighterGroups[group->key] = group;
129 			break;
130 		default:
131 			LOG_EE("CMilitary::requestGroup invalid group behaviour: " << type)
132 	}
133 
134 	return group;
135 }
136 
update(int frame)137 void CMilitary::update(int frame) {
138 	int busyScoutGroups = 0;
139 	std::vector<int> keys;
140 	std::vector<int> occupied;
141 	std::map<int, bool> isOccupied;
142 	std::map<int, ATask*>::iterator itTask;
143 	std::map<MilitaryGroupBehaviour, std::map<int, CGroup*>* >::iterator itGroup;
144 	TargetsFilter tf;
145 
146 	// NOTE: we store occupied targets in two formats because vector is used
147 	// for list of targets (which can be used if no suitable primary
148 	// targets are found), second one is used for TargetFilter to filter out
149 	// occupied targets when searching for primary targets
150 	for (itTask = ai->tasks->activeTasks[TASK_ATTACK].begin(); itTask != ai->tasks->activeTasks[TASK_ATTACK].end(); ++itTask) {
151 		AttackTask *task = (AttackTask*)itTask->second;
152 		occupied.push_back(task->target);
153 		isOccupied[task->target] = true;
154 	}
155 
156 	for(itGroup = groups.begin(); itGroup != groups.end(); itGroup++) {
157 		int target = -2;
158 		MilitaryGroupBehaviour behaviour = itGroup->first;
159 
160 		// setup common target filter params per behaviour...
161 		tf.reset();
162 		switch(behaviour) {
163 			case SCOUT:
164 				tf.threatRadius = 300.0f;
165 				tf.threatFactor = 1000.0f;
166 				break;
167 			case ENGAGE:
168 				tf.threatFactor = 0.001f;
169 				break;
170 			case BOMBER:
171 				tf.threatFactor = 100.0f;
172 				// TODO: replace constant with maneuvering radius of plane?
173 				tf.threatRadius = 1000.0f;
174 				break;
175 			case AIRFIGHTER:
176 				tf.threatFactor = 100.0f;
177 				break;
178 			case HARASS: //FIXME
179 				break;
180 		}
181 
182 		// NOTE: start with random group ID because some groups can't reach the
183 		// target (e.g. Fleas); this helps to overcome the problem when there
184 		// is a target, but first group can't reach it, and AI constantly
185 		// trying to add same task again and again which leads to attack stall
186 		keys.clear();
187 		util::GetShuffledKeys<int, CGroup*>(keys, *(itGroup->second));
188 
189 		const std::vector<CategoryMatcher>& targetBlocks = ai->intel->targets[behaviour];
190 
191 		for (int i = 0; i < keys.size(); ++i) {
192 			CGroup *group = (*(itGroup->second))[keys[i]];
193 
194 			// if group is busy, don't bother...
195 			if (group->busy || !group->canPerformTasks()) {
196 				if (group->busy) {
197 					if (behaviour == SCOUT)
198 						busyScoutGroups++;
199 
200     				if (drawTasks)
201     					visualizeTasks(group);
202 				}
203 
204 				continue;
205 			}
206 
207 			// NOTE: each group can have different score on the same target
208 			// because of their disposition, strength etc.
209 			tf.scoreCeiling = std::numeric_limits<float>::max();
210 			tf.excludeId = &isOccupied;
211 
212 			// setup custom target filter params per current group...
213 			switch(behaviour) {
214 				case SCOUT:
215 					if ((group->cats&AIR).any())
216 						tf.threatCeiling = 1.1f;
217 					else
218 						tf.threatCeiling = (std::max<float>((float)MAX_SCOUTS_IN_GROUP / group->units.size(), 1.0f)) * group->strength - EPS;
219 					break;
220 				case BOMBER:
221 					tf.threatCeiling = group->strength + group->firstUnit()->type->dps;
222 					break;
223 				case HARASS: case ENGAGE: case AIRFIGHTER: //FIXME
224 					break;
225 			}
226 
227 			// basic target selection...
228 			if (target != -1) {
229 				for(int b = 0; b < targetBlocks.size(); b++) {
230 					target = group->selectTarget(ai->intel->enemies.getUnits(targetBlocks[b]), tf);
231 				}
232 			}
233 
234 			bool isAssembling = isAssemblingGroup(group);
235 
236 			if ((!isAssembling && behaviour == SCOUT) || target < 0) {
237 				// scan for better target among existing targets...
238 				tf.excludeId = NULL;
239 				int assistTarget = group->selectTarget(occupied, tf);
240 				if (assistTarget >= 0 && assistTarget != target) {
241 					ATask *task = ai->tasks->getTaskByTarget(assistTarget);
242 					if (task) {
243 						bool canAssist = false;
244 						int assisters = task->assisters.size();
245 						float cumulativeStrength = task->firstGroup()->strength;
246 						std::list<ATask*>::iterator itATask;
247 						for (itATask = task->assisters.begin(); itATask != task->assisters.end(); itATask++) {
248 							cumulativeStrength += (*itATask)->firstGroup()->strength;
249 						}
250 
251 						switch(behaviour) {
252 							case SCOUT:
253 								canAssist = assisters == 0;
254 								break;
255 							case ENGAGE:
256 								canAssist = cumulativeStrength < 2.0f * tf.threatValue;
257 								break;
258 							case BOMBER:
259 								canAssist = assisters < 9;
260 								break;
261 							case AIRFIGHTER:
262 								canAssist = assisters < 3;
263 								break;
264 							case HARASS: //FIXME
265 								break;
266 						}
267 
268 						if (canAssist) {
269 							mergeGroups.erase(group->key);
270 
271 							if (!ai->tasks->addTask(new AssistTask(ai, *task, *group)))
272 								group->addBadTarget(assistTarget);
273 							break;
274 						}
275 					}
276 				}
277 			}
278 
279 			bool isStrongEnough = true;
280 
281 			if (target >= 0) {
282 				isStrongEnough = group->strength >= (tf.threatValue - EPS);
283 				bool isSizeEnough = (behaviour == ENGAGE) ? group->units.size() >= ai->cfgparser->getMinGroupSize(group->techlvl) : true;
284 
285 				if (behaviour != ENGAGE)
286 					isAssembling = false;
287 
288 				if ((isAssembling && isSizeEnough) || (!isAssembling && isStrongEnough)) {
289 					ATask::NPriority taskPriority = (behaviour == BOMBER || behaviour == AIRFIGHTER) ? ATask::HIGH : ATask::NORMAL;
290 					mergeGroups.erase(group->key);
291 					if (ai->tasks->addTask(new AttackTask(ai, target, *group), taskPriority)) {
292 						occupied.push_back(target);
293 						isOccupied[target] = true;
294 					}
295 					else {
296 						group->addBadTarget(target);
297 					}
298 					break;
299 				}
300 			}
301 
302 			bool bMerge = !(isStrongEnough || isAssembling);
303 
304 			switch(behaviour) {
305 				case SCOUT:
306 					bMerge = bMerge && activeScoutGroups.size() > 1 && group->units.size() < MAX_SCOUTS_IN_GROUP;
307 					break;
308 				default:
309 					bMerge = bMerge && groups[behaviour]->size() > 1;
310 			}
311 
312 			if (bMerge)
313 				mergeGroups[group->key] = group;
314 		}
315 	}
316 
317 	/* Merge the groups that were not strong enough */
318 	if (mergeGroups.size() >= 2) {
319 		std::list<CGroup*> merge;
320 		for (std::map<int, CGroup*>::iterator base = mergeGroups.begin(); base != mergeGroups.end(); ++base) {
321 			if (!base->second->busy) {
322 				for (std::map<int,CGroup*>::iterator compare = mergeGroups.begin(); compare != mergeGroups.end(); ++compare) {
323 					if (!compare->second->busy && base->first != compare->first) {
324 						if (base->second->canMerge(compare->second)) {
325 							bool canMerge = false;
326 
327 							if ((base->second->cats&SCOUTER).any())
328 								// TODO: replace MERGE_DISTANCE with ETA?
329 								canMerge = (base->second->pos().distance2D(compare->second->pos()) < MERGE_DISTANCE);
330 							else
331 								canMerge = true;
332 
333 							if (canMerge) {
334 								if (merge.empty())
335 									merge.push_back(base->second);
336 								merge.push_back(compare->second);
337 								break;
338 							}
339 						}
340 					}
341 				}
342 
343 				if (!merge.empty()) {
344 					ai->tasks->addTask(new MergeTask(ai, merge));
345 					merge.clear();
346 					break;
347 				}
348 			}
349 		}
350 
351 		// remove busy (merging) groups...
352 		std::map<int, CGroup*>::iterator it = mergeGroups.begin();
353 		while(it != mergeGroups.end()) {
354 			int key = it->first; ++it;
355 			if (mergeGroups[key]->busy)
356 				mergeGroups.erase(key);
357 		}
358 	}
359 
360 	//bool gotAirFactory = ai->unittable->gotFactory(AIRCRAFT);
361 	//bool gotSeaFactory = (ai->unittable->gotFactory(NAVAL) || ai->unittable->gotFactory(HOVER));
362 
363 	if (ai->difficulty == DIFFICULTY_HARD) {
364 		// when all scouts are busy create some more...
365 
366 		// FIXME: when scouts are stucked AI will not build them anymore,
367 		// while there are scout targets available
368 
369 		if (busyScoutGroups == activeScoutGroups.size()) {
370 			//unitCategory baseType = ai->gamemap->IsWaterMap() && gotSeaFactory ? SEA|SUB : LAND;
371 			Wish::NPriority p = activeScoutGroups.size() < ai->cfgparser->getMinScouts() ? Wish::HIGH: Wish::NORMAL;
372 
373 			//if(gotAirFactory && rng.RandFloat() > 0.66f)
374 			//	baseType = AIR;
375 			ai->wishlist->push(MOBILE | SCOUTER | allowedEnvCats, 0, p);
376 		}
377 	}
378 
379 	// TODO: build units on real need only, not always
380 	ai->wishlist->push(requestUnit(allowedEnvCats), 0, Wish::NORMAL);
381 
382 	/*
383 	if (gotAirFactory && rng.RandFloat() > 0.66f) {
384 		ai->wishlist->push(requestUnit(AIR), forbiddenCats);
385 	}
386 	else {
387 		if (ai->gamemap->IsWaterMap() && gotSeaFactory)
388 			ai->wishlist->push(requestUnit(SEA|SUB), forbiddenCats, Wish::NORMAL);
389 		else
390 			ai->wishlist->push(requestUnit(LAND), forbiddenCats, Wish::NORMAL);
391 	}
392 	*/
393 }
394 
requestUnit(unitCategory basecat)395 unitCategory CMilitary::requestUnit(unitCategory basecat) {
396 	float r = rng.RandFloat();
397 	float sum = 0.0f;
398 	std::multimap<float, unitCategory>::iterator i;
399 
400 	for (i = ai->intel->roulette.begin(); i != ai->intel->roulette.end(); i++) {
401 		sum += i->first;
402 		if (r <= sum) {
403 			return basecat | MOBILE | i->second;
404 		}
405 	}
406 
407 	return basecat | MOBILE | ASSAULT; // unreachable code :)
408 }
409 
idleScoutGroupsNum()410 int CMilitary::idleScoutGroupsNum() {
411 	int result = 0;
412 	std::map<int, CGroup*>::iterator i;
413 	for(i = activeScoutGroups.begin(); i != activeScoutGroups.end(); ++i)
414 		if(!i->second->busy)
415 			result++;
416 	return result;
417 }
418 
isAssemblingGroup(CGroup * group)419 bool CMilitary::isAssemblingGroup(CGroup *group) {
420 	std::map<int, CGroup*>::iterator i;
421 	for (i = assemblingGroups.begin(); i != assemblingGroups.end(); ++i) {
422 		if (i->second->key == group->key) {
423 			return true;
424 		}
425 	}
426 	return false;
427 }
428 
onEnemyDestroyed(int enemy,int attacker)429 void CMilitary::onEnemyDestroyed(int enemy, int attacker) {
430 	std::map<int, CGroup*> *activeGroups;
431 	std::map<int, CGroup*>::iterator itGroup;
432 	std::map<MilitaryGroupBehaviour, std::map<int, CGroup*>* >::iterator itGroups;
433 
434 	for (itGroups = groups.begin(); itGroups != groups.end(); ++itGroups) {
435 		activeGroups = itGroups->second;
436 		for (itGroup = activeGroups->begin(); itGroup != activeGroups->end(); ++itGroup) {
437 			if (!itGroup->second->badTargets.empty()) {
438 				LOG_II("CMilitary::onEnemyDestroyed bad target Unit(" << enemy << ") destroyed for " << (*(itGroup->second)))
439 				itGroup->second->badTargets.erase(enemy);
440 			}
441 		}
442 	}
443 }
444 
switchDebugMode()445 bool CMilitary::switchDebugMode() {
446 	drawTasks = !drawTasks;
447 	return drawTasks;
448 }
449 
visualizeTasks(CGroup * g)450 void CMilitary::visualizeTasks(CGroup *g) {
451 	const ATask* task = ai->tasks->getTask(*g);
452 
453 	if (task == NULL)
454 		return;
455 
456 	float R, G, B;
457 
458 	switch(task->t) {
459 		case TASK_ATTACK:
460 			R = 1.0f; G = 0.0f; B = 0.0f;
461 			break;
462 		case TASK_MERGE:
463 			R = 1.0f; G = 1.0f; B = 0.0f;
464 			break;
465 		case TASK_ASSIST:
466 			R = 1.0f; G = 0.0f; B = 1.0f;
467 			break;
468 		default:
469 			R = G = B = 0.0f;
470 	}
471 
472 	float3 fp = g->pos();
473 	fp.y = ai->cb->GetElevation(fp.x, fp.z) + 50.0f;
474 	float3 fn = task->pos;
475 	fn.y = ai->cb->GetElevation(fn.x, fn.z) + 50.0f;
476 	// draw arrow for MERGE task only because otherwise it looks ugly (arrow
477 	// is stretched, not fixed in size)
478 	ai->cb->CreateLineFigure(fp, fn, 6.0f, task->t == TASK_MERGE ? 1 : 0, MULTIPLEXER, task->t);
479 	ai->cb->SetFigureColor(task->t, R, G, B, 0.5f);
480 }
481