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