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

          Line data    Source code
       1             : /**
       2             :  * Armies used by the defense manager.
       3             :  * An army is a collection of own entities and enemy entities.
       4             :  *
       5             :  * Types of armies:
       6             :  * "default":   army to counter an invading army
       7             :  * "capturing": army set to capture a gaia building or recover capture points to one of its own structures
       8             :  *            It must contain only one foe (the building to capture) and never be merged
       9             :  */
      10           0 : PETRA.DefenseArmy = function(gameState, foeEntities, type)
      11             : {
      12           0 :         this.ID = gameState.ai.uniqueIDs.armies++;
      13           0 :         this.type = type || "default";
      14             : 
      15           0 :         this.Config = gameState.ai.Config;
      16           0 :         this.compactSize = this.Config.Defense.armyCompactSize;
      17           0 :         this.breakawaySize = this.Config.Defense.armyBreakawaySize;
      18             : 
      19             :         // average
      20           0 :         this.foePosition = [0, 0];
      21           0 :         this.positionLastUpdate = gameState.ai.elapsedTime;
      22             : 
      23             :         // Some caching
      24             :         // A list of our defenders that were tasked with attacking a particular unit
      25             :         // This doesn't mean that they actually are since they could move on to something else on their own.
      26           0 :         this.assignedAgainst = {};
      27             :         // who we assigned against, for quick removal.
      28           0 :         this.assignedTo = {};
      29             : 
      30           0 :         this.foeEntities = [];
      31           0 :         this.foeStrength = 0;
      32             : 
      33           0 :         this.ownEntities = [];
      34           0 :         this.ownStrength = 0;
      35             : 
      36             :         // actually add units
      37           0 :         for (let id of foeEntities)
      38           0 :                 this.addFoe(gameState, id, true);
      39             : 
      40           0 :         this.recalculatePosition(gameState, true);
      41             : 
      42           0 :         return true;
      43             : };
      44             : 
      45             : /**
      46             :  * add an entity to the enemy army
      47             :  * Will return true if the entity was added and false otherwise.
      48             :  * won't recalculate our position but will dirty it.
      49             :  * force is true at army creation or when merging armies, so in this case we should add it even if far
      50             :  */
      51           0 : PETRA.DefenseArmy.prototype.addFoe = function(gameState, enemyId, force)
      52             : {
      53           0 :         if (this.foeEntities.indexOf(enemyId) !== -1)
      54           0 :                 return false;
      55           0 :         let ent = gameState.getEntityById(enemyId);
      56           0 :         if (!ent || !ent.position())
      57           0 :                 return false;
      58             : 
      59             :         // check distance
      60           0 :         if (!force && API3.SquareVectorDistance(ent.position(), this.foePosition) > this.compactSize)
      61           0 :                 return false;
      62             : 
      63           0 :         this.foeEntities.push(enemyId);
      64           0 :         this.assignedAgainst[enemyId] = [];
      65           0 :         this.positionLastUpdate = 0;
      66           0 :         this.evaluateStrength(ent);
      67           0 :         ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
      68             : 
      69           0 :         return true;
      70             : };
      71             : 
      72             : /**
      73             :  * returns true if the entity was removed and false otherwise.
      74             :  * TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs will happen.
      75             :  */
      76           0 : PETRA.DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity)
      77             : {
      78           0 :         let idx = this.foeEntities.indexOf(enemyId);
      79           0 :         if (idx === -1)
      80           0 :                 return false;
      81             : 
      82           0 :         this.foeEntities.splice(idx, 1);
      83             : 
      84           0 :         this.assignedAgainst[enemyId] = undefined;
      85           0 :         for (let to in this.assignedTo)
      86           0 :                 if (this.assignedTo[to] == enemyId)
      87           0 :                         this.assignedTo[to] = undefined;
      88             : 
      89           0 :         let ent = enemyEntity ? enemyEntity : gameState.getEntityById(enemyId);
      90           0 :         if (ent)    // TODO recompute strength when no entities (could happen if capture+destroy)
      91             :         {
      92           0 :                 this.evaluateStrength(ent, false, true);
      93           0 :                 ent.setMetadata(PlayerID, "PartOfArmy", undefined);
      94             :         }
      95             : 
      96           0 :         return true;
      97             : };
      98             : 
      99             : /**
     100             :  * adds a defender but doesn't assign him yet.
     101             :  * force is true when merging armies, so in this case we should add it even if no position as it can be in a ship
     102             :  */
     103           0 : PETRA.DefenseArmy.prototype.addOwn = function(gameState, id, force)
     104             : {
     105           0 :         if (this.ownEntities.indexOf(id) !== -1)
     106           0 :                 return false;
     107           0 :         let ent = gameState.getEntityById(id);
     108           0 :         if (!ent || !ent.position() && !force)
     109           0 :                 return false;
     110             : 
     111           0 :         this.ownEntities.push(id);
     112           0 :         this.evaluateStrength(ent, true);
     113           0 :         ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
     114           0 :         this.assignedTo[id] = 0;
     115             : 
     116           0 :         let plan = ent.getMetadata(PlayerID, "plan");
     117           0 :         if (plan !== undefined)
     118           0 :                 ent.setMetadata(PlayerID, "plan", -2);
     119             :         else
     120           0 :                 ent.setMetadata(PlayerID, "plan", -3);
     121           0 :         let subrole = ent.getMetadata(PlayerID, "subrole");
     122           0 :         if (subrole === undefined || subrole !== PETRA.Worker.SUBROLE_DEFENDER)
     123           0 :                 ent.setMetadata(PlayerID, "formerSubrole", subrole);
     124           0 :         ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_DEFENDER);
     125           0 :         return true;
     126             : };
     127             : 
     128           0 : PETRA.DefenseArmy.prototype.removeOwn = function(gameState, id, Entity)
     129             : {
     130           0 :         let idx = this.ownEntities.indexOf(id);
     131           0 :         if (idx === -1)
     132           0 :                 return false;
     133             : 
     134           0 :         this.ownEntities.splice(idx, 1);
     135             : 
     136           0 :         if (this.assignedTo[id] !== 0)
     137             :         {
     138           0 :                 let temp = this.assignedAgainst[this.assignedTo[id]];
     139           0 :                 if (temp)
     140           0 :                         temp.splice(temp.indexOf(id), 1);
     141             :         }
     142           0 :         this.assignedTo[id] = undefined;
     143             : 
     144           0 :         let ent = Entity ? Entity : gameState.getEntityById(id);
     145           0 :         if (!ent)
     146           0 :                 return true;
     147             : 
     148           0 :         this.evaluateStrength(ent, true, true);
     149           0 :         ent.setMetadata(PlayerID, "PartOfArmy", undefined);
     150           0 :         if (ent.getMetadata(PlayerID, "plan") === -2)
     151           0 :                 ent.setMetadata(PlayerID, "plan", -1);
     152             :         else
     153           0 :                 ent.setMetadata(PlayerID, "plan", undefined);
     154             : 
     155           0 :         let formerSubrole = ent.getMetadata(PlayerID, "formerSubrole");
     156           0 :         if (formerSubrole !== undefined)
     157           0 :                 ent.setMetadata(PlayerID, "subrole", formerSubrole);
     158             :         else
     159           0 :                 ent.setMetadata(PlayerID, "subrole", undefined);
     160           0 :         ent.setMetadata(PlayerID, "formerSubrole", undefined);
     161             : 
     162             :         // Remove from transport plan if not yet on Board
     163           0 :         if (ent.getMetadata(PlayerID, "transport") !== undefined)
     164             :         {
     165           0 :                 let plan = gameState.ai.HQ.navalManager.getPlan(ent.getMetadata(PlayerID, "transport"));
     166           0 :                 if (plan && plan.state === PETRA.TransportPlan.BOARDING && ent.position())
     167           0 :                         plan.removeUnit(gameState, ent);
     168             :         }
     169             : 
     170             :         /*
     171             :         // TODO be sure that all units in the transport need the cancelation
     172             :         if (!ent.position())    // this unit must still be in a transport plan ... try to cancel it
     173             :         {
     174             :                 let planID = ent.getMetadata(PlayerID, "transport");
     175             :                 // no plans must mean that the unit was in a ship which was destroyed, so do nothing
     176             :                 if (planID)
     177             :                 {
     178             :                         if (gameState.ai.Config.debug > 0)
     179             :                                 warn("ent from army still in transport plan: plan " + planID + " canceled");
     180             :                         let plan = gameState.ai.HQ.navalManager.getPlan(planID);
     181             :                         if (plan && !plan.canceled)
     182             :                                 plan.cancelTransport(gameState);
     183             :                 }
     184             :         }
     185             : */
     186             : 
     187           0 :         return true;
     188             : };
     189             : 
     190             : /**
     191             :  * resets the army properly.
     192             :  * assumes we already cleared dead units.
     193             :  */
     194           0 : PETRA.DefenseArmy.prototype.clear = function(gameState)
     195             : {
     196           0 :         while (this.foeEntities.length > 0)
     197           0 :                 this.removeFoe(gameState, this.foeEntities[0]);
     198             : 
     199             :         // Go back to our or allied territory if needed
     200           0 :         let posOwn = [0, 0];
     201           0 :         let nOwn = 0;
     202           0 :         let posAlly = [0, 0];
     203           0 :         let nAlly = 0;
     204           0 :         let posOther = [0, 0];
     205           0 :         let nOther = 0;
     206           0 :         for (let entId of this.ownEntities)
     207             :         {
     208           0 :                 let ent = gameState.getEntityById(entId);
     209           0 :                 if (!ent || !ent.position())
     210           0 :                         continue;
     211           0 :                 let pos = ent.position();
     212           0 :                 let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(pos);
     213           0 :                 if (territoryOwner === PlayerID)
     214             :                 {
     215           0 :                         posOwn[0] += pos[0];
     216           0 :                         posOwn[1] += pos[1];
     217           0 :                         ++nOwn;
     218             :                 }
     219           0 :                 else if (gameState.isPlayerMutualAlly(territoryOwner))
     220             :                 {
     221           0 :                         posAlly[0] += pos[0];
     222           0 :                         posAlly[1] += pos[1];
     223           0 :                         ++nAlly;
     224             :                 }
     225             :                 else
     226             :                 {
     227           0 :                         posOther[0] += pos[0];
     228           0 :                         posOther[1] += pos[1];
     229           0 :                         ++nOther;
     230             :                 }
     231             :         }
     232             :         let destination;
     233             :         let defensiveFound;
     234             :         let distmin;
     235           0 :         let radius = 0;
     236           0 :         if (nOwn > 0)
     237           0 :                 destination = [posOwn[0]/nOwn, posOwn[1]/nOwn];
     238           0 :         else if (nAlly > 0)
     239           0 :                 destination = [posAlly[0]/nAlly, posAlly[1]/nAlly];
     240             :         else
     241             :         {
     242           0 :                 posOther[0] /= nOther;
     243           0 :                 posOther[1] /= nOther;
     244           0 :                 let armyAccess = gameState.ai.accessibility.getAccessValue(posOther);
     245           0 :                 for (let struct of gameState.getAllyStructures().values())
     246             :                 {
     247           0 :                         let pos = struct.position();
     248           0 :                         if (!pos || !gameState.isPlayerMutualAlly(gameState.ai.HQ.territoryMap.getOwner(pos)))
     249           0 :                                 continue;
     250           0 :                         if (PETRA.getLandAccess(gameState, struct) !== armyAccess)
     251           0 :                                 continue;
     252           0 :                         let defensiveStruct = struct.hasDefensiveFire();
     253           0 :                         if (defensiveFound && !defensiveStruct)
     254           0 :                                 continue;
     255           0 :                         let dist = API3.SquareVectorDistance(posOther, pos);
     256           0 :                         if (distmin && dist > distmin && (defensiveFound || !defensiveStruct))
     257           0 :                                 continue;
     258           0 :                         if (defensiveStruct)
     259           0 :                                 defensiveFound = true;
     260           0 :                         distmin = dist;
     261           0 :                         destination = pos;
     262           0 :                         radius = struct.obstructionRadius().max;
     263             :                 }
     264             :         }
     265           0 :         while (this.ownEntities.length > 0)
     266             :         {
     267           0 :                 let entId = this.ownEntities[0];
     268           0 :                 this.removeOwn(gameState, entId);
     269           0 :                 let ent = gameState.getEntityById(entId);
     270           0 :                 if (ent)
     271             :                 {
     272           0 :                         if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined ||
     273             :                                                ent.getMetadata(PlayerID, "transporter") !== undefined)
     274           0 :                                 continue;
     275           0 :                         if (ent.healthLevel() < this.Config.garrisonHealthLevel.low &&
     276             :                             gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, ent))
     277           0 :                                 continue;
     278             : 
     279           0 :                         if (destination && !gameState.isPlayerMutualAlly(gameState.ai.HQ.territoryMap.getOwner(ent.position())))
     280           0 :                                 ent.moveToRange(destination[0], destination[1], radius, radius + 5);
     281             :                         else
     282           0 :                                 ent.stopMoving();
     283             :                 }
     284             :         }
     285             : 
     286           0 :         this.assignedAgainst = {};
     287           0 :         this.assignedTo = {};
     288             : 
     289           0 :         this.recalculateStrengths(gameState);
     290           0 :         this.recalculatePosition(gameState);
     291             : };
     292             : 
     293           0 : PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID)
     294             : {
     295             :         // we'll assume this defender is ours already.
     296             :         // we'll also override any previous assignment
     297             : 
     298           0 :         let ent = gameState.getEntityById(entID);
     299           0 :         if (!ent || !ent.position())
     300           0 :                 return false;
     301             : 
     302             :         // try to return its resources, and if any, the attack order will be queued
     303           0 :         let queued = PETRA.returnResources(gameState, ent);
     304             : 
     305             :         let idMin;
     306             :         let distMin;
     307             :         let idMinAll;
     308             :         let distMinAll;
     309           0 :         for (let id of this.foeEntities)
     310             :         {
     311           0 :                 let eEnt = gameState.getEntityById(id);
     312           0 :                 if (!eEnt || !eEnt.position())  // probably can't happen.
     313           0 :                         continue;
     314             : 
     315           0 :                 if (!ent.canAttackTarget(eEnt, PETRA.allowCapture(gameState, ent, eEnt)))
     316           0 :                         continue;
     317             : 
     318           0 :                 if (eEnt.hasClass("Unit") && eEnt.unitAIOrderData() && eEnt.unitAIOrderData().length &&
     319             :                         eEnt.unitAIOrderData()[0].target && eEnt.unitAIOrderData()[0].target == entID)
     320             :                 {   // being attacked  >>> target the unit
     321           0 :                         idMin = id;
     322           0 :                         break;
     323             :                 }
     324             : 
     325             :                 // already enough units against it
     326           0 :                 if (this.assignedAgainst[id].length > 8 ||
     327             :                         this.assignedAgainst[id].length > 5 && !eEnt.hasClass("Hero") && !PETRA.isSiegeUnit(eEnt))
     328           0 :                         continue;
     329             : 
     330           0 :                 let dist = API3.SquareVectorDistance(ent.position(), eEnt.position());
     331           0 :                 if (idMinAll === undefined || dist < distMinAll)
     332             :                 {
     333           0 :                         idMinAll = id;
     334           0 :                         distMinAll = dist;
     335             :                 }
     336           0 :                 if (this.assignedAgainst[id].length > 2)
     337           0 :                         continue;
     338           0 :                 if (idMin === undefined || dist < distMin)
     339             :                 {
     340           0 :                         idMin = id;
     341           0 :                         distMin = dist;
     342             :                 }
     343             :         }
     344             : 
     345             :         let idFoe;
     346           0 :         if (idMin !== undefined)
     347           0 :                 idFoe = idMin;
     348           0 :         else if (idMinAll !== undefined)
     349           0 :                 idFoe = idMinAll;
     350             :         else
     351           0 :                 return false;
     352             : 
     353           0 :         let ownIndex = PETRA.getLandAccess(gameState, ent);
     354           0 :         let foeEnt = gameState.getEntityById(idFoe);
     355           0 :         let foePosition = foeEnt.position();
     356           0 :         let foeIndex = gameState.ai.accessibility.getAccessValue(foePosition);
     357           0 :         if (ownIndex == foeIndex || ent.hasClass("Ship"))
     358             :         {
     359           0 :                 this.assignedTo[entID] = idFoe;
     360           0 :                 this.assignedAgainst[idFoe].push(entID);
     361           0 :                 ent.attack(idFoe, PETRA.allowCapture(gameState, ent, foeEnt), queued);
     362             :         }
     363             :         else
     364           0 :                 gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
     365           0 :         return true;
     366             : };
     367             : 
     368           0 : PETRA.DefenseArmy.prototype.getType = function()
     369             : {
     370           0 :         return this.type;
     371             : };
     372             : 
     373           0 : PETRA.DefenseArmy.prototype.getState = function()
     374             : {
     375           0 :         if (!this.foeEntities.length)
     376           0 :                 return 0;
     377           0 :         return 1;
     378             : };
     379             : 
     380             : /**
     381             :  * merge this army with another properly.
     382             :  * assumes units are in only one army.
     383             :  * also assumes that all have been properly cleaned up (no dead units).
     384             :  */
     385           0 : PETRA.DefenseArmy.prototype.merge = function(gameState, otherArmy)
     386             : {
     387             :         // copy over all parameters.
     388           0 :         for (let i in otherArmy.assignedAgainst)
     389             :         {
     390           0 :                 if (this.assignedAgainst[i] === undefined)
     391           0 :                         this.assignedAgainst[i] = otherArmy.assignedAgainst[i];
     392             :                 else
     393           0 :                         this.assignedAgainst[i] = this.assignedAgainst[i].concat(otherArmy.assignedAgainst[i]);
     394             :         }
     395           0 :         for (let i in otherArmy.assignedTo)
     396           0 :                 this.assignedTo[i] = otherArmy.assignedTo[i];
     397             : 
     398           0 :         for (let id of otherArmy.foeEntities)
     399           0 :                 this.addFoe(gameState, id, true);
     400             :         // TODO: reassign those ?
     401           0 :         for (let id of otherArmy.ownEntities)
     402           0 :                 this.addOwn(gameState, id, true);
     403             : 
     404           0 :         this.recalculatePosition(gameState, true);
     405           0 :         this.recalculateStrengths(gameState);
     406             : 
     407           0 :         return true;
     408             : };
     409             : 
     410           0 : PETRA.DefenseArmy.prototype.needsDefenders = function(gameState)
     411             : {
     412             :         let defenseRatio;
     413           0 :         let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(this.foePosition);
     414           0 :         if (territoryOwner == PlayerID)
     415           0 :                 defenseRatio = this.Config.Defense.defenseRatio.own;
     416           0 :         else if (gameState.isPlayerAlly(territoryOwner))
     417             :         {
     418           0 :                 defenseRatio = this.Config.Defense.defenseRatio.ally;
     419           0 :                 let numExclusiveAllies = 0;
     420           0 :                 for (let p = 1; p < gameState.sharedScript.playersData.length; ++p)
     421           0 :                         if (p != territoryOwner && gameState.sharedScript.playersData[p].isAlly[territoryOwner])
     422           0 :                                 ++numExclusiveAllies;
     423           0 :                 defenseRatio /= 1 + 0.5*Math.max(0, numExclusiveAllies-1);
     424             :         }
     425             :         else
     426           0 :                 defenseRatio = this.Config.Defense.defenseRatio.neutral;
     427             : 
     428             :         // some preliminary checks because we don't update for tech so entStrength removed can be > entStrength added
     429           0 :         if (this.foeStrength <= 0 || this.ownStrength <= 0)
     430           0 :                 this.recalculateStrengths(gameState);
     431             : 
     432           0 :         if (this.foeStrength * defenseRatio <= this.ownStrength)
     433           0 :                 return false;
     434           0 :         return this.foeStrength * defenseRatio - this.ownStrength;
     435             : };
     436             : 
     437             : 
     438             : /** if not forced, will only recalculate if on a different turn. */
     439           0 : PETRA.DefenseArmy.prototype.recalculatePosition = function(gameState, force)
     440             : {
     441           0 :         if (!force && this.positionLastUpdate === gameState.ai.elapsedTime)
     442           0 :                 return;
     443             : 
     444           0 :         let npos = 0;
     445           0 :         let pos = [0, 0];
     446           0 :         for (let id of this.foeEntities)
     447             :         {
     448           0 :                 let ent = gameState.getEntityById(id);
     449           0 :                 if (!ent || !ent.position())
     450           0 :                         continue;
     451           0 :                 npos++;
     452           0 :                 let epos = ent.position();
     453           0 :                 pos[0] += epos[0];
     454           0 :                 pos[1] += epos[1];
     455             :         }
     456             :         // if npos = 0, the army must have been destroyed and will be removed next turn. keep previous position
     457           0 :         if (npos > 0)
     458             :         {
     459           0 :                 this.foePosition[0] = pos[0]/npos;
     460           0 :                 this.foePosition[1] = pos[1]/npos;
     461             :         }
     462             : 
     463           0 :         this.positionLastUpdate = gameState.ai.elapsedTime;
     464             : };
     465             : 
     466           0 : PETRA.DefenseArmy.prototype.recalculateStrengths = function(gameState)
     467             : {
     468           0 :         this.ownStrength = 0;
     469           0 :         this.foeStrength = 0;
     470             : 
     471           0 :         for (let id of this.foeEntities)
     472           0 :                 this.evaluateStrength(gameState.getEntityById(id));
     473           0 :         for (let id of this.ownEntities)
     474           0 :                 this.evaluateStrength(gameState.getEntityById(id), true);
     475             : };
     476             : 
     477             : /** adds or remove the strength of the entity either to the enemy or to our units. */
     478           0 : PETRA.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove)
     479             : {
     480           0 :         if (!ent)
     481           0 :                 return;
     482             : 
     483             :         let entStrength;
     484           0 :         if (ent.hasClass("Structure"))
     485             :         {
     486           0 :                 if (ent.owner() !== PlayerID)
     487           0 :                         entStrength = ent.getDefaultArrow() ? 6*ent.getDefaultArrow() : 4;
     488             :                 else    // small strength used only when we try to recover capture points
     489           0 :                         entStrength = 2;
     490             :         }
     491             :         else
     492           0 :                 entStrength = PETRA.getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance);
     493             : 
     494             :         // TODO adapt the getMaxStrength function for animals.
     495             :         // For the time being, just increase it for elephants as the returned value is too small.
     496           0 :         if (ent.hasClasses(["Animal+Elephant"]))
     497           0 :                 entStrength *= 3;
     498             : 
     499           0 :         if (remove)
     500           0 :                 entStrength *= -1;
     501             : 
     502           0 :         if (isOwn)
     503           0 :                 this.ownStrength += entStrength;
     504             :         else
     505           0 :                 this.foeStrength += entStrength;
     506             : };
     507             : 
     508           0 : PETRA.DefenseArmy.prototype.checkEvents = function(gameState, events)
     509             : {
     510             :         // Warning the metadata is already cloned in shared.js. Futhermore, changes should be done before destroyEvents
     511             :         // otherwise it would remove the old entity from this army list
     512             :         // TODO we should may-be reevaluate the strength
     513           0 :         for (let evt of events.EntityRenamed)   // take care of promoted and packed units
     514             :         {
     515           0 :                 if (this.foeEntities.indexOf(evt.entity) !== -1)
     516             :                 {
     517           0 :                         let ent = gameState.getEntityById(evt.newentity);
     518           0 :                         if (ent && ent.templateName().indexOf("resource|") !== -1)  // corpse of animal killed
     519           0 :                                 continue;
     520           0 :                         let idx = this.foeEntities.indexOf(evt.entity);
     521           0 :                         this.foeEntities[idx] = evt.newentity;
     522           0 :                         this.assignedAgainst[evt.newentity] = this.assignedAgainst[evt.entity];
     523           0 :                         this.assignedAgainst[evt.entity] = undefined;
     524           0 :                         for (let to in this.assignedTo)
     525           0 :                                 if (this.assignedTo[to] === evt.entity)
     526           0 :                                         this.assignedTo[to] = evt.newentity;
     527             :                 }
     528           0 :                 else if (this.ownEntities.indexOf(evt.entity) !== -1)
     529             :                 {
     530           0 :                         let idx = this.ownEntities.indexOf(evt.entity);
     531           0 :                         this.ownEntities[idx] = evt.newentity;
     532           0 :                         this.assignedTo[evt.newentity] = this.assignedTo[evt.entity];
     533           0 :                         this.assignedTo[evt.entity] = undefined;
     534           0 :                         for (let against in this.assignedAgainst)
     535             :                         {
     536           0 :                                 if (!this.assignedAgainst[against])
     537           0 :                                         continue;
     538           0 :                                 if (this.assignedAgainst[against].indexOf(evt.entity) !== -1)
     539           0 :                                         this.assignedAgainst[against][this.assignedAgainst[against].indexOf(evt.entity)] = evt.newentity;
     540             :                         }
     541             :                 }
     542             :         }
     543             : 
     544           0 :         for (let evt of events.Garrison)
     545           0 :                 this.removeFoe(gameState, evt.entity);
     546             : 
     547           0 :         for (let evt of events.OwnershipChanged)        // captured
     548             :         {
     549           0 :                 if (!gameState.isPlayerEnemy(evt.to))
     550           0 :                         this.removeFoe(gameState, evt.entity);
     551           0 :                 else if (evt.from === PlayerID)
     552           0 :                         this.removeOwn(gameState, evt.entity);
     553             :         }
     554             : 
     555           0 :         for (let evt of events.Destroy)
     556             :         {
     557           0 :                 let entityObj = evt.entityObj || undefined;
     558             :                 // we may have capture+destroy, so do not trust owner and check all possibilities
     559           0 :                 this.removeOwn(gameState, evt.entity, entityObj);
     560           0 :                 this.removeFoe(gameState, evt.entity, entityObj);
     561             :         }
     562             : };
     563             : 
     564           0 : PETRA.DefenseArmy.prototype.update = function(gameState)
     565             : {
     566           0 :         for (let entId of this.ownEntities)
     567             :         {
     568           0 :                 let ent = gameState.getEntityById(entId);
     569           0 :                 if (!ent)
     570           0 :                         continue;
     571           0 :                 let orderData = ent.unitAIOrderData();
     572           0 :                 if (!orderData.length && !ent.getMetadata(PlayerID, "transport"))
     573           0 :                         this.assignUnit(gameState, entId);
     574           0 :                 else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
     575             :                 {
     576           0 :                         let target = gameState.getEntityById(orderData[0].target);
     577           0 :                         if (target && !PETRA.allowCapture(gameState, ent, target))
     578           0 :                                 ent.attack(orderData[0].target, false);
     579             :                 }
     580             :         }
     581             : 
     582           0 :         if (this.type == "capturing")
     583             :         {
     584           0 :                 if (this.foeEntities.length && gameState.getEntityById(this.foeEntities[0]))
     585             :                 {
     586             :                         // Check if we still still some capturePoints to recover
     587             :                         // and if not, remove this foe from the list (capture army have only one foe)
     588           0 :                         let capture = gameState.getEntityById(this.foeEntities[0]).capturePoints();
     589           0 :                         if (capture)
     590           0 :                                 for (let j = 0; j < capture.length; ++j)
     591           0 :                                         if (gameState.isPlayerEnemy(j) && capture[j] > 0)
     592           0 :                                                 return [];
     593           0 :                         this.removeFoe(gameState, this.foeEntities[0]);
     594             :                 }
     595           0 :                 return [];
     596             :         }
     597             : 
     598           0 :         let breakaways = [];
     599             :         // TODO: assign unassigned defenders, cleanup of a few things.
     600             :         // perhaps occasional strength recomputation
     601             : 
     602             :         // occasional update or breakaways, positions…
     603           0 :         if (gameState.ai.elapsedTime - this.positionLastUpdate > 5)
     604             :         {
     605           0 :                 this.recalculatePosition(gameState);
     606           0 :                 this.positionLastUpdate = gameState.ai.elapsedTime;
     607             : 
     608             :                 // Check for breakaways.
     609           0 :                 for (let i = 0; i < this.foeEntities.length; ++i)
     610             :                 {
     611           0 :                         let id = this.foeEntities[i];
     612           0 :                         let ent = gameState.getEntityById(id);
     613           0 :                         if (!ent || !ent.position())
     614           0 :                                 continue;
     615           0 :                         if (API3.SquareVectorDistance(ent.position(), this.foePosition) > this.breakawaySize)
     616             :                         {
     617           0 :                                 breakaways.push(id);
     618           0 :                                 if (this.removeFoe(gameState, id))
     619           0 :                                         i--;
     620             :                         }
     621             :                 }
     622             : 
     623           0 :                 this.recalculatePosition(gameState);
     624             :         }
     625             : 
     626           0 :         return breakaways;
     627             : };
     628             : 
     629           0 : PETRA.DefenseArmy.prototype.Serialize = function()
     630             : {
     631           0 :         return {
     632             :                 "ID": this.ID,
     633             :                 "type": this.type,
     634             :                 "foePosition": this.foePosition,
     635             :                 "positionLastUpdate": this.positionLastUpdate,
     636             :                 "assignedAgainst": this.assignedAgainst,
     637             :                 "assignedTo": this.assignedTo,
     638             :                 "foeEntities": this.foeEntities,
     639             :                 "foeStrength": this.foeStrength,
     640             :                 "ownEntities": this.ownEntities,
     641             :                 "ownStrength": this.ownStrength
     642             :         };
     643             : };
     644             : 
     645           0 : PETRA.DefenseArmy.prototype.Deserialize = function(data)
     646             : {
     647           0 :         for (let key in data)
     648           0 :                 this[key] = data[key];
     649             : };

Generated by: LCOV version 1.14