1 #include "CE323AI.h"
2
3 #include <string>
4 #include <algorithm>
5 #include <cctype>
6
7 #include "CAI.h"
8 #include "CRNG.h"
9 #include "CConfigParser.h"
10 #include "GameMap.hpp"
11 #include "CUnitTable.h"
12 #include "CEconomy.h"
13 #include "CWishList.h"
14 #include "CTaskHandler.h"
15 #include "CThreatMap.h"
16 #include "CPathfinder.h"
17 #include "CIntel.h"
18 #include "CMilitary.h"
19 #include "CDefenseMatrix.h"
20 #include "CUnit.h"
21 #include "CGroup.h"
22 #include "CScopedTimer.h"
23 #include "Util.hpp"
24 #include "CCoverageHandler.h"
25 #include "ReusableObjectFactory.hpp"
26
27 namespace springLegacyAI {
28 class IGlobalAICallback;
29 }
30 #include "LegacyCpp/IAICallback.h"
31
32
CE323AI()33 CE323AI::CE323AI() {
34 isRunning = false;
35 attachedAtFrame = -1;
36 }
37
InitAI(IGlobalAICallback * callback,int team)38 void CE323AI::InitAI(IGlobalAICallback* callback, int team) {
39 static const char
40 optionDifficulty[] = "difficulty",
41 optionLoggingLevel[] = "logging";
42
43 CLogger::logLevel loggingLevel = CLogger::VERBOSE;
44
45 ai = new AIClasses(callback);
46
47 std::map<std::string, std::string> options = ai->cb->GetMyOptionValues();
48 if (options.find(optionDifficulty) != options.end()) {
49 ai->difficulty = static_cast<difficultyLevel>(atoi(options[optionDifficulty].c_str()));
50 }
51 if (options.find(optionLoggingLevel) != options.end()) {
52 loggingLevel = static_cast<CLogger::logLevel>(atoi(options[optionLoggingLevel].c_str()));
53 }
54
55 ai->logger = new CLogger(ai, /*CLogger::LOG_STDOUT |*/ CLogger::LOG_FILE, loggingLevel);
56
57 LOG_II("CE323AI::InitAI allyIndex = " << ai->allyIndex)
58
59 ai->cfgparser = new CConfigParser(ai);
60 ai->unittable = new CUnitTable(ai);
61
62 std::string configfile = ai->cfgparser->getFilename(GET_CFG);
63 ai->cfgparser->parseConfig(configfile);
64 if (ai->cfgparser->isUsable()) {
65 // try loading overload file...
66 configfile = ai->cfgparser->getFilename(GET_CFG|GET_VER);
67 if (ai->cfgparser->fileExists(configfile))
68 ai->cfgparser->parseConfig(configfile);
69 }
70 if (!ai->cfgparser->isUsable()) {
71 ai->cb->SendTextMsg("No usable config file available for this Mod/Game.", 0);
72 const std::string confFileLine = "A template can be found at: " + configfile;
73 ai->cb->SendTextMsg(confFileLine.c_str(), 0);
74 ai->cb->SendTextMsg("Shutting down...", 0);
75
76 // we have to cleanup here, as ReleaseAI() will not be called
77 // in case of an error in InitAI().
78 delete ai->cfgparser;
79 delete ai->logger;
80 delete ai->unittable;
81 delete ai;
82 // this will kill this AI instance gracefully
83 throw 33;
84 }
85
86 ai->gamemap = new GameMap(ai);
87 ai->economy = new CEconomy(ai);
88 ai->wishlist = new CWishList(ai);
89 ai->tasks = new CTaskHandler(ai);
90 ai->threatmap = new CThreatMap(ai);
91 ai->pathfinder = new CPathfinder(ai);
92 ai->intel = new CIntel(ai);
93 ai->military = new CMilitary(ai);
94 ai->defensematrix = new CDefenseMatrix(ai);
95 ai->coverage = new CCoverageHandler(ai);
96
97 #if !defined(BUILDING_AI_FOR_SPRING_0_81_2)
98 /* Set the new graph stuff */
99 ai->cb->DebugDrawerSetGraphPos(-0.4f, -0.4f);
100 ai->cb->DebugDrawerSetGraphSize(0.8f, 0.6f);
101 #endif
102 }
103
ReleaseAI()104 void CE323AI::ReleaseAI() {
105
106 if (ai->isSole()) {
107 ReusableObjectFactory<CGroup>::Shutdown();
108 ReusableObjectFactory<CUnit>::Shutdown();
109 ReusableObjectFactory<CCoverageCell>::Shutdown();
110 }
111
112 delete ai->coverage;
113 delete ai->defensematrix;
114 delete ai->military;
115 delete ai->intel;
116 delete ai->pathfinder;
117 delete ai->threatmap;
118 delete ai->tasks;
119 delete ai->wishlist;
120 delete ai->economy;
121 delete ai->gamemap;
122 delete ai->unittable;
123 delete ai->cfgparser;
124 delete ai->logger;
125 delete ai;
126 }
127
128 /************************
129 * Unit related callins *
130 ************************/
131
132 /* Called when units are spawned in a factory or when game starts */
UnitCreated(int uid,int bid)133 void CE323AI::UnitCreated(int uid, int bid) {
134 CUnit* unit = ai->unittable->requestUnit(uid, bid);
135
136 LOG_II("CE323AI::UnitCreated " << (*unit))
137
138 if ((unit->type->cats&COMMANDER).any() && !ai->economy->isInitialized()) {
139 ai->economy->init(*unit);
140 }
141
142 // HACK: for metal extractors & geoplants only
143 ai->economy->addUnitOnCreated(*unit);
144
145 ai->coverage->addUnit(unit);
146
147 if (bid < 0)
148 return; // unit was spawned from nowhere (e.g. commander), or given by another player
149
150 unitCategory c = unit->type->cats;
151 if ((c&MOBILE).any()) {
152 CUnit* builder = ai->unittable->getUnit(bid);
153 // if builder is a mobile unit (like Consul in BA) then do not
154 // assign move command...
155 if ((builder->type->cats&STATIC).any()) {
156 // NOTE: factories should be already rotated in proper direction
157 // to prevent units going outside the map
158 if ((c&AIR).any()) {
159 if ((c&ANTIAIR).any())
160 unit->guard(bid, true);
161 else
162 unit->moveRandom(450.0f, true);
163 }
164 else if ((c&BUILDER).any())
165 unit->moveForward(200.0f);
166 else
167 unit->moveForward(400.0f);
168 }
169 }
170 else {
171 if ((c&NANOTOWER).any()) {
172 unit->patrol(unit->getForwardPos(100.0f), true);
173 }
174 }
175
176 // TODO: check if UnitIdle for factory/builder is called after
177 // UnitCreated then we do not need "unitsUnderConstruction" map
178 std::map<int, Wish>::iterator it = ai->unittable->unitsBuilding.find(bid);
179 if (it != ai->unittable->unitsBuilding.end())
180 ai->unittable->unitsUnderConstruction[uid] = it->second.goalCats;
181 else
182 ai->unittable->unitsUnderConstruction[uid] = 0;
183 }
184
185 /* Called when units are finished in a factory and able to move */
UnitFinished(int uid)186 void CE323AI::UnitFinished(int uid) {
187 CUnit* unit = ai->unittable->getUnit(uid);
188
189 if(!unit) {
190 const UnitDef *ud = ai->cb->GetUnitDef(uid);
191 LOG_EE("CE323AI::UnitFinished unregistered " << (ud ? ud->humanName : std::string("UnknownUnit")) << "(" << uid << ")")
192 return;
193 }
194 else
195 LOG_II("CE323AI::UnitFinished " << (*unit))
196
197 // NOTE: commanders and static units should start actions earlier than
198 // usual units
199 if (unit->builtBy == -1 || (unit->type->cats&STATIC).any())
200 unit->aliveFrames = IDLE_UNIT_TIMEOUT;
201 else
202 unit->aliveFrames = 0; // reset time at which unit was building
203
204 ai->unittable->idle[uid] = true;
205
206 if (unit->builtBy >= 0) {
207 // mark builder has finished its job
208 ai->unittable->builders[unit->builtBy] = true;
209 }
210
211 if (unit->isEconomy()) {
212 ai->economy->addUnitOnFinished(*unit);
213 }
214 else if(!ai->military->addUnit(*unit)) {
215 LOG_WW("CE323AI::UnitFinished unit " << (*unit) << " is NOT under AI control")
216 }
217
218 // NOTE: very important to place this line AFTER registering a unit in
219 // either economy or military blocks
220 ai->unittable->unitsUnderConstruction.erase(uid);
221 }
222
223 /* Called on a destroyed unit */
UnitDestroyed(int uid,int attacker)224 void CE323AI::UnitDestroyed(int uid, int attacker) {
225 ai->tasks->onUnitDestroyed(uid, attacker);
226
227 CUnit *unit = ai->unittable->getUnit(uid);
228 if (unit) {
229 LOG_II("CE323AI::UnitDestroyed " << (*unit))
230 unit->remove();
231 }
232 }
233
234 /* Called when unit is idle */
UnitIdle(int uid)235 void CE323AI::UnitIdle(int uid) {
236 CUnit* unit = ai->unittable->getUnit(uid);
237
238 if (unit == NULL) {
239 const UnitDef *ud = ai->cb->GetUnitDef(uid);
240 LOG_EE("CE323AI::UnitIdle unregistered " << (ud ? ud->humanName : std::string("UnknownUnit")) << "(" << uid << ")")
241 return;
242 }
243
244 if (ai->unittable->unitsUnderPlayerControl.find(uid) != ai->unittable->unitsUnderPlayerControl.end()) {
245 ai->unittable->unitsUnderPlayerControl.erase(uid);
246 assert(unit->group == NULL);
247 LOG_II("CE323AI::UnitIdle " << (*unit) << " is under AI control again")
248 // re-assign unit to appropriate group
249 UnitFinished(uid);
250 return;
251 }
252
253 ai->unittable->idle[uid] = true;
254
255 if ((unit->type->cats&(BUILDER|FACTORY)).any())
256 ai->unittable->unitsBuilding.erase(uid);
257 }
258
259 /* Called when unit is damaged */
UnitDamaged(int damaged,int attacker,float damage,float3 dir)260 void CE323AI::UnitDamaged(int damaged, int attacker, float damage, float3 dir) {
261 // TODO: introduce quick imrovement for builders to reclaim their attacker
262 // but next it should return to its current job, so we need to delay
263 // current task which is impossible while there is no task queue per unit
264 // group (curently we have a single task per group)
265
266 if (ai->cb->UnitBeingBuilt(damaged) || ai->cb->IsUnitParalyzed(damaged) || ai->cb->GetUnitHealth(damaged) < EPS)
267 return;
268 if (attacker < 0)
269 return;
270 if (ai->cbc->GetUnitHealth(attacker) < EPS)
271 return;
272
273 CUnit* unit = ai->unittable->getUnit(damaged);
274 if (unit == NULL)
275 return; // invalid unit
276 if (unit->group == NULL)
277 return; // unit is not under AI control
278
279 /*
280 const unsigned int cats = unit->type->cats;
281 if (cats&MOBILE) {
282 bool attack = false;
283 if (cats&(ATTACKER|BUILDER)) {
284 const UnitDef *eud = ai->cbc->GetUnitDef(attacker);
285 if (!eud)
286 return;
287 const unsigned int ecats = UC(eud->id);
288 float3 epos = ai->cbc->GetUnitPos(attacker);
289 float range = cats&BUILDER ? unit->group->buildRange: unit->group->range;
290 if (unit->group->pos().distance2D(epos) < 1.1f*range) {
291 // always strikeback if attacker is a scouter or less powerful
292 if ((ecats&SCOUTER) || ai->threatmap->getThreat(epos, 0.0f) <= unit->group->strength) {
293 ATask* task = ai->tasks->getTask(*unit->group);
294 if (!task || (task->t != ATTACK && task->t != FACTORY_BUILD)) {
295 if (task && task->active)
296 task->remove();
297 attack = true;
298 }
299 }
300 }
301 }
302
303 if (attack) {
304 ai->tasks->addAttackTask(attacker, *unit->group);
305 } else {
306 // TODO: run away or continue its task depending on group style
307 }
308 }
309 */
310 }
311
312 /* Called on move fail e.g. can't reach the point */
UnitMoveFailed(int uid)313 void CE323AI::UnitMoveFailed(int uid) {
314 CUnit* unit = ai->unittable->getUnit(uid);
315 if (unit && (unit->type->cats&(LAND|SEA)).any()) {
316 // if unit is inside a factory then force moving...
317 float3 pos = ai->cb->GetUnitPos(unit->key);
318 std::map<int, CUnit*>::iterator it;
319 for (it = ai->unittable->factories.begin(); it != ai->unittable->factories.end(); ++it) {
320 float distance = ai->cb->GetUnitPos(it->first).distance2D(pos);
321 if (distance < 16.0) {
322 unit->moveForward(200.0f);
323 if (!unit->canPerformTasks())
324 unit->aliveFrames = 0; // prolong idle timeout
325 }
326 }
327 }
328 }
329
330
331 /***********************
332 * Enemy related callins *
333 ***********************/
334
EnemyEnterLOS(int enemy)335 void CE323AI::EnemyEnterLOS(int enemy) {
336 }
337
EnemyLeaveLOS(int enemy)338 void CE323AI::EnemyLeaveLOS(int enemy) {
339 }
340
EnemyEnterRadar(int enemy)341 void CE323AI::EnemyEnterRadar(int enemy) {
342 }
343
EnemyLeaveRadar(int enemy)344 void CE323AI::EnemyLeaveRadar(int enemy) {
345 }
346
EnemyCreated(int enemy)347 void CE323AI::EnemyCreated(int enemy) {
348 ai->intel->onEnemyCreated(enemy);
349 }
350
EnemyDestroyed(int enemy,int attacker)351 void CE323AI::EnemyDestroyed(int enemy, int attacker) {
352 ai->military->onEnemyDestroyed(enemy, attacker);
353 ai->tasks->onEnemyDestroyed(enemy, attacker);
354 ai->intel->onEnemyDestroyed(enemy, attacker);
355 }
356
EnemyDamaged(int damaged,int attacker,float damage,float3 dir)357 void CE323AI::EnemyDamaged(int damaged, int attacker, float damage, float3 dir) {
358 }
359
360
361 /****************
362 * Misc callins *
363 ****************/
364
GotChatMsg(const char * msg,int player)365 void CE323AI::GotChatMsg(const char* msg, int player) {
366 static const char
367 cmdPrefix[] = "!e323ai",
368 modTM[] = "threatmap",
369 modMil[] = "military",
370 // modEco[] = "economy",
371 modPF[] = "pathfinder",
372 modPG[] = "pathgraph",
373 modDM[] = "defensematrix",
374 modCL[] = "coverage";
375
376 // NOTE: accept AI commands from spectators only
377 if (ai->cb->GetPlayerTeam(player) >= 0)
378 return;
379
380 std::string line(msg);
381 std::transform(line.begin(), line.end(), line.begin(), ::tolower);
382
383 if (line.find(cmdPrefix) == 0) {
384 bool isDebugOn = false;
385 size_t pos = line.find_first_not_of(' ', sizeof(cmdPrefix) - 1);
386 if (pos == std::string::npos) {
387 if (ai->isMaster()) {
388 ai->cb->SendTextMsg("Usage: !e323ai <module>", 0);
389 ai->cb->SendTextMsg("where", 0);
390 ai->cb->SendTextMsg("\t<module> ::= pathfinder|pathgraph|military|threatmap|defensematrix|coverage", 0);
391 ai->cb->SendTextMsg("\t<module> ::= pf|pg|mil|tm|dm|cl", 0);
392 }
393 return;
394 }
395
396 std::string cmd = line.substr(pos);
397 if (cmd == modTM || cmd == "tm") {
398 isDebugOn = ai->threatmap->switchDebugMode();
399 cmd.assign(modTM);
400 }
401 else if (cmd == modMil || cmd == "mil") {
402 isDebugOn = ai->military->switchDebugMode();
403 cmd.assign(modMil);
404 }
405 else if (cmd == modPF || cmd == "pf") {
406 isDebugOn = ai->pathfinder->switchDebugMode(false);
407 cmd.assign(modPF);
408 }
409 else if (cmd == modPG || cmd == "pg") {
410 isDebugOn = ai->pathfinder->switchDebugMode(true);
411 cmd.assign(modPG);
412 }
413 else if (cmd == modDM || cmd == "dm") {
414 isDebugOn = ai->defensematrix->switchDebugMode();
415 cmd.assign(modDM);
416 }
417 else if (cmd == modCL || cmd == "cl") {
418 isDebugOn = ai->coverage->toggleVisualization();
419 cmd.assign(modCL);
420 }
421 else {
422 if (cmd == "unit") {
423 // dump selected unit info...
424 line.clear();
425
426 if (ai->cb->GetSelectedUnits(&ai->unitIDs[0], 1) > 0) {
427 std::stringstream buffer;
428 CUnit* unit = ai->unittable->getUnit(ai->unitIDs[0]);
429 if (unit) {
430 buffer << ".id = " << unit->key << "\n";
431 buffer << ".humanName = " << unit->def->humanName << "\n";
432 buffer << ".isMicroing = " << unit->isMicroing() << "\n";
433 buffer << ".microingFrames = " << unit->microingFrames << "\n";
434 buffer << ".aliveFrames = " << unit->aliveFrames << "\n";
435 buffer << ".waiting = " << unit->waiting << "\n";
436 buffer << ".idle = " << ai->unittable->idle[unit->key] << "\n";
437
438 ai->cb->SendTextMsg(buffer.str().c_str(), 0);
439 }
440 }
441 }
442 else if (cmd == "tmv") {
443 // dump threat value at mouse cursor...
444 float value = 0.0f;
445 float3 pos = ai->cb->GetMousePos();
446 CUnit* unit = NULL;
447 if (ai->cb->GetSelectedUnits(&ai->unitIDs[0], 1) > 0) {
448 unit = ai->unittable->getUnit(ai->unitIDs[0]);
449 if (unit && unit->group) {
450 value = ai->threatmap->getThreat(pos, 0.0f, unit->group);
451 }
452 }
453 else {
454 value = ai->threatmap->getThreat(pos, 0.0f);
455 }
456
457 std::stringstream buffer;
458 buffer << "Threat value";
459 if (unit)
460 buffer << " for " << unit->def->humanName;
461 buffer << " at position (" << pos.x << "," << pos.z << ") is " << value;
462
463 ai->cb->SendTextMsg(buffer.str().c_str(), 0);
464 }
465 else {
466 line.assign("Module \"" + cmd + "\" is unknown or unsupported for visual debugging");
467
468 }
469
470 if (!line.empty())
471 ai->cb->SendTextMsg(line.c_str(), 0);
472
473 return;
474 }
475
476 line.assign("Debug mode is switched ");
477 if (isDebugOn)
478 line += "ON";
479 else
480 line += "OFF";
481 line += " for \"" + cmd + "\" module";
482
483 ai->cb->SendTextMsg(line.c_str(), 0);
484 }
485 }
486
HandleEvent(int msg,const void * data)487 int CE323AI::HandleEvent(int msg, const void* data) {
488 const ChangeTeamEvent* cte = (const ChangeTeamEvent*) data;
489
490 switch(msg) {
491 case AI_EVENT_UNITGIVEN:
492 /* Unit gained */
493 if ((cte->newteam) == ai->team) {
494 UnitCreated(cte->unit, -1);
495 UnitFinished(cte->unit);
496
497 // NOTE: getting "unit" for logging only
498 CUnit *unit = ai->unittable->getUnit(cte->unit);
499
500 LOG_II("CE323AI::UnitGiven " << (*unit))
501 }
502 break;
503
504 case AI_EVENT_UNITCAPTURED:
505 /* Unit lost */
506 if ((cte->oldteam) == ai->team) {
507 // NOTE: getting "unit" for logging only
508 CUnit *unit = ai->unittable->getUnit(cte->unit);
509
510 LOG_II("CE323AI::UnitCaptured " << (*unit))
511
512 UnitDestroyed(cte->unit, 0);
513 }
514 break;
515
516 case AI_EVENT_PLAYER_COMMAND:
517 /* Player incoming command */
518 const PlayerCommandEvent* pce = (const PlayerCommandEvent*) data;
519 bool importantCommand = false;
520
521 if(pce->command.id < 0)
522 importantCommand = true;
523 else {
524 switch(pce->command.id)
525 {
526 case CMD_MOVE:
527 case CMD_PATROL:
528 case CMD_FIGHT:
529 case CMD_ATTACK:
530 case CMD_AREA_ATTACK:
531 case CMD_GUARD:
532 case CMD_REPAIR:
533 case CMD_LOAD_UNITS:
534 case CMD_UNLOAD_UNITS:
535 case CMD_UNLOAD_UNIT:
536 case CMD_RECLAIM:
537 case CMD_DGUN:
538 case CMD_RESTORE:
539 case CMD_RESURRECT:
540 case CMD_CAPTURE:
541 importantCommand = true;
542 break;
543 }
544 }
545
546 if(importantCommand && !pce->units.empty()) {
547 for(int i = 0; i < pce->units.size(); i++) {
548 const int uid = pce->units[i];
549 if(ai->unittable->unitsUnderPlayerControl.find(uid) == ai->unittable->unitsUnderPlayerControl.end()) {
550 // we have to remove unit from a group, but not
551 // to emulate unit death
552 CUnit* unit = ai->unittable->getUnit(uid);
553
554 if (unit == NULL)
555 continue;
556
557 // remove unit from group so it will not receive
558 // AI commands anymore...
559 if(unit->group) {
560 unit->group->remove(*unit);
561 }
562
563 unit->micro(false);
564 ai->unittable->idle[uid] = false; // because player controls it
565 ai->unittable->unitsUnderPlayerControl[uid] = unit;
566
567 LOG_II("CE323AI::PlayerCommand " << (*unit) << " is under human control")
568 }
569 }
570 }
571 break;
572 }
573
574 return 0;
575 }
576
577 /* Update AI per logical frame = 1/30 sec on gamespeed 1.0 */
Update()578 void CE323AI::Update() {
579 const int currentFrame = ai->cb->GetCurrentFrame();
580
581 if (currentFrame < 0)
582 return; // some shit happened with engine? (stolen from AAI)
583
584 // NOTE: AI can be attached in mid-game state with /aicontrol command
585 int localFrame;
586
587 if (attachedAtFrame < 0) {
588 attachedAtFrame = currentFrame - 1;
589 }
590
591 localFrame = currentFrame - attachedAtFrame;
592
593 if(localFrame == 1)
594 ai->intel->init();
595
596 if(!ai->economy->isInitialized())
597 return;
598
599 // anyway show AI is loaded even if it is not playing actually...
600 if (localFrame == 800 && ai->isMaster()) {
601 LOG_SS("*** " << AI_VERSION << " ***");
602 LOG_SS("*** " << AI_CREDITS << " ***");
603 LOG_SS("*** " << AI_NOTES << " ***");
604 }
605
606 /* Make sure we shift the multiplexer for each instance of E323AI */
607 int aiframe = localFrame + ai->team;
608
609 // Make sure we start playing from "eco-incomes" update
610 if(!isRunning) {
611 isRunning = aiframe % MULTIPLEXER == 0;
612 }
613
614 if(!isRunning)
615 return;
616
617 /* Rotate through the different update events to distribute computations */
618 switch(aiframe % MULTIPLEXER) {
619 case 0: { /* update incomes */
620 ai->economy->updateIncomes();
621 }
622 break;
623
624 case 1: { /* update threatmap */
625 PROFILE(threatmap)
626 ai->threatmap->update(localFrame);
627 }
628 break;
629
630 case 2: { /* update the path itself of a group */
631 PROFILE(A*)
632 ai->pathfinder->updatePaths();
633 }
634 break;
635
636 case 3: { /* update the groups following a path */
637 PROFILE(following)
638 ai->pathfinder->updateFollowers();
639 }
640 break;
641
642 case 4: { /* update enemy intel */
643 PROFILE(intel)
644 ai->intel->update(localFrame);
645 }
646 break;
647
648 case 5: { /* update defense matrix */
649 PROFILE(defensematrix)
650 ai->defensematrix->update();
651 ai->coverage->update();
652 }
653
654 case 6: { /* update military */
655 PROFILE(military)
656 ai->military->update(localFrame);
657 }
658 break;
659
660 case 7: { /* update economy */
661 PROFILE(economy)
662 ai->economy->update();
663 }
664 break;
665
666 case 8: { /* update taskhandler */
667 PROFILE(taskhandler)
668 ai->tasks->update();
669 }
670
671 case 9: { /* update unit table */
672 ai->unittable->update();
673 }
674 break;
675 }
676 }
677