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

          Line data    Source code
       1             : /**
       2             :  * Headquarters
       3             :  * Deal with high level logic for the AI. Most of the interesting stuff gets done here.
       4             :  * Some tasks:
       5             :  *  -defining RESS needs
       6             :  *  -BO decisions.
       7             :  *     > training workers
       8             :  *     > building stuff (though we'll send that to bases)
       9             :  *  -picking strategy (specific manager?)
      10             :  *  -diplomacy -> diplomacyManager
      11             :  *  -planning attacks -> attackManager
      12             :  *  -picking new CC locations.
      13             :  */
      14           0 : PETRA.HQ = function(Config)
      15             : {
      16           0 :         this.Config = Config;
      17           0 :         this.phasing = 0;       // existing values: 0 means no, i > 0 means phasing towards phase i
      18             : 
      19             :         // Cache various quantities.
      20           0 :         this.turnCache = {};
      21           0 :         this.lastFailedGather = {};
      22             : 
      23           0 :         this.firstBaseConfig = false;
      24             : 
      25             :         // Workers configuration.
      26           0 :         this.targetNumWorkers = this.Config.Economy.targetNumWorkers;
      27           0 :         this.supportRatio = this.Config.Economy.supportRatio;
      28             : 
      29           0 :         this.fortStartTime = 180;       // Sentry towers, will start at fortStartTime + towerLapseTime.
      30           0 :         this.towerStartTime = 0;        // Stone towers, will start as soon as available (town phase).
      31           0 :         this.towerLapseTime = this.Config.Military.towerLapseTime;
      32           0 :         this.fortressStartTime = 0;     // Fortresses, will start as soon as available (city phase).
      33           0 :         this.fortressLapseTime = this.Config.Military.fortressLapseTime;
      34           0 :         this.extraTowers = Math.round(Math.min(this.Config.difficulty, 3) * this.Config.personality.defensive);
      35           0 :         this.extraFortresses = Math.round(Math.max(Math.min(this.Config.difficulty - 1, 2), 0) * this.Config.personality.defensive);
      36             : 
      37           0 :         this.basesManager = new PETRA.BasesManager(this.Config);
      38           0 :         this.attackManager = new PETRA.AttackManager(this.Config);
      39           0 :         this.buildManager = new PETRA.BuildManager();
      40           0 :         this.defenseManager = new PETRA.DefenseManager(this.Config);
      41           0 :         this.tradeManager = new PETRA.TradeManager(this.Config);
      42           0 :         this.navalManager = new PETRA.NavalManager(this.Config);
      43           0 :         this.researchManager = new PETRA.ResearchManager(this.Config);
      44           0 :         this.diplomacyManager = new PETRA.DiplomacyManager(this.Config);
      45           0 :         this.garrisonManager = new PETRA.GarrisonManager(this.Config);
      46           0 :         this.victoryManager = new PETRA.VictoryManager(this.Config);
      47           0 :         this.emergencyManager = new PETRA.EmergencyManager(this.Config);
      48             : 
      49           0 :         this.capturableTargets = new Map();
      50           0 :         this.capturableTargetsTime = 0;
      51             : };
      52             : 
      53             : /** More initialisation for stuff that needs the gameState */
      54           0 : PETRA.HQ.prototype.init = function(gameState, queues)
      55             : {
      56           0 :         this.territoryMap = PETRA.createTerritoryMap(gameState);
      57             :         // create borderMap: flag cells on the border of the map
      58             :         // then this map will be completed with our frontier in updateTerritories
      59           0 :         this.borderMap = PETRA.createBorderMap(gameState);
      60             :         // list of allowed regions
      61           0 :         this.landRegions = {};
      62             :         // try to determine if we have a water map
      63           0 :         this.navalMap = false;
      64           0 :         this.navalRegions = {};
      65             : 
      66           0 :         this.treasures = gameState.getEntities().filter(ent => ent.isTreasure());
      67           0 :         this.treasures.registerUpdates();
      68           0 :         this.currentPhase = gameState.currentPhase();
      69           0 :         this.decayingStructures = new Set();
      70           0 :         this.emergencyManager.init(gameState);
      71             : };
      72             : 
      73             : /**
      74             :  * initialization needed after deserialization (only called when deserialization)
      75             :  */
      76           0 : PETRA.HQ.prototype.postinit = function(gameState)
      77             : {
      78           0 :         this.basesManager.postinit(gameState);
      79           0 :         this.updateTerritories(gameState);
      80             : };
      81             : 
      82             : /**
      83             :  * returns the sea index linking regions 1 and region 2 (supposed to be different land region)
      84             :  * otherwise return undefined
      85             :  * for the moment, only the case land-sea-land is supported
      86             :  */
      87           0 : PETRA.HQ.prototype.getSeaBetweenIndices = function(gameState, index1, index2)
      88             : {
      89           0 :         let path = gameState.ai.accessibility.getTrajectToIndex(index1, index2);
      90           0 :         if (path && path.length == 3 && gameState.ai.accessibility.regionType[path[1]] == "water")
      91           0 :                 return path[1];
      92             : 
      93           0 :         if (this.Config.debug > 1)
      94             :         {
      95           0 :                 API3.warn("bad path from " + index1 + " to " + index2 + " ??? " + uneval(path));
      96           0 :                 API3.warn(" regionLinks start " + uneval(gameState.ai.accessibility.regionLinks[index1]));
      97           0 :                 API3.warn(" regionLinks end   " + uneval(gameState.ai.accessibility.regionLinks[index2]));
      98             :         }
      99           0 :         return undefined;
     100             : };
     101             : 
     102           0 : PETRA.HQ.prototype.checkEvents = function(gameState, events)
     103             : {
     104           0 :         this.buildManager.checkEvents(gameState, events);
     105             : 
     106           0 :         if (events.TerritoriesChanged.length || events.DiplomacyChanged.length)
     107           0 :                 this.updateTerritories(gameState);
     108             : 
     109           0 :         for (let evt of events.DiplomacyChanged)
     110             :         {
     111           0 :                 if (evt.player != PlayerID && evt.otherPlayer != PlayerID)
     112           0 :                         continue;
     113             :                 // Reset the entities collections which depend on diplomacy
     114           0 :                 gameState.resetOnDiplomacyChanged();
     115           0 :                 break;
     116             :         }
     117             : 
     118           0 :         this.basesManager.checkEvents(gameState, events);
     119             : 
     120           0 :         for (let evt of events.ConstructionFinished)
     121             :         {
     122           0 :                 if (evt.newentity == evt.entity)  // repaired building
     123           0 :                         continue;
     124           0 :                 let ent = gameState.getEntityById(evt.newentity);
     125           0 :                 if (!ent || ent.owner() != PlayerID)
     126           0 :                         continue;
     127           0 :                 if (ent.hasClass("Market") && this.maxFields)
     128           0 :                         this.maxFields = false;
     129             :         }
     130             : 
     131           0 :         for (let evt of events.OwnershipChanged)   // capture events
     132             :         {
     133           0 :                 if (evt.to != PlayerID)
     134           0 :                         continue;
     135           0 :                 let ent = gameState.getEntityById(evt.entity);
     136           0 :                 if (!ent)
     137           0 :                         continue;
     138           0 :                 if (!ent.hasClass("Unit"))
     139             :                 {
     140           0 :                         if (ent.decaying())
     141             :                         {
     142           0 :                                 if (ent.isGarrisonHolder() && this.garrisonManager.addDecayingStructure(gameState, evt.entity, true))
     143           0 :                                         continue;
     144           0 :                                 if (!this.decayingStructures.has(evt.entity))
     145           0 :                                         this.decayingStructures.add(evt.entity);
     146             :                         }
     147           0 :                         continue;
     148             :                 }
     149             : 
     150           0 :                 ent.setMetadata(PlayerID, "role", undefined);
     151           0 :                 ent.setMetadata(PlayerID, "subrole", undefined);
     152           0 :                 ent.setMetadata(PlayerID, "plan", undefined);
     153           0 :                 ent.setMetadata(PlayerID, "PartOfArmy", undefined);
     154           0 :                 if (ent.hasClass("Trader"))
     155             :                 {
     156           0 :                         ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER);
     157           0 :                         ent.setMetadata(PlayerID, "route", undefined);
     158             :                 }
     159           0 :                 if (ent.hasClass("Worker"))
     160             :                 {
     161           0 :                         ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_WORKER);
     162           0 :                         ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_IDLE);
     163             :                 }
     164           0 :                 if (ent.hasClass("Ship"))
     165           0 :                         PETRA.setSeaAccess(gameState, ent);
     166           0 :                 if (!ent.hasClasses(["Support", "Ship"]) && ent.attackTypes() !== undefined)
     167           0 :                         ent.setMetadata(PlayerID, "plan", -1);
     168             :         }
     169             : 
     170             :         // deal with the different rally points of training units: the rally point is set when the training starts
     171             :         // for the time being, only autogarrison is used
     172             : 
     173           0 :         for (let evt of events.TrainingStarted)
     174             :         {
     175           0 :                 let ent = gameState.getEntityById(evt.entity);
     176           0 :                 if (!ent || !ent.isOwn(PlayerID))
     177           0 :                         continue;
     178             : 
     179           0 :                 if (!ent._entity.trainingQueue || !ent._entity.trainingQueue.length)
     180           0 :                         continue;
     181           0 :                 let metadata = ent._entity.trainingQueue[0].metadata;
     182           0 :                 if (metadata && metadata.garrisonType)
     183           0 :                         ent.setRallyPoint(ent, "garrison");  // trained units will autogarrison
     184             :                 else
     185           0 :                         ent.unsetRallyPoint();
     186             :         }
     187             : 
     188           0 :         for (let evt of events.TrainingFinished)
     189             :         {
     190           0 :                 for (let entId of evt.entities)
     191             :                 {
     192           0 :                         let ent = gameState.getEntityById(entId);
     193           0 :                         if (!ent || !ent.isOwn(PlayerID))
     194           0 :                                 continue;
     195             : 
     196           0 :                         if (!ent.position())
     197             :                         {
     198             :                                 // we are autogarrisoned, check that the holder is registered in the garrisonManager
     199           0 :                                 let holder = gameState.getEntityById(ent.garrisonHolderID());
     200           0 :                                 if (holder)
     201           0 :                                         this.garrisonManager.registerHolder(gameState, holder);
     202             :                         }
     203           0 :                         else if (ent.getMetadata(PlayerID, "garrisonType"))
     204             :                         {
     205             :                                 // we were supposed to be autogarrisoned, but this has failed (may-be full)
     206           0 :                                 ent.setMetadata(PlayerID, "garrisonType", undefined);
     207             :                         }
     208             : 
     209             :                         // Check if this unit is no more needed in its attack plan
     210             :                         // (happen when the training ends after the attack is started or aborted)
     211           0 :                         let plan = ent.getMetadata(PlayerID, "plan");
     212           0 :                         if (plan !== undefined && plan >= 0)
     213             :                         {
     214           0 :                                 let attack = this.attackManager.getPlan(plan);
     215           0 :                                 if (!attack || attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED)
     216           0 :                                         ent.setMetadata(PlayerID, "plan", -1);
     217             :                         }
     218             :                 }
     219             :         }
     220             : 
     221           0 :         for (let evt of events.TerritoryDecayChanged)
     222             :         {
     223           0 :                 let ent = gameState.getEntityById(evt.entity);
     224           0 :                 if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() !== undefined)
     225           0 :                         continue;
     226           0 :                 if (evt.to)
     227             :                 {
     228           0 :                         if (ent.isGarrisonHolder() && this.garrisonManager.addDecayingStructure(gameState, evt.entity))
     229           0 :                                 continue;
     230           0 :                         if (!this.decayingStructures.has(evt.entity))
     231           0 :                                 this.decayingStructures.add(evt.entity);
     232             :                 }
     233           0 :                 else if (ent.isGarrisonHolder())
     234           0 :                         this.garrisonManager.removeDecayingStructure(evt.entity);
     235             :         }
     236             : 
     237             :         // Then deals with decaying structures: destroy them if being lost to enemy (except in easier difficulties)
     238           0 :         if (this.Config.difficulty < PETRA.DIFFICULTY_EASY)
     239           0 :                 return;
     240           0 :         for (let entId of this.decayingStructures)
     241             :         {
     242           0 :                 let ent = gameState.getEntityById(entId);
     243           0 :                 if (ent && ent.decaying() && ent.isOwn(PlayerID))
     244             :                 {
     245           0 :                         let capture = ent.capturePoints();
     246           0 :                         if (!capture)
     247           0 :                                 continue;
     248           0 :                         let captureRatio = capture[PlayerID] / capture.reduce((a, b) => a + b);
     249           0 :                         if (captureRatio < 0.50)
     250           0 :                                 continue;
     251           0 :                         let decayToGaia = true;
     252           0 :                         for (let i = 1; i < capture.length; ++i)
     253             :                         {
     254           0 :                                 if (gameState.isPlayerAlly(i) || !capture[i])
     255           0 :                                         continue;
     256           0 :                                 decayToGaia = false;
     257           0 :                                 break;
     258             :                         }
     259           0 :                         if (decayToGaia)
     260           0 :                                 continue;
     261           0 :                         let ratioMax = 0.7 + randFloat(0, 0.1);
     262           0 :                         for (let evt of events.Attacked)
     263             :                         {
     264           0 :                                 if (ent.id() != evt.target)
     265           0 :                                         continue;
     266           0 :                                 ratioMax = 0.85 + randFloat(0, 0.1);
     267           0 :                                 break;
     268             :                         }
     269           0 :                         if (captureRatio > ratioMax)
     270           0 :                                 continue;
     271           0 :                         ent.destroy();
     272             :                 }
     273           0 :                 this.decayingStructures.delete(entId);
     274             :         }
     275             : };
     276             : 
     277           0 : PETRA.HQ.prototype.handleNewBase = function(gameState)
     278             : {
     279           0 :         if (!this.firstBaseConfig)
     280             :                 // This is our first base, let us configure our starting resources.
     281           0 :                 this.configFirstBase(gameState);
     282             :         else
     283             :         {
     284             :                 // Let us hope this new base will fix our possible resource shortage.
     285           0 :                 this.saveResources = undefined;
     286           0 :                 this.saveSpace = undefined;
     287           0 :                 this.maxFields = false;
     288             :         }
     289             : };
     290             : 
     291             : /** Ensure that all requirements are met when phasing up*/
     292           0 : PETRA.HQ.prototype.checkPhaseRequirements = function(gameState, queues)
     293             : {
     294           0 :         if (gameState.getNumberOfPhases() == this.currentPhase)
     295           0 :                 return;
     296             : 
     297           0 :         let requirements = gameState.getPhaseEntityRequirements(this.currentPhase + 1);
     298             :         let plan;
     299             :         let queue;
     300           0 :         for (let entityReq of requirements)
     301             :         {
     302             :                 // Village requirements are met elsewhere by constructing more houses
     303           0 :                 if (entityReq.class == "Village" || entityReq.class == "NotField")
     304           0 :                         continue;
     305           0 :                 if (gameState.getOwnEntitiesByClass(entityReq.class, true).length >= entityReq.count)
     306           0 :                         continue;
     307           0 :                 switch (entityReq.class)
     308             :                 {
     309             :                 case "Town":
     310           0 :                         if (!queues.economicBuilding.hasQueuedUnits() &&
     311             :                             !queues.militaryBuilding.hasQueuedUnits())
     312             :                         {
     313           0 :                                 if (!gameState.getOwnEntitiesByClass("Market", true).hasEntities() &&
     314             :                                     this.canBuild(gameState, "structures/{civ}/market"))
     315             :                                 {
     316           0 :                                         plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/market", { "phaseUp": true });
     317           0 :                                         queue = "economicBuilding";
     318           0 :                                         break;
     319             :                                 }
     320           0 :                                 if (!gameState.getOwnEntitiesByClass("Temple", true).hasEntities() &&
     321             :                                     this.canBuild(gameState, "structures/{civ}/temple"))
     322             :                                 {
     323           0 :                                         plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/temple", { "phaseUp": true });
     324           0 :                                         queue = "economicBuilding";
     325           0 :                                         break;
     326             :                                 }
     327           0 :                                 if (!gameState.getOwnEntitiesByClass("Forge", true).hasEntities() &&
     328             :                                     this.canBuild(gameState, "structures/{civ}/forge"))
     329             :                                 {
     330           0 :                                         plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/forge", { "phaseUp": true });
     331           0 :                                         queue = "militaryBuilding";
     332           0 :                                         break;
     333             :                                 }
     334             :                         }
     335           0 :                         break;
     336             :                 default:
     337             :                         // All classes not dealt with inside vanilla game.
     338             :                         // We put them for the time being on the economic queue, except if wonder
     339           0 :                         queue = entityReq.class == "Wonder" ? "wonder" : "economicBuilding";
     340           0 :                         if (!queues[queue].hasQueuedUnits())
     341             :                         {
     342           0 :                                 let structure = this.buildManager.findStructureWithClass(gameState, [entityReq.class]);
     343           0 :                                 if (structure && this.canBuild(gameState, structure))
     344           0 :                                         plan = new PETRA.ConstructionPlan(gameState, structure, { "phaseUp": true });
     345             :                         }
     346             :                 }
     347             : 
     348           0 :                 if (plan)
     349             :                 {
     350           0 :                         if (queue == "wonder")
     351             :                         {
     352           0 :                                 gameState.ai.queueManager.changePriority("majorTech", 400, { "phaseUp": true });
     353           0 :                                 plan.queueToReset = "majorTech";
     354             :                         }
     355             :                         else
     356             :                         {
     357           0 :                                 gameState.ai.queueManager.changePriority(queue, 1000, { "phaseUp": true });
     358           0 :                                 plan.queueToReset = queue;
     359             :                         }
     360           0 :                         queues[queue].addPlan(plan);
     361           0 :                         return;
     362             :                 }
     363             :         }
     364             : };
     365             : 
     366             : /** Called by any "phase" research plan once it's started */
     367           0 : PETRA.HQ.prototype.OnPhaseUp = function(gameState, phase)
     368             : {
     369             : };
     370             : 
     371             : /** This code trains citizen workers, trying to keep close to a ratio of worker/soldiers */
     372           0 : PETRA.HQ.prototype.trainMoreWorkers = function(gameState, queues)
     373             : {
     374             :         // default template
     375           0 :         let requirementsDef = [ ["costsResource", 1, "food"] ];
     376           0 :         const classesDef = ["Support+Worker"];
     377           0 :         let templateDef = this.findBestTrainableUnit(gameState, classesDef, requirementsDef);
     378             : 
     379             :         // counting the workers that aren't part of a plan
     380           0 :         let numberOfWorkers = 0;   // all workers
     381           0 :         let numberOfSupports = 0;  // only support workers (i.e. non fighting)
     382           0 :         gameState.getOwnUnits().forEach(ent => {
     383           0 :                 if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER && ent.getMetadata(PlayerID, "plan") === undefined)
     384             :                 {
     385           0 :                         ++numberOfWorkers;
     386           0 :                         if (ent.hasClass("Support"))
     387           0 :                                 ++numberOfSupports;
     388             :                 }
     389             :         });
     390           0 :         let numberInTraining = 0;
     391           0 :         gameState.getOwnTrainingFacilities().forEach(function(ent) {
     392           0 :                 for (let item of ent.trainingQueue())
     393             :                 {
     394           0 :                         numberInTraining += item.count;
     395           0 :                         if (item.metadata && item.metadata.role && item.metadata.role === PETRA.Worker.ROLE_WORKER &&
     396             :                             item.metadata.plan === undefined)
     397             :                         {
     398           0 :                                 numberOfWorkers += item.count;
     399           0 :                                 if (item.metadata.support)
     400           0 :                                         numberOfSupports += item.count;
     401             :                         }
     402             :                 }
     403             :         });
     404             : 
     405             :         // Anticipate the optimal batch size when this queue will start
     406             :         // and adapt the batch size of the first and second queued workers to the present population
     407             :         // to ease a possible recovery if our population was drastically reduced by an attack
     408             :         // (need to go up to second queued as it is accounted in queueManager)
     409           0 :         let size = numberOfWorkers < 12 ? 1 : Math.min(5, Math.ceil(numberOfWorkers / 10));
     410           0 :         if (queues.villager.plans[0])
     411             :         {
     412           0 :                 queues.villager.plans[0].number = Math.min(queues.villager.plans[0].number, size);
     413           0 :                 if (queues.villager.plans[1])
     414           0 :                         queues.villager.plans[1].number = Math.min(queues.villager.plans[1].number, size);
     415             :         }
     416           0 :         if (queues.citizenSoldier.plans[0])
     417             :         {
     418           0 :                 queues.citizenSoldier.plans[0].number = Math.min(queues.citizenSoldier.plans[0].number, size);
     419           0 :                 if (queues.citizenSoldier.plans[1])
     420           0 :                         queues.citizenSoldier.plans[1].number = Math.min(queues.citizenSoldier.plans[1].number, size);
     421             :         }
     422             : 
     423           0 :         let numberOfQueuedSupports = queues.villager.countQueuedUnits();
     424           0 :         let numberOfQueuedSoldiers = queues.citizenSoldier.countQueuedUnits();
     425           0 :         let numberQueued = numberOfQueuedSupports + numberOfQueuedSoldiers;
     426           0 :         let numberTotal = numberOfWorkers + numberQueued;
     427             : 
     428           0 :         if (this.saveResources && numberTotal > this.Config.Economy.popPhase2 + 10)
     429           0 :                 return;
     430           0 :         if (numberTotal > this.targetNumWorkers || (numberTotal >= this.Config.Economy.popPhase2 &&
     431             :                 this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2))))
     432           0 :                 return;
     433           0 :         if (numberQueued > 50 || (numberOfQueuedSupports > 20 && numberOfQueuedSoldiers > 20) || numberInTraining > 15)
     434           0 :                 return;
     435             : 
     436             :         // Choose whether we want soldiers or support units: when full pop, we aim at targetNumWorkers workers
     437             :         // with supportRatio fraction of support units. But we want to have more support (less cost) at startup.
     438             :         // So we take: supportRatio*targetNumWorkers*(1 - exp(-alfa*currentWorkers/supportRatio/targetNumWorkers))
     439             :         // This gives back supportRatio*targetNumWorkers when currentWorkers >> supportRatio*targetNumWorkers
     440             :         // and gives a ratio alfa at startup.
     441             : 
     442           0 :         let supportRatio = this.supportRatio;
     443           0 :         let alpha = 0.85;
     444           0 :         if (!gameState.isTemplateAvailable(gameState.applyCiv("structures/{civ}/field")))
     445           0 :                 supportRatio = Math.min(this.supportRatio, 0.1);
     446           0 :         if (this.attackManager.rushNumber < this.attackManager.maxRushes || this.attackManager.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].length)
     447           0 :                 alpha = 0.7;
     448           0 :         if (gameState.isCeasefireActive())
     449           0 :                 alpha += (1 - alpha) * Math.min(Math.max(gameState.ceasefireTimeRemaining - 120, 0), 180) / 180;
     450           0 :         let supportMax = supportRatio * this.targetNumWorkers;
     451           0 :         let supportNum = supportMax * (1 - Math.exp(-alpha*numberTotal/supportMax));
     452             : 
     453             :         let template;
     454           0 :         if (!templateDef || numberOfSupports + numberOfQueuedSupports > supportNum)
     455             :         {
     456             :                 let requirements;
     457           0 :                 if (numberTotal < 45)
     458           0 :                         requirements = [ ["speed", 0.5], ["costsResource", 0.5, "stone"], ["costsResource", 0.5, "metal"] ];
     459             :                 else
     460           0 :                         requirements = [ ["strength", 1] ];
     461             : 
     462           0 :                 const classes = [["CitizenSoldier", "Infantry"]];
     463             :                 // We want at least 33% ranged and 33% melee.
     464           0 :                 classes[0].push(pickRandom(["Ranged", "Melee", "Infantry"]));
     465             : 
     466           0 :                 template = this.findBestTrainableUnit(gameState, classes, requirements);
     467             :         }
     468             : 
     469             :         // If the template variable is empty, the default unit (Support unit) will be used
     470             :         // base "0" means automatic choice of base
     471           0 :         if (!template && templateDef)
     472           0 :                 queues.villager.addPlan(new PETRA.TrainingPlan(gameState, templateDef, { "role": PETRA.Worker.ROLE_WORKER, "base": 0, "support": true }, size, size));
     473           0 :         else if (template)
     474           0 :                 queues.citizenSoldier.addPlan(new PETRA.TrainingPlan(gameState, template, { "role": PETRA.Worker.ROLE_WORKER, "base": 0 }, size, size));
     475             : };
     476             : 
     477             : /** picks the best template based on parameters and classes */
     478           0 : PETRA.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirements)
     479             : {
     480             :         let units;
     481           0 :         if (classes.indexOf("Hero") != -1)
     482           0 :                 units = gameState.findTrainableUnits(classes, []);
     483             :         // We do not want siege tower as AI does not know how to use it nor hero when not explicitely specified.
     484             :         else
     485           0 :                 units = gameState.findTrainableUnits(classes, ["Hero", "SiegeTower"]);
     486             : 
     487           0 :         if (!units.length)
     488           0 :                 return undefined;
     489             : 
     490           0 :         let parameters = requirements.slice();
     491           0 :         let remainingResources = this.getTotalResourceLevel(gameState);    // resources (estimation) still gatherable in our territory
     492           0 :         let availableResources = gameState.ai.queueManager.getAvailableResources(gameState); // available (gathered) resources
     493           0 :         for (let type in remainingResources)
     494             :         {
     495           0 :                 if (availableResources[type] > 800)
     496           0 :                         continue;
     497           0 :                 if (remainingResources[type] > 800)
     498           0 :                         continue;
     499           0 :                 let costsResource = remainingResources[type] > 400 ? 0.6 : 0.2;
     500           0 :                 let toAdd = true;
     501           0 :                 for (let param of parameters)
     502             :                 {
     503           0 :                         if (param[0] != "costsResource" || param[2] != type)
     504           0 :                                 continue;
     505           0 :                         param[1] = Math.min(param[1], costsResource);
     506           0 :                         toAdd = false;
     507           0 :                         break;
     508             :                 }
     509           0 :                 if (toAdd)
     510           0 :                         parameters.push(["costsResource", costsResource, type]);
     511             :         }
     512             : 
     513           0 :         units.sort((a, b) => {
     514           0 :                 let aCost = 1 + a[1].costSum();
     515           0 :                 let bCost = 1 + b[1].costSum();
     516           0 :                 let aValue = 0.1;
     517           0 :                 let bValue = 0.1;
     518           0 :                 for (let param of parameters)
     519             :                 {
     520           0 :                         if (param[0] == "strength")
     521             :                         {
     522           0 :                                 aValue += PETRA.getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1];
     523           0 :                                 bValue += PETRA.getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1];
     524             :                         }
     525           0 :                         else if (param[0] == "siegeStrength")
     526             :                         {
     527           0 :                                 aValue += PETRA.getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1];
     528           0 :                                 bValue += PETRA.getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1];
     529             :                         }
     530           0 :                         else if (param[0] == "speed")
     531             :                         {
     532           0 :                                 aValue += a[1].walkSpeed() * param[1];
     533           0 :                                 bValue += b[1].walkSpeed() * param[1];
     534             :                         }
     535           0 :                         else if (param[0] == "costsResource")
     536             :                         {
     537             :                                 // requires a third parameter which is the resource
     538           0 :                                 if (a[1].cost()[param[2]])
     539           0 :                                         aValue *= param[1];
     540           0 :                                 if (b[1].cost()[param[2]])
     541           0 :                                         bValue *= param[1];
     542             :                         }
     543           0 :                         else if (param[0] == "canGather")
     544             :                         {
     545             :                                 // checking against wood, could be anything else really.
     546           0 :                                 if (a[1].resourceGatherRates() && a[1].resourceGatherRates()["wood.tree"])
     547           0 :                                         aValue *= param[1];
     548           0 :                                 if (b[1].resourceGatherRates() && b[1].resourceGatherRates()["wood.tree"])
     549           0 :                                         bValue *= param[1];
     550             :                         }
     551             :                         else
     552           0 :                                 API3.warn(" trainMoreUnits avec non prevu " + uneval(param));
     553             :                 }
     554           0 :                 return -aValue/aCost + bValue/bCost;
     555             :         });
     556           0 :         return units[0][0];
     557             : };
     558             : 
     559             : /**
     560             :  * returns an entity collection of workers through BaseManager.pickBuilders
     561             :  * TODO: when same accessIndex, sort by distance
     562             :  */
     563           0 : PETRA.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number)
     564             : {
     565           0 :         return this.basesManager.bulkPickWorkers(gameState, baseRef, number);
     566             : };
     567             : 
     568           0 : PETRA.HQ.prototype.getTotalResourceLevel = function(gameState, resources, proximity)
     569             : {
     570           0 :         return this.basesManager.getTotalResourceLevel(gameState, resources, proximity);
     571             : };
     572             : 
     573             : /**
     574             :  * Returns the current gather rate
     575             :  * This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that.
     576             :  */
     577           0 : PETRA.HQ.prototype.GetCurrentGatherRates = function(gameState)
     578             : {
     579           0 :         return this.basesManager.GetCurrentGatherRates(gameState);
     580             : };
     581             : 
     582             : /**
     583             :  * Returns the wanted gather rate.
     584             :  */
     585           0 : PETRA.HQ.prototype.GetWantedGatherRates = function(gameState)
     586             : {
     587           0 :         if (!this.turnCache.wantedRates)
     588           0 :                 this.turnCache.wantedRates = gameState.ai.queueManager.wantedGatherRates(gameState);
     589             : 
     590           0 :         return this.turnCache.wantedRates;
     591             : };
     592             : 
     593             : /**
     594             :  * Pick the resource which most needs another worker
     595             :  * How this works:
     596             :  * We get the rates we would want to have to be able to deal with our plans
     597             :  * We get our current rates
     598             :  * We compare; we pick the one where the discrepancy is highest.
     599             :  * Need to balance long-term needs and possible short-term needs.
     600             :  */
     601           0 : PETRA.HQ.prototype.pickMostNeededResources = function(gameState, allowedResources = [])
     602             : {
     603           0 :         let wantedRates = this.GetWantedGatherRates(gameState);
     604           0 :         let currentRates = this.GetCurrentGatherRates(gameState);
     605           0 :         if (!allowedResources.length)
     606           0 :                 allowedResources = Resources.GetCodes();
     607             : 
     608           0 :         let needed = [];
     609           0 :         for (let res of allowedResources)
     610           0 :                 needed.push({ "type": res, "wanted": wantedRates[res], "current": currentRates[res] });
     611             : 
     612           0 :         needed.sort((a, b) => {
     613           0 :                 if (a.current < a.wanted && b.current < b.wanted)
     614             :                 {
     615           0 :                         if (a.current && b.current)
     616           0 :                                 return b.wanted / b.current - a.wanted / a.current;
     617           0 :                         if (a.current)
     618           0 :                                 return 1;
     619           0 :                         if (b.current)
     620           0 :                                 return -1;
     621           0 :                         return b.wanted - a.wanted;
     622             :                 }
     623           0 :                 if (a.current < a.wanted || a.wanted && !b.wanted)
     624           0 :                         return -1;
     625           0 :                 if (b.current < b.wanted || b.wanted && !a.wanted)
     626           0 :                         return 1;
     627           0 :                 return a.current - a.wanted - b.current + b.wanted;
     628             :         });
     629           0 :         return needed;
     630             : };
     631             : 
     632             : /**
     633             :  * Returns the best position to build a new Civil Center
     634             :  * Whose primary function would be to reach new resources of type "resource".
     635             :  */
     636           0 : PETRA.HQ.prototype.findEconomicCCLocation = function(gameState, template, resource, proximity, fromStrategic)
     637             : {
     638             :         // This builds a map. The procedure is fairly simple. It adds the resource maps
     639             :         //      (which are dynamically updated and are made so that they will facilitate DP placement)
     640             :         // Then look for a good spot.
     641             : 
     642           0 :         Engine.ProfileStart("findEconomicCCLocation");
     643             : 
     644             :         // obstruction map
     645           0 :         let obstructions = PETRA.createObstructionMap(gameState, 0, template);
     646           0 :         let halfSize = 0;
     647           0 :         if (template.get("Footprint/Square"))
     648           0 :                 halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2;
     649           0 :         else if (template.get("Footprint/Circle"))
     650           0 :                 halfSize = +template.get("Footprint/Circle/@radius");
     651             : 
     652           0 :         let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
     653           0 :         const dpEnts = gameState.getOwnDropsites().filter(API3.Filters.not(API3.Filters.byClasses(["CivCentre", "Unit"])));
     654           0 :         let ccList = [];
     655           0 :         for (let cc of ccEnts.values())
     656           0 :                 ccList.push({ "ent": cc, "pos": cc.position(), "ally": gameState.isPlayerAlly(cc.owner()) });
     657           0 :         let dpList = [];
     658           0 :         for (let dp of dpEnts.values())
     659           0 :                 dpList.push({ "ent": dp, "pos": dp.position(), "territory": this.territoryMap.getOwner(dp.position()) });
     660             : 
     661             :         let bestIdx;
     662             :         let bestVal;
     663           0 :         let radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize);
     664           0 :         let scale = 250 * 250;
     665             :         let proxyAccess;
     666           0 :         let nbShips = this.navalManager.transportShips.length;
     667           0 :         if (proximity)  // this is our first base
     668             :         {
     669             :                 // if our first base, ensure room around
     670           0 :                 radius = Math.ceil((template.obstructionRadius().max + 8) / obstructions.cellSize);
     671             :                 // scale is the typical scale at which we want to find a location for our first base
     672             :                 // look for bigger scale if we start from a ship (access < 2) or from a small island
     673           0 :                 let cellArea = gameState.getPassabilityMap().cellSize * gameState.getPassabilityMap().cellSize;
     674           0 :                 proxyAccess = gameState.ai.accessibility.getAccessValue(proximity);
     675           0 :                 if (proxyAccess < 2 || cellArea*gameState.ai.accessibility.regionSize[proxyAccess] < 24000)
     676           0 :                         scale = 400 * 400;
     677             :         }
     678             : 
     679           0 :         let width = this.territoryMap.width;
     680           0 :         let cellSize = this.territoryMap.cellSize;
     681             : 
     682             :         // DistanceSquare cuts to other ccs (bigger or no cuts on inaccessible ccs to allow colonizing other islands).
     683           0 :         let reduce = (template.hasClass("Colony") ? 30 : 0) + 30 * this.Config.personality.defensive;
     684           0 :         let nearbyRejected = Math.square(120);                  // Reject if too near from any cc
     685           0 :         let nearbyAllyRejected = Math.square(200);              // Reject if too near from an allied cc
     686           0 :         let nearbyAllyDisfavored = Math.square(250);            // Disfavor if quite near an allied cc
     687           0 :         let maxAccessRejected = Math.square(410);               // Reject if too far from an accessible ally cc
     688           0 :         let maxAccessDisfavored = Math.square(360 - reduce);    // Disfavor if quite far from an accessible ally cc
     689           0 :         let maxNoAccessDisfavored = Math.square(500);           // Disfavor if quite far from an inaccessible ally cc
     690             : 
     691           0 :         let cut = 60;
     692           0 :         if (fromStrategic || proximity)  // be less restrictive
     693           0 :                 cut = 30;
     694             : 
     695           0 :         for (let j = 0; j < this.territoryMap.length; ++j)
     696             :         {
     697           0 :                 if (this.territoryMap.getOwnerIndex(j) != 0)
     698           0 :                         continue;
     699             :                 // With enough room around to build the cc
     700           0 :                 let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions);
     701           0 :                 if (i < 0)
     702           0 :                         continue;
     703             :                 // We require that it is accessible
     704           0 :                 let index = gameState.ai.accessibility.landPassMap[i];
     705           0 :                 if (!this.landRegions[index])
     706           0 :                         continue;
     707           0 :                 if (proxyAccess && nbShips == 0 && proxyAccess != index)
     708           0 :                         continue;
     709             : 
     710           0 :                 let norm = 0.5;   // TODO adjust it, knowing that we will sum 5 maps
     711             :                 // Checking distance to other cc
     712           0 :                 let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
     713             :                 // We will be more tolerant for cc around our oversea docks
     714           0 :                 let oversea = false;
     715             : 
     716           0 :                 if (proximity)  // This is our first cc, let's do it near our units
     717           0 :                         norm /= 1 + API3.SquareVectorDistance(proximity, pos) / scale;
     718             :                 else
     719             :                 {
     720           0 :                         let minDist = Math.min();
     721           0 :                         let accessible = false;
     722             : 
     723           0 :                         for (let cc of ccList)
     724             :                         {
     725           0 :                                 let dist = API3.SquareVectorDistance(cc.pos, pos);
     726           0 :                                 if (dist < nearbyRejected)
     727             :                                 {
     728           0 :                                         norm = 0;
     729           0 :                                         break;
     730             :                                 }
     731           0 :                                 if (!cc.ally)
     732           0 :                                         continue;
     733           0 :                                 if (dist < nearbyAllyRejected)
     734             :                                 {
     735           0 :                                         norm = 0;
     736           0 :                                         break;
     737             :                                 }
     738           0 :                                 if (dist < nearbyAllyDisfavored)
     739           0 :                                         norm *= 0.5;
     740             : 
     741           0 :                                 if (dist < minDist)
     742           0 :                                         minDist = dist;
     743           0 :                                 accessible = accessible || index == PETRA.getLandAccess(gameState, cc.ent);
     744             :                         }
     745           0 :                         if (norm == 0)
     746           0 :                                 continue;
     747             : 
     748           0 :                         if (accessible && minDist > maxAccessRejected)
     749           0 :                                 continue;
     750             : 
     751           0 :                         if (minDist > maxAccessDisfavored)     // Disfavor if quite far from any allied cc
     752             :                         {
     753           0 :                                 if (!accessible)
     754             :                                 {
     755           0 :                                         if (minDist > maxNoAccessDisfavored)
     756           0 :                                                 norm *= 0.5;
     757             :                                         else
     758           0 :                                                 norm *= 0.8;
     759             :                                 }
     760             :                                 else
     761           0 :                                         norm *= 0.5;
     762             :                         }
     763             : 
     764             :                         // Not near any of our dropsite, except for oversea docks
     765           0 :                         oversea = !accessible && dpList.some(dp => PETRA.getLandAccess(gameState, dp.ent) == index);
     766           0 :                         if (!oversea)
     767             :                         {
     768           0 :                                 for (let dp of dpList)
     769             :                                 {
     770           0 :                                         let dist = API3.SquareVectorDistance(dp.pos, pos);
     771           0 :                                         if (dist < 3600)
     772             :                                         {
     773           0 :                                                 norm = 0;
     774           0 :                                                 break;
     775             :                                         }
     776           0 :                                         else if (dist < 6400)
     777           0 :                                                 norm *= 0.5;
     778             :                                 }
     779             :                         }
     780           0 :                         if (norm == 0)
     781           0 :                                 continue;
     782             :                 }
     783             : 
     784           0 :                 if (this.borderMap.map[j] & PETRA.fullBorder_Mask)  // disfavor the borders of the map
     785           0 :                         norm *= 0.5;
     786             : 
     787           0 :                 let val = 2 * gameState.sharedScript.ccResourceMaps[resource].map[j];
     788           0 :                 for (let res in gameState.sharedScript.resourceMaps)
     789           0 :                         if (res != "food")
     790           0 :                                 val += gameState.sharedScript.ccResourceMaps[res].map[j];
     791           0 :                 val *= norm;
     792             : 
     793             :                 // If oversea, be just above threshold to be accepted if nothing else
     794           0 :                 if (oversea)
     795           0 :                         val = Math.max(val, cut + 0.1);
     796             : 
     797           0 :                 if (bestVal !== undefined && val < bestVal)
     798           0 :                         continue;
     799           0 :                 if (this.isDangerousLocation(gameState, pos, halfSize))
     800           0 :                         continue;
     801           0 :                 bestVal = val;
     802           0 :                 bestIdx = i;
     803             :         }
     804             : 
     805           0 :         Engine.ProfileStop();
     806             : 
     807           0 :         if (bestVal === undefined)
     808           0 :                 return false;
     809           0 :         if (this.Config.debug > 1)
     810           0 :                 API3.warn("we have found a base for " + resource + " with best (cut=" + cut + ") = " + bestVal);
     811             :         // not good enough.
     812           0 :         if (bestVal < cut)
     813           0 :                 return false;
     814             : 
     815           0 :         let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
     816           0 :         let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
     817             : 
     818             :         // Define a minimal number of wanted ships in the seas reaching this new base
     819           0 :         let indexIdx = gameState.ai.accessibility.landPassMap[bestIdx];
     820           0 :         for (const base of this.baseManagers())
     821             :         {
     822           0 :                 if (!base.anchor || base.accessIndex == indexIdx)
     823           0 :                         continue;
     824           0 :                 let sea = this.getSeaBetweenIndices(gameState, base.accessIndex, indexIdx);
     825           0 :                 if (sea !== undefined)
     826           0 :                         this.navalManager.setMinimalTransportShips(gameState, sea, 1);
     827             :         }
     828             : 
     829           0 :         return [x, z];
     830             : };
     831             : 
     832             : /**
     833             :  * Returns the best position to build a new Civil Center
     834             :  * Whose primary function would be to assure territorial continuity with our allies
     835             :  */
     836           0 : PETRA.HQ.prototype.findStrategicCCLocation = function(gameState, template)
     837             : {
     838             :         // This builds a map. The procedure is fairly simple.
     839             :         // We minimize the Sum((dist - 300)^2) where the sum is on the three nearest allied CC
     840             :         // with the constraints that all CC have dist > 200 and at least one have dist < 400
     841             :         // This needs at least 2 CC. Otherwise, go back to economic CC.
     842             : 
     843           0 :         let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
     844           0 :         let ccList = [];
     845           0 :         let numAllyCC = 0;
     846           0 :         for (let cc of ccEnts.values())
     847             :         {
     848           0 :                 let ally = gameState.isPlayerAlly(cc.owner());
     849           0 :                 ccList.push({ "pos": cc.position(), "ally": ally });
     850           0 :                 if (ally)
     851           0 :                         ++numAllyCC;
     852             :         }
     853           0 :         if (numAllyCC < 2)
     854           0 :                 return this.findEconomicCCLocation(gameState, template, "wood", undefined, true);
     855             : 
     856           0 :         Engine.ProfileStart("findStrategicCCLocation");
     857             : 
     858             :         // obstruction map
     859           0 :         let obstructions = PETRA.createObstructionMap(gameState, 0, template);
     860           0 :         let halfSize = 0;
     861           0 :         if (template.get("Footprint/Square"))
     862           0 :                 halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2;
     863           0 :         else if (template.get("Footprint/Circle"))
     864           0 :                 halfSize = +template.get("Footprint/Circle/@radius");
     865             : 
     866             :         let bestIdx;
     867             :         let bestVal;
     868           0 :         let radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize);
     869             : 
     870           0 :         let width = this.territoryMap.width;
     871           0 :         let cellSize = this.territoryMap.cellSize;
     872             :         let currentVal, delta;
     873             :         let distcc0, distcc1, distcc2;
     874           0 :         let favoredDistance = (template.hasClass("Colony") ? 220 : 280) - 40 * this.Config.personality.defensive;
     875             : 
     876           0 :         for (let j = 0; j < this.territoryMap.length; ++j)
     877             :         {
     878           0 :                 if (this.territoryMap.getOwnerIndex(j) != 0)
     879           0 :                         continue;
     880             :                 // with enough room around to build the cc
     881           0 :                 let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions);
     882           0 :                 if (i < 0)
     883           0 :                         continue;
     884             :                 // we require that it is accessible
     885           0 :                 let index = gameState.ai.accessibility.landPassMap[i];
     886           0 :                 if (!this.landRegions[index])
     887           0 :                         continue;
     888             : 
     889             :                 // checking distances to other cc
     890           0 :                 let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
     891           0 :                 let minDist = Math.min();
     892           0 :                 distcc0 = undefined;
     893             : 
     894           0 :                 for (let cc of ccList)
     895             :                 {
     896           0 :                         let dist = API3.SquareVectorDistance(cc.pos, pos);
     897           0 :                         if (dist < 14000)    // Reject if too near from any cc
     898             :                         {
     899           0 :                                 minDist = 0;
     900           0 :                                 break;
     901             :                         }
     902           0 :                         if (!cc.ally)
     903           0 :                                 continue;
     904           0 :                         if (dist < 62000)    // Reject if quite near from ally cc
     905             :                         {
     906           0 :                                 minDist = 0;
     907           0 :                                 break;
     908             :                         }
     909           0 :                         if (dist < minDist)
     910           0 :                                 minDist = dist;
     911             : 
     912           0 :                         if (!distcc0 || dist < distcc0)
     913             :                         {
     914           0 :                                 distcc2 = distcc1;
     915           0 :                                 distcc1 = distcc0;
     916           0 :                                 distcc0 = dist;
     917             :                         }
     918           0 :                         else if (!distcc1 || dist < distcc1)
     919             :                         {
     920           0 :                                 distcc2 = distcc1;
     921           0 :                                 distcc1 = dist;
     922             :                         }
     923           0 :                         else if (!distcc2 || dist < distcc2)
     924           0 :                                 distcc2 = dist;
     925             :                 }
     926           0 :                 if (minDist < 1 || minDist > 170000 && !this.navalMap)
     927           0 :                         continue;
     928             : 
     929           0 :                 delta = Math.sqrt(distcc0) - favoredDistance;
     930           0 :                 currentVal = delta*delta;
     931           0 :                 delta = Math.sqrt(distcc1) - favoredDistance;
     932           0 :                 currentVal += delta*delta;
     933           0 :                 if (distcc2)
     934             :                 {
     935           0 :                         delta = Math.sqrt(distcc2) - favoredDistance;
     936           0 :                         currentVal += delta*delta;
     937             :                 }
     938             :                 // disfavor border of the map
     939           0 :                 if (this.borderMap.map[j] & PETRA.fullBorder_Mask)
     940           0 :                         currentVal += 10000;
     941             : 
     942           0 :                 if (bestVal !== undefined && currentVal > bestVal)
     943           0 :                         continue;
     944           0 :                 if (this.isDangerousLocation(gameState, pos, halfSize))
     945           0 :                         continue;
     946           0 :                 bestVal = currentVal;
     947           0 :                 bestIdx = i;
     948             :         }
     949             : 
     950           0 :         if (this.Config.debug > 1)
     951           0 :                 API3.warn("We've found a strategic base with bestVal = " + bestVal);
     952             : 
     953           0 :         Engine.ProfileStop();
     954             : 
     955           0 :         if (bestVal === undefined)
     956           0 :                 return undefined;
     957             : 
     958           0 :         let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
     959           0 :         let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
     960             : 
     961             :         // Define a minimal number of wanted ships in the seas reaching this new base
     962           0 :         let indexIdx = gameState.ai.accessibility.landPassMap[bestIdx];
     963           0 :         for (const base of this.baseManagers())
     964             :         {
     965           0 :                 if (!base.anchor || base.accessIndex == indexIdx)
     966           0 :                         continue;
     967           0 :                 let sea = this.getSeaBetweenIndices(gameState, base.accessIndex, indexIdx);
     968           0 :                 if (sea !== undefined)
     969           0 :                         this.navalManager.setMinimalTransportShips(gameState, sea, 1);
     970             :         }
     971             : 
     972           0 :         return [x, z];
     973             : };
     974             : 
     975             : /**
     976             :  * Returns the best position to build a new market: if the allies already have a market, build it as far as possible
     977             :  * from it, although not in our border to be able to defend it easily. If no allied market, our second market will
     978             :  * follow the same logic.
     979             :  * To do so, we suppose that the gain/distance is an increasing function of distance and look for the max distance
     980             :  * for performance reasons.
     981             :  */
     982           0 : PETRA.HQ.prototype.findMarketLocation = function(gameState, template)
     983             : {
     984           0 :         let markets = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities()).toEntityArray();
     985           0 :         if (!markets.length)
     986           0 :                 markets = gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Trade"), gameState.getOwnStructures()).toEntityArray();
     987             : 
     988           0 :         if (!markets.length)    // this is the first market. For the time being, place it arbitrarily by the ConstructionPlan
     989           0 :                 return [-1, -1, -1, 0];
     990             : 
     991             :         // No need for more than one market when we cannot trade.
     992           0 :         if (!Resources.GetTradableCodes().length)
     993           0 :                 return false;
     994             : 
     995             :         // obstruction map
     996           0 :         let obstructions = PETRA.createObstructionMap(gameState, 0, template);
     997           0 :         let halfSize = 0;
     998           0 :         if (template.get("Footprint/Square"))
     999           0 :                 halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2;
    1000           0 :         else if (template.get("Footprint/Circle"))
    1001           0 :                 halfSize = +template.get("Footprint/Circle/@radius");
    1002             : 
    1003             :         let bestIdx;
    1004             :         let bestJdx;
    1005             :         let bestVal;
    1006             :         let bestDistSq;
    1007             :         let bestGainMult;
    1008           0 :         let radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize);
    1009           0 :         const isNavalMarket = template.hasClasses(["Naval+Trade"]);
    1010             : 
    1011           0 :         let width = this.territoryMap.width;
    1012           0 :         let cellSize = this.territoryMap.cellSize;
    1013             : 
    1014           0 :         let traderTemplatesGains = gameState.getTraderTemplatesGains();
    1015             : 
    1016           0 :         for (let j = 0; j < this.territoryMap.length; ++j)
    1017             :         {
    1018             :                 // do not try on the narrow border of our territory
    1019           0 :                 if (this.borderMap.map[j] & PETRA.narrowFrontier_Mask)
    1020           0 :                         continue;
    1021           0 :                 if (this.baseAtIndex(j) == 0)   // only in our territory
    1022           0 :                         continue;
    1023             :                 // with enough room around to build the market
    1024           0 :                 let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions);
    1025           0 :                 if (i < 0)
    1026           0 :                         continue;
    1027           0 :                 let index = gameState.ai.accessibility.landPassMap[i];
    1028           0 :                 if (!this.landRegions[index])
    1029           0 :                         continue;
    1030           0 :                 let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
    1031             :                 // checking distances to other markets
    1032           0 :                 let maxVal = 0;
    1033             :                 let maxDistSq;
    1034             :                 let maxGainMult;
    1035             :                 let gainMultiplier;
    1036           0 :                 for (let market of markets)
    1037             :                 {
    1038           0 :                         if (isNavalMarket && template.hasClasses(["Naval+Trade"]))
    1039             :                         {
    1040           0 :                                 if (PETRA.getSeaAccess(gameState, market) != gameState.ai.accessibility.getAccessValue(pos, true))
    1041           0 :                                         continue;
    1042           0 :                                 gainMultiplier = traderTemplatesGains.navalGainMultiplier;
    1043             :                         }
    1044           0 :                         else if (PETRA.getLandAccess(gameState, market) == index &&
    1045             :                                 !PETRA.isLineInsideEnemyTerritory(gameState, market.position(), pos))
    1046           0 :                                 gainMultiplier = traderTemplatesGains.landGainMultiplier;
    1047             :                         else
    1048           0 :                                 continue;
    1049           0 :                         if (!gainMultiplier)
    1050           0 :                                 continue;
    1051           0 :                         let distSq = API3.SquareVectorDistance(market.position(), pos);
    1052           0 :                         if (gainMultiplier * distSq > maxVal)
    1053             :                         {
    1054           0 :                                 maxVal = gainMultiplier * distSq;
    1055           0 :                                 maxDistSq = distSq;
    1056           0 :                                 maxGainMult = gainMultiplier;
    1057             :                         }
    1058             :                 }
    1059           0 :                 if (maxVal == 0)
    1060           0 :                         continue;
    1061           0 :                 if (bestVal !== undefined && maxVal < bestVal)
    1062           0 :                         continue;
    1063           0 :                 if (this.isDangerousLocation(gameState, pos, halfSize))
    1064           0 :                         continue;
    1065           0 :                 bestVal = maxVal;
    1066           0 :                 bestDistSq = maxDistSq;
    1067           0 :                 bestGainMult = maxGainMult;
    1068           0 :                 bestIdx = i;
    1069           0 :                 bestJdx = j;
    1070             :         }
    1071             : 
    1072           0 :         if (this.Config.debug > 1)
    1073           0 :                 API3.warn("We found a market position with bestVal = " + bestVal);
    1074             : 
    1075           0 :         if (bestVal === undefined)  // no constraints. For the time being, place it arbitrarily by the ConstructionPlan
    1076           0 :                 return [-1, -1, -1, 0];
    1077           0 :         let expectedGain = Math.round(bestGainMult * TradeGain(bestDistSq, gameState.sharedScript.mapSize));
    1078           0 :         if (this.Config.debug > 1)
    1079           0 :                 API3.warn("this would give a trading gain of " + expectedGain);
    1080             :         // Do not keep it if gain is too small, except if this is our first Market.
    1081             :         let idx;
    1082           0 :         if (expectedGain < this.tradeManager.minimalGain)
    1083             :         {
    1084           0 :                 if (template.hasClass("Market") &&
    1085             :                     !gameState.getOwnEntitiesByClass("Market", true).hasEntities())
    1086           0 :                         idx = -1; // Needed by queueplanBuilding manager to keep that Market.
    1087             :                 else
    1088           0 :                         return false;
    1089             :         }
    1090             :         else
    1091           0 :                 idx = this.baseAtIndex(bestJdx);
    1092             : 
    1093           0 :         let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
    1094           0 :         let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
    1095           0 :         return [x, z, idx, expectedGain];
    1096             : };
    1097             : 
    1098             : /**
    1099             :  * Returns the best position to build defensive buildings (fortress and towers)
    1100             :  * Whose primary function is to defend our borders
    1101             :  */
    1102           0 : PETRA.HQ.prototype.findDefensiveLocation = function(gameState, template)
    1103             : {
    1104             :         // We take the point in our territory which is the nearest to any enemy cc
    1105             :         // but requiring a minimal distance with our other defensive structures
    1106             :         // and not in range of any enemy defensive structure to avoid building under fire.
    1107             : 
    1108           0 :         const ownStructures = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Fortress", "Tower"])).toEntityArray();
    1109           0 :         let enemyStructures = gameState.getEnemyStructures().filter(API3.Filters.not(API3.Filters.byOwner(0))).
    1110             :                 filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"]));
    1111           0 :         if (!enemyStructures.hasEntities())     // we may be in cease fire mode, build defense against neutrals
    1112             :         {
    1113           0 :                 enemyStructures = gameState.getNeutralStructures().filter(API3.Filters.not(API3.Filters.byOwner(0))).
    1114             :                         filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"]));
    1115           0 :                 if (!enemyStructures.hasEntities() && !gameState.getAlliedVictory())
    1116           0 :                         enemyStructures = gameState.getAllyStructures().filter(API3.Filters.not(API3.Filters.byOwner(PlayerID))).
    1117             :                                 filter(API3.Filters.byClasses(["CivCentre", "Fortress", "Tower"]));
    1118           0 :                 if (!enemyStructures.hasEntities())
    1119           0 :                         return undefined;
    1120             :         }
    1121           0 :         enemyStructures = enemyStructures.toEntityArray();
    1122             : 
    1123           0 :         let wonderMode = gameState.getVictoryConditions().has("wonder");
    1124             :         let wonderDistmin;
    1125             :         let wonders;
    1126           0 :         if (wonderMode)
    1127             :         {
    1128           0 :                 wonders = gameState.getOwnStructures().filter(API3.Filters.byClass("Wonder")).toEntityArray();
    1129           0 :                 wonderMode = wonders.length != 0;
    1130           0 :                 if (wonderMode)
    1131           0 :                         wonderDistmin = (50 + wonders[0].footprintRadius()) * (50 + wonders[0].footprintRadius());
    1132             :         }
    1133             : 
    1134             :         // obstruction map
    1135           0 :         let obstructions = PETRA.createObstructionMap(gameState, 0, template);
    1136           0 :         let halfSize = 0;
    1137           0 :         if (template.get("Footprint/Square"))
    1138           0 :                 halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2;
    1139           0 :         else if (template.get("Footprint/Circle"))
    1140           0 :                 halfSize = +template.get("Footprint/Circle/@radius");
    1141             : 
    1142             :         let bestIdx;
    1143             :         let bestJdx;
    1144             :         let bestVal;
    1145           0 :         let width = this.territoryMap.width;
    1146           0 :         let cellSize = this.territoryMap.cellSize;
    1147             : 
    1148           0 :         let isTower = template.hasClass("Tower");
    1149           0 :         let isFortress = template.hasClass("Fortress");
    1150             :         let radius;
    1151           0 :         if (isFortress)
    1152           0 :                 radius = Math.floor((template.obstructionRadius().max + 8) / obstructions.cellSize);
    1153             :         else
    1154           0 :                 radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize);
    1155             : 
    1156           0 :         for (let j = 0; j < this.territoryMap.length; ++j)
    1157             :         {
    1158           0 :                 if (!wonderMode)
    1159             :                 {
    1160             :                         // do not try if well inside or outside territory
    1161           0 :                         if (!(this.borderMap.map[j] & PETRA.fullFrontier_Mask))
    1162           0 :                                 continue;
    1163           0 :                         if (this.borderMap.map[j] & PETRA.largeFrontier_Mask && isTower)
    1164           0 :                                 continue;
    1165             :                 }
    1166           0 :                 if (this.baseAtIndex(j) == 0)   // inaccessible cell
    1167           0 :                         continue;
    1168             :                 // with enough room around to build the cc
    1169           0 :                 let i = this.territoryMap.getNonObstructedTile(j, radius, obstructions);
    1170           0 :                 if (i < 0)
    1171           0 :                         continue;
    1172             : 
    1173           0 :                 let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
    1174             :                 // checking distances to other structures
    1175           0 :                 let minDist = Math.min();
    1176             : 
    1177           0 :                 let dista = 0;
    1178           0 :                 if (wonderMode)
    1179             :                 {
    1180           0 :                         dista = API3.SquareVectorDistance(wonders[0].position(), pos);
    1181           0 :                         if (dista < wonderDistmin)
    1182           0 :                                 continue;
    1183           0 :                         dista *= 200;   // empirical factor (TODO should depend on map size) to stay near the wonder
    1184             :                 }
    1185             : 
    1186           0 :                 for (let str of enemyStructures)
    1187             :                 {
    1188           0 :                         if (str.foundationProgress() !== undefined)
    1189           0 :                                 continue;
    1190           0 :                         let strPos = str.position();
    1191           0 :                         if (!strPos)
    1192           0 :                                 continue;
    1193           0 :                         let dist = API3.SquareVectorDistance(strPos, pos);
    1194           0 :                         if (dist < 6400) // TODO check on true attack range instead of this 80×80
    1195             :                         {
    1196           0 :                                 minDist = -1;
    1197           0 :                                 break;
    1198             :                         }
    1199           0 :                         if (str.hasClass("CivCentre") && dist + dista < minDist)
    1200           0 :                                 minDist = dist + dista;
    1201             :                 }
    1202           0 :                 if (minDist < 0)
    1203           0 :                         continue;
    1204             : 
    1205           0 :                 let cutDist = 900;  // 30×30 TODO maybe increase it
    1206           0 :                 for (let str of ownStructures)
    1207             :                 {
    1208           0 :                         let strPos = str.position();
    1209           0 :                         if (!strPos)
    1210           0 :                                 continue;
    1211           0 :                         if (API3.SquareVectorDistance(strPos, pos) < cutDist)
    1212             :                         {
    1213           0 :                                 minDist = -1;
    1214           0 :                                 break;
    1215             :                         }
    1216             :                 }
    1217           0 :                 if (minDist < 0 || minDist == Math.min())
    1218           0 :                         continue;
    1219           0 :                 if (bestVal !== undefined && minDist > bestVal)
    1220           0 :                         continue;
    1221           0 :                 if (this.isDangerousLocation(gameState, pos, halfSize))
    1222           0 :                         continue;
    1223           0 :                 bestVal = minDist;
    1224           0 :                 bestIdx = i;
    1225           0 :                 bestJdx = j;
    1226             :         }
    1227             : 
    1228           0 :         if (bestVal === undefined)
    1229           0 :                 return undefined;
    1230             : 
    1231           0 :         let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
    1232           0 :         let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
    1233           0 :         return [x, z, this.baseAtIndex(bestJdx)];
    1234             : };
    1235             : 
    1236           0 : PETRA.HQ.prototype.buildTemple = function(gameState, queues)
    1237             : {
    1238             :         // at least one market (which have the same queue) should be build before any temple
    1239           0 :         if (queues.economicBuilding.hasQueuedUnits() ||
    1240             :                 gameState.getOwnEntitiesByClass("Temple", true).hasEntities() ||
    1241             :                 !gameState.getOwnEntitiesByClass("Market", true).hasEntities())
    1242           0 :                 return;
    1243             :         // Try to build a temple earlier if in regicide to recruit healer guards
    1244           0 :         if (this.currentPhase < 3 && !gameState.getVictoryConditions().has("regicide"))
    1245           0 :                 return;
    1246             : 
    1247           0 :         let templateName = "structures/{civ}/temple";
    1248           0 :         if (this.canBuild(gameState, "structures/{civ}/temple_vesta"))
    1249           0 :                 templateName = "structures/{civ}/temple_vesta";
    1250           0 :         else if (!this.canBuild(gameState, templateName))
    1251           0 :                 return;
    1252           0 :         queues.economicBuilding.addPlan(new PETRA.ConstructionPlan(gameState, templateName));
    1253             : };
    1254             : 
    1255           0 : PETRA.HQ.prototype.buildMarket = function(gameState, queues)
    1256             : {
    1257           0 :         if (gameState.getOwnEntitiesByClass("Market", true).hasEntities() ||
    1258             :                 !this.canBuild(gameState, "structures/{civ}/market"))
    1259           0 :                 return;
    1260             : 
    1261           0 :         if (queues.economicBuilding.hasQueuedUnitsWithClass("Market"))
    1262             :         {
    1263           0 :                 if (!queues.economicBuilding.paused)
    1264             :                 {
    1265             :                         // Put available resources in this market
    1266           0 :                         let queueManager = gameState.ai.queueManager;
    1267           0 :                         let cost = queues.economicBuilding.plans[0].getCost();
    1268           0 :                         queueManager.setAccounts(gameState, cost, "economicBuilding");
    1269           0 :                         if (!queueManager.canAfford("economicBuilding", cost))
    1270             :                         {
    1271           0 :                                 for (let q in queueManager.queues)
    1272             :                                 {
    1273           0 :                                         if (q == "economicBuilding")
    1274           0 :                                                 continue;
    1275           0 :                                         queueManager.transferAccounts(cost, q, "economicBuilding");
    1276           0 :                                         if (queueManager.canAfford("economicBuilding", cost))
    1277           0 :                                                 break;
    1278             :                                 }
    1279             :                         }
    1280             :                 }
    1281           0 :                 return;
    1282             :         }
    1283             : 
    1284           0 :         gameState.ai.queueManager.changePriority("economicBuilding", 3 * this.Config.priorities.economicBuilding);
    1285           0 :         let plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/market");
    1286           0 :         plan.queueToReset = "economicBuilding";
    1287           0 :         queues.economicBuilding.addPlan(plan);
    1288             : };
    1289             : 
    1290             : /** Build a farmstead */
    1291           0 : PETRA.HQ.prototype.buildFarmstead = function(gameState, queues)
    1292             : {
    1293             :         // Only build one farmstead for the time being ("DropsiteFood" does not refer to CCs)
    1294           0 :         if (gameState.getOwnEntitiesByClass("Farmstead", true).hasEntities())
    1295           0 :                 return;
    1296             :         // Wait to have at least one dropsite and house before the farmstead
    1297           0 :         if (!gameState.getOwnEntitiesByClass("Storehouse", true).hasEntities())
    1298           0 :                 return;
    1299           0 :         if (!gameState.getOwnEntitiesByClass("House", true).hasEntities())
    1300           0 :                 return;
    1301           0 :         if (queues.economicBuilding.hasQueuedUnitsWithClass("DropsiteFood"))
    1302           0 :                 return;
    1303           0 :         if (!this.canBuild(gameState, "structures/{civ}/farmstead"))
    1304           0 :                 return;
    1305             : 
    1306           0 :         queues.economicBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/farmstead"));
    1307             : };
    1308             : 
    1309             : /**
    1310             :  * Try to build a wonder when required
    1311             :  * force = true when called from the victoryManager in case of Wonder victory condition.
    1312             :  */
    1313           0 : PETRA.HQ.prototype.buildWonder = function(gameState, queues, force = false)
    1314             : {
    1315           0 :         if (queues.wonder && queues.wonder.hasQueuedUnits() ||
    1316             :             gameState.getOwnEntitiesByClass("Wonder", true).hasEntities() ||
    1317             :             !this.canBuild(gameState, "structures/{civ}/wonder"))
    1318           0 :                 return;
    1319             : 
    1320           0 :         if (!force)
    1321             :         {
    1322           0 :                 let template = gameState.getTemplate(gameState.applyCiv("structures/{civ}/wonder"));
    1323             :                 // Check that we have enough resources to start thinking to build a wonder
    1324           0 :                 let cost = template.cost();
    1325           0 :                 let resources = gameState.getResources();
    1326           0 :                 let highLevel = 0;
    1327           0 :                 let lowLevel = 0;
    1328           0 :                 for (let res in cost)
    1329             :                 {
    1330           0 :                         if (resources[res] && resources[res] > 0.7 * cost[res])
    1331           0 :                                 ++highLevel;
    1332           0 :                         else if (!resources[res] || resources[res] < 0.3 * cost[res])
    1333           0 :                                 ++lowLevel;
    1334             :                 }
    1335           0 :                 if (highLevel == 0 || lowLevel > 1)
    1336           0 :                         return;
    1337             :         }
    1338             : 
    1339           0 :         queues.wonder.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/wonder"));
    1340             : };
    1341             : 
    1342             : /** Build a corral, and train animals there */
    1343           0 : PETRA.HQ.prototype.manageCorral = function(gameState, queues)
    1344             : {
    1345           0 :         if (queues.corral.hasQueuedUnits())
    1346           0 :                 return;
    1347             : 
    1348           0 :         let nCorral = gameState.getOwnEntitiesByClass("Corral", true).length;
    1349           0 :         if (!nCorral || !gameState.isTemplateAvailable(gameState.applyCiv("structures/{civ}/field")) &&
    1350             :                         nCorral < this.currentPhase && gameState.getPopulation() > 30 * nCorral)
    1351             :         {
    1352           0 :                 if (this.canBuild(gameState, "structures/{civ}/corral"))
    1353             :                 {
    1354           0 :                         queues.corral.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/corral"));
    1355           0 :                         return;
    1356             :                 }
    1357           0 :                 if (!nCorral)
    1358           0 :                         return;
    1359             :         }
    1360             : 
    1361             :         // And train some animals
    1362           0 :         let civ = gameState.getPlayerCiv();
    1363           0 :         for (let corral of gameState.getOwnEntitiesByClass("Corral", true).values())
    1364             :         {
    1365           0 :                 if (corral.foundationProgress() !== undefined)
    1366           0 :                         continue;
    1367           0 :                 let trainables = corral.trainableEntities(civ);
    1368           0 :                 for (let trainable of trainables)
    1369             :                 {
    1370           0 :                         if (gameState.isTemplateDisabled(trainable))
    1371           0 :                                 continue;
    1372           0 :                         let template = gameState.getTemplate(trainable);
    1373           0 :                         if (!template || !template.isHuntable())
    1374           0 :                                 continue;
    1375           0 :                         let count = gameState.countEntitiesByType(trainable, true);
    1376           0 :                         for (let item of corral.trainingQueue())
    1377           0 :                                 count += item.count;
    1378           0 :                         if (count > nCorral)
    1379           0 :                                 continue;
    1380           0 :                         queues.corral.addPlan(new PETRA.TrainingPlan(gameState, trainable, { "trainer": corral.id() }));
    1381           0 :                         return;
    1382             :                 }
    1383             :         }
    1384             : };
    1385             : 
    1386             : /**
    1387             :  * build more houses if needed.
    1388             :  * kinda ugly, lots of special cases to both build enough houses but not tooo many…
    1389             :  */
    1390           0 : PETRA.HQ.prototype.buildMoreHouses = function(gameState, queues)
    1391             : {
    1392           0 :         let houseTemplateString = "structures/{civ}/apartment";
    1393           0 :         if (!gameState.isTemplateAvailable(gameState.applyCiv(houseTemplateString)) ||
    1394             :                 !this.canBuild(gameState, houseTemplateString))
    1395             :         {
    1396           0 :                 houseTemplateString = "structures/{civ}/house";
    1397           0 :                 if (!gameState.isTemplateAvailable(gameState.applyCiv(houseTemplateString)))
    1398           0 :                         return;
    1399             :         }
    1400           0 :         if (gameState.getPopulationMax() <= gameState.getPopulationLimit())
    1401           0 :                 return;
    1402             : 
    1403           0 :         let numPlanned = queues.house.length();
    1404           0 :         if (numPlanned < 3 || numPlanned < 5 && gameState.getPopulation() > 80)
    1405             :         {
    1406           0 :                 let plan = new PETRA.ConstructionPlan(gameState, houseTemplateString);
    1407             :                 // change the starting condition according to the situation.
    1408           0 :                 plan.goRequirement = "houseNeeded";
    1409           0 :                 queues.house.addPlan(plan);
    1410             :         }
    1411             : 
    1412           0 :         if (numPlanned > 0 && this.phasing && gameState.getPhaseEntityRequirements(this.phasing).length)
    1413             :         {
    1414           0 :                 let houseTemplateName = gameState.applyCiv(houseTemplateString);
    1415           0 :                 let houseTemplate = gameState.getTemplate(houseTemplateName);
    1416             : 
    1417           0 :                 let needed = 0;
    1418           0 :                 for (let entityReq of gameState.getPhaseEntityRequirements(this.phasing))
    1419             :                 {
    1420           0 :                         if (!houseTemplate.hasClass(entityReq.class))
    1421           0 :                                 continue;
    1422             : 
    1423           0 :                         let count = gameState.getOwnStructures().filter(API3.Filters.byClass(entityReq.class)).length;
    1424           0 :                         if (count < entityReq.count && this.buildManager.isUnbuildable(gameState, houseTemplateName))
    1425             :                         {
    1426           0 :                                 if (this.Config.debug > 1)
    1427           0 :                                         API3.warn("no room to place a house ... try to be less restrictive");
    1428           0 :                                 this.buildManager.setBuildable(houseTemplateName);
    1429           0 :                                 this.requireHouses = true;
    1430             :                         }
    1431           0 :                         needed = Math.max(needed, entityReq.count - count);
    1432             :                 }
    1433             : 
    1434           0 :                 let houseQueue = queues.house.plans;
    1435           0 :                 for (let i = 0; i < numPlanned; ++i)
    1436           0 :                         if (houseQueue[i].isGo(gameState))
    1437           0 :                                 --needed;
    1438           0 :                         else if (needed > 0)
    1439             :                         {
    1440           0 :                                 houseQueue[i].goRequirement = undefined;
    1441           0 :                                 --needed;
    1442             :                         }
    1443             :         }
    1444             : 
    1445           0 :         if (this.requireHouses)
    1446             :         {
    1447           0 :                 let houseTemplate = gameState.getTemplate(gameState.applyCiv(houseTemplateString));
    1448           0 :                 if (!this.phasing || gameState.getPhaseEntityRequirements(this.phasing).every(req =>
    1449           0 :                         !houseTemplate.hasClass(req.class) || gameState.getOwnStructures().filter(API3.Filters.byClass(req.class)).length >= req.count))
    1450           0 :                         this.requireHouses = undefined;
    1451             :         }
    1452             : 
    1453             :         // When population limit too tight
    1454             :         //    - if no room to build, try to improve with technology
    1455             :         //    - otherwise increase temporarily the priority of houses
    1456           0 :         let house = gameState.applyCiv(houseTemplateString);
    1457           0 :         let HouseNb = gameState.getOwnFoundations().filter(API3.Filters.byClass("House")).length;
    1458           0 :         let popBonus = gameState.getTemplate(house).getPopulationBonus();
    1459           0 :         let freeSlots = gameState.getPopulationLimit() + HouseNb*popBonus - this.getAccountedPopulation(gameState);
    1460             :         let priority;
    1461           0 :         if (freeSlots < 5)
    1462             :         {
    1463           0 :                 if (this.buildManager.isUnbuildable(gameState, house))
    1464             :                 {
    1465           0 :                         if (this.Config.debug > 1)
    1466           0 :                                 API3.warn("no room to place a house ... try to improve with technology");
    1467           0 :                         this.researchManager.researchPopulationBonus(gameState, queues);
    1468             :                 }
    1469             :                 else
    1470           0 :                         priority = 2 * this.Config.priorities.house;
    1471             :         }
    1472             :         else
    1473           0 :                 priority = this.Config.priorities.house;
    1474             : 
    1475           0 :         if (priority && priority != gameState.ai.queueManager.getPriority("house"))
    1476           0 :                 gameState.ai.queueManager.changePriority("house", priority);
    1477             : };
    1478             : 
    1479             : /** Checks the status of the territory expansion. If no new economic bases created, build some strategic ones. */
    1480           0 : PETRA.HQ.prototype.checkBaseExpansion = function(gameState, queues)
    1481             : {
    1482           0 :         if (queues.civilCentre.hasQueuedUnits())
    1483           0 :                 return;
    1484             :         // First build one cc if all have been destroyed
    1485           0 :         if (!this.hasPotentialBase())
    1486             :         {
    1487           0 :                 this.buildFirstBase(gameState);
    1488           0 :                 return;
    1489             :         }
    1490             :         // Then expand if we have not enough room available for buildings
    1491           0 :         if (this.buildManager.numberMissingRoom(gameState) > 1)
    1492             :         {
    1493           0 :                 if (this.Config.debug > 2)
    1494           0 :                         API3.warn("try to build a new base because not enough room to build ");
    1495           0 :                 this.buildNewBase(gameState, queues);
    1496           0 :                 return;
    1497             :         }
    1498             :         // If we've already planned to phase up, wait a bit before trying to expand
    1499           0 :         if (this.phasing)
    1500           0 :                 return;
    1501             :         // Finally expand if we have lots of units (threshold depending on the aggressivity value)
    1502           0 :         let activeBases = this.numActiveBases();
    1503           0 :         let numUnits = gameState.getOwnUnits().length;
    1504           0 :         let numvar = 10 * (1 - this.Config.personality.aggressive);
    1505           0 :         if (numUnits > activeBases * (65 + numvar + (10 + numvar)*(activeBases-1)) || this.saveResources && numUnits > 50)
    1506             :         {
    1507           0 :                 if (this.Config.debug > 2)
    1508           0 :                         API3.warn("try to build a new base because of population " + numUnits + " for " + activeBases + " CCs");
    1509           0 :                 this.buildNewBase(gameState, queues);
    1510             :         }
    1511             : };
    1512             : 
    1513           0 : PETRA.HQ.prototype.buildNewBase = function(gameState, queues, resource)
    1514             : {
    1515           0 :         if (this.hasPotentialBase() && this.currentPhase == 1 && !gameState.isResearching(gameState.getPhaseName(2)))
    1516           0 :                 return false;
    1517           0 :         if (gameState.getOwnFoundations().filter(API3.Filters.byClass("CivCentre")).hasEntities() || queues.civilCentre.hasQueuedUnits())
    1518           0 :                 return false;
    1519             : 
    1520             :         let template;
    1521             :         // We require at least one of this civ civCentre as they may allow specific units or techs
    1522           0 :         let hasOwnCC = false;
    1523           0 :         for (let ent of gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")).values())
    1524             :         {
    1525           0 :                 if (ent.owner() != PlayerID || ent.templateName() != gameState.applyCiv("structures/{civ}/civil_centre"))
    1526           0 :                         continue;
    1527           0 :                 hasOwnCC = true;
    1528           0 :                 break;
    1529             :         }
    1530           0 :         if (hasOwnCC && this.canBuild(gameState, "structures/{civ}/military_colony"))
    1531           0 :                 template = "structures/{civ}/military_colony";
    1532           0 :         else if (this.canBuild(gameState, "structures/{civ}/civil_centre"))
    1533           0 :                 template = "structures/{civ}/civil_centre";
    1534           0 :         else if (!hasOwnCC && this.canBuild(gameState, "structures/{civ}/military_colony"))
    1535           0 :                 template = "structures/{civ}/military_colony";
    1536             :         else
    1537           0 :                 return false;
    1538             : 
    1539             :         // base "-1" means new base.
    1540           0 :         if (this.Config.debug > 1)
    1541           0 :                 API3.warn("new base " + gameState.applyCiv(template) + " planned with resource " + resource);
    1542           0 :         queues.civilCentre.addPlan(new PETRA.ConstructionPlan(gameState, template, { "base": -1, "resource": resource }));
    1543           0 :         return true;
    1544             : };
    1545             : 
    1546             : /** Deals with building fortresses and towers along our border with enemies. */
    1547           0 : PETRA.HQ.prototype.buildDefenses = function(gameState, queues)
    1548             : {
    1549           0 :         if (this.saveResources && !this.canBarter || queues.defenseBuilding.hasQueuedUnits())
    1550           0 :                 return;
    1551             : 
    1552           0 :         if (!this.saveResources && (this.currentPhase > 2 || gameState.isResearching(gameState.getPhaseName(3))))
    1553             :         {
    1554             :                 // Try to build fortresses.
    1555           0 :                 if (this.canBuild(gameState, "structures/{civ}/fortress"))
    1556             :                 {
    1557           0 :                         let numFortresses = gameState.getOwnEntitiesByClass("Fortress", true).length;
    1558           0 :                         if ((!numFortresses || gameState.ai.elapsedTime > (1 + 0.10 * numFortresses) * this.fortressLapseTime + this.fortressStartTime) &&
    1559             :                                 numFortresses < this.numActiveBases() + 1 + this.extraFortresses &&
    1560             :                                 numFortresses < Math.floor(gameState.getPopulation() / 25) &&
    1561             :                                 gameState.getOwnFoundationsByClass("Fortress").length < 2)
    1562             :                         {
    1563           0 :                                 this.fortressStartTime = gameState.ai.elapsedTime;
    1564           0 :                                 if (!numFortresses)
    1565           0 :                                         gameState.ai.queueManager.changePriority("defenseBuilding", 2 * this.Config.priorities.defenseBuilding);
    1566           0 :                                 let plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/fortress");
    1567           0 :                                 plan.queueToReset = "defenseBuilding";
    1568           0 :                                 queues.defenseBuilding.addPlan(plan);
    1569           0 :                                 return;
    1570             :                         }
    1571             :                 }
    1572             :         }
    1573             : 
    1574           0 :         if (this.Config.Military.numSentryTowers && this.currentPhase < 2 && this.canBuild(gameState, "structures/{civ}/sentry_tower"))
    1575             :         {
    1576             :                 // Count all towers + wall towers.
    1577           0 :                 let numTowers = gameState.getOwnEntitiesByClass("Tower", true).length + gameState.getOwnEntitiesByClass("WallTower", true).length;
    1578           0 :                 let towerLapseTime = this.saveResource ? (1 + 0.5 * numTowers) * this.towerLapseTime : this.towerLapseTime;
    1579           0 :                 if (numTowers < this.Config.Military.numSentryTowers && gameState.ai.elapsedTime > towerLapseTime + this.fortStartTime)
    1580             :                 {
    1581           0 :                         this.fortStartTime = gameState.ai.elapsedTime;
    1582           0 :                         queues.defenseBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/sentry_tower"));
    1583             :                 }
    1584           0 :                 return;
    1585             :         }
    1586             : 
    1587           0 :         if (this.currentPhase < 2 || !this.canBuild(gameState, "structures/{civ}/defense_tower"))
    1588           0 :                 return;
    1589             : 
    1590           0 :         let numTowers = gameState.getOwnEntitiesByClass("StoneTower", true).length;
    1591           0 :         let towerLapseTime = this.saveResource ? (1 + numTowers) * this.towerLapseTime : this.towerLapseTime;
    1592           0 :         if ((!numTowers || gameState.ai.elapsedTime > (1 + 0.1 * numTowers) * towerLapseTime + this.towerStartTime) &&
    1593             :                 numTowers < 2 * this.numActiveBases() + 3 + this.extraTowers &&
    1594             :                 numTowers < Math.floor(gameState.getPopulation() / 8) &&
    1595             :                 gameState.getOwnFoundationsByClass("Tower").length < 3)
    1596             :         {
    1597           0 :                 this.towerStartTime = gameState.ai.elapsedTime;
    1598           0 :                 if (numTowers > 2 * this.numActiveBases() + 3)
    1599           0 :                         gameState.ai.queueManager.changePriority("defenseBuilding", Math.round(0.7 * this.Config.priorities.defenseBuilding));
    1600           0 :                 let plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/defense_tower");
    1601           0 :                 plan.queueToReset = "defenseBuilding";
    1602           0 :                 queues.defenseBuilding.addPlan(plan);
    1603             :         }
    1604             : };
    1605             : 
    1606           0 : PETRA.HQ.prototype.buildForge = function(gameState, queues)
    1607             : {
    1608           0 :         if (this.getAccountedPopulation(gameState) < this.Config.Military.popForForge ||
    1609             :                 queues.militaryBuilding.hasQueuedUnits() || gameState.getOwnEntitiesByClass("Forge", true).length)
    1610           0 :                 return;
    1611             :         // Build a Market before the Forge.
    1612           0 :         if (!gameState.getOwnEntitiesByClass("Market", true).hasEntities())
    1613           0 :                 return;
    1614             : 
    1615           0 :         if (this.canBuild(gameState, "structures/{civ}/forge"))
    1616           0 :                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/forge"));
    1617             : };
    1618             : 
    1619             : /**
    1620             :  * Deals with constructing military buildings (e.g. barracks, stable).
    1621             :  * They are mostly defined by Config.js. This is unreliable since changes could be done easily.
    1622             :  */
    1623           0 : PETRA.HQ.prototype.constructTrainingBuildings = function(gameState, queues)
    1624             : {
    1625           0 :         if (this.saveResources && !this.canBarter || queues.militaryBuilding.hasQueuedUnits())
    1626           0 :                 return;
    1627             : 
    1628           0 :         let numBarracks = gameState.getOwnEntitiesByClass("Barracks", true).length;
    1629           0 :         if (this.saveResources && numBarracks != 0)
    1630           0 :                 return;
    1631             : 
    1632           0 :         let barracksTemplate = this.canBuild(gameState, "structures/{civ}/barracks") ? "structures/{civ}/barracks" : undefined;
    1633             : 
    1634           0 :         let rangeTemplate = this.canBuild(gameState, "structures/{civ}/range") ? "structures/{civ}/range" : undefined;
    1635           0 :         let numRanges = gameState.getOwnEntitiesByClass("Range", true).length;
    1636             : 
    1637           0 :         let stableTemplate = this.canBuild(gameState, "structures/{civ}/stable") ? "structures/{civ}/stable" : undefined;
    1638           0 :         let numStables = gameState.getOwnEntitiesByClass("Stable", true).length;
    1639             : 
    1640           0 :         if (this.getAccountedPopulation(gameState) > this.Config.Military.popForBarracks1 ||
    1641             :             this.phasing == 2 && gameState.getOwnStructures().filter(API3.Filters.byClass("Village")).length < 5)
    1642             :         {
    1643             :                 // First barracks/range and stable.
    1644           0 :                 if (numBarracks + numRanges == 0)
    1645             :                 {
    1646           0 :                         let template = barracksTemplate || rangeTemplate;
    1647           0 :                         if (template)
    1648             :                         {
    1649           0 :                                 gameState.ai.queueManager.changePriority("militaryBuilding", 2 * this.Config.priorities.militaryBuilding);
    1650           0 :                                 let plan = new PETRA.ConstructionPlan(gameState, template, { "militaryBase": true });
    1651           0 :                                 plan.queueToReset = "militaryBuilding";
    1652           0 :                                 queues.militaryBuilding.addPlan(plan);
    1653           0 :                                 return;
    1654             :                         }
    1655             :                 }
    1656           0 :                 if (numStables == 0 && stableTemplate)
    1657             :                 {
    1658           0 :                         queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, stableTemplate, { "militaryBase": true }));
    1659           0 :                         return;
    1660             :                 }
    1661             : 
    1662             :                 // Second barracks/range and stable.
    1663           0 :                 if (numBarracks + numRanges == 1 && this.getAccountedPopulation(gameState) > this.Config.Military.popForBarracks2)
    1664             :                 {
    1665           0 :                         let template = numBarracks == 0 ? (barracksTemplate || rangeTemplate) : (rangeTemplate || barracksTemplate);
    1666           0 :                         if (template)
    1667             :                         {
    1668           0 :                                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, template, { "militaryBase": true }));
    1669           0 :                                 return;
    1670             :                         }
    1671             :                 }
    1672           0 :                 if (numStables == 1 && stableTemplate && this.getAccountedPopulation(gameState) > this.Config.Military.popForBarracks2)
    1673             :                 {
    1674           0 :                         queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, stableTemplate, { "militaryBase": true }));
    1675           0 :                         return;
    1676             :                 }
    1677             : 
    1678             :                 // Third barracks/range and stable, if needed.
    1679           0 :                 if (numBarracks + numRanges + numStables == 2 && this.getAccountedPopulation(gameState) > this.Config.Military.popForBarracks2 + 30)
    1680             :                 {
    1681           0 :                         let template = barracksTemplate || stableTemplate || rangeTemplate;
    1682           0 :                         if (template)
    1683             :                         {
    1684           0 :                                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, template, { "militaryBase": true }));
    1685           0 :                                 return;
    1686             :                         }
    1687             :                 }
    1688             :         }
    1689             : 
    1690           0 :         if (this.saveResources)
    1691           0 :                 return;
    1692             : 
    1693           0 :         if (this.currentPhase < 3)
    1694           0 :                 return;
    1695             : 
    1696           0 :         if (this.canBuild(gameState, "structures/{civ}/elephant_stable") && !gameState.getOwnEntitiesByClass("ElephantStable", true).hasEntities())
    1697             :         {
    1698           0 :                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/elephant_stable", { "militaryBase": true }));
    1699           0 :                 return;
    1700             :         }
    1701             : 
    1702           0 :         if (this.canBuild(gameState, "structures/{civ}/arsenal") && !gameState.getOwnEntitiesByClass("Arsenal", true).hasEntities())
    1703             :         {
    1704           0 :                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/arsenal", { "militaryBase": true }));
    1705           0 :                 return;
    1706             :         }
    1707             : 
    1708           0 :         if (this.getAccountedPopulation(gameState) < 80 || !this.bAdvanced.length)
    1709           0 :                 return;
    1710             : 
    1711             :         // Build advanced military buildings
    1712           0 :         let nAdvanced = 0;
    1713           0 :         for (let advanced of this.bAdvanced)
    1714           0 :                 nAdvanced += gameState.countEntitiesAndQueuedByType(advanced, true);
    1715             : 
    1716           0 :         if (!nAdvanced || nAdvanced < this.bAdvanced.length && this.getAccountedPopulation(gameState) > 110)
    1717             :         {
    1718           0 :                 for (let advanced of this.bAdvanced)
    1719             :                 {
    1720           0 :                         if (gameState.countEntitiesAndQueuedByType(advanced, true) > 0 || !this.canBuild(gameState, advanced))
    1721           0 :                                 continue;
    1722           0 :                         let template = gameState.getTemplate(advanced);
    1723           0 :                         if (!template)
    1724           0 :                                 continue;
    1725           0 :                         let civ = gameState.getPlayerCiv();
    1726           0 :                         if (template.hasDefensiveFire() || template.trainableEntities(civ))
    1727           0 :                                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, advanced, { "militaryBase": true }));
    1728             :                         else    // not a military building, but still use this queue
    1729           0 :                                 queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, advanced));
    1730           0 :                         return;
    1731             :                 }
    1732             :         }
    1733             : };
    1734             : 
    1735             : /**
    1736             :  *  Find base nearest to ennemies for military buildings.
    1737             :  */
    1738           0 : PETRA.HQ.prototype.findBestBaseForMilitary = function(gameState)
    1739             : {
    1740           0 :         let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")).toEntityArray();
    1741             :         let bestBase;
    1742           0 :         let enemyFound = false;
    1743           0 :         let distMin = Math.min();
    1744           0 :         for (let cce of ccEnts)
    1745             :         {
    1746           0 :                 if (gameState.isPlayerAlly(cce.owner()))
    1747           0 :                         continue;
    1748           0 :                 if (enemyFound && !gameState.isPlayerEnemy(cce.owner()))
    1749           0 :                         continue;
    1750           0 :                 let access = PETRA.getLandAccess(gameState, cce);
    1751           0 :                 let isEnemy = gameState.isPlayerEnemy(cce.owner());
    1752           0 :                 for (let cc of ccEnts)
    1753             :                 {
    1754           0 :                         if (cc.owner() != PlayerID)
    1755           0 :                                 continue;
    1756           0 :                         if (PETRA.getLandAccess(gameState, cc) != access)
    1757           0 :                                 continue;
    1758           0 :                         let dist = API3.SquareVectorDistance(cc.position(), cce.position());
    1759           0 :                         if (!enemyFound && isEnemy)
    1760           0 :                                 enemyFound = true;
    1761           0 :                         else if (dist > distMin)
    1762           0 :                                 continue;
    1763           0 :                         bestBase = cc.getMetadata(PlayerID, "base");
    1764           0 :                         distMin = dist;
    1765             :                 }
    1766             :         }
    1767           0 :         return bestBase;
    1768             : };
    1769             : 
    1770             : /**
    1771             :  * train with highest priority ranged infantry in the nearest civil center from a given set of positions
    1772             :  * and garrison them there for defense
    1773             :  */
    1774           0 : PETRA.HQ.prototype.trainEmergencyUnits = function(gameState, positions)
    1775             : {
    1776           0 :         if (gameState.ai.queues.emergency.hasQueuedUnits())
    1777           0 :                 return false;
    1778             : 
    1779           0 :         let civ = gameState.getPlayerCiv();
    1780             :         // find nearest base anchor
    1781           0 :         let distcut = 20000;
    1782             :         let nearestAnchor;
    1783             :         let distmin;
    1784           0 :         for (let pos of positions)
    1785             :         {
    1786           0 :                 let access = gameState.ai.accessibility.getAccessValue(pos);
    1787             :                 // check nearest base anchor
    1788           0 :                 for (const base of this.baseManagers())
    1789             :                 {
    1790           0 :                         if (!base.anchor || !base.anchor.position())
    1791           0 :                                 continue;
    1792           0 :                         if (PETRA.getLandAccess(gameState, base.anchor) != access)
    1793           0 :                                 continue;
    1794           0 :                         if (!base.anchor.trainableEntities(civ))        // base still in construction
    1795           0 :                                 continue;
    1796           0 :                         let queue = base.anchor._entity.trainingQueue;
    1797           0 :                         if (queue)
    1798             :                         {
    1799           0 :                                 let time = 0;
    1800           0 :                                 for (let item of queue)
    1801           0 :                                         if (item.progress > 0 || item.metadata && item.metadata.garrisonType)
    1802           0 :                                                 time += item.timeRemaining;
    1803           0 :                                 if (time/1000 > 5)
    1804           0 :                                         continue;
    1805             :                         }
    1806           0 :                         let dist = API3.SquareVectorDistance(base.anchor.position(), pos);
    1807           0 :                         if (nearestAnchor && dist > distmin)
    1808           0 :                                 continue;
    1809           0 :                         distmin = dist;
    1810           0 :                         nearestAnchor = base.anchor;
    1811             :                 }
    1812             :         }
    1813           0 :         if (!nearestAnchor || distmin > distcut)
    1814           0 :                 return false;
    1815             : 
    1816             :         // We will choose randomly ranged and melee units, except when garrisonHolder is full
    1817             :         // in which case we prefer melee units
    1818           0 :         let numGarrisoned = this.garrisonManager.numberOfGarrisonedSlots(nearestAnchor);
    1819           0 :         if (nearestAnchor._entity.trainingQueue)
    1820             :         {
    1821           0 :                 for (let item of nearestAnchor._entity.trainingQueue)
    1822             :                 {
    1823           0 :                         if (item.metadata && item.metadata.garrisonType)
    1824           0 :                                 numGarrisoned += item.count;
    1825           0 :                         else if (!item.progress && (!item.metadata || !item.metadata.trainer))
    1826           0 :                                 nearestAnchor.stopProduction(item.id);
    1827             :                 }
    1828             :         }
    1829           0 :         let autogarrison = numGarrisoned < nearestAnchor.garrisonMax() &&
    1830             :                            nearestAnchor.hitpoints() > nearestAnchor.garrisonEjectHealth() * nearestAnchor.maxHitpoints();
    1831           0 :         let rangedWanted = randBool() && autogarrison;
    1832             : 
    1833           0 :         let total = gameState.getResources();
    1834             :         let templateFound;
    1835           0 :         let trainables = nearestAnchor.trainableEntities(civ);
    1836           0 :         let garrisonArrowClasses = nearestAnchor.getGarrisonArrowClasses();
    1837           0 :         for (let trainable of trainables)
    1838             :         {
    1839           0 :                 if (gameState.isTemplateDisabled(trainable))
    1840           0 :                         continue;
    1841           0 :                 let template = gameState.getTemplate(trainable);
    1842           0 :                 if (!template || !template.hasClasses(["Infantry+CitizenSoldier"]))
    1843           0 :                         continue;
    1844           0 :                 if (autogarrison && !template.hasClasses(garrisonArrowClasses))
    1845           0 :                         continue;
    1846           0 :                 if (!total.canAfford(new API3.Resources(template.cost())))
    1847           0 :                         continue;
    1848           0 :                 templateFound = [trainable, template];
    1849           0 :                 if (template.hasClass("Ranged") == rangedWanted)
    1850           0 :                         break;
    1851             :         }
    1852           0 :         if (!templateFound)
    1853           0 :                 return false;
    1854             : 
    1855             :         // Check first if we can afford it without touching the other accounts
    1856             :         // and if not, take some of other accounted resources
    1857             :         // TODO sort the queues to be substracted
    1858           0 :         let queueManager = gameState.ai.queueManager;
    1859           0 :         let cost = new API3.Resources(templateFound[1].cost());
    1860           0 :         queueManager.setAccounts(gameState, cost, "emergency");
    1861           0 :         if (!queueManager.canAfford("emergency", cost))
    1862             :         {
    1863           0 :                 for (let q in queueManager.queues)
    1864             :                 {
    1865           0 :                         if (q == "emergency")
    1866           0 :                                 continue;
    1867           0 :                         queueManager.transferAccounts(cost, q, "emergency");
    1868           0 :                         if (queueManager.canAfford("emergency", cost))
    1869           0 :                                 break;
    1870             :                 }
    1871             :         }
    1872           0 :         const metadata = { "role": PETRA.Worker.ROLE_WORKER, "base": nearestAnchor.getMetadata(PlayerID, "base"), "plan": -1, "trainer": nearestAnchor.id() };
    1873           0 :         if (autogarrison)
    1874           0 :                 metadata.garrisonType = PETRA.GarrisonManager.TYPE_PROTECTION;
    1875           0 :         gameState.ai.queues.emergency.addPlan(new PETRA.TrainingPlan(gameState, templateFound[0], metadata, 1, 1));
    1876           0 :         return true;
    1877             : };
    1878             : 
    1879           0 : PETRA.HQ.prototype.canBuild = function(gameState, structure)
    1880             : {
    1881           0 :         let type = gameState.applyCiv(structure);
    1882           0 :         if (this.buildManager.isUnbuildable(gameState, type))
    1883           0 :                 return false;
    1884             : 
    1885           0 :         if (gameState.isTemplateDisabled(type))
    1886             :         {
    1887           0 :                 this.buildManager.setUnbuildable(gameState, type, Infinity, "disabled");
    1888           0 :                 return false;
    1889             :         }
    1890             : 
    1891           0 :         let template = gameState.getTemplate(type);
    1892           0 :         if (!template)
    1893             :         {
    1894           0 :                 this.buildManager.setUnbuildable(gameState, type, Infinity, "notemplate");
    1895           0 :                 return false;
    1896             :         }
    1897             : 
    1898           0 :         if (!template.available(gameState))
    1899             :         {
    1900           0 :                 this.buildManager.setUnbuildable(gameState, type, 30, "requirements");
    1901           0 :                 return false;
    1902             :         }
    1903             : 
    1904           0 :         if (!this.buildManager.hasBuilder(type))
    1905             :         {
    1906           0 :                 this.buildManager.setUnbuildable(gameState, type, 120, "nobuilder");
    1907           0 :                 return false;
    1908             :         }
    1909             : 
    1910           0 :         if (!this.hasActiveBase())
    1911             :         {
    1912             :                 // if no base, check that we can build outside our territory
    1913           0 :                 let buildTerritories = template.buildTerritories();
    1914           0 :                 if (buildTerritories && (!buildTerritories.length || buildTerritories.length == 1 && buildTerritories[0] == "own"))
    1915             :                 {
    1916           0 :                         this.buildManager.setUnbuildable(gameState, type, 180, "room");
    1917           0 :                         return false;
    1918             :                 }
    1919             :         }
    1920             : 
    1921             :         // build limits
    1922           0 :         let limits = gameState.getEntityLimits();
    1923           0 :         let category = template.buildCategory();
    1924           0 :         if (category && limits[category] !== undefined && gameState.getEntityCounts()[category] >= limits[category])
    1925             :         {
    1926           0 :                 this.buildManager.setUnbuildable(gameState, type, 90, "limit");
    1927           0 :                 return false;
    1928             :         }
    1929             : 
    1930           0 :         return true;
    1931             : };
    1932             : 
    1933           0 : PETRA.HQ.prototype.updateTerritories = function(gameState)
    1934             : {
    1935           0 :         const around = [ [-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0] ];
    1936           0 :         let alliedVictory = gameState.getAlliedVictory();
    1937           0 :         let passabilityMap = gameState.getPassabilityMap();
    1938           0 :         let width = this.territoryMap.width;
    1939           0 :         let cellSize = this.territoryMap.cellSize;
    1940           0 :         let insideSmall = Math.round(45 / cellSize);
    1941           0 :         let insideLarge = Math.round(80 / cellSize);    // should be about the range of towers
    1942           0 :         let expansion = 0;
    1943             : 
    1944           0 :         for (let j = 0; j < this.territoryMap.length; ++j)
    1945             :         {
    1946           0 :                 if (this.borderMap.map[j] & PETRA.outside_Mask)
    1947           0 :                         continue;
    1948           0 :                 if (this.borderMap.map[j] & PETRA.fullFrontier_Mask)
    1949           0 :                         this.borderMap.map[j] &= ~PETRA.fullFrontier_Mask;  // reset the frontier
    1950             : 
    1951           0 :                 if (this.territoryMap.getOwnerIndex(j) != PlayerID)
    1952           0 :                         this.basesManager.removeBaseFromTerritoryIndex(j);
    1953             :                 else
    1954             :                 {
    1955             :                         // Update the frontier
    1956           0 :                         let ix = j%width;
    1957           0 :                         let iz = Math.floor(j/width);
    1958           0 :                         let onFrontier = false;
    1959           0 :                         for (let a of around)
    1960             :                         {
    1961           0 :                                 let jx = ix + Math.round(insideSmall*a[0]);
    1962           0 :                                 if (jx < 0 || jx >= width)
    1963           0 :                                         continue;
    1964           0 :                                 let jz = iz + Math.round(insideSmall*a[1]);
    1965           0 :                                 if (jz < 0 || jz >= width)
    1966           0 :                                         continue;
    1967           0 :                                 if (this.borderMap.map[jx+width*jz] & PETRA.outside_Mask)
    1968           0 :                                         continue;
    1969           0 :                                 let territoryOwner = this.territoryMap.getOwnerIndex(jx+width*jz);
    1970           0 :                                 if (territoryOwner != PlayerID && !(alliedVictory && gameState.isPlayerAlly(territoryOwner)))
    1971             :                                 {
    1972           0 :                                         this.borderMap.map[j] |= PETRA.narrowFrontier_Mask;
    1973           0 :                                         break;
    1974             :                                 }
    1975           0 :                                 jx = ix + Math.round(insideLarge*a[0]);
    1976           0 :                                 if (jx < 0 || jx >= width)
    1977           0 :                                         continue;
    1978           0 :                                 jz = iz + Math.round(insideLarge*a[1]);
    1979           0 :                                 if (jz < 0 || jz >= width)
    1980           0 :                                         continue;
    1981           0 :                                 if (this.borderMap.map[jx+width*jz] & PETRA.outside_Mask)
    1982           0 :                                         continue;
    1983           0 :                                 territoryOwner = this.territoryMap.getOwnerIndex(jx+width*jz);
    1984           0 :                                 if (territoryOwner != PlayerID && !(alliedVictory && gameState.isPlayerAlly(territoryOwner)))
    1985           0 :                                         onFrontier = true;
    1986             :                         }
    1987           0 :                         if (onFrontier && !(this.borderMap.map[j] & PETRA.narrowFrontier_Mask))
    1988           0 :                                 this.borderMap.map[j] |= PETRA.largeFrontier_Mask;
    1989             : 
    1990           0 :                         if (this.basesManager.addTerritoryIndexToBase(gameState, j, passabilityMap))
    1991           0 :                                 expansion++;
    1992             :                 }
    1993             :         }
    1994             : 
    1995           0 :         if (!expansion)
    1996           0 :                 return;
    1997             :         // We've increased our territory, so we may have some new room to build
    1998           0 :         this.buildManager.resetMissingRoom(gameState);
    1999             :         // And if sufficient expansion, check if building a new market would improve our present trade routes
    2000           0 :         let cellArea = this.territoryMap.cellSize * this.territoryMap.cellSize;
    2001           0 :         if (expansion * cellArea > 960)
    2002           0 :                 this.tradeManager.routeProspection = true;
    2003             : };
    2004             : 
    2005             : /**
    2006             :  * returns the base corresponding to baseID
    2007             :  */
    2008           0 : PETRA.HQ.prototype.getBaseByID = function(baseID)
    2009             : {
    2010           0 :         return this.basesManager.getBaseByID(baseID);
    2011             : };
    2012             : 
    2013             : /**
    2014             :  * returns the number of bases with a cc
    2015             :  * ActiveBases includes only those with a built cc
    2016             :  * PotentialBases includes also those with a cc in construction
    2017             :  */
    2018           0 : PETRA.HQ.prototype.numActiveBases = function()
    2019             : {
    2020           0 :         return this.basesManager.numActiveBases();
    2021             : };
    2022             : 
    2023           0 : PETRA.HQ.prototype.hasActiveBase = function()
    2024             : {
    2025           0 :         return this.basesManager.hasActiveBase();
    2026             : };
    2027             : 
    2028           0 : PETRA.HQ.prototype.numPotentialBases = function()
    2029             : {
    2030           0 :         return this.basesManager.numPotentialBases();
    2031             : };
    2032             : 
    2033           0 : PETRA.HQ.prototype.hasPotentialBase = function()
    2034             : {
    2035           0 :         return this.basesManager.hasPotentialBase();
    2036             : };
    2037             : 
    2038           0 : PETRA.HQ.prototype.isDangerousLocation = function(gameState, pos, radius)
    2039             : {
    2040           0 :         return this.isNearInvadingArmy(pos) || this.isUnderEnemyFire(gameState, pos, radius);
    2041             : };
    2042             : 
    2043             : /** Check that the chosen position is not too near from an invading army */
    2044           0 : PETRA.HQ.prototype.isNearInvadingArmy = function(pos)
    2045             : {
    2046           0 :         for (let army of this.defenseManager.armies)
    2047           0 :                 if (army.foePosition && API3.SquareVectorDistance(army.foePosition, pos) < 12000)
    2048           0 :                         return true;
    2049           0 :         return false;
    2050             : };
    2051             : 
    2052           0 : PETRA.HQ.prototype.isUnderEnemyFire = function(gameState, pos, radius = 0)
    2053             : {
    2054           0 :         if (!this.turnCache.firingStructures)
    2055           0 :                 this.turnCache.firingStructures = gameState.updatingCollection("diplo-FiringStructures", API3.Filters.hasDefensiveFire(), gameState.getEnemyStructures());
    2056           0 :         for (let ent of this.turnCache.firingStructures.values())
    2057             :         {
    2058           0 :                 let range = radius + ent.attackRange("Ranged").max;
    2059           0 :                 if (API3.SquareVectorDistance(ent.position(), pos) < range*range)
    2060           0 :                         return true;
    2061             :         }
    2062           0 :         return false;
    2063             : };
    2064             : 
    2065             : /** Compute the capture strength of all units attacking a capturable target */
    2066           0 : PETRA.HQ.prototype.updateCaptureStrength = function(gameState)
    2067             : {
    2068           0 :         this.capturableTargets.clear();
    2069           0 :         for (let ent of gameState.getOwnUnits().values())
    2070             :         {
    2071           0 :                 if (!ent.canCapture())
    2072           0 :                         continue;
    2073           0 :                 let state = ent.unitAIState();
    2074           0 :                 if (!state || !state.split(".")[1] || state.split(".")[1] != "COMBAT")
    2075           0 :                         continue;
    2076           0 :                 let orderData = ent.unitAIOrderData();
    2077           0 :                 if (!orderData || !orderData.length || !orderData[0].target)
    2078           0 :                         continue;
    2079           0 :                 let targetId = orderData[0].target;
    2080           0 :                 let target = gameState.getEntityById(targetId);
    2081           0 :                 if (!target || !target.isCapturable() || !ent.canCapture(target))
    2082           0 :                         continue;
    2083           0 :                 if (!this.capturableTargets.has(targetId))
    2084           0 :                         this.capturableTargets.set(targetId, {
    2085             :                                 "strength": ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture"),
    2086             :                                 "ents": new Set([ent.id()])
    2087             :                         });
    2088             :                 else
    2089             :                 {
    2090           0 :                         let capturableTarget = this.capturableTargets.get(target.id());
    2091           0 :                         capturableTarget.strength += ent.captureStrength() * PETRA.getAttackBonus(ent, target, "Capture");
    2092           0 :                         capturableTarget.ents.add(ent.id());
    2093             :                 }
    2094             :         }
    2095             : 
    2096           0 :         for (let [targetId, capturableTarget] of this.capturableTargets)
    2097             :         {
    2098           0 :                 let target = gameState.getEntityById(targetId);
    2099             :                 let allowCapture;
    2100           0 :                 for (let entId of capturableTarget.ents)
    2101             :                 {
    2102           0 :                         let ent = gameState.getEntityById(entId);
    2103           0 :                         if (allowCapture === undefined)
    2104           0 :                                 allowCapture = PETRA.allowCapture(gameState, ent, target);
    2105           0 :                         let orderData = ent.unitAIOrderData();
    2106           0 :                         if (!orderData || !orderData.length || !orderData[0].attackType)
    2107           0 :                                 continue;
    2108           0 :                         if ((orderData[0].attackType == "Capture") !== allowCapture)
    2109           0 :                                 ent.attack(targetId, allowCapture);
    2110             :                 }
    2111             :         }
    2112             : 
    2113           0 :         this.capturableTargetsTime = gameState.ai.elapsedTime;
    2114             : };
    2115             : 
    2116             : /**
    2117             :  * Check if a structure in blinking territory should/can be defended (currently if it has some attacking armies around)
    2118             :  */
    2119           0 : PETRA.HQ.prototype.isDefendable = function(ent)
    2120             : {
    2121           0 :         if (!this.turnCache.numAround)
    2122           0 :                 this.turnCache.numAround = {};
    2123           0 :         if (this.turnCache.numAround[ent.id()] === undefined)
    2124           0 :                 this.turnCache.numAround[ent.id()] = this.attackManager.numAttackingUnitsAround(ent.position(), 130);
    2125           0 :         return +this.turnCache.numAround[ent.id()] > 8;
    2126             : };
    2127             : 
    2128             : /**
    2129             :  * Get the number of population already accounted for
    2130             :  */
    2131           0 : PETRA.HQ.prototype.getAccountedPopulation = function(gameState)
    2132             : {
    2133           0 :         if (this.turnCache.accountedPopulation == undefined)
    2134             :         {
    2135           0 :                 let pop = gameState.getPopulation();
    2136           0 :                 for (let ent of gameState.getOwnTrainingFacilities().values())
    2137             :                 {
    2138           0 :                         for (let item of ent.trainingQueue())
    2139             :                         {
    2140           0 :                                 if (!item.unitTemplate)
    2141           0 :                                         continue;
    2142           0 :                                 let unitPop = gameState.getTemplate(item.unitTemplate).get("Cost/Population");
    2143           0 :                                 if (unitPop)
    2144           0 :                                         pop += item.count * unitPop;
    2145             :                         }
    2146             :                 }
    2147           0 :                 this.turnCache.accountedPopulation = pop;
    2148             :         }
    2149           0 :         return this.turnCache.accountedPopulation;
    2150             : };
    2151             : 
    2152             : /**
    2153             :  * Get the number of workers already accounted for
    2154             :  */
    2155           0 : PETRA.HQ.prototype.getAccountedWorkers = function(gameState)
    2156             : {
    2157           0 :         if (this.turnCache.accountedWorkers == undefined)
    2158             :         {
    2159           0 :                 let workers = gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length;
    2160           0 :                 for (let ent of gameState.getOwnTrainingFacilities().values())
    2161             :                 {
    2162           0 :                         for (let item of ent.trainingQueue())
    2163             :                         {
    2164           0 :                                 if (!item.metadata || !item.metadata.role || item.metadata.role !== PETRA.Worker.ROLE_WORKER)
    2165           0 :                                         continue;
    2166           0 :                                 workers += item.count;
    2167             :                         }
    2168             :                 }
    2169           0 :                 this.turnCache.accountedWorkers = workers;
    2170             :         }
    2171           0 :         return this.turnCache.accountedWorkers;
    2172             : };
    2173             : 
    2174           0 : PETRA.HQ.prototype.baseManagers = function()
    2175             : {
    2176           0 :         return this.basesManager.baseManagers;
    2177             : };
    2178             : 
    2179             : /**
    2180             :  * @param {number} territoryIndex - The index to get the map for.
    2181             :  * @return {number} - The ID of the base at the given territory index.
    2182             :  */
    2183           0 : PETRA.HQ.prototype.baseAtIndex = function(territoryIndex)
    2184             : {
    2185           0 :         return this.basesManager.baseAtIndex(territoryIndex);
    2186             : };
    2187             : 
    2188             : /**
    2189             :  * Some functions are run every turn
    2190             :  * Others once in a while
    2191             :  */
    2192           0 : PETRA.HQ.prototype.update = function(gameState, queues, events)
    2193             : {
    2194           0 :         Engine.ProfileStart("Headquarters update");
    2195           0 :         this.emergencyManager.update(gameState);
    2196           0 :         this.turnCache = {};
    2197           0 :         this.territoryMap = PETRA.createTerritoryMap(gameState);
    2198           0 :         this.canBarter = gameState.getOwnEntitiesByClass("Market", true).filter(API3.Filters.isBuilt()).hasEntities();
    2199             :         // TODO find a better way to update
    2200           0 :         if (this.currentPhase != gameState.currentPhase())
    2201             :         {
    2202           0 :                 if (this.Config.debug > 0)
    2203           0 :                         API3.warn(" civ " + gameState.getPlayerCiv() + " has phasedUp from " + this.currentPhase +
    2204             :                                   " to " + gameState.currentPhase() + " at time " + gameState.ai.elapsedTime +
    2205             :                                   " phasing " + this.phasing);
    2206           0 :                 this.currentPhase = gameState.currentPhase();
    2207             : 
    2208             :                 // In principle, this.phasing should be already reset to 0 when starting the research
    2209             :                 // but this does not work in case of an autoResearch tech
    2210           0 :                 if (this.phasing)
    2211           0 :                         this.phasing = 0;
    2212             :         }
    2213             : 
    2214             :         /*
    2215             :         if (this.Config.debug > 1)
    2216             :         {
    2217             :                 gameState.getOwnUnits().forEach (function (ent) {
    2218             :                         if (!ent.position())
    2219             :                                 return;
    2220             :                         PETRA.dumpEntity(ent);
    2221             :                 });
    2222             :         }
    2223             :         */
    2224             : 
    2225           0 :         this.checkEvents(gameState, events);
    2226           0 :         this.navalManager.checkEvents(gameState, queues, events);
    2227             : 
    2228           0 :         if (this.phasing)
    2229           0 :                 this.checkPhaseRequirements(gameState, queues);
    2230             :         else
    2231           0 :                 this.researchManager.checkPhase(gameState, queues);
    2232             : 
    2233           0 :         if (this.hasActiveBase())
    2234             :         {
    2235           0 :                 if (gameState.ai.playedTurn % 4 == 0)
    2236           0 :                         this.trainMoreWorkers(gameState, queues);
    2237             : 
    2238           0 :                 if (gameState.ai.playedTurn % 4 == 1)
    2239           0 :                         this.buildMoreHouses(gameState, queues);
    2240             : 
    2241           0 :                 if ((!this.saveResources || this.canBarter) && gameState.ai.playedTurn % 4 == 2)
    2242           0 :                         this.buildFarmstead(gameState, queues);
    2243             : 
    2244           0 :                 if (this.needCorral && gameState.ai.playedTurn % 4 == 3)
    2245           0 :                         this.manageCorral(gameState, queues);
    2246             : 
    2247           0 :                 if (gameState.ai.playedTurn % 5 == 1)
    2248           0 :                         this.researchManager.update(gameState, queues);
    2249             :         }
    2250             : 
    2251           0 :         if (!this.hasPotentialBase() ||
    2252             :             this.canExpand && gameState.ai.playedTurn % 10 == 7 && this.currentPhase > 1)
    2253           0 :                 this.checkBaseExpansion(gameState, queues);
    2254             : 
    2255           0 :         if (this.currentPhase > 1 && gameState.ai.playedTurn % 3 == 0)
    2256             :         {
    2257           0 :                 if (!this.canBarter)
    2258           0 :                         this.buildMarket(gameState, queues);
    2259             : 
    2260           0 :                 if (!this.saveResources)
    2261             :                 {
    2262           0 :                         this.buildForge(gameState, queues);
    2263           0 :                         this.buildTemple(gameState, queues);
    2264             :                 }
    2265             : 
    2266           0 :                 if (gameState.ai.playedTurn % 30 == 0 &&
    2267             :                     gameState.getPopulation() > 0.9 * gameState.getPopulationMax())
    2268           0 :                         this.buildWonder(gameState, queues, false);
    2269             :         }
    2270             : 
    2271           0 :         this.tradeManager.update(gameState, events, queues);
    2272             : 
    2273           0 :         this.garrisonManager.update(gameState, events);
    2274           0 :         this.defenseManager.update(gameState, events);
    2275             : 
    2276           0 :         if (gameState.ai.playedTurn % 3 == 0)
    2277             :         {
    2278           0 :                 this.constructTrainingBuildings(gameState, queues);
    2279           0 :                 if (this.Config.difficulty > PETRA.DIFFICULTY_SANDBOX)
    2280           0 :                         this.buildDefenses(gameState, queues);
    2281             :         }
    2282             : 
    2283           0 :         this.basesManager.update(gameState, queues, events);
    2284             : 
    2285           0 :         this.navalManager.update(gameState, queues, events);
    2286             : 
    2287           0 :         if (this.Config.difficulty > PETRA.DIFFICULTY_SANDBOX && (this.hasActiveBase() || !this.canBuildUnits))
    2288           0 :                 this.attackManager.update(gameState, queues, events);
    2289             : 
    2290           0 :         this.diplomacyManager.update(gameState, events);
    2291             : 
    2292           0 :         this.victoryManager.update(gameState, events, queues);
    2293             : 
    2294             :         // We update the capture strength at the end as it can change attack orders
    2295           0 :         if (gameState.ai.elapsedTime - this.capturableTargetsTime > 3)
    2296           0 :                 this.updateCaptureStrength(gameState);
    2297             : 
    2298           0 :         Engine.ProfileStop();
    2299             : };
    2300             : 
    2301           0 : PETRA.HQ.prototype.Serialize = function()
    2302             : {
    2303           0 :         let properties = {
    2304             :                 "phasing": this.phasing,
    2305             :                 "lastFailedGather": this.lastFailedGather,
    2306             :                 "firstBaseConfig": this.firstBaseConfig,
    2307             :                 "supportRatio": this.supportRatio,
    2308             :                 "targetNumWorkers": this.targetNumWorkers,
    2309             :                 "fortStartTime": this.fortStartTime,
    2310             :                 "towerStartTime": this.towerStartTime,
    2311             :                 "fortressStartTime": this.fortressStartTime,
    2312             :                 "bAdvanced": this.bAdvanced,
    2313             :                 "saveResources": this.saveResources,
    2314             :                 "saveSpace": this.saveSpace,
    2315             :                 "needCorral": this.needCorral,
    2316             :                 "needFarm": this.needFarm,
    2317             :                 "needFish": this.needFish,
    2318             :                 "maxFields": this.maxFields,
    2319             :                 "canExpand": this.canExpand,
    2320             :                 "canBuildUnits": this.canBuildUnits,
    2321             :                 "navalMap": this.navalMap,
    2322             :                 "landRegions": this.landRegions,
    2323             :                 "navalRegions": this.navalRegions,
    2324             :                 "decayingStructures": this.decayingStructures,
    2325             :                 "capturableTargets": this.capturableTargets,
    2326             :                 "capturableTargetsTime": this.capturableTargetsTime
    2327             :         };
    2328             : 
    2329           0 :         if (this.Config.debug == -100)
    2330             :         {
    2331           0 :                 API3.warn(" HQ serialization ---------------------");
    2332           0 :                 API3.warn(" properties " + uneval(properties));
    2333           0 :                 API3.warn(" basesManager " + uneval(this.basesManager.Serialize()));
    2334           0 :                 API3.warn(" attackManager " + uneval(this.attackManager.Serialize()));
    2335           0 :                 API3.warn(" buildManager " + uneval(this.buildManager.Serialize()));
    2336           0 :                 API3.warn(" defenseManager " + uneval(this.defenseManager.Serialize()));
    2337           0 :                 API3.warn(" tradeManager " + uneval(this.tradeManager.Serialize()));
    2338           0 :                 API3.warn(" navalManager " + uneval(this.navalManager.Serialize()));
    2339           0 :                 API3.warn(" researchManager " + uneval(this.researchManager.Serialize()));
    2340           0 :                 API3.warn(" diplomacyManager " + uneval(this.diplomacyManager.Serialize()));
    2341           0 :                 API3.warn(" garrisonManager " + uneval(this.garrisonManager.Serialize()));
    2342           0 :                 API3.warn(" victoryManager " + uneval(this.victoryManager.Serialize()));
    2343           0 :                 API3.warn(" emergencyManager " + uneval(this.emergencyManager.Serialize()));
    2344             :         }
    2345             : 
    2346           0 :         return {
    2347             :                 "properties": properties,
    2348             : 
    2349             :                 "basesManager": this.basesManager.Serialize(),
    2350             :                 "attackManager": this.attackManager.Serialize(),
    2351             :                 "buildManager": this.buildManager.Serialize(),
    2352             :                 "defenseManager": this.defenseManager.Serialize(),
    2353             :                 "tradeManager": this.tradeManager.Serialize(),
    2354             :                 "navalManager": this.navalManager.Serialize(),
    2355             :                 "researchManager": this.researchManager.Serialize(),
    2356             :                 "diplomacyManager": this.diplomacyManager.Serialize(),
    2357             :                 "garrisonManager": this.garrisonManager.Serialize(),
    2358             :                 "victoryManager": this.victoryManager.Serialize(),
    2359             :                 "emergencyManager": this.emergencyManager.Serialize(),
    2360             :         };
    2361             : };
    2362             : 
    2363           0 : PETRA.HQ.prototype.Deserialize = function(gameState, data)
    2364             : {
    2365           0 :         for (let key in data.properties)
    2366           0 :                 this[key] = data.properties[key];
    2367             : 
    2368             : 
    2369           0 :         this.basesManager = new PETRA.BasesManager(this.Config);
    2370           0 :         this.basesManager.init(gameState);
    2371           0 :         this.basesManager.Deserialize(gameState, data.basesManager);
    2372             : 
    2373           0 :         this.navalManager = new PETRA.NavalManager(this.Config);
    2374           0 :         this.navalManager.init(gameState, true);
    2375           0 :         this.navalManager.Deserialize(gameState, data.navalManager);
    2376             : 
    2377           0 :         this.attackManager = new PETRA.AttackManager(this.Config);
    2378           0 :         this.attackManager.Deserialize(gameState, data.attackManager);
    2379           0 :         this.attackManager.init(gameState);
    2380           0 :         this.attackManager.Deserialize(gameState, data.attackManager);
    2381             : 
    2382           0 :         this.buildManager = new PETRA.BuildManager();
    2383           0 :         this.buildManager.Deserialize(data.buildManager);
    2384             : 
    2385           0 :         this.defenseManager = new PETRA.DefenseManager(this.Config);
    2386           0 :         this.defenseManager.Deserialize(gameState, data.defenseManager);
    2387             : 
    2388           0 :         this.tradeManager = new PETRA.TradeManager(this.Config);
    2389           0 :         this.tradeManager.init(gameState);
    2390           0 :         this.tradeManager.Deserialize(gameState, data.tradeManager);
    2391             : 
    2392           0 :         this.researchManager = new PETRA.ResearchManager(this.Config);
    2393           0 :         this.researchManager.Deserialize(data.researchManager);
    2394             : 
    2395           0 :         this.diplomacyManager = new PETRA.DiplomacyManager(this.Config);
    2396           0 :         this.diplomacyManager.Deserialize(data.diplomacyManager);
    2397             : 
    2398           0 :         this.garrisonManager = new PETRA.GarrisonManager(this.Config);
    2399           0 :         this.garrisonManager.Deserialize(data.garrisonManager);
    2400             : 
    2401           0 :         this.victoryManager = new PETRA.VictoryManager(this.Config);
    2402           0 :         this.victoryManager.Deserialize(data.victoryManager);
    2403             : 
    2404           0 :         this.emergencyManager = new PETRA.EmergencyManager(this.Config);
    2405           0 :         this.emergencyManager.Deserialize(data.emergencyManager);
    2406             : };

Generated by: LCOV version 1.14