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

          Line data    Source code
       1             : /**
       2             :  * Manage the garrisonHolders
       3             :  * When a unit is ordered to garrison, it must be done through this.garrison() function so that
       4             :  * an object in this.holders is created. This object contains an array with the entities
       5             :  * in the process of being garrisoned. To have all garrisoned units, we must add those in holder.garrisoned().
       6             :  * Futhermore garrison units have a metadata garrisonType describing its reason (protection, transport, ...)
       7             :  */
       8             : 
       9           0 : PETRA.GarrisonManager = function(Config)
      10             : {
      11           0 :         this.Config = Config;
      12           0 :         this.holders = new Map();
      13           0 :         this.decayingStructures = new Map();
      14             : };
      15             : 
      16           0 : PETRA.GarrisonManager.TYPE_FORCE = "force";
      17           0 : PETRA.GarrisonManager.TYPE_TRADE = "trade";
      18           0 : PETRA.GarrisonManager.TYPE_PROTECTION = "protection";
      19           0 : PETRA.GarrisonManager.TYPE_DECAY = "decay";
      20           0 : PETRA.GarrisonManager.TYPE_EMERGENCY = "emergency";
      21             : 
      22           0 : PETRA.GarrisonManager.prototype.update = function(gameState, events)
      23             : {
      24             :         // First check for possible upgrade of a structure
      25           0 :         for (let evt of events.EntityRenamed)
      26             :         {
      27           0 :                 for (let id of this.holders.keys())
      28             :                 {
      29           0 :                         if (id != evt.entity)
      30           0 :                                 continue;
      31           0 :                         let data = this.holders.get(id);
      32           0 :                         let newHolder = gameState.getEntityById(evt.newentity);
      33           0 :                         if (newHolder && newHolder.isGarrisonHolder())
      34             :                         {
      35           0 :                                 this.holders.delete(id);
      36           0 :                                 this.holders.set(evt.newentity, data);
      37             :                         }
      38             :                         else
      39             :                         {
      40           0 :                                 for (let entId of data.list)
      41             :                                 {
      42           0 :                                         let ent = gameState.getEntityById(entId);
      43           0 :                                         if (!ent || ent.getMetadata(PlayerID, "garrisonHolder") != id)
      44           0 :                                                 continue;
      45           0 :                                         this.leaveGarrison(ent);
      46           0 :                                         ent.stopMoving();
      47             :                                 }
      48           0 :                                 this.holders.delete(id);
      49             :                         }
      50             :                 }
      51             : 
      52           0 :                 for (let id of this.decayingStructures.keys())
      53             :                 {
      54           0 :                         if (id !== evt.entity)
      55           0 :                                 continue;
      56           0 :                         this.decayingStructures.delete(id);
      57           0 :                         if (this.decayingStructures.has(evt.newentity))
      58           0 :                                 continue;
      59           0 :                         let ent = gameState.getEntityById(evt.newentity);
      60           0 :                         if (!ent || !ent.territoryDecayRate() || !ent.garrisonRegenRate())
      61           0 :                                 continue;
      62           0 :                         let gmin = Math.ceil((ent.territoryDecayRate() - ent.defaultRegenRate()) / ent.garrisonRegenRate());
      63           0 :                         this.decayingStructures.set(evt.newentity, gmin);
      64             :                 }
      65             :         }
      66             : 
      67           0 :         for (let [id, data] of this.holders.entries())
      68             :         {
      69           0 :                 let list = data.list;
      70           0 :                 let holder = gameState.getEntityById(id);
      71           0 :                 if (!holder || !gameState.isPlayerAlly(holder.owner()))
      72             :                 {
      73             :                         // this holder was certainly destroyed or captured. Let's remove it
      74           0 :                         for (let entId of list)
      75             :                         {
      76           0 :                                 let ent = gameState.getEntityById(entId);
      77           0 :                                 if (!ent || ent.getMetadata(PlayerID, "garrisonHolder") != id)
      78           0 :                                         continue;
      79           0 :                                 this.leaveGarrison(ent);
      80           0 :                                 ent.stopMoving();
      81             :                         }
      82           0 :                         this.holders.delete(id);
      83           0 :                         continue;
      84             :                 }
      85             : 
      86             :                 // Update the list of garrisoned units
      87           0 :                 for (let j = 0; j < list.length; ++j)
      88             :                 {
      89           0 :                         for (let evt of events.EntityRenamed)
      90           0 :                                 if (evt.entity === list[j])
      91           0 :                                         list[j] = evt.newentity;
      92             : 
      93           0 :                         let ent = gameState.getEntityById(list[j]);
      94           0 :                         if (!ent)       // unit must have been killed while garrisoning
      95           0 :                                 list.splice(j--, 1);
      96           0 :                         else if (holder.garrisoned().indexOf(list[j]) !== -1)   // unit is garrisoned
      97             :                         {
      98           0 :                                 this.leaveGarrison(ent);
      99           0 :                                 list.splice(j--, 1);
     100             :                         }
     101             :                         else
     102             :                         {
     103           0 :                                 if (ent.unitAIOrderData().some(order => order.target && order.target == id))
     104           0 :                                         continue;
     105           0 :                                 if (ent.getMetadata(PlayerID, "garrisonHolder") == id)
     106             :                                 {
     107             :                                         // The garrison order must have failed
     108           0 :                                         this.leaveGarrison(ent);
     109           0 :                                         list.splice(j--, 1);
     110             :                                 }
     111             :                                 else
     112             :                                 {
     113           0 :                                         if (gameState.ai.Config.debug > 0)
     114             :                                         {
     115           0 :                                                 API3.warn("Petra garrison error: unit " + ent.id() + " (" + ent.genericName() +
     116             :                                                           ") is expected to garrison in " + id + " (" + holder.genericName() +
     117             :                                                           "), but has no such garrison order " + uneval(ent.unitAIOrderData()));
     118           0 :                                                 PETRA.dumpEntity(ent);
     119             :                                         }
     120           0 :                                         list.splice(j--, 1);
     121             :                                 }
     122             :                         }
     123             : 
     124             :                 }
     125             : 
     126           0 :                 if (!holder.position())     // could happen with siege unit inside a ship
     127           0 :                         continue;
     128             : 
     129           0 :                 if (gameState.ai.elapsedTime - holder.getMetadata(PlayerID, "holderTimeUpdate") > 3)
     130             :                 {
     131           0 :                         let range = holder.attackRange("Ranged") ? holder.attackRange("Ranged").max : 80;
     132           0 :                         let around = { "defenseStructure": false, "meleeSiege": false, "rangeSiege": false, "unit": false };
     133           0 :                         for (let ent of gameState.getEnemyEntities().values())
     134             :                         {
     135           0 :                                 if (ent.hasClass("Structure"))
     136             :                                 {
     137           0 :                                         if (!ent.attackRange("Ranged"))
     138           0 :                                                 continue;
     139             :                                 }
     140           0 :                                 else if (ent.hasClass("Unit"))
     141             :                                 {
     142           0 :                                         if (ent.owner() == 0 && (!ent.unitAIState() || ent.unitAIState().split(".")[1] != "COMBAT"))
     143           0 :                                                 continue;
     144             :                                 }
     145             :                                 else
     146           0 :                                         continue;
     147           0 :                                 if (!ent.position())
     148           0 :                                         continue;
     149           0 :                                 let dist = API3.SquareVectorDistance(ent.position(), holder.position());
     150           0 :                                 if (dist > range*range)
     151           0 :                                         continue;
     152           0 :                                 if (ent.hasClass("Structure"))
     153           0 :                                         around.defenseStructure = true;
     154           0 :                                 else if (PETRA.isSiegeUnit(ent))
     155             :                                 {
     156           0 :                                         if (ent.attackTypes().indexOf("Melee") !== -1)
     157           0 :                                                 around.meleeSiege = true;
     158             :                                         else
     159           0 :                                                 around.rangeSiege = true;
     160             :                                 }
     161             :                                 else
     162             :                                 {
     163           0 :                                         around.unit = true;
     164           0 :                                         break;
     165             :                                 }
     166             :                         }
     167             :                         // Keep defenseManager.garrisonUnitsInside in sync to avoid garrisoning-ungarrisoning some units
     168           0 :                         data.allowMelee = around.defenseStructure || around.unit;
     169             : 
     170           0 :                         for (let entId of holder.garrisoned())
     171             :                         {
     172           0 :                                 let ent = gameState.getEntityById(entId);
     173           0 :                                 if (ent.owner() === PlayerID && !this.keepGarrisoned(ent, holder, around))
     174           0 :                                         holder.unload(entId);
     175             :                         }
     176           0 :                         for (let j = 0; j < list.length; ++j)
     177             :                         {
     178           0 :                                 let ent = gameState.getEntityById(list[j]);
     179           0 :                                 if (this.keepGarrisoned(ent, holder, around))
     180           0 :                                         continue;
     181           0 :                                 if (ent.getMetadata(PlayerID, "garrisonHolder") == id)
     182             :                                 {
     183           0 :                                         this.leaveGarrison(ent);
     184           0 :                                         ent.stopMoving();
     185             :                                 }
     186           0 :                                 list.splice(j--, 1);
     187             :                         }
     188           0 :                         if (this.numberOfGarrisonedSlots(holder) === 0)
     189           0 :                                 this.holders.delete(id);
     190             :                         else
     191           0 :                                 holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime);
     192             :                 }
     193             :         }
     194             : 
     195             :         // Warning new garrison orders (as in the following lines) should be done after having updated the holders
     196             :         // (or TODO we should add a test that the garrison order is from a previous turn when updating)
     197           0 :         for (let [id, gmin] of this.decayingStructures.entries())
     198             :         {
     199           0 :                 let ent = gameState.getEntityById(id);
     200           0 :                 if (!ent || ent.owner() !== PlayerID)
     201           0 :                         this.decayingStructures.delete(id);
     202           0 :                 else if (this.numberOfGarrisonedSlots(ent) < gmin)
     203           0 :                         gameState.ai.HQ.defenseManager.garrisonUnitsInside(gameState, ent, { "min": gmin, "type": PETRA.GarrisonManager.TYPE_DECAY });
     204             :         }
     205             : };
     206             : 
     207             : /** TODO should add the units garrisoned inside garrisoned units */
     208           0 : PETRA.GarrisonManager.prototype.numberOfGarrisonedUnits = function(holder)
     209             : {
     210           0 :         if (!this.holders.has(holder.id()))
     211           0 :                 return holder.garrisoned().length;
     212             : 
     213           0 :         return holder.garrisoned().length + this.holders.get(holder.id()).list.length;
     214             : };
     215             : 
     216             : /** TODO should add the units garrisoned inside garrisoned units */
     217           0 : PETRA.GarrisonManager.prototype.numberOfGarrisonedSlots = function(holder)
     218             : {
     219           0 :         if (!this.holders.has(holder.id()))
     220           0 :                 return holder.garrisonedSlots();
     221             : 
     222           0 :         return holder.garrisonedSlots() + this.holders.get(holder.id()).list.length;
     223             : };
     224             : 
     225           0 : PETRA.GarrisonManager.prototype.allowMelee = function(holder)
     226             : {
     227           0 :         if (!this.holders.has(holder.id()))
     228           0 :                 return undefined;
     229             : 
     230           0 :         return this.holders.get(holder.id()).allowMelee;
     231             : };
     232             : 
     233             : /** This is just a pre-garrison state, while the entity walk to the garrison holder */
     234           0 : PETRA.GarrisonManager.prototype.garrison = function(gameState, ent, holder, type)
     235             : {
     236           0 :         if (this.numberOfGarrisonedSlots(holder) >= holder.garrisonMax() || !ent.canGarrison())
     237           0 :                 return;
     238             : 
     239           0 :         this.registerHolder(gameState, holder);
     240           0 :         this.holders.get(holder.id()).list.push(ent.id());
     241             : 
     242           0 :         if (gameState.ai.Config.debug > 2)
     243             :         {
     244           0 :                 warn("garrison unit " + ent.genericName() + " in " + holder.genericName() + " with type " + type);
     245           0 :                 warn(" we try to garrison a unit with plan " + ent.getMetadata(PlayerID, "plan") + " and role " + ent.getMetadata(PlayerID, "role") +
     246             :                      " and subrole " + ent.getMetadata(PlayerID, "subrole") + " and transport " + ent.getMetadata(PlayerID, "transport"));
     247             :         }
     248             : 
     249           0 :         if (ent.getMetadata(PlayerID, "plan") !== undefined)
     250           0 :                 ent.setMetadata(PlayerID, "plan", -2);
     251             :         else
     252           0 :                 ent.setMetadata(PlayerID, "plan", -3);
     253           0 :         ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_GARRISONING);
     254           0 :         ent.setMetadata(PlayerID, "garrisonHolder", holder.id());
     255           0 :         ent.setMetadata(PlayerID, "garrisonType", type);
     256           0 :         ent.garrison(holder);
     257             : };
     258             : 
     259             : /**
     260             :  This is the end of the pre-garrison state, either because the entity is really garrisoned
     261             :  or because it has changed its order (i.e. because the garrisonHolder was destroyed)
     262             :  This function is for internal use inside garrisonManager. From outside, you should also update
     263             :  the holder and then using cancelGarrison should be the preferred solution
     264             :  */
     265           0 : PETRA.GarrisonManager.prototype.leaveGarrison = function(ent)
     266             : {
     267           0 :         ent.setMetadata(PlayerID, "subrole", undefined);
     268           0 :         if (ent.getMetadata(PlayerID, "plan") === -2)
     269           0 :                 ent.setMetadata(PlayerID, "plan", -1);
     270             :         else
     271           0 :                 ent.setMetadata(PlayerID, "plan", undefined);
     272           0 :         ent.setMetadata(PlayerID, "garrisonHolder", undefined);
     273             : };
     274             : 
     275             : /** Cancel a pre-garrison state */
     276           0 : PETRA.GarrisonManager.prototype.cancelGarrison = function(ent)
     277             : {
     278           0 :         ent.stopMoving();
     279           0 :         this.leaveGarrison(ent);
     280           0 :         let holderId = ent.getMetadata(PlayerID, "garrisonHolder");
     281           0 :         if (!holderId || !this.holders.has(holderId))
     282           0 :                 return;
     283           0 :         let list = this.holders.get(holderId).list;
     284           0 :         let index = list.indexOf(ent.id());
     285           0 :         if (index !== -1)
     286           0 :                 list.splice(index, 1);
     287             : };
     288             : 
     289           0 : PETRA.GarrisonManager.prototype.keepGarrisoned = function(ent, holder, around)
     290             : {
     291           0 :         switch (ent.getMetadata(PlayerID, "garrisonType"))
     292             :         {
     293             :         case PETRA.GarrisonManager.TYPE_FORCE:           // force the ungarrisoning
     294           0 :                 return false;
     295             :         case PETRA.GarrisonManager.TYPE_TRADE:          // trader garrisoned in ship
     296           0 :                 return true;
     297             :         case PETRA.GarrisonManager.TYPE_PROTECTION:     // hurt unit for healing or infantry for defense
     298           0 :                 if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high)
     299           0 :                         return true;
     300           0 :                 let capture = ent.capturePoints();
     301           0 :                 if (capture && capture[PlayerID] / capture.reduce((a, b) => a + b) < 0.8)
     302           0 :                         return true;
     303           0 :                 if (ent.hasClasses(holder.getGarrisonArrowClasses()))
     304             :                 {
     305           0 :                         if (around.unit || around.defenseStructure)
     306           0 :                                 return true;
     307           0 :                         if (around.meleeSiege || around.rangeSiege)
     308           0 :                                 return ent.attackTypes().indexOf("Melee") === -1 || ent.healthLevel() < this.Config.garrisonHealthLevel.low;
     309           0 :                         return false;
     310             :                 }
     311           0 :                 if (ent.attackTypes() && ent.attackTypes().indexOf("Melee") !== -1)
     312           0 :                         return false;
     313           0 :                 if (around.unit)
     314           0 :                         return ent.hasClass("Support") || PETRA.isSiegeUnit(ent);     // only ranged siege here and below as melee siege already released above
     315           0 :                 if (PETRA.isSiegeUnit(ent))
     316           0 :                         return around.meleeSiege;
     317           0 :                 return holder.buffHeal() && ent.needsHeal();
     318             :         case PETRA.GarrisonManager.TYPE_DECAY:
     319           0 :                 return ent.captureStrength() && this.decayingStructures.has(holder.id());
     320             :         case PETRA.GarrisonManager.TYPE_EMERGENCY: // f.e. hero in regicide mode
     321           0 :                 if (holder.buffHeal() && ent.isHealable() && ent.healthLevel() < this.Config.garrisonHealthLevel.high)
     322           0 :                         return true;
     323           0 :                 if (around.unit || around.defenseStructure || around.meleeSiege ||
     324             :                         around.rangeSiege && ent.healthLevel() < this.Config.garrisonHealthLevel.high)
     325           0 :                         return true;
     326           0 :                 return holder.buffHeal() && ent.needsHeal();
     327             :         default:
     328           0 :                 if (ent.getMetadata(PlayerID, "onBoard") === "onBoard")  // transport is not (yet ?) managed by garrisonManager
     329           0 :                         return true;
     330           0 :                 API3.warn("unknown type in garrisonManager " + ent.getMetadata(PlayerID, "garrisonType") +
     331             :                           " for " + ent.genericName() + " id " + ent.id() +
     332             :                           " inside " + holder.genericName() + " id " + holder.id());
     333           0 :                 ent.setMetadata(PlayerID, "garrisonType", PETRA.GarrisonManager.TYPE_PROTECTION);
     334           0 :                 return true;
     335             :         }
     336             : };
     337             : 
     338             : /** Add this holder in the list managed by the garrisonManager */
     339           0 : PETRA.GarrisonManager.prototype.registerHolder = function(gameState, holder)
     340             : {
     341           0 :         if (this.holders.has(holder.id()))    // already registered
     342           0 :                 return;
     343           0 :         this.holders.set(holder.id(), { "list": [], "allowMelee": true });
     344           0 :         holder.setMetadata(PlayerID, "holderTimeUpdate", gameState.ai.elapsedTime);
     345             : };
     346             : 
     347             : /**
     348             :  * Garrison units in decaying structures to stop their decay
     349             :  * do it only for structures useful for defense, except if we are expanding (justCaptured=true)
     350             :  * in which case we also do it for structures useful for unit trainings (TODO only Barracks are done)
     351             :  */
     352           0 : PETRA.GarrisonManager.prototype.addDecayingStructure = function(gameState, entId, justCaptured)
     353             : {
     354           0 :         if (this.decayingStructures.has(entId))
     355           0 :                 return true;
     356           0 :         let ent = gameState.getEntityById(entId);
     357           0 :         if (!ent || !(ent.hasClass("Barracks") && justCaptured) && !ent.hasDefensiveFire())
     358           0 :                 return false;
     359           0 :         if (!ent.territoryDecayRate() || !ent.garrisonRegenRate())
     360           0 :                 return false;
     361           0 :         let gmin = Math.ceil((ent.territoryDecayRate() - ent.defaultRegenRate()) / ent.garrisonRegenRate());
     362           0 :         this.decayingStructures.set(entId, gmin);
     363           0 :         return true;
     364             : };
     365             : 
     366           0 : PETRA.GarrisonManager.prototype.removeDecayingStructure = function(entId)
     367             : {
     368           0 :         if (!this.decayingStructures.has(entId))
     369           0 :                 return;
     370           0 :         this.decayingStructures.delete(entId);
     371             : };
     372             : 
     373           0 : PETRA.GarrisonManager.prototype.Serialize = function()
     374             : {
     375           0 :         return { "holders": this.holders, "decayingStructures": this.decayingStructures };
     376             : };
     377             : 
     378           0 : PETRA.GarrisonManager.prototype.Deserialize = function(data)
     379             : {
     380           0 :         for (let key in data)
     381           0 :                 this[key] = data[key];
     382             : };

Generated by: LCOV version 1.14