1 #include "CEconomy.h"
2
3 #include <cfloat>
4 #include <limits>
5
6 #include "headers/HEngine.h"
7
8 #include "CAI.h"
9 #include "CRNG.h"
10 #include "CTaskHandler.h"
11 #include "CUnitTable.h"
12 #include "GameMap.hpp"
13 #include "CWishList.h"
14 #include "CDefenseMatrix.h"
15 #include "CGroup.h"
16 #include "CUnit.h"
17 #include "CPathfinder.h"
18 #include "CConfigParser.h"
19 #include "CIntel.h"
20 #include "GameMap.hpp"
21 #include "ReusableObjectFactory.hpp"
22 #include "CCoverageHandler.h"
23
24 CEconomy::UnitCategory2UnitCategoryMap CEconomy::canBuildEnv;
25
CEconomy(AIClasses * ai)26 CEconomy::CEconomy(AIClasses *ai): ARegistrar(700) {
27 this->ai = ai;
28 state = 0;
29 incomes = 0;
30 mNow = mNowSummed = eNow = eNowSummed = 0.0f;
31 mIncome = mIncomeSummed = eIncome = eIncomeSummed = 0.0f;
32 uMIncome = uMIncomeSummed = uEIncome = uEIncomeSummed = 0.0f;
33 mUsage = mUsageSummed = eUsage = eUsageSummed = 0.0f;
34 mStorage = eStorage = 0.0f;
35 mstall = estall = mexceeding = eexceeding = mRequest = eRequest = false;
36 initialized = stallThresholdsReady = false;
37 utCommander = NULL;
38 areMMakersEnabled = ai->gamemap->IsMetalFreeMap();
39 windmap = worthBuildingTidal = false;
40
41 if (canBuildEnv.empty()) {
42 canBuildEnv[AIR] = LAND|SEA|SUB; // TODO: -SUB?
43 canBuildEnv[LAND] = LAND; // TODO: +SEA?
44 canBuildEnv[SEA] = SEA|SUB; // TODO: +LAND?
45 canBuildEnv[SUB] = SEA|SUB;
46 }
47 }
48
init(CUnit & unit)49 void CEconomy::init(CUnit &unit) {
50 if (initialized) return;
51 // NOTE: expecting "unit" is a commander unit
52 const UnitDef *ud = ai->cb->GetUnitDef(unit.key);
53
54 utCommander = UT(ud->id);
55 windmap = ((ai->cb->GetMaxWind() + ai->cb->GetMinWind()) / 2.0f) >= 10.0f;
56 //float avgWind = (ai->cb->GetMinWind() + ai->cb->GetMaxWind()) / 2.0f;
57 //float windProf = avgWind / utWind->cost;
58 //float solarProf = utSolar->energyMake / utSolar->cost;
59 worthBuildingTidal = ai->cb->GetTidalStrength() > 5.0f;
60
61 initialized = true;
62 }
63
hasBegunBuilding(CGroup & group) const64 bool CEconomy::hasBegunBuilding(CGroup &group) const {
65 std::map<int, CUnit*>::const_iterator i;
66 for (i = group.units.begin(); i != group.units.end(); ++i) {
67 CUnit *unit = i->second;
68 if (ai->unittable->idle.find(unit->key) == ai->unittable->idle.end()
69 || !ai->unittable->idle[unit->key])
70 return true;
71 }
72
73 return false;
74 }
75
hasFinishedBuilding(CGroup & group)76 bool CEconomy::hasFinishedBuilding(CGroup &group) {
77 std::map<int, CUnit*>::iterator i;
78 for (i = group.units.begin(); i != group.units.end(); i++) {
79 CUnit *unit = i->second;
80 if (ai->unittable->builders.find(unit->key) != ai->unittable->builders.end() &&
81 ai->unittable->builders[unit->key]) {
82 ai->unittable->builders[unit->key] = false;
83 return true;
84 }
85 }
86
87 return false;
88 }
89
requestGroup()90 CGroup* CEconomy::requestGroup() {
91 CGroup *group = ReusableObjectFactory<CGroup>::Instance();
92 group->ai = ai;
93 group->reset();
94 group->reg(*this);
95
96 activeGroups[group->key] = group;
97
98 return group;
99 }
100
remove(ARegistrar & object)101 void CEconomy::remove(ARegistrar &object) {
102 CGroup *group = dynamic_cast<CGroup*>(&object);
103 LOG_II("CEconomy::remove " << (*group))
104
105 activeGroups.erase(group->key);
106 takenMexes.erase(group->key);
107 takenGeo.erase(group->key);
108
109 group->unreg(*this);
110
111 ReusableObjectFactory<CGroup>::Release(group);
112 }
113
addUnitOnCreated(CUnit & unit)114 void CEconomy::addUnitOnCreated(CUnit& unit) {
115 unitCategory c = unit.type->cats;
116 if ((c&MEXTRACTOR).any()) {
117 CGroup *group = requestGroup();
118 group->addUnit(unit);
119 takenMexes[group->key] = group->pos();
120 CUnit *builder = ai->unittable->getUnit(group->firstUnit()->builtBy);
121 if (builder) {
122 assert(group->key != builder->group->key);
123 takenMexes.erase(builder->group->key);
124 }
125 }
126 else if(unit.type->def->needGeo) {
127 CGroup *group = requestGroup();
128 group->addUnit(unit);
129 takenGeo[group->key] = group->pos();
130 CUnit *builder = ai->unittable->getUnit(group->firstUnit()->builtBy);
131 if (builder) {
132 assert(group->key != builder->group->key);
133 takenGeo.erase(builder->group->key);
134 }
135 }
136 }
137
addUnitOnFinished(CUnit & unit)138 void CEconomy::addUnitOnFinished(CUnit &unit) {
139 LOG_II("CEconomy::addUnitOnFinished " << unit)
140
141 unitCategory c = unit.type->cats;
142
143 if ((c&BUILDER).any() || ((c&ASSISTER).any() && (c&MOBILE).any())) {
144 CGroup *group = requestGroup();
145 group->addUnit(unit);
146 }
147 }
148
buildOrAssist(CGroup & group,buildType bt,unitCategory include,unitCategory exclude)149 void CEconomy::buildOrAssist(CGroup& group, buildType bt, unitCategory include, unitCategory exclude) {
150 ATask* task = canAssist(bt, group);
151 if (task != NULL) {
152 ai->tasks->addTask(new AssistTask(ai, *task, group));
153 return;
154 }
155
156 if ((group.cats&BUILDER).none())
157 return;
158
159 float3 pos = group.pos();
160 float3 goal = pos;
161 unitCategory catsWhere = canBuildWhere(group.cats);
162 unitCategory catsWhereStrict = canBuildWhere(group.cats, true);
163
164 if (bt == BUILD_EPROVIDER) {
165 if (!windmap)
166 exclude |= WIND;
167 if (!worthBuildingTidal)
168 exclude |= TIDAL;
169 }
170 else if (bt == BUILD_MPROVIDER) {
171 if ((include&MEXTRACTOR).any()) {
172 goal = getClosestOpenMetalSpot(group);
173 if (goal != ERRORVECTOR) {
174 if (goal.y < 0.0f)
175 exclude |= (LAND|AIR);
176 else
177 exclude |= (SEA|SUB);
178 }
179 }
180 }
181
182 const CUnit* unit = group.firstUnit();
183 bool isComm = (unit->type->cats&COMMANDER).any();
184 std::multimap<float, UnitType*> candidates;
185
186 // retrieve the allowed buildable units...
187 if ((include&MEXTRACTOR).any())
188 ai->unittable->getBuildables(unit->type, include|catsWhereStrict, exclude, candidates);
189 else
190 ai->unittable->getBuildables(unit->type, include|catsWhere, exclude, candidates);
191
192 std::multimap<float, UnitType*>::iterator i = candidates.begin();
193 int iterations = candidates.size() / (ai->cfgparser->getTotalStates() - state + 1);
194 bool affordable = false;
195
196 if (!candidates.empty()) {
197 /* Determine which of these we can afford */
198 while(iterations >= 0) {
199 if (canAffordToBuild(unit->type, i->second))
200 affordable = true;
201 else
202 break;
203
204 if (i == --candidates.end())
205 break;
206 iterations--;
207 i++;
208 }
209 }
210 else if (bt != BUILD_MPROVIDER) {
211 return;
212 }
213 else {
214 goal = ERRORVECTOR;
215 }
216
217 /* Perform the build */
218 switch(bt) {
219 case BUILD_EPROVIDER: {
220 if (i->second->def->needGeo) {
221 goal = getClosestOpenGeoSpot(group);
222 if (goal != ERRORVECTOR) {
223 // TODO: prevent commander walking?
224 if (!eRequest && ai->pathfinder->getETA(group, goal) > 450.0f) {
225 goal = ERRORVECTOR;
226 }
227 }
228 }
229 else if ((i->second->cats&EBOOSTER).any()) {
230 goal = ai->coverage->getNextClosestBuildSite(unit, i->second);
231 }
232
233 if (goal == ERRORVECTOR) {
234 goal = pos;
235 if (i != candidates.begin())
236 --i;
237 else
238 ++i;
239 }
240
241 if (i != candidates.end())
242 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
243
244 break;
245 }
246
247 case BUILD_MPROVIDER: {
248 bool canBuildMMaker = (eIncome - eUsage) >= METAL2ENERGY || eexceeding;
249
250 if (goal != ERRORVECTOR) {
251 bool allowDirectTask = true;
252
253 // TODO: there is a flaw in logic because when spot is under
254 // threat we anyway send builders there
255
256 if (isComm) {
257 int numOtherBuilders = ai->unittable->builders.size() - ai->unittable->factories.size();
258 // if commander can build mmakers and there is enough
259 // ordinary builders then prevent it walking for more than
260 // 20 sec...
261 if (numOtherBuilders > 2 && ai->unittable->canBuild(unit->type, MMAKER))
262 allowDirectTask = ai->pathfinder->getETA(group, goal) < 600.0f;
263 }
264
265 if (allowDirectTask) {
266 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
267 }
268 else if (mstall && (isComm || areMMakersEnabled) && canBuildMMaker) {
269 UnitType* mmaker = ai->unittable->canBuild(unit->type, MMAKER);
270 if (mmaker != NULL)
271 ai->tasks->addTask(new BuildTask(ai, bt, mmaker, group, pos));
272 }
273 }
274 else if (mstall && areMMakersEnabled && canBuildMMaker) {
275 UnitType* mmaker = ai->unittable->canBuild(unit->type, MMAKER);
276 if (mmaker != NULL)
277 ai->tasks->addTask(new BuildTask(ai, bt, mmaker, group, pos));
278 }
279 else if (!eexceeding) {
280 buildOrAssist(group, BUILD_EPROVIDER, EMAKER);
281 }
282 break;
283 }
284
285 case BUILD_MSTORAGE: case BUILD_ESTORAGE: {
286 /* Start building storage after enough ingame time */
287 if (!taskInProgress(bt) && ai->cb->GetCurrentFrame() > 30*60*7) {
288 goal = ai->defensematrix->getBestDefendedPos(0);
289 if (goal == ERRORVECTOR)
290 goal = pos;
291 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
292 }
293 break;
294 }
295
296 case BUILD_FACTORY: {
297 if (!taskInProgress(bt)) {
298 int numFactories = ai->unittable->factories.size();
299
300 bool build = numFactories <= 0;
301
302 // TODO: add some delay before building next factory
303
304 if (!build && affordable && !eRequest) {
305 float m = mNow / mStorage;
306
307 switch(state) {
308 case 0: {
309 build = (m > 0.45f);
310 break;
311 }
312 case 1: {
313 build = (m > 0.40f);
314 break;
315 }
316 case 2: {
317 build = (m > 0.35f);
318 break;
319 }
320 case 3: {
321 build = (m > 0.3f);
322 break;
323 }
324 case 4: {
325 build = (m > 0.25f);
326 break;
327 }
328 case 5: {
329 build = (m > 0.2f);
330 break;
331 }
332 case 6: {
333 build = (m > 0.15f);
334 break;
335 }
336 default: {
337 build = (m > 0.1f);
338 }
339 }
340 }
341
342 if (build) {
343 // TODO: fix getBestDefendedPos() for static builders
344 if (numFactories > 1 && (unit->type->cats&MOBILE).any()) {
345 goal = ai->coverage->getClosestDefendedPos(pos);
346 if (goal == ERRORVECTOR) {
347 goal = pos;
348 }
349 }
350 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
351 }
352 }
353 break;
354 }
355
356 case BUILD_IMP_DEFENSE: {
357 // NOTE: important defense placement selects important place,
358 // not closest one as other BUILD_XX_DEFENSE algoes do
359 if (!taskInProgress(bt)) {
360 bool allowTask = true;
361
362 // TODO: implement in getNextXXXBuildSite() "radius" argument
363 // and fill it for static builders
364 goal = ai->coverage->getNextImportantBuildSite(i->second);
365
366 allowTask = (goal != ERRORVECTOR);
367
368 if (allowTask && isComm)
369 allowTask = ai->pathfinder->getETA(group, goal) < 300.0f;
370
371 if (allowTask)
372 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
373 }
374 break;
375 }
376
377 case BUILD_AG_DEFENSE: case BUILD_AA_DEFENSE: case BUILD_UW_DEFENSE: case BUILD_MISC_DEFENSE: {
378 if (affordable && !taskInProgress(bt)) {
379 bool allowTask = true;
380
381 // TODO: implement in getNextBuildSite() "radius" argument
382 // and fill it for static builders
383 goal = ai->coverage->getNextClosestBuildSite(unit, i->second);
384
385 allowTask = (goal != ERRORVECTOR);
386
387 if (allowTask && isComm)
388 allowTask = ai->pathfinder->getETA(group, goal) < 300.0f;
389
390 if (allowTask)
391 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
392 }
393 break;
394 }
395
396 default: {
397 if (affordable && !taskInProgress(bt))
398 ai->tasks->addTask(new BuildTask(ai, bt, i->second, group, goal));
399 break;
400 }
401 }
402 }
403
getBestSpot(CGroup & group,std::list<float3> & resources,std::map<int,float3> & tracker,bool metal)404 float3 CEconomy::getBestSpot(CGroup& group, std::list<float3>& resources, std::map<int, float3>& tracker, bool metal) {
405 bool staticBuilder = (group.cats&STATIC).any();
406 bool canBuildUnderWater = (group.cats&(SEA|SUB|AIR)).any();
407 bool canBuildAboveWater = (group.cats&(LAND|AIR)).any();
408 float bestDist = std::numeric_limits<float>::max();
409 float3 bestSpot = ERRORVECTOR;
410 float3 gpos = group.pos();
411 float radius;
412
413 if (metal)
414 radius = ai->cb->GetExtractorRadius();
415 else
416 radius = 16.0f;
417
418 std::list<float3>::iterator i;
419 std::map<int, float3>::iterator j;
420 for (i = resources.begin(); i != resources.end(); ++i) {
421 if (i->y < 0.0f) {
422 if (!canBuildUnderWater)
423 continue;
424 }
425 else {
426 if (!canBuildAboveWater)
427 continue;
428 }
429
430 bool taken = false;
431 for (j = tracker.begin(); j != tracker.end(); ++j) {
432 if (i->distance2D(j->second) < radius) {
433 taken = true;
434 break;
435 }
436 }
437 if (taken) continue; // already taken or scheduled by current AI
438
439 int numUnits = ai->cb->GetFriendlyUnits(&ai->unitIDs[0], *i, 1.1f * radius);
440 for (int u = 0; u < numUnits; u++) {
441 const int uid = ai->unitIDs[u];
442 const UnitDef *ud = ai->cb->GetUnitDef(uid);
443 if (metal)
444 taken = (UC(ud->id) & MEXTRACTOR).any();
445 else
446 taken = ud->needGeo;
447 if (taken) break;
448 }
449 if (taken) continue; // already taken by ally team
450
451 float dist = gpos.distance2D(*i);
452
453 if (staticBuilder) {
454 if (dist > group.buildRange)
455 continue; // spot is out of range
456 }
457
458 /*
459 // NOTE: getPathLength() also considers static units
460 float dist = ai->pathfinder->getPathLength(group, *i);
461 if (dist < 0.0f)
462 continue; // spot is out of build range or unreachable
463 */
464
465 // TODO: actually any spot with any threat should be skipped;
466 // to implement this effectively we need to refactor tasks, cause
467 // builder during approaching should scan target place for threat
468 // periodically; currently it does not, so skipping dangerous spot
469 // has no real profit
470
471 dist += 1000.0f * group.getThreat(*i, 300.0f);
472 if (dist < bestDist) {
473 bestDist = dist;
474 bestSpot = *i;
475 }
476 }
477
478 if (bestSpot != ERRORVECTOR)
479 // TODO: improper place for this
480 tracker[group.key] = bestSpot;
481
482 return bestSpot;
483 }
484
getClosestOpenMetalSpot(CGroup & group)485 float3 CEconomy::getClosestOpenMetalSpot(CGroup &group) {
486 return getBestSpot(group, GameMap::metalspots, takenMexes, true);
487 }
488
getClosestOpenGeoSpot(CGroup & group)489 float3 CEconomy::getClosestOpenGeoSpot(CGroup &group) {
490 return getBestSpot(group, GameMap::geospots, takenGeo, false);
491 }
492
update()493 void CEconomy::update() {
494 int buildersCount = 0;
495 int assistersCount = 0;
496 int maxTechLevel = ai->cfgparser->getMaxTechLevel();
497
498 /* See if we can improve our eco by controlling metalmakers */
499 controlMetalMakers();
500
501 /* If we are stalling, do something about it */
502 preventStalling();
503
504 /* Update idle worker groups */
505 std::map<int, CGroup*>::iterator i;
506 for (i = activeGroups.begin(); i != activeGroups.end(); ++i) {
507 CGroup *group = i->second;
508 CUnit *unit = group->firstUnit();
509
510 if ((group->cats&MOBILE).any() && (group->cats&BUILDER).any())
511 buildersCount++;
512 if ((group->cats&MOBILE).any() && (group->cats&ASSISTER).any() && (group->cats&BUILDER).none())
513 assistersCount++;
514
515 if (group->busy || !group->canPerformTasks())
516 continue;
517
518 if ((group->cats&FACTORY).any()) {
519 ai->tasks->addTask(new FactoryTask(ai, *group));
520 continue;
521 }
522
523 if ((group->cats&STATIC).any() && (group->cats&BUILDER).none()) {
524 // we don't have a task for current unit type yet (these types are:
525 // MEXTRACTOR & geoplant)
526 continue;
527 }
528
529 //float3 pos = group->pos();
530
531 // NOTE: we're using special algo for commander to prevent
532 // it walking outside the base
533 if ((unit->type->cats&COMMANDER).any()) {
534 tryFixingStall(group);
535 if (group->busy) continue;
536
537 /* If we don't have a factory, build one */
538 if (ai->unittable->factories.empty() || mexceeding) {
539 unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel);
540 if (factory.any())
541 buildOrAssist(*group, BUILD_FACTORY, factory);
542 if (group->busy) continue;
543 }
544
545 /* If we are exceeding and don't have estorage yet, build estorage */
546 if (eexceeding && !ai->unittable->factories.empty()) {
547 if (ai->unittable->energyStorages.size() < ai->cfgparser->getMaxTechLevel())
548 buildOrAssist(*group, BUILD_ESTORAGE, ESTORAGE);
549 if (group->busy) continue;
550 }
551
552 // NOTE: in NOTA only static commanders can build TECH1 factories
553 if ((unit->type->cats&STATIC).any()) {
554 /* See if this unit can build desired factory */
555 unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel);
556 if (factory.any())
557 buildOrAssist(*group, BUILD_FACTORY, factory);
558 if (group->busy) continue;
559
560 factory = getNextTypeToBuild(unit, BUILDER|STATIC, maxTechLevel);
561 if (factory.any())
562 // TODO: invoke BUILD_ASSISTER building algo instead of
563 // BUILD_FACTORY to properly place static builders?
564 buildOrAssist(*group, BUILD_FACTORY, factory);
565 if (group->busy) continue;
566 }
567
568 // build nearby metal extractors if possible...
569 if (mstall && !ai->gamemap->IsMetalMap()) {
570 // NOTE: there is a special hack withing buildOrAssist()
571 // to prevent building unnecessary MMAKERS
572 buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
573 if (group->busy) continue;
574 }
575
576 // see if we can build defense
577 tryBuildingDefense(group);
578 if (group->busy) continue;
579
580 tryAssistingFactory(group);
581 if (group->busy) continue;
582 }
583 else if ((unit->type->cats&BUILDER).any()) {
584 tryFixingStall(group);
585 if (group->busy) continue;
586
587 /* See if this unit can build desired factory */
588 unitCategory factory = getNextTypeToBuild(unit, FACTORY, maxTechLevel);
589 if (factory.any())
590 buildOrAssist(*group, BUILD_FACTORY, factory);
591 if (group->busy) continue;
592
593 /* If we are overflowing energy build an estorage */
594 if (eexceeding && !mRequest) {
595 if (ai->unittable->energyStorages.size() < ai->cfgparser->getMaxTechLevel())
596 buildOrAssist(*group, BUILD_ESTORAGE, ESTORAGE);
597 if (group->busy) continue;
598 }
599
600 /* If we are overflowing metal build an mstorage */
601 if (mexceeding && !eRequest) {
602 buildOrAssist(*group, BUILD_MSTORAGE, MSTORAGE);
603 if (group->busy) continue;
604 }
605
606 /* If both requested, see what is required most */
607 if (eRequest && mRequest) {
608 if ((mNow / mStorage) > (eNow / eStorage))
609 buildOrAssist(*group, BUILD_EPROVIDER, EMAKER);
610 else
611 buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
612 if (group->busy) continue;
613 }
614
615 /* See if we can build defense */
616 tryBuildingDefense(group);
617 if (group->busy) continue;
618
619 tryBuildingAntiNuke(group);
620 if (group->busy) continue;
621
622 tryBuildingJammer(group);
623 if (group->busy) continue;
624
625 tryBuildingStaticAssister(group);
626 if (group->busy) continue;
627
628 /* Else just provide what is requested */
629 if (eRequest) {
630 buildOrAssist(*group, BUILD_EPROVIDER, EMAKER);
631 if (group->busy) continue;
632 }
633
634 if (mRequest) {
635 buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
636 if (group->busy) continue;
637 }
638
639 tryAssistingFactory(group);
640 if (group->busy) continue;
641
642 tryBuildingShield(group);
643 if (group->busy) continue;
644
645 /* Otherwise just expand */
646 if (!ai->gamemap->IsMetalMap()) {
647 if ((mNow / mStorage) > (eNow / eStorage))
648 buildOrAssist(*group, BUILD_EPROVIDER, EMAKER);
649 else
650 buildOrAssist(*group, BUILD_MPROVIDER, MEXTRACTOR);
651 }
652 }
653 else if ((unit->type->cats&ASSISTER).any()) {
654 // TODO: repair damaged buildings (& non-moving units?)
655 // TODO: finish unfinished bulidings
656 tryAssist(group, BUILD_IMP_DEFENSE);
657 if (group->busy) continue;
658 tryAssistingFactory(group);
659 if (group->busy) continue;
660 tryAssist(group, BUILD_AG_DEFENSE);
661 if (group->busy) continue;
662 tryAssist(group, BUILD_AA_DEFENSE);
663 if (group->busy) continue;
664 tryAssist(group, BUILD_UW_DEFENSE);
665 if (group->busy) continue;
666 tryAssist(group, BUILD_MISC_DEFENSE);
667 if (group->busy) continue;
668 }
669 }
670
671 // TODO: consider assistersCount & military groups count for
672 // requesting assisters
673
674 if (buildersCount < ai->cfgparser->getMaxWorkers()
675 && (buildersCount < ai->cfgparser->getMinWorkers()))
676 ai->wishlist->push(BUILDER, 0, Wish::HIGH);
677 else {
678 if (buildersCount < ai->cfgparser->getMaxWorkers())
679 ai->wishlist->push(BUILDER, 0, Wish::NORMAL);
680 }
681 }
682
taskInProgress(buildType bt)683 bool CEconomy::taskInProgress(buildType bt) {
684 int tasksCounter = 0;
685 //int maxTechLevel = ai->cfgparser->getMaxTechLevel();
686 std::map<int, ATask*>::iterator it;
687
688 for (it = ai->tasks->activeTasks[TASK_BUILD].begin(); it != ai->tasks->activeTasks[TASK_BUILD].end(); ++it) {
689 if (((BuildTask*)(it->second))->bt == bt) {
690 tasksCounter++;
691 }
692 }
693
694 if (tasksCounter > 0) {
695 /*
696 switch (ai->difficulty) {
697 case DIFFICULTY_EASY:
698 return true;
699 case DIFFICULTY_NORMAL:
700 if (maxTechLevel > MIN_TECHLEVEL && !(mRequest || eRequest))
701 return (tasksCounter >= maxTechLevel);
702 break;
703 case DIFFICULTY_HARD:
704 if (!(mRequest || estall))
705 return (tasksCounter >= maxTechLevel * 2);
706 break;
707 }
708 */
709 return true;
710 }
711
712 return false;
713 }
714
controlMetalMakers()715 void CEconomy::controlMetalMakers() {
716 float eRatio = eNow / eStorage;
717
718 if (eRatio < 0.3f) {
719 int count = ai->unittable->setOnOff(ai->unittable->metalMakers, false);
720 if (count > 0) {
721 if (METAL2ENERGY < 1.0f)
722 estall = false;
723 areMMakersEnabled = false;
724 return;
725 }
726 }
727
728 if (eRatio > 0.7f) {
729 int count = ai->unittable->setOnOff(ai->unittable->metalMakers, true);
730 if (count > 0) {
731 if (METAL2ENERGY > 1.0f)
732 mstall = false;
733 areMMakersEnabled = true;
734 return;
735 }
736 }
737 }
738
preventStalling()739 void CEconomy::preventStalling() {
740 bool taskRemoved = false;
741 // for tracking so only one task is removed per update
742 bool needMStallFixing, needEStallFixing;
743 AssistTask
744 *taskWithPowerBuilder = NULL,
745 *taskWithFastBuilder = NULL;
746 std::map<int, ATask*>::iterator it;
747
748 // if we're not stalling, return...
749 if (!mstall && !estall) {
750 // if factorytask is on wait, unwait it...
751 for (it = ai->tasks->activeTasks[TASK_FACTORY].begin(); it != ai->tasks->activeTasks[TASK_FACTORY].end(); ++it) {
752 FactoryTask *task = (FactoryTask*)it->second;
753 task->setWait(false);
754 }
755 return;
756 }
757
758 needMStallFixing = mstall;
759 needEStallFixing = estall;
760
761 // check if there are tasks which are fixing current resource stall problems...
762 for (it = ai->tasks->activeTasks[TASK_BUILD].begin(); it != ai->tasks->activeTasks[TASK_BUILD].end(); ++it) {
763 BuildTask *task = (BuildTask*)it->second;
764 if (needMStallFixing && task->bt == BUILD_MPROVIDER)
765 needMStallFixing = false;
766 if (needEStallFixing && task->bt == BUILD_EPROVIDER)
767 needEStallFixing = false;
768 }
769
770 if (needMStallFixing || needEStallFixing) {
771 // stop all guarding workers which do not help in fixing the problem...
772 it = ai->tasks->activeTasks[TASK_ASSIST].begin();
773 while (it != ai->tasks->activeTasks[TASK_ASSIST].end()) {
774 AssistTask *task = (AssistTask*)it->second; ++it;
775
776 // if the assisting group is moving, skip it...
777 if (task->isMoving)
778 continue;
779
780 if (task->assist->t == TASK_BUILD) {
781 // ignore builders which are fixing the problems...
782 BuildTask* build = dynamic_cast<BuildTask*>(task->assist);
783 if ((mstall || mRequest) && build->bt == BUILD_MPROVIDER)
784 continue;
785 if ((estall || eRequest) && build->bt == BUILD_EPROVIDER)
786 continue;
787
788 task->remove(); taskRemoved = true;
789
790 break;
791 }
792
793 // detect fastest & most powerful builders among factory assisters...
794 if (task->assist->t == TASK_FACTORY) {
795 if (taskWithPowerBuilder) {
796 if (task->firstGroup()->firstUnit()->type->def->buildSpeed > taskWithPowerBuilder->firstGroup()->firstUnit()->type->def->buildSpeed)
797 taskWithPowerBuilder = task;
798 }
799 else
800 taskWithPowerBuilder = task;
801
802 if (taskWithFastBuilder) {
803 // NOTE: after commander walking has been reduced it has
804 // limited power to solve mstall problem
805 if ((task->firstGroup()->cats&COMMANDER).none()
806 && task->firstGroup()->speed > taskWithFastBuilder->firstGroup()->speed)
807 taskWithFastBuilder = task;
808 }
809 else
810 taskWithFastBuilder = task;
811 }
812 }
813 }
814
815 if (!taskRemoved && needMStallFixing && taskWithFastBuilder) {
816 taskWithFastBuilder->remove();
817 taskRemoved = true;
818 }
819
820 if (!taskRemoved && needEStallFixing && taskWithPowerBuilder) {
821 taskWithPowerBuilder->remove();
822 taskRemoved = true;
823 }
824
825 // wait all factories and their assisters...
826 for (it = ai->tasks->activeTasks[TASK_FACTORY].begin(); it != ai->tasks->activeTasks[TASK_FACTORY].end(); ++it) {
827 FactoryTask *task = (FactoryTask*)it->second;
828 task->setWait(true);
829 }
830 }
831
updateIncomes()832 void CEconomy::updateIncomes() {
833 const int currentFrame = ai->cb->GetCurrentFrame();
834
835 // FIXME:
836 // 1) algo sucks when game is started without initial resources
837 // 2) these values should be recalculated each time AI has lost almost
838 // all of its units (it is like another game start)
839 // 3) these values should slowly decay to zero
840 if (!stallThresholdsReady) {
841 if ((utCommander->cats&FACTORY).any()) {
842 mStart = utCommander->def->metalMake;
843 eStart = 0.0f;
844 }
845 else {
846 bool oldAlgo = true;
847 unitCategory initialFactory = getNextTypeToBuild(utCommander, FACTORY, MIN_TECHLEVEL);
848 if (initialFactory.any()) {
849 UnitType* facType = ai->unittable->canBuild(utCommander, initialFactory);
850 if (facType != NULL) {
851 float buildTime = facType->def->buildTime / utCommander->def->buildSpeed;
852 //float mDrain = facType->def->metalCost / buildTime;
853 float eDrain = facType->def->energyCost / buildTime;
854 float mTotalIncome = utCommander->def->metalMake * buildTime;
855
856 mStart = (1.5f * facType->def->metalCost - ai->cb->GetMetal() - mTotalIncome) / buildTime;
857 if (mStart < 0.0f)
858 mStart = 0.0f;
859 eStart = 0.9f * eDrain;
860
861 oldAlgo = false;
862 }
863 }
864
865 if (oldAlgo) {
866 mStart = 2.0f * utCommander->def->metalMake;
867 eStart = 1.5f * utCommander->def->energyMake;
868 }
869 }
870
871 LOG_II("CEconomy::updateIncomes Metal stall threshold: " << mStart)
872 LOG_II("CEconomy::updateIncomes Energy stall threshold: " << eStart)
873
874 stallThresholdsReady = true;
875 }
876
877 incomes++;
878
879 mUsageSummed += ai->cb->GetMetalUsage();
880 eUsageSummed += ai->cb->GetEnergyUsage();
881 mStorage = ai->cb->GetMetalStorage();
882 eStorage = ai->cb->GetEnergyStorage();
883
884 mNow = ai->cb->GetMetal();
885 eNow = ai->cb->GetEnergy();
886 mIncome = ai->cb->GetMetalIncome();
887 eIncome = ai->cb->GetEnergyIncome();
888 mUsage = alpha*(mUsageSummed / incomes) + (1.0f-alpha)*(ai->cb->GetMetalUsage());
889 eUsage = beta *(eUsageSummed / incomes) + (1.0f-beta) *(ai->cb->GetEnergyUsage());
890
891 //LOG_II("mIncome = " << mIncome << "; mNow = " << mNow << "; mStorage = " << mStorage)
892 //LOG_II("eIncome = " << eIncome << "; eNow = " << eNow << "; eStorage = " << eStorage)
893
894 // FIXME: think smth better to avoid hardcoding, e.g. implement decaying
895
896 if (mIncome < EPS && currentFrame > 32) {
897 mstall = (mNow < (mStorage*0.1f));
898 }
899 else {
900 if (ai->unittable->activeUnits.size() < 5)
901 mstall = (mNow < (mStorage*0.1f) && mUsage > mIncome) || mIncome < mStart;
902 else
903 mstall = (mNow < (mStorage*0.1f) && mUsage > mIncome);
904 }
905
906 if (eIncome < EPS && currentFrame > 32) {
907 estall = (eNow < (eStorage*0.1f));
908 }
909 else {
910 if (ai->unittable->activeUnits.size() < 5)
911 estall = (eNow < (eStorage*0.1f) && eUsage > eIncome) || eIncome < eStart;
912 else
913 estall = (eNow < (eStorage*0.1f) && eUsage > eIncome);
914 }
915
916 mexceeding = (mNow > (mStorage*0.9f) && mUsage < mIncome);
917 eexceeding = (eNow > (eStorage*0.9f) && eUsage < eIncome);
918
919 mRequest = (mNow < (mStorage*0.5f) && mUsage > mIncome);
920 eRequest = (eNow < (eStorage*0.5f) && eUsage > eIncome);
921
922 int tstate = ai->cfgparser->determineState(mIncome, eIncome);
923 if (tstate != state) {
924 char buf[64];
925 sprintf(buf, "State changed to %d, activated techlevel %d", tstate, ai->cfgparser->getMaxTechLevel());
926 LOG_II(buf);
927 state = tstate;
928 }
929 }
930
canAssist(buildType t,CGroup & group)931 ATask* CEconomy::canAssist(buildType t, CGroup &group) {
932 std::map<int, ATask*>::iterator i;
933 std::multimap<float, BuildTask*> suited;
934
935 if (!group.canAssist())
936 return NULL;
937
938 for (i = ai->tasks->activeTasks[TASK_BUILD].begin(); i != ai->tasks->activeTasks[TASK_BUILD].end(); ++i) {
939 BuildTask* buildtask = (BuildTask*)i->second;
940
941 // only build tasks we are interested in...
942 float travelTime;
943 if (buildtask->bt != t || !buildtask->assistable(group, travelTime))
944 continue;
945
946 suited.insert(std::pair<float, BuildTask*>(travelTime, buildtask));
947 }
948
949 // there are no suited tasks that require assistance
950 if (suited.empty())
951 return NULL;
952
953 bool isCommander = (group.cats&COMMANDER).any();
954
955 if (isCommander) {
956 float eta = (suited.begin())->first;
957
958 // don't pursuit as commander when walkdistance is more than 15 seconds
959 if (eta > 450.0f) return NULL;
960 }
961
962 return suited.begin()->second;
963 }
964
canAssistFactory(CGroup & group)965 ATask* CEconomy::canAssistFactory(CGroup &group) {
966 if (!group.canAssist())
967 return NULL;
968
969 float3 pos = group.pos();
970 std::map<int, ATask*>::iterator i;
971 std::map<float, FactoryTask*> candidates;
972 int maxTechLevel = ai->cfgparser->getMaxTechLevel();
973 unitCategory maxTechLevelCat;
974
975 maxTechLevelCat.set(maxTechLevel - 1);
976
977 // NOTE: prefer assising more advanced factories depending
978 // on current possible tech level. Skip factories full of assisters to
979 // prevent unneeded cotinuous assist attempts on the same factory.
980
981 for (i = ai->tasks->activeTasks[TASK_FACTORY].begin(); i != ai->tasks->activeTasks[TASK_FACTORY].end(); ++i) {
982 /* TODO: instead of euclid distance, use pathfinder distance */
983 float dist = pos.distance2D(i->second->pos);
984 if ((i->second->firstGroup()->cats&maxTechLevelCat).any())
985 dist -= 1000.0f;
986 dist += (1000.0f * i->second->assisters.size()) / FACTORY_ASSISTERS;
987 candidates[dist] = (FactoryTask*)i->second;
988 }
989
990 if (candidates.empty())
991 return NULL;
992
993 std::map<float, FactoryTask*>::iterator j;
994 for (j = candidates.begin(); j != candidates.end(); ++j) {
995 if (!j->second->assistable(group))
996 continue;
997 return j->second;
998 }
999
1000 return NULL;
1001 }
1002
canAffordToBuild(UnitType * builder,UnitType * utToBuild)1003 bool CEconomy::canAffordToBuild(UnitType *builder, UnitType *utToBuild) {
1004 float buildTime = utToBuild->def->buildTime / builder->def->buildSpeed;
1005 float mPrediction = mNow + (mIncome - mUsage) * buildTime - utToBuild->def->metalCost;
1006 float ePrediction = eNow + (eIncome - eUsage) * buildTime - utToBuild->def->energyCost;
1007
1008 if (!mRequest)
1009 mRequest = mPrediction < 0.0f;
1010 if (!eRequest)
1011 eRequest = ePrediction < 0.0f;
1012
1013 return (mPrediction >= 0.0f && ePrediction >= 0.0f && mNow/mStorage >= 0.1f);
1014 }
1015
getNextTypeToBuild(CUnit * unit,unitCategory cats,int maxteachlevel)1016 unitCategory CEconomy::getNextTypeToBuild(CUnit *unit, unitCategory cats, int maxteachlevel) {
1017 return getNextTypeToBuild(unit->type, cats, maxteachlevel);
1018 }
1019
getNextTypeToBuild(UnitType * ut,unitCategory cats,int maxteachlevel)1020 unitCategory CEconomy::getNextTypeToBuild(UnitType *ut, unitCategory cats, int maxteachlevel) {
1021 std::list<unitCategory>::iterator f;
1022
1023 if (ai->intel->strategyTechUp) {
1024 for(f = ai->intel->allowedFactories.begin(); f != ai->intel->allowedFactories.end(); ++f) {
1025 for(int techlevel = maxteachlevel; techlevel >= MIN_TECHLEVEL; techlevel--) {
1026 unitCategory type((*f)|cats);
1027 type.set(techlevel - 1);
1028 if (isTypeRequired(ut, type, maxteachlevel))
1029 return type;
1030 }
1031 }
1032 }
1033 else {
1034 for(int techlevel = MIN_TECHLEVEL; techlevel <= maxteachlevel; techlevel++) {
1035 unitCategory techlevelCat;
1036 techlevelCat.set(techlevel - 1);
1037 for(f = ai->intel->allowedFactories.begin(); f != ai->intel->allowedFactories.end(); f++) {
1038 unitCategory type((*f)|cats|techlevelCat);
1039 if (isTypeRequired(ut, type, maxteachlevel))
1040 return type;
1041 }
1042 }
1043 }
1044
1045 return 0;
1046 }
1047
isTypeRequired(UnitType * builder,unitCategory cats,int maxteachlevel)1048 bool CEconomy::isTypeRequired(UnitType* builder, unitCategory cats, int maxteachlevel) {
1049 UnitType* ut = ai->unittable->canBuild(builder, cats);
1050 if(ut) {
1051 if ((cats&FACTORY).any()) {
1052 int numRequired = ut->def->canBeAssisted ? 1: maxteachlevel;
1053 if (ai->unittable->factoryCount(cats) < numRequired)
1054 return true;
1055 }
1056 else if(ai->unittable->unitCount(cats) == 0)
1057 return true;
1058 }
1059 return false;
1060 }
1061
tryBuildingDefense(CGroup * group)1062 void CEconomy::tryBuildingDefense(CGroup* group) {
1063 if (group->busy)
1064 return;
1065 if (mstall || estall)
1066 return;
1067
1068 unitCategory incCats, excCats;
1069 buildType bt;
1070 // CCoverageCell::NType layer;
1071
1072 if (ai->intel->getEnemyCount(AIR) > 0 && rng.RandFloat() > 0.66f) {
1073 bt = BUILD_AA_DEFENSE;
1074 // layer = CCoverageCell::DEFENSE_ANTIAIR;
1075 // TODO: replace STATIC with DEFENSE after all config files updated
1076 incCats = STATIC|ANTIAIR;
1077 excCats = TORPEDO;
1078 }
1079 else if (ai->gamemap->IsWaterMap() && rng.RandFloat() > 0.5f) {
1080 bt = BUILD_UW_DEFENSE;
1081 // layer = CCoverageCell::DEFENSE_UNDERWATER;
1082 // TODO: replace STATIC with DEFENSE after all config files updated
1083 incCats = STATIC|TORPEDO;
1084 // NOTE: we do not support coastal torpedo launchers
1085 excCats = LAND;
1086 }
1087 else
1088 {
1089 bt = BUILD_AG_DEFENSE;
1090 // layer = CCoverageCell::DEFENSE_GROUND;
1091 incCats = ATTACKER|DEFENSE;
1092 excCats = ANTIAIR|TORPEDO;
1093 }
1094
1095 /*
1096 int size = ai->coverage->getLayerSize(layer);
1097 const float k = size / (ai->unittable->staticUnits.size() - size + 1.0f);
1098 bool allow;
1099
1100 switch (ai->difficulty) {
1101 case DIFFICULTY_EASY:
1102 allow = k < 0.11f;
1103 break;
1104 case DIFFICULTY_NORMAL:
1105 allow = k < 0.31f;
1106 break;
1107 case DIFFICULTY_HARD:
1108 allow = k < 0.51f;
1109 break;
1110 }
1111 */
1112
1113 buildOrAssist(*group, bt, incCats, excCats);
1114 }
1115
tryBuildingShield(CGroup * group)1116 void CEconomy::tryBuildingShield(CGroup* group) {
1117 if (group->busy)
1118 return;
1119 if (estall)
1120 return;
1121 if (ai->difficulty == DIFFICULTY_EASY)
1122 return;
1123 if (ai->intel->getEnemyCount(ARTILLERY) < 20)
1124 return;
1125
1126 // TODO: also build shields when enemy got LRPC
1127
1128 // TODO: introduce importance threshold otherwise AI
1129 // finally will cover all buildings, including cheap one
1130
1131 buildOrAssist(*group, BUILD_IMP_DEFENSE, SHIELD);
1132 }
1133
tryBuildingStaticAssister(CGroup * group)1134 void CEconomy::tryBuildingStaticAssister(CGroup* group) {
1135 if (group->busy)
1136 return;
1137 if (ai->difficulty == DIFFICULTY_EASY)
1138 return;
1139 if (mstall || estall)
1140 return;
1141
1142 buildOrAssist(*group, BUILD_MISC_DEFENSE, NANOTOWER);
1143 }
1144
tryBuildingJammer(CGroup * group)1145 void CEconomy::tryBuildingJammer(CGroup* group) {
1146 if (group->busy)
1147 return;
1148 if (ai->difficulty == DIFFICULTY_EASY)
1149 return;
1150 if (mstall || estall)
1151 return;
1152
1153 buildOrAssist(*group, BUILD_MISC_DEFENSE, JAMMER);
1154 }
1155
tryBuildingAntiNuke(CGroup * group)1156 void CEconomy::tryBuildingAntiNuke(CGroup* group) {
1157 if (group->busy)
1158 return;
1159 if (ai->difficulty == DIFFICULTY_EASY)
1160 return;
1161 // TODO: avoid using unitCount()
1162 if (ai->unittable->unitCount(ANTINUKE) >= ai->intel->enemies.getUnits(NUKE)->size())
1163 return;
1164
1165 buildOrAssist(*group, BUILD_IMP_DEFENSE, ANTINUKE);
1166 }
1167
tryAssistingFactory(CGroup * group)1168 void CEconomy::tryAssistingFactory(CGroup* group) {
1169 if (group->busy)
1170 return;
1171 if (mstall || estall)
1172 return;
1173
1174 ATask *task;
1175 if ((task = canAssistFactory(*group)) != NULL) {
1176 ai->tasks->addTask(new AssistTask(ai, *task, *group));
1177 }
1178 }
1179
tryAssist(CGroup * group,buildType bt)1180 void CEconomy::tryAssist(CGroup* group, buildType bt) {
1181 if (group->busy)
1182 return;
1183 if (mstall || estall)
1184 return;
1185
1186 ATask *task = canAssist(bt, *group);
1187 if (task != NULL) {
1188 ai->tasks->addTask(new AssistTask(ai, *task, *group));
1189 return;
1190 }
1191 }
1192
tryFixingStall(CGroup * group)1193 void CEconomy::tryFixingStall(CGroup* group) {
1194 bool mStall = (mstall && !mexceeding);
1195 bool eStall = (estall && !eexceeding);
1196 std::list<buildType> order;
1197
1198 if (group->busy)
1199 return;
1200
1201 if (mStall && eStall
1202 && (((mIncome - mUsage) * METAL2ENERGY) < (eIncome - eUsage))) {
1203 order.push_back(BUILD_MPROVIDER);
1204 order.push_back(BUILD_EPROVIDER);
1205 }
1206 else {
1207 if (eStall)
1208 order.push_back(BUILD_EPROVIDER);
1209 if (mStall)
1210 order.push_back(BUILD_MPROVIDER);
1211 }
1212
1213 for (std::list<buildType>::const_iterator it = order.begin(); it != order.end(); ++it) {
1214 buildOrAssist(*group, *it, *it == BUILD_EPROVIDER ? EMAKER : MEXTRACTOR);
1215 if (group->busy) break;
1216 }
1217 }
1218
canBuildWhere(unitCategory unitCats,bool strictly)1219 unitCategory CEconomy::canBuildWhere(unitCategory unitCats, bool strictly) {
1220 unitCategory result;
1221 UnitCategory2UnitCategoryMap::iterator it;
1222
1223 for (it = canBuildEnv.begin(); it != canBuildEnv.end(); ++it) {
1224 if ((unitCats&it->first).any())
1225 result |= it->second;
1226 }
1227
1228 if (strictly)
1229 return result;
1230
1231 // explicitly exclude useless units on specific maps...
1232 if (ai->gamemap->IsLandFreeMap()) {
1233 result &= ~(LAND);
1234 }
1235 else if (ai->gamemap->IsWaterFreeMap()) {
1236 result &= ~(SEA|SUB);
1237 }
1238 else {
1239 // TODO: detect unit's sector land/sea percent and enforce/remove tags
1240 // accordingly
1241 }
1242
1243 return result;
1244 }
1245