LCOV - code coverage report
Current view: top level - simulation/ai/petra - victoryManager.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 408 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 18 0.0 %

          Line data    Source code
       1             : /**
       2             :  * Handle events that are important to specific victory conditions:
       3             :  *   in capture_the_relic, capture gaia relics and train military guards.
       4             :  *   in regicide, train healer and military guards for the hero.
       5             :  *   in wonder, train military guards.
       6             :  */
       7             : 
       8           0 : PETRA.VictoryManager = function(Config)
       9             : {
      10           0 :         this.Config = Config;
      11           0 :         this.criticalEnts = new Map();
      12             :         // Holds ids of all ents who are (or can be) guarding and if the ent is currently guarding
      13           0 :         this.guardEnts = new Map();
      14           0 :         this.healersPerCriticalEnt = 2 + Math.round(this.Config.personality.defensive * 2);
      15           0 :         this.tryCaptureGaiaRelic = false;
      16           0 :         this.tryCaptureGaiaRelicLapseTime = -1;
      17             :         // Gaia relics which we are targeting currently and have not captured yet
      18           0 :         this.targetedGaiaRelics = new Map();
      19             : };
      20             : 
      21             : /**
      22             :  * Cache the ids of any inital victory-critical entities.
      23             :  */
      24           0 : PETRA.VictoryManager.prototype.init = function(gameState)
      25             : {
      26           0 :         if (gameState.getVictoryConditions().has("wonder"))
      27             :         {
      28           0 :                 for (let wonder of gameState.getOwnEntitiesByClass("Wonder", true).values())
      29           0 :                         this.criticalEnts.set(wonder.id(), { "guardsAssigned": 0, "guards": new Map() });
      30             :         }
      31             : 
      32           0 :         if (gameState.getVictoryConditions().has("regicide"))
      33             :         {
      34           0 :                 for (let hero of gameState.getOwnEntitiesByClass("Hero", true).values())
      35             :                 {
      36           0 :                         let defaultStance = hero.hasClass("Soldier") ? "aggressive" : "passive";
      37           0 :                         if (hero.getStance() != defaultStance)
      38           0 :                                 hero.setStance(defaultStance);
      39           0 :                         this.criticalEnts.set(hero.id(), {
      40             :                                 "garrisonEmergency": false,
      41             :                                 "healersAssigned": 0,
      42             :                                 "guardsAssigned": 0, // for non-healer guards
      43             :                                 "guards": new Map() // ids of ents who are currently guarding this hero
      44             :                         });
      45             :                 }
      46             :         }
      47             : 
      48           0 :         if (gameState.getVictoryConditions().has("capture_the_relic"))
      49             :         {
      50           0 :                 for (let relic of gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).values())
      51             :                 {
      52           0 :                         if (relic.owner() == PlayerID)
      53           0 :                                 this.criticalEnts.set(relic.id(), { "guardsAssigned": 0, "guards": new Map() });
      54             :                 }
      55             :         }
      56             : };
      57             : 
      58             : /**
      59             :  * In regicide victory condition, if the hero has less than 70% health, try to garrison it in a healing structure
      60             :  * If it is less than 40%, try to garrison in the closest possible structure
      61             :  * If the hero cannot garrison, retreat it to the closest base
      62             :  */
      63           0 : PETRA.VictoryManager.prototype.checkEvents = function(gameState, events)
      64             : {
      65           0 :         if (gameState.getVictoryConditions().has("wonder"))
      66             :         {
      67           0 :                 for (let evt of events.Create)
      68             :                 {
      69           0 :                         let ent = gameState.getEntityById(evt.entity);
      70           0 :                         if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() === undefined ||
      71             :                                 !ent.hasClass("Wonder"))
      72           0 :                                 continue;
      73             : 
      74             :                         // Let's get a few units from other bases to build the wonder.
      75           0 :                         let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
      76           0 :                         let builders = gameState.ai.HQ.bulkPickWorkers(gameState, base, 10);
      77           0 :                         if (builders)
      78           0 :                                 for (let worker of builders.values())
      79             :                                 {
      80           0 :                                         worker.setMetadata(PlayerID, "base", base.ID);
      81           0 :                                         worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER);
      82           0 :                                         worker.setMetadata(PlayerID, "target-foundation", ent.id());
      83             :                                 }
      84             :                 }
      85             : 
      86           0 :                 for (let evt of events.ConstructionFinished)
      87             :                 {
      88           0 :                         if (!evt || !evt.newentity)
      89           0 :                                 continue;
      90             : 
      91           0 :                         let ent = gameState.getEntityById(evt.newentity);
      92           0 :                         if (ent && ent.isOwn(PlayerID) && ent.hasClass("Wonder"))
      93           0 :                                 this.criticalEnts.set(ent.id(), { "guardsAssigned": 0, "guards": new Map() });
      94             :                 }
      95             :         }
      96             : 
      97           0 :         if (gameState.getVictoryConditions().has("regicide"))
      98             :         {
      99           0 :                 for (let evt of events.Attacked)
     100             :                 {
     101           0 :                         if (!this.criticalEnts.has(evt.target))
     102           0 :                                 continue;
     103             : 
     104           0 :                         let target = gameState.getEntityById(evt.target);
     105           0 :                         if (!target || !target.position() || target.healthLevel() > this.Config.garrisonHealthLevel.high)
     106           0 :                                 continue;
     107             : 
     108           0 :                         let plan = target.getMetadata(PlayerID, "plan");
     109           0 :                         let hero = this.criticalEnts.get(evt.target);
     110           0 :                         if (plan != -2 && plan != -3)
     111             :                         {
     112           0 :                                 target.stopMoving();
     113             : 
     114           0 :                                 if (plan >= 0)
     115             :                                 {
     116           0 :                                         let attackPlan = gameState.ai.HQ.attackManager.getPlan(plan);
     117           0 :                                         if (attackPlan)
     118           0 :                                                 attackPlan.removeUnit(target, true);
     119             :                                 }
     120             : 
     121           0 :                                 if (target.getMetadata(PlayerID, "PartOfArmy"))
     122             :                                 {
     123           0 :                                         let army = gameState.ai.HQ.defenseManager.getArmy(target.getMetadata(PlayerID, "PartOfArmy"));
     124           0 :                                         if (army)
     125           0 :                                                 army.removeOwn(gameState, target.id());
     126             :                                 }
     127             : 
     128           0 :                                 hero.garrisonEmergency = target.healthLevel() < this.Config.garrisonHealthLevel.low;
     129           0 :                                 this.pickCriticalEntRetreatLocation(gameState, target, hero.garrisonEmergency);
     130             :                         }
     131           0 :                         else if (target.healthLevel() < this.Config.garrisonHealthLevel.low && !hero.garrisonEmergency)
     132             :                         {
     133             :                                 // the hero is severely wounded, try to retreat/garrison quicker
     134           0 :                                 gameState.ai.HQ.garrisonManager.cancelGarrison(target);
     135           0 :                                 this.pickCriticalEntRetreatLocation(gameState, target, true);
     136           0 :                                 hero.garrisonEmergency = true;
     137             :                         }
     138             :                 }
     139             : 
     140           0 :                 for (let evt of events.TrainingFinished)
     141           0 :                         for (let entId of evt.entities)
     142             :                         {
     143           0 :                                 let ent = gameState.getEntityById(entId);
     144           0 :                                 if (ent && ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER)
     145           0 :                                         this.assignGuardToCriticalEnt(gameState, ent);
     146             :                         }
     147             : 
     148           0 :                 for (let evt of events.Garrison)
     149             :                 {
     150           0 :                         if (!this.criticalEnts.has(evt.entity))
     151           0 :                                 continue;
     152             : 
     153           0 :                         let hero = this.criticalEnts.get(evt.entity);
     154           0 :                         if (hero.garrisonEmergency)
     155           0 :                                 hero.garrisonEmergency = false;
     156             : 
     157           0 :                         let holderEnt = gameState.getEntityById(evt.holder);
     158           0 :                         if (!holderEnt)
     159           0 :                                 continue;
     160             : 
     161           0 :                         if (holderEnt.hasClass("Ship"))
     162             :                         {
     163             :                                 // If the hero is garrisoned on a ship, remove its guards
     164           0 :                                 for (let guardId of hero.guards.keys())
     165             :                                 {
     166           0 :                                         let guardEnt = gameState.getEntityById(guardId);
     167           0 :                                         if (!guardEnt)
     168           0 :                                                 continue;
     169             : 
     170           0 :                                         guardEnt.removeGuard();
     171           0 :                                         this.guardEnts.set(guardId, false);
     172             :                                 }
     173           0 :                                 hero.guards.clear();
     174           0 :                                 continue;
     175             :                         }
     176             : 
     177             :                         // Move the current guards to the garrison location.
     178             :                         // TODO: try to garrison them with the critical ent.
     179           0 :                         for (let guardId of hero.guards.keys())
     180             :                         {
     181           0 :                                 let guardEnt = gameState.getEntityById(guardId);
     182           0 :                                 if (!guardEnt)
     183           0 :                                         continue;
     184             : 
     185           0 :                                 let plan = guardEnt.getMetadata(PlayerID, "plan");
     186             : 
     187             :                                 // Current military guards (with Soldier class) will have been assigned plan metadata, but healer guards
     188             :                                 // are not assigned a plan, and so they could be already moving to garrison somewhere due to low health.
     189           0 :                                 if (!guardEnt.hasClass("Soldier") && (plan == -2 || plan == -3))
     190           0 :                                         continue;
     191             : 
     192           0 :                                 let pos = holderEnt.position();
     193           0 :                                 let radius = holderEnt.obstructionRadius().max;
     194           0 :                                 if (pos)
     195           0 :                                         guardEnt.moveToRange(pos[0], pos[1], radius, radius + 5);
     196             :                         }
     197             :                 }
     198             :         }
     199             : 
     200           0 :         for (let evt of events.EntityRenamed)
     201             :         {
     202           0 :                 if (!this.guardEnts.has(evt.entity))
     203           0 :                         continue;
     204           0 :                 for (let data of this.criticalEnts.values())
     205             :                 {
     206           0 :                         if (!data.guards.has(evt.entity))
     207           0 :                                 continue;
     208           0 :                         data.guards.set(evt.newentity, data.guards.get(evt.entity));
     209           0 :                         data.guards.delete(evt.entity);
     210           0 :                         break;
     211             :                 }
     212           0 :                 this.guardEnts.set(evt.newentity, this.guardEnts.get(evt.entity));
     213           0 :                 this.guardEnts.delete(evt.entity);
     214             :         }
     215             : 
     216             :         // Check if new healers/guards need to be assigned to an ent
     217           0 :         for (let evt of events.Destroy)
     218             :         {
     219           0 :                 if (!evt.entityObj || evt.entityObj.owner() != PlayerID)
     220           0 :                         continue;
     221             : 
     222           0 :                 let entId = evt.entityObj.id();
     223           0 :                 if (this.criticalEnts.has(entId))
     224             :                 {
     225           0 :                         this.removeCriticalEnt(gameState, entId);
     226           0 :                         continue;
     227             :                 }
     228             : 
     229           0 :                 if (!this.guardEnts.has(entId))
     230           0 :                         continue;
     231             : 
     232           0 :                 for (let data of this.criticalEnts.values())
     233           0 :                         if (data.guards.has(entId))
     234             :                         {
     235           0 :                                 data.guards.delete(entId);
     236           0 :                                 if (evt.entityObj.hasClass("Healer"))
     237           0 :                                         --data.healersAssigned;
     238             :                                 else
     239           0 :                                         --data.guardsAssigned;
     240           0 :                                 break;
     241             :                         }
     242             : 
     243           0 :                 this.guardEnts.delete(entId);
     244             :         }
     245             : 
     246           0 :         for (let evt of events.UnGarrison)
     247             :         {
     248           0 :                 if (!this.guardEnts.has(evt.entity) && !this.criticalEnts.has(evt.entity))
     249           0 :                         continue;
     250             : 
     251           0 :                 let ent = gameState.getEntityById(evt.entity);
     252           0 :                 if (!ent)
     253           0 :                         continue;
     254             : 
     255             :                 // If this ent travelled to a criticalEnt's accessValue, try again to assign as a guard
     256           0 :                 if ((ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER ||
     257             :                      ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_GUARD) && !this.guardEnts.get(evt.entity))
     258             :                 {
     259           0 :                         this.assignGuardToCriticalEnt(gameState, ent, ent.getMetadata(PlayerID, "guardedEnt"));
     260           0 :                         continue;
     261             :                 }
     262             : 
     263           0 :                 if (!this.criticalEnts.has(evt.entity))
     264           0 :                         continue;
     265             : 
     266             :                 // If this is a hero, try to assign ents that should be guarding it, but couldn't previously
     267           0 :                 let criticalEnt = this.criticalEnts.get(evt.entity);
     268           0 :                 for (let [id, isGuarding] of this.guardEnts)
     269             :                 {
     270           0 :                         if (criticalEnt.guards.size >= this.healersPerCriticalEnt)
     271           0 :                                 break;
     272             : 
     273           0 :                         if (!isGuarding)
     274             :                         {
     275           0 :                                 let guardEnt = gameState.getEntityById(id);
     276           0 :                                 if (guardEnt)
     277           0 :                                         this.assignGuardToCriticalEnt(gameState, guardEnt, evt.entity);
     278             :                         }
     279             :                 }
     280             :         }
     281             : 
     282           0 :         for (let evt of events.OwnershipChanged)
     283             :         {
     284           0 :                 if (evt.from == PlayerID && this.criticalEnts.has(evt.entity))
     285             :                 {
     286           0 :                         this.removeCriticalEnt(gameState, evt.entity);
     287           0 :                         continue;
     288             :                 }
     289           0 :                 if (evt.from == 0 && this.targetedGaiaRelics.has(evt.entity))
     290           0 :                         this.abortCaptureGaiaRelic(gameState, evt.entity);
     291             : 
     292           0 :                 if (evt.to != PlayerID)
     293           0 :                         continue;
     294             : 
     295           0 :                 let ent = gameState.getEntityById(evt.entity);
     296           0 :                 if (ent && (gameState.getVictoryConditions().has("wonder") && ent.hasClass("Wonder") ||
     297             :                             gameState.getVictoryConditions().has("capture_the_relic") && ent.hasClass("Relic")))
     298             :                 {
     299           0 :                         this.criticalEnts.set(ent.id(), { "guardsAssigned": 0, "guards": new Map() });
     300             :                         // Move captured relics to the closest base
     301           0 :                         if (ent.hasClass("Relic"))
     302           0 :                                 this.pickCriticalEntRetreatLocation(gameState, ent, false);
     303             :                 }
     304             :         }
     305             : };
     306             : 
     307           0 : PETRA.VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalEntId)
     308             : {
     309           0 :         for (let [guardId, role] of this.criticalEnts.get(criticalEntId).guards)
     310             :         {
     311           0 :                 let guardEnt = gameState.getEntityById(guardId);
     312           0 :                 if (!guardEnt)
     313           0 :                         continue;
     314             : 
     315           0 :                 if (role == "healer")
     316           0 :                         this.guardEnts.set(guardId, false);
     317             :                 else
     318             :                 {
     319           0 :                         guardEnt.setMetadata(PlayerID, "plan", -1);
     320           0 :                         guardEnt.setMetadata(PlayerID, "role", undefined);
     321           0 :                         this.guardEnts.delete(guardId);
     322             :                 }
     323             : 
     324           0 :                 if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
     325           0 :                         guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
     326             :         }
     327           0 :         this.criticalEnts.delete(criticalEntId);
     328             : };
     329             : 
     330             : /**
     331             :  * Train more healers to be later affected to critical entities if needed
     332             :  */
     333           0 : PETRA.VictoryManager.prototype.manageCriticalEntHealers = function(gameState, queues)
     334             : {
     335           0 :         if (gameState.ai.HQ.saveResources || queues.healer.hasQueuedUnits() ||
     336             :             !gameState.getOwnEntitiesByClass("Temple", true).hasEntities() ||
     337             :             this.guardEnts.size > Math.min(gameState.getPopulationMax() / 10, gameState.getPopulation() / 4))
     338           0 :                 return;
     339             : 
     340           0 :         for (let data of this.criticalEnts.values())
     341             :         {
     342           0 :                 if (data.healersAssigned === undefined || data.healersAssigned >= this.healersPerCriticalEnt)
     343           0 :                         continue;
     344           0 :                 let template = gameState.applyCiv("units/{civ}/support_healer_b");
     345           0 :                 queues.healer.addPlan(new PETRA.TrainingPlan(gameState, template, { "role": PETRA.Worker.ROLE_CRITICAL_ENT_HEALER, "base": 0 }, 1, 1));
     346           0 :                 return;
     347             :         }
     348             : };
     349             : 
     350             : /**
     351             :  * Try to keep some military units guarding any criticalEnts, if we can afford it.
     352             :  * If we have too low a population and require units for other needs, remove guards so they can be reassigned.
     353             :  * TODO: Swap citizen soldier guards with champions if they become available.
     354             :  */
     355           0 : PETRA.VictoryManager.prototype.manageCriticalEntGuards = function(gameState)
     356             : {
     357           0 :         let numWorkers = gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length;
     358           0 :         if (numWorkers < 20)
     359             :         {
     360           0 :                 for (let data of this.criticalEnts.values())
     361             :                 {
     362           0 :                         for (let guardId of data.guards.keys())
     363             :                         {
     364           0 :                                 let guardEnt = gameState.getEntityById(guardId);
     365           0 :                                 if (!guardEnt || !guardEnt.hasClass("CitizenSoldier") ||
     366             :                                     guardEnt.getMetadata(PlayerID, "role") !== PETRA.Worker.ROLE_CRITICAL_ENT_GUARD)
     367           0 :                                         continue;
     368             : 
     369           0 :                                 guardEnt.removeGuard();
     370           0 :                                 guardEnt.setMetadata(PlayerID, "plan", -1);
     371           0 :                                 guardEnt.setMetadata(PlayerID, "role", undefined);
     372           0 :                                 this.guardEnts.delete(guardId);
     373           0 :                                 --data.guardsAssigned;
     374             : 
     375           0 :                                 if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
     376           0 :                                         guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
     377             : 
     378           0 :                                 if (++numWorkers >= 20)
     379           0 :                                         break;
     380             :                         }
     381           0 :                         if (numWorkers >= 20)
     382           0 :                                 break;
     383             :                 }
     384             :         }
     385             : 
     386           0 :         let minWorkers = 25;
     387           0 :         let deltaWorkers = 3;
     388           0 :         for (let [id, data] of this.criticalEnts)
     389             :         {
     390           0 :                 let criticalEnt = gameState.getEntityById(id);
     391           0 :                 if (!criticalEnt)
     392           0 :                         continue;
     393             : 
     394           0 :                 let militaryGuardsPerCriticalEnt = (criticalEnt.hasClass("Wonder") ? 10 : 4) +
     395             :                         Math.round(this.Config.personality.defensive * 5);
     396             : 
     397           0 :                 if (data.guardsAssigned >= militaryGuardsPerCriticalEnt)
     398           0 :                         continue;
     399             : 
     400             :                 // First try to pick guards in the criticalEnt's accessIndex, to avoid unnecessary transports
     401           0 :                 for (let checkForSameAccess of [true, false])
     402             :                 {
     403             :                         // First try to assign any Champion units we might have
     404           0 :                         for (let entity of gameState.getOwnEntitiesByClass("Champion", true).values())
     405             :                         {
     406           0 :                                 if (!this.tryAssignMilitaryGuard(gameState, entity, criticalEnt, checkForSameAccess))
     407           0 :                                         continue;
     408           0 :                                 if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt)
     409           0 :                                         break;
     410             :                         }
     411             : 
     412           0 :                         if (data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
     413           0 :                                 break;
     414             : 
     415           0 :                         for (let entity of gameState.ai.HQ.attackManager.outOfPlan.values())
     416             :                         {
     417           0 :                                 if (!this.tryAssignMilitaryGuard(gameState, entity, criticalEnt, checkForSameAccess))
     418           0 :                                         continue;
     419           0 :                                 --numWorkers;
     420           0 :                                 if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
     421           0 :                                         break;
     422             :                         }
     423             : 
     424           0 :                         if (data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
     425           0 :                                 break;
     426             : 
     427           0 :                         for (let entity of gameState.getOwnEntitiesByClass("Soldier", true).values())
     428             :                         {
     429           0 :                                 if (!this.tryAssignMilitaryGuard(gameState, entity, criticalEnt, checkForSameAccess))
     430           0 :                                         continue;
     431           0 :                                 --numWorkers;
     432           0 :                                 if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
     433           0 :                                         break;
     434             :                         }
     435             : 
     436           0 :                         if (data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
     437           0 :                                 break;
     438             :                 }
     439             :         }
     440             : };
     441             : 
     442           0 : PETRA.VictoryManager.prototype.tryAssignMilitaryGuard = function(gameState, guardEnt, criticalEnt, checkForSameAccess)
     443             : {
     444           0 :         if (guardEnt.getMetadata(PlayerID, "plan") !== undefined ||
     445             :             guardEnt.getMetadata(PlayerID, "transport") !== undefined || this.criticalEnts.has(guardEnt.id()) ||
     446             :             checkForSameAccess && (!guardEnt.position() || !criticalEnt.position() ||
     447             :             PETRA.getLandAccess(gameState, criticalEnt) != PETRA.getLandAccess(gameState, guardEnt)))
     448           0 :                 return false;
     449             : 
     450           0 :         if (!this.assignGuardToCriticalEnt(gameState, guardEnt, criticalEnt.id()))
     451           0 :                 return false;
     452             : 
     453           0 :         guardEnt.setMetadata(PlayerID, "plan", -2);
     454           0 :         guardEnt.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_CRITICAL_ENT_GUARD);
     455           0 :         return true;
     456             : };
     457             : 
     458           0 : PETRA.VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency)
     459             : {
     460           0 :         gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency);
     461           0 :         let plan = criticalEnt.getMetadata(PlayerID, "plan");
     462             : 
     463           0 :         if (plan == -2 || plan == -3)
     464           0 :                 return;
     465             : 
     466           0 :         if (this.criticalEnts.get(criticalEnt.id()).garrisonEmergency)
     467           0 :                 this.criticalEnts.get(criticalEnt.id()).garrisonEmergency = false;
     468             : 
     469             :         // Couldn't find a place to garrison, so the ent will flee from attacks
     470           0 :         if (!criticalEnt.hasClass("Relic") && criticalEnt.getStance() != "passive")
     471           0 :                 criticalEnt.setStance("passive");
     472           0 :         let accessIndex = PETRA.getLandAccess(gameState, criticalEnt);
     473           0 :         let bestBase = PETRA.getBestBase(gameState, criticalEnt, true);
     474           0 :         if (bestBase.accessIndex == accessIndex)
     475             :         {
     476           0 :                 const bestBasePos = bestBase.anchor.position();
     477           0 :                 criticalEnt.moveToRange(bestBasePos[0], bestBasePos[1],
     478             :                         0, bestBase.anchor.obstructionRadius().max);
     479             :         }
     480             : };
     481             : 
     482             : /**
     483             :  * Only send the guard command if the guard's accessIndex is the same as the critical ent
     484             :  * and the critical ent has a position (i.e. not garrisoned).
     485             :  * Request a transport if the accessIndex value is different, and if a transport is needed,
     486             :  * the guardEnt will be given metadata describing which entity it is being sent to guard,
     487             :  * which will be used once its transport has finished.
     488             :  * Return false if the guardEnt is not a valid guard unit (i.e. cannot guard or is being transported).
     489             :  */
     490           0 : PETRA.VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt, criticalEntId)
     491             : {
     492           0 :         if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard())
     493           0 :                 return false;
     494             : 
     495           0 :         if (criticalEntId && !this.criticalEnts.has(criticalEntId))
     496             :         {
     497           0 :                 criticalEntId = undefined;
     498           0 :                 if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
     499           0 :                         guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
     500             :         }
     501             : 
     502           0 :         if (!criticalEntId)
     503             :         {
     504           0 :                 let isHealer = guardEnt.hasClass("Healer");
     505             : 
     506             :                 // Assign to the critical ent with the fewest guards
     507           0 :                 let min = Math.min();
     508           0 :                 for (let [id, data] of this.criticalEnts)
     509             :                 {
     510           0 :                         if (isHealer && (data.healersAssigned === undefined || data.healersAssigned > min))
     511           0 :                                 continue;
     512           0 :                         if (!isHealer && data.guardsAssigned > min)
     513           0 :                                 continue;
     514             : 
     515           0 :                         criticalEntId = id;
     516           0 :                         min = isHealer ? data.healersAssigned : data.guardsAssigned;
     517             :                 }
     518           0 :                 if (criticalEntId)
     519             :                 {
     520           0 :                         let data = this.criticalEnts.get(criticalEntId);
     521           0 :                         if (isHealer)
     522           0 :                                 ++data.healersAssigned;
     523             :                         else
     524           0 :                                 ++data.guardsAssigned;
     525             :                 }
     526             :         }
     527             : 
     528           0 :         if (!criticalEntId)
     529             :         {
     530           0 :                 if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
     531           0 :                         guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
     532           0 :                 return false;
     533             :         }
     534             : 
     535           0 :         let criticalEnt = gameState.getEntityById(criticalEntId);
     536           0 :         if (!criticalEnt || !criticalEnt.position() || !guardEnt.position())
     537             :         {
     538           0 :                 this.guardEnts.set(guardEnt.id(), false);
     539           0 :                 return false;
     540             :         }
     541             : 
     542           0 :         if (guardEnt.getMetadata(PlayerID, "guardedEnt") != criticalEntId)
     543           0 :                 guardEnt.setMetadata(PlayerID, "guardedEnt", criticalEntId);
     544             : 
     545           0 :         let guardEntAccess = PETRA.getLandAccess(gameState, guardEnt);
     546           0 :         let criticalEntAccess = PETRA.getLandAccess(gameState, criticalEnt);
     547           0 :         if (guardEntAccess == criticalEntAccess)
     548             :         {
     549           0 :                 let queued = PETRA.returnResources(gameState, guardEnt);
     550           0 :                 guardEnt.guard(criticalEnt, queued);
     551           0 :                 const guardRole = guardEnt.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER ? "healer" : "guard";
     552           0 :                 this.criticalEnts.get(criticalEntId).guards.set(guardEnt.id(), guardRole);
     553             : 
     554             :                 // Switch this guard ent to the criticalEnt's base
     555           0 :                 if (criticalEnt.hasClass("Structure") && criticalEnt.getMetadata(PlayerID, "base") !== undefined)
     556           0 :                         guardEnt.setMetadata(PlayerID, "base", criticalEnt.getMetadata(PlayerID, "base"));
     557             :         }
     558             :         else
     559           0 :                 gameState.ai.HQ.navalManager.requireTransport(gameState, guardEnt, guardEntAccess, criticalEntAccess, criticalEnt.position());
     560             : 
     561           0 :         this.guardEnts.set(guardEnt.id(), guardEntAccess == criticalEntAccess);
     562           0 :         return true;
     563             : };
     564             : 
     565           0 : PETRA.VictoryManager.prototype.resetCaptureGaiaRelic = function(gameState)
     566             : {
     567             :         // Do not capture gaia relics too frequently as the ai has access to the entire map
     568           0 :         this.tryCaptureGaiaRelicLapseTime = gameState.ai.elapsedTime + 240 - 30 * (this.Config.difficulty - 3);
     569           0 :         this.tryCaptureGaiaRelic = false;
     570             : };
     571             : 
     572           0 : PETRA.VictoryManager.prototype.update = function(gameState, events, queues)
     573             : {
     574             :         // Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide)
     575           0 :         if (gameState.ai.playedTurn == 1)
     576           0 :                 this.init(gameState);
     577             : 
     578           0 :         this.checkEvents(gameState, events);
     579             : 
     580           0 :         if (gameState.ai.playedTurn % 10 != 0 ||
     581             :             !gameState.getVictoryConditions().has("wonder") && !gameState.getVictoryConditions().has("regicide") &&
     582             :             !gameState.getVictoryConditions().has("capture_the_relic"))
     583           0 :                 return;
     584             : 
     585           0 :         this.manageCriticalEntGuards(gameState);
     586             : 
     587           0 :         if (gameState.getVictoryConditions().has("wonder"))
     588           0 :                 gameState.ai.HQ.buildWonder(gameState, queues, true);
     589             : 
     590           0 :         if (gameState.getVictoryConditions().has("regicide"))
     591             :         {
     592           0 :                 for (let id of this.criticalEnts.keys())
     593             :                 {
     594           0 :                         let ent = gameState.getEntityById(id);
     595           0 :                         if (ent && ent.healthLevel() > this.Config.garrisonHealthLevel.high && ent.hasClass("Soldier") &&
     596             :                             ent.getStance() != "aggressive")
     597           0 :                                 ent.setStance("aggressive");
     598             :                 }
     599           0 :                 this.manageCriticalEntHealers(gameState, queues);
     600             :         }
     601             : 
     602           0 :         if (gameState.getVictoryConditions().has("capture_the_relic"))
     603             :         {
     604           0 :                 if (!this.tryCaptureGaiaRelic && gameState.ai.elapsedTime > this.tryCaptureGaiaRelicLapseTime)
     605           0 :                         this.tryCaptureGaiaRelic = true;
     606             : 
     607             :                 // Reinforce (if needed) any raid currently trying to capture a gaia relic
     608           0 :                 for (let relicId of this.targetedGaiaRelics.keys())
     609             :                 {
     610           0 :                         let relic = gameState.getEntityById(relicId);
     611           0 :                         if (!relic || relic.owner() != 0)
     612           0 :                                 this.abortCaptureGaiaRelic(gameState, relicId);
     613             :                         else
     614           0 :                                 this.captureGaiaRelic(gameState, relic);
     615             :                 }
     616             :                 // And look for some new gaia relics visible by any of our units
     617             :                 // or that may be on our territory
     618           0 :                 let allGaiaRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).filter(relic => relic.owner() == 0);
     619           0 :                 for (let relic of allGaiaRelics.values())
     620             :                 {
     621           0 :                         let relicPosition = relic.position();
     622           0 :                         if (!relicPosition || this.targetedGaiaRelics.has(relic.id()))
     623           0 :                                 continue;
     624           0 :                         let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(relicPosition);
     625           0 :                         if (territoryOwner == PlayerID)
     626             :                         {
     627           0 :                                 this.targetedGaiaRelics.set(relic.id(), []);
     628           0 :                                 this.captureGaiaRelic(gameState, relic);
     629           0 :                                 break;
     630             :                         }
     631             : 
     632           0 :                         if (territoryOwner != 0 && gameState.isPlayerEnemy(territoryOwner))
     633           0 :                                 continue;
     634             : 
     635           0 :                         for (let ent of gameState.getOwnUnits().values())
     636             :                         {
     637           0 :                                 if (!ent.position() || !ent.visionRange())
     638           0 :                                         continue;
     639           0 :                                 if (API3.SquareVectorDistance(ent.position(), relicPosition) > Math.square(ent.visionRange()))
     640           0 :                                         continue;
     641           0 :                                 this.targetedGaiaRelics.set(relic.id(), []);
     642           0 :                                 this.captureGaiaRelic(gameState, relic);
     643           0 :                                 break;
     644             :                         }
     645             :                 }
     646             :         }
     647             : };
     648             : 
     649             : /**
     650             :  * Send an expedition to capture a gaia relic, or reinforce an existing one.
     651             :  */
     652           0 : PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic)
     653             : {
     654           0 :         let capture = -relic.defaultRegenRate();
     655           0 :         let sumCapturePoints = relic.capturePoints().reduce((a, b) => a + b);
     656           0 :         let plans = this.targetedGaiaRelics.get(relic.id());
     657           0 :         for (let plan of plans)
     658             :         {
     659           0 :                 let attack = gameState.ai.HQ.attackManager.getPlan(plan);
     660           0 :                 if (!attack)
     661           0 :                         continue;
     662           0 :                 for (let ent of attack.unitCollection.values())
     663           0 :                         capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture");
     664             :         }
     665             :         // No need to make a new attack if already enough units
     666           0 :         if (capture > sumCapturePoints / 50)
     667           0 :                 return;
     668           0 :         let relicPosition = relic.position();
     669           0 :         let access = PETRA.getLandAccess(gameState, relic);
     670           0 :         let units = gameState.getOwnUnits().filter(ent => {
     671           0 :                 if (!ent.position() || !ent.canCapture(relic))
     672           0 :                         return false;
     673           0 :                 if (ent.getMetadata(PlayerID, "transport") !== undefined)
     674           0 :                         return false;
     675           0 :                 if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined)
     676           0 :                         return false;
     677           0 :                 let plan = ent.getMetadata(PlayerID, "plan");
     678           0 :                 if (plan == -2 || plan == -3)
     679           0 :                         return false;
     680           0 :                 if (plan !== undefined && plan >= 0)
     681             :                 {
     682           0 :                         let attack = gameState.ai.HQ.attackManager.getPlan(plan);
     683           0 :                         if (attack && (attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED || attack.type === PETRA.AttackPlan.TYPE_RAID))
     684           0 :                                 return false;
     685             :                 }
     686           0 :                 if (PETRA.getLandAccess(gameState, ent) != access)
     687           0 :                         return false;
     688           0 :                 return true;
     689             :         }).filterNearest(relicPosition);
     690           0 :         let expedition = [];
     691           0 :         for (let ent of units.values())
     692             :         {
     693           0 :                 capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture");
     694           0 :                 expedition.push(ent);
     695           0 :                 if (capture > sumCapturePoints / 25)
     696           0 :                         break;
     697             :         }
     698           0 :         if (!expedition.length || !plans.length && capture < sumCapturePoints / 100)
     699           0 :                 return;
     700           0 :         let attack = gameState.ai.HQ.attackManager.raidTargetEntity(gameState, relic);
     701           0 :         if (!attack)
     702           0 :                 return;
     703           0 :         let plan = attack.name;
     704           0 :         attack.rallyPoint = undefined;
     705           0 :         for (let ent of expedition)
     706             :         {
     707           0 :                 ent.setMetadata(PlayerID, "plan", plan);
     708           0 :                 attack.unitCollection.updateEnt(ent);
     709           0 :                 if (!attack.rallyPoint)
     710           0 :                         attack.rallyPoint = ent.position();
     711             :         }
     712           0 :         attack.forceStart();
     713           0 :         this.targetedGaiaRelics.get(relic.id()).push(plan);
     714             : };
     715             : 
     716           0 : PETRA.VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relicId)
     717             : {
     718           0 :         for (let plan of this.targetedGaiaRelics.get(relicId))
     719             :         {
     720           0 :                 let attack = gameState.ai.HQ.attackManager.getPlan(plan);
     721           0 :                 if (attack)
     722           0 :                         attack.Abort(gameState);
     723             :         }
     724           0 :         this.targetedGaiaRelics.delete(relicId);
     725             : };
     726             : 
     727           0 : PETRA.VictoryManager.prototype.Serialize = function()
     728             : {
     729           0 :         return {
     730             :                 "criticalEnts": this.criticalEnts,
     731             :                 "guardEnts": this.guardEnts,
     732             :                 "healersPerCriticalEnt": this.healersPerCriticalEnt,
     733             :                 "tryCaptureGaiaRelic": this.tryCaptureGaiaRelic,
     734             :                 "tryCaptureGaiaRelicLapseTime": this.tryCaptureGaiaRelicLapseTime,
     735             :                 "targetedGaiaRelics": this.targetedGaiaRelics
     736             :         };
     737             : };
     738             : 
     739           0 : PETRA.VictoryManager.prototype.Deserialize = function(data)
     740             : {
     741           0 :         for (let key in data)
     742           0 :                 this[key] = data[key];
     743             : };

Generated by: LCOV version 1.14