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

          Line data    Source code
       1             : /**
       2             :  * Bases Manager
       3             :  * Manages the list of available bases and queries information from those (e.g. resource levels).
       4             :  * Only one base is run every turn.
       5             :  */
       6             : 
       7           0 : PETRA.BasesManager = function(Config)
       8             : {
       9           0 :         this.Config = Config;
      10             : 
      11           0 :         this.currentBase = 0;
      12             : 
      13             :         // Cache some quantities for performance.
      14           0 :         this.turnCache = {};
      15             : 
      16             :         // Deals with unit/structure without base.
      17           0 :         this.noBase = undefined;
      18             : 
      19           0 :         this.baseManagers = [];
      20             : };
      21             : 
      22           0 : PETRA.BasesManager.prototype.init = function(gameState)
      23             : {
      24             :         // Initialize base map. Each pixel is a base ID, or 0 if not or not accessible.
      25           0 :         this.basesMap = new API3.Map(gameState.sharedScript, "territory");
      26             : 
      27           0 :         this.noBase = new PETRA.BaseManager(gameState, this);
      28           0 :         this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
      29           0 :         this.noBase.accessIndex = 0;
      30             : 
      31           0 :         for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values())
      32           0 :                 if (cc.foundationProgress() === undefined)
      33           0 :                         this.createBase(gameState, cc, PETRA.BaseManager.STATE_WITH_ANCHOR);
      34             :                 else
      35           0 :                         this.createBase(gameState, cc, PETRA.BaseManager.STATE_UNCONSTRUCTED);
      36             : };
      37             : 
      38             : /**
      39             :  * Initialization needed after deserialization (only called when deserialising).
      40             :  */
      41           0 : PETRA.BasesManager.prototype.postinit = function(gameState)
      42             : {
      43             :         // Rebuild the base maps from the territory indices of each base.
      44           0 :         this.basesMap = new API3.Map(gameState.sharedScript, "territory");
      45           0 :         for (const base of this.baseManagers)
      46           0 :                 for (const j of base.territoryIndices)
      47           0 :                         this.basesMap.map[j] = base.ID;
      48             : 
      49           0 :         for (const ent of gameState.getOwnEntities().values())
      50             :         {
      51           0 :                 if (!ent.resourceDropsiteTypes() || !ent.hasClass("Structure"))
      52           0 :                         continue;
      53             :                 // Entities which have been built or have changed ownership after the last AI turn have no base.
      54             :                 // they will be dealt with in the next checkEvents
      55           0 :                 const baseID = ent.getMetadata(PlayerID, "base");
      56           0 :                 if (baseID === undefined)
      57           0 :                         continue;
      58           0 :                 const base = this.getBaseByID(baseID);
      59           0 :                 base.assignResourceToDropsite(gameState, ent);
      60             :         }
      61             : };
      62             : 
      63             : /**
      64             :  * Create a new base in the baseManager:
      65             :  * If an existing one without anchor already exist, use it.
      66             :  * Otherwise create a new one.
      67             :  * TODO when buildings, criteria should depend on distance
      68             :  */
      69           0 : PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA.BaseManager.STATE_WITH_ANCHOR)
      70             : {
      71           0 :         const access = PETRA.getLandAccess(gameState, ent);
      72             :         let newbase;
      73           0 :         for (const base of this.baseManagers)
      74             :         {
      75           0 :                 if (base.accessIndex != access)
      76           0 :                         continue;
      77           0 :                 if (type !== PETRA.BaseManager.STATE_ANCHORLESS && base.anchor)
      78           0 :                         continue;
      79           0 :                 if (type !== PETRA.BaseManager.STATE_ANCHORLESS)
      80             :                 {
      81             :                         // TODO we keep the first one, we should rather use the nearest if buildings
      82             :                         // and possibly also cut on distance
      83           0 :                         newbase = base;
      84           0 :                         break;
      85             :                 }
      86             :                 else
      87             :                 {
      88             :                         // TODO here also test on distance instead of first
      89           0 :                         if (newbase && !base.anchor)
      90           0 :                                 continue;
      91           0 :                         newbase = base;
      92           0 :                         if (newbase.anchor)
      93           0 :                                 break;
      94             :                 }
      95             :         }
      96             : 
      97           0 :         if (this.Config.debug > 0)
      98             :         {
      99           0 :                 API3.warn(" ----------------------------------------------------------");
     100           0 :                 API3.warn(" BasesManager createBase entrance avec access " + access + " and type " + type);
     101           0 :                 API3.warn(" with access " + uneval(this.baseManagers.map(base => base.accessIndex)) +
     102           0 :                           " and base nbr " + uneval(this.baseManagers.map(base => base.ID)) +
     103           0 :                           " and anchor " + uneval(this.baseManagers.map(base => !!base.anchor)));
     104             :         }
     105             : 
     106           0 :         if (!newbase)
     107             :         {
     108           0 :                 newbase = new PETRA.BaseManager(gameState, this);
     109           0 :                 newbase.init(gameState, type);
     110           0 :                 this.baseManagers.push(newbase);
     111             :         }
     112             :         else
     113           0 :                 newbase.reset(type);
     114             : 
     115           0 :         if (type !== PETRA.BaseManager.STATE_ANCHORLESS)
     116           0 :                 newbase.setAnchor(gameState, ent);
     117             :         else
     118           0 :                 newbase.setAnchorlessEntity(gameState, ent);
     119             : 
     120           0 :         return newbase;
     121             : };
     122             : 
     123             : /** TODO check if the new anchorless bases should be added to addBase */
     124           0 : PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
     125             : {
     126           0 :         let addBase = false;
     127             : 
     128           0 :         for (const evt of events.Destroy)
     129             :         {
     130             :                 // Let's check we haven't lost an important building here.
     131           0 :                 if (evt && !evt.SuccessfulFoundation && evt.entityObj && evt.metadata && evt.metadata[PlayerID] &&
     132             :                         evt.metadata[PlayerID].base)
     133             :                 {
     134           0 :                         const ent = evt.entityObj;
     135           0 :                         if (evt?.metadata?.[PlayerID]?.assignedResource)
     136           0 :                                 this.getBaseByID(evt.metadata[PlayerID].base).removeFromAssignedDropsite(ent);
     137           0 :                         if (ent.owner() != PlayerID)
     138           0 :                                 continue;
     139             :                         // A new base foundation was created and destroyed on the same (AI) turn
     140           0 :                         if (evt.metadata[PlayerID].base == -1 || evt.metadata[PlayerID].base == -2)
     141           0 :                                 continue;
     142           0 :                         const base = this.getBaseByID(evt.metadata[PlayerID].base);
     143           0 :                         if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
     144           0 :                                 base.removeDropsite(gameState, ent);
     145           0 :                         if (evt.metadata[PlayerID].baseAnchor && evt.metadata[PlayerID].baseAnchor === true)
     146           0 :                                 base.anchorLost(gameState, ent);
     147             :                 }
     148             :         }
     149             : 
     150           0 :         for (const evt of events.EntityRenamed)
     151             :         {
     152           0 :                 const ent = gameState.getEntityById(evt.newentity);
     153           0 :                 if (!ent || ent.owner() != PlayerID || ent.getMetadata(PlayerID, "base") === undefined)
     154           0 :                         continue;
     155           0 :                 const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
     156           0 :                 if (!base.anchorId || base.anchorId != evt.entity)
     157           0 :                         continue;
     158           0 :                 base.anchorId = evt.newentity;
     159           0 :                 base.anchor = ent;
     160             :         }
     161             : 
     162           0 :         for (const evt of events.Create)
     163             :         {
     164             :                 // Let's check if we have a valuable foundation needing builders quickly
     165             :                 // (normal foundations are taken care in baseManager.assignToFoundations)
     166           0 :                 const ent = gameState.getEntityById(evt.entity);
     167           0 :                 if (!ent || ent.owner() != PlayerID || ent.foundationProgress() === undefined)
     168           0 :                         continue;
     169             : 
     170           0 :                 if (ent.getMetadata(PlayerID, "base") == -1)  // Standard base around a cc
     171             :                 {
     172             :                         // Okay so let's try to create a new base around this.
     173           0 :                         const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
     174             :                         // Let's get a few units from other bases there to build this.
     175           0 :                         const builders = this.bulkPickWorkers(gameState, newbase, 10);
     176           0 :                         if (builders !== false)
     177             :                         {
     178           0 :                                 builders.forEach(worker => {
     179           0 :                                         worker.setMetadata(PlayerID, "base", newbase.ID);
     180           0 :                                         worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER);
     181           0 :                                         worker.setMetadata(PlayerID, "target-foundation", ent.id());
     182             :                                 });
     183             :                         }
     184             :                 }
     185           0 :                 else if (ent.getMetadata(PlayerID, "base") == -2)     // anchorless base around a dock
     186             :                 {
     187           0 :                         const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
     188             :                         // Let's get a few units from other bases there to build this.
     189           0 :                         const builders = this.bulkPickWorkers(gameState, newbase, 4);
     190           0 :                         if (builders != false)
     191             :                         {
     192           0 :                                 builders.forEach(worker => {
     193           0 :                                         worker.setMetadata(PlayerID, "base", newbase.ID);
     194           0 :                                         worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER);
     195           0 :                                         worker.setMetadata(PlayerID, "target-foundation", ent.id());
     196             :                                 });
     197             :                         }
     198             :                 }
     199             :         }
     200             : 
     201           0 :         for (const evt of events.ConstructionFinished)
     202             :         {
     203           0 :                 if (evt.newentity == evt.entity)  // repaired building
     204           0 :                         continue;
     205           0 :                 const ent = gameState.getEntityById(evt.newentity);
     206           0 :                 if (!ent || ent.owner() != PlayerID)
     207           0 :                         continue;
     208           0 :                 if (ent.getMetadata(PlayerID, "base") === undefined)
     209           0 :                         continue;
     210           0 :                 const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
     211           0 :                 base.buildings.updateEnt(ent);
     212           0 :                 if (ent.resourceDropsiteTypes())
     213           0 :                         base.assignResourceToDropsite(gameState, ent);
     214             : 
     215           0 :                 if (ent.getMetadata(PlayerID, "baseAnchor") === true)
     216             :                 {
     217           0 :                         if (base.constructing)
     218           0 :                                 base.constructing = false;
     219           0 :                         addBase = true;
     220             :                 }
     221             :         }
     222             : 
     223           0 :         for (const evt of events.OwnershipChanged)
     224             :         {
     225           0 :                 if (evt.from == PlayerID)
     226             :                 {
     227           0 :                         const ent = gameState.getEntityById(evt.entity);
     228           0 :                         if (!ent || ent.getMetadata(PlayerID, "base") === undefined)
     229           0 :                                 continue;
     230           0 :                         const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
     231           0 :                         if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
     232           0 :                                 base.removeDropsite(gameState, ent);
     233           0 :                         if (ent.getMetadata(PlayerID, "baseAnchor") === true)
     234           0 :                                 base.anchorLost(gameState, ent);
     235             :                 }
     236             : 
     237           0 :                 if (evt.to != PlayerID)
     238           0 :                         continue;
     239           0 :                 const ent = gameState.getEntityById(evt.entity);
     240           0 :                 if (!ent)
     241           0 :                         continue;
     242           0 :                 if (ent.hasClass("Unit"))
     243             :                 {
     244           0 :                         PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent);
     245           0 :                         continue;
     246             :                 }
     247           0 :                 if (ent.hasClass("CivCentre"))   // build a new base around it
     248             :                 {
     249             :                         let newbase;
     250           0 :                         if (ent.foundationProgress() !== undefined)
     251           0 :                                 newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
     252             :                         else
     253             :                         {
     254           0 :                                 newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_CAPTURED);
     255           0 :                                 addBase = true;
     256             :                         }
     257           0 :                         newbase.assignEntity(gameState, ent);
     258             :                 }
     259             :                 else
     260             :                 {
     261             :                         let base;
     262             :                         // If dropsite on new island, create a base around it
     263           0 :                         if (!ent.decaying() && ent.resourceDropsiteTypes())
     264           0 :                                 base = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
     265             :                         else
     266           0 :                                 base = PETRA.getBestBase(gameState, ent) || this.noBase;
     267           0 :                         base.assignEntity(gameState, ent);
     268             :                 }
     269             :         }
     270             : 
     271           0 :         for (const evt of events.TrainingFinished)
     272             :         {
     273           0 :                 for (const entId of evt.entities)
     274             :                 {
     275           0 :                         const ent = gameState.getEntityById(entId);
     276           0 :                         if (!ent || !ent.isOwn(PlayerID))
     277           0 :                                 continue;
     278             : 
     279             :                         // Assign it immediately to something useful to do.
     280           0 :                         if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER)
     281             :                         {
     282             :                                 let base;
     283           0 :                                 if (ent.getMetadata(PlayerID, "base") === undefined)
     284             :                                 {
     285           0 :                                         base = PETRA.getBestBase(gameState, ent);
     286           0 :                                         base.assignEntity(gameState, ent);
     287             :                                 }
     288             :                                 else
     289           0 :                                         base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
     290           0 :                                 base.reassignIdleWorkers(gameState, [ent]);
     291           0 :                                 base.workerObject.update(gameState, ent);
     292             :                         }
     293           0 :                         else if (ent.resourceSupplyType() && ent.position())
     294             :                         {
     295           0 :                                 const type = ent.resourceSupplyType();
     296           0 :                                 if (!type.generic)
     297           0 :                                         continue;
     298           0 :                                 const dropsites = gameState.getOwnDropsites(type.generic);
     299           0 :                                 const pos = ent.position();
     300           0 :                                 const access = PETRA.getLandAccess(gameState, ent);
     301           0 :                                 let distmin = Math.min();
     302             :                                 let goal;
     303           0 :                                 for (const dropsite of dropsites.values())
     304             :                                 {
     305           0 :                                         if (!dropsite.position() || PETRA.getLandAccess(gameState, dropsite) != access)
     306           0 :                                                 continue;
     307           0 :                                         const dist = API3.SquareVectorDistance(pos, dropsite.position());
     308           0 :                                         if (dist > distmin)
     309           0 :                                                 continue;
     310           0 :                                         distmin = dist;
     311           0 :                                         goal = dropsite.position();
     312             :                                 }
     313           0 :                                 if (goal)
     314           0 :                                         ent.moveToRange(goal[0], goal[1]);
     315             :                         }
     316             :                 }
     317             :         }
     318             : 
     319           0 :         if (addBase)
     320           0 :                 gameState.ai.HQ.handleNewBase(gameState);
     321             : };
     322             : 
     323             : /**
     324             :  * returns an entity collection of workers through BaseManager.pickBuilders
     325             :  * TODO: when same accessIndex, sort by distance
     326             :  */
     327           0 : PETRA.BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number)
     328             : {
     329           0 :         const accessIndex = baseRef.accessIndex;
     330           0 :         if (!accessIndex)
     331           0 :                 return false;
     332           0 :         const baseBest = this.baseManagers.slice();
     333             :         // We can also use workers without a base.
     334           0 :         baseBest.push(this.noBase);
     335           0 :         baseBest.sort((a, b) => {
     336           0 :                 if (a.accessIndex == accessIndex && b.accessIndex != accessIndex)
     337           0 :                         return -1;
     338           0 :                 else if (b.accessIndex == accessIndex && a.accessIndex != accessIndex)
     339           0 :                         return 1;
     340           0 :                 return 0;
     341             :         });
     342             : 
     343           0 :         let needed = number;
     344           0 :         const workers = new API3.EntityCollection(gameState.sharedScript);
     345           0 :         for (const base of baseBest)
     346             :         {
     347           0 :                 if (base.ID == baseRef.ID)
     348           0 :                         continue;
     349           0 :                 base.pickBuilders(gameState, workers, needed);
     350           0 :                 if (workers.length >= number)
     351           0 :                         break;
     352           0 :                 needed = number - workers.length;
     353             :         }
     354           0 :         if (!workers.length)
     355           0 :                 return false;
     356           0 :         return workers;
     357             : };
     358             : 
     359             : /**
     360             :  * @return {Object} - Resources (estimation) still gatherable in our territory.
     361             :  */
     362           0 : PETRA.BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"])
     363             : {
     364           0 :         const total = {};
     365           0 :         for (const res of resources)
     366           0 :                 total[res] = 0;
     367           0 :         for (const base of this.baseManagers)
     368           0 :                 for (const res in total)
     369           0 :                         total[res] += base.getResourceLevel(gameState, res, proximity);
     370             : 
     371           0 :         return total;
     372             : };
     373             : 
     374             : /**
     375             :  * Returns the current gather rate
     376             :  * This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that.
     377             :  */
     378           0 : PETRA.BasesManager.prototype.GetCurrentGatherRates = function(gameState)
     379             : {
     380           0 :         if (!this.turnCache.currentRates)
     381             :         {
     382           0 :                 const currentRates = {};
     383           0 :                 for (const res of Resources.GetCodes())
     384           0 :                         currentRates[res] = 0.5 * this.GetTCResGatherer(res);
     385             : 
     386           0 :                 this.addGatherRates(gameState, currentRates);
     387             : 
     388           0 :                 for (const res of Resources.GetCodes())
     389           0 :                         currentRates[res] = Math.max(currentRates[res], 0);
     390             : 
     391           0 :                 this.turnCache.currentRates = currentRates;
     392             :         }
     393             : 
     394           0 :         return this.turnCache.currentRates;
     395             : };
     396             : 
     397             : /** Some functions that register that we assigned a gatherer to a resource this turn */
     398             : 
     399             : /** Add a gatherer to the turn cache for this supply. */
     400           0 : PETRA.BasesManager.prototype.AddTCGatherer = function(supplyID)
     401             : {
     402           0 :         if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined)
     403           0 :                 ++this.turnCache.resourceGatherer[supplyID];
     404             :         else
     405             :         {
     406           0 :                 if (!this.turnCache.resourceGatherer)
     407           0 :                         this.turnCache.resourceGatherer = {};
     408           0 :                 this.turnCache.resourceGatherer[supplyID] = 1;
     409             :         }
     410             : };
     411             : 
     412             : /** Remove a gatherer from the turn cache for this supply. */
     413           0 : PETRA.BasesManager.prototype.RemoveTCGatherer = function(supplyID)
     414             : {
     415           0 :         if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
     416           0 :                 --this.turnCache.resourceGatherer[supplyID];
     417             :         else
     418             :         {
     419           0 :                 if (!this.turnCache.resourceGatherer)
     420           0 :                         this.turnCache.resourceGatherer = {};
     421           0 :                 this.turnCache.resourceGatherer[supplyID] = -1;
     422             :         }
     423             : };
     424             : 
     425           0 : PETRA.BasesManager.prototype.GetTCGatherer = function(supplyID)
     426             : {
     427           0 :         if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
     428           0 :                 return this.turnCache.resourceGatherer[supplyID];
     429             : 
     430           0 :         return 0;
     431             : };
     432             : 
     433             : /** The next two are to register that we assigned a gatherer to a resource this turn. */
     434           0 : PETRA.BasesManager.prototype.AddTCResGatherer = function(resource)
     435             : {
     436           0 :         const check = "resourceGatherer-" + resource;
     437           0 :         if (this.turnCache[check])
     438           0 :                 ++this.turnCache[check];
     439             :         else
     440           0 :                 this.turnCache[check] = 1;
     441             : 
     442           0 :         if (this.turnCache.currentRates)
     443           0 :                 this.turnCache.currentRates[resource] += 0.5;
     444             : };
     445             : 
     446           0 : PETRA.BasesManager.prototype.GetTCResGatherer = function(resource)
     447             : {
     448           0 :         const check = "resourceGatherer-" + resource;
     449           0 :         if (this.turnCache[check])
     450           0 :                 return this.turnCache[check];
     451             : 
     452           0 :         return 0;
     453             : };
     454             : 
     455             : /**
     456             :  * flag a resource as exhausted
     457             :  */
     458           0 : PETRA.BasesManager.prototype.isResourceExhausted = function(resource)
     459             : {
     460           0 :         const check = "exhausted-" + resource;
     461           0 :         if (this.turnCache[check] == undefined)
     462           0 :                 this.turnCache[check] = this.basesManager.isResourceExhausted(resource);
     463             : 
     464           0 :         return this.turnCache[check];
     465             : };
     466             : 
     467             : /**
     468             :  * returns the number of bases with a cc
     469             :  * ActiveBases includes only those with a built cc
     470             :  * PotentialBases includes also those with a cc in construction
     471             :  */
     472           0 : PETRA.BasesManager.prototype.numActiveBases = function()
     473             : {
     474           0 :         if (!this.turnCache.base)
     475           0 :                 this.updateBaseCache();
     476           0 :         return this.turnCache.base.active;
     477             : };
     478             : 
     479           0 : PETRA.BasesManager.prototype.hasActiveBase = function()
     480             : {
     481           0 :         return !!this.numActiveBases();
     482             : };
     483             : 
     484           0 : PETRA.BasesManager.prototype.numPotentialBases = function()
     485             : {
     486           0 :         if (!this.turnCache.base)
     487           0 :                 this.updateBaseCache();
     488           0 :         return this.turnCache.base.potential;
     489             : };
     490             : 
     491           0 : PETRA.BasesManager.prototype.hasPotentialBase = function()
     492             : {
     493           0 :         return !!this.numPotentialBases();
     494             : };
     495             : 
     496             : /**
     497             :  * Updates the number of active and potential bases.
     498             :  *              .potential {number} - Bases that may or may not still be a foundation.
     499             :  *              .active {number} - Usable bases.
     500             :  */
     501           0 : PETRA.BasesManager.prototype.updateBaseCache = function()
     502             : {
     503           0 :         this.turnCache.base = { "active": 0, "potential": 0 };
     504           0 :         for (const base of this.baseManagers)
     505             :         {
     506           0 :                 if (!base.anchor)
     507           0 :                         continue;
     508           0 :                 ++this.turnCache.base.potential;
     509           0 :                 if (base.anchor.foundationProgress() === undefined)
     510           0 :                         ++this.turnCache.base.active;
     511             :         }
     512             : };
     513             : 
     514           0 : PETRA.BasesManager.prototype.resetBaseCache = function()
     515             : {
     516           0 :         this.turnCache.base = undefined;
     517             : };
     518             : 
     519           0 : PETRA.BasesManager.prototype.baselessBase = function()
     520             : {
     521           0 :         return this.noBase;
     522             : };
     523             : 
     524             : /**
     525             :  * @param {number} baseID
     526             :  * @return {Object} - The base corresponding to baseID.
     527             :  */
     528           0 : PETRA.BasesManager.prototype.getBaseByID = function(baseID)
     529             : {
     530           0 :         if (this.noBase.ID === baseID)
     531           0 :                 return this.noBase;
     532           0 :         return this.baseManagers.find(base => base.ID === baseID);
     533             : };
     534             : 
     535             : /**
     536             :  * flag a resource as exhausted
     537             :  */
     538           0 : PETRA.BasesManager.prototype.isResourceExhausted = function(resource)
     539             : {
     540           0 :         return this.baseManagers.every(base =>
     541           0 :                 !base.dropsiteSupplies[resource].nearby.length &&
     542             :                 !base.dropsiteSupplies[resource].medium.length &&
     543             :                 !base.dropsiteSupplies[resource].faraway.length);
     544             : };
     545             : 
     546             : /**
     547             :  * Count gatherers returning resources in the number of gatherers of resourceSupplies
     548             :  * to prevent the AI always reassigning idle workers to these resourceSupplies (specially in naval maps).
     549             :  */
     550           0 : PETRA.BasesManager.prototype.assignGatherers = function()
     551             : {
     552           0 :         for (const base of this.baseManagers)
     553           0 :                 for (const worker of base.workers.values())
     554             :                 {
     555           0 :                         if (worker.unitAIState().split(".").indexOf("RETURNRESOURCE") === -1)
     556           0 :                                 continue;
     557           0 :                         const orders = worker.unitAIOrderData();
     558           0 :                         if (orders.length < 2 || !orders[1].target || orders[1].target != worker.getMetadata(PlayerID, "supply"))
     559           0 :                                 continue;
     560           0 :                         this.AddTCGatherer(orders[1].target);
     561             :                 }
     562             : };
     563             : 
     564             : /**
     565             :  * Assign an entity to the closest base.
     566             :  * Used by the starting strategy.
     567             :  */
     568           0 : PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex)
     569             : {
     570             :         let bestbase;
     571           0 :         for (const base of this.baseManagers)
     572             :         {
     573           0 :                 if ((!ent.getMetadata(PlayerID, "base") || ent.getMetadata(PlayerID, "base") != base.ID) &&
     574             :                     base.territoryIndices.indexOf(territoryIndex) == -1)
     575           0 :                         continue;
     576           0 :                 base.assignEntity(gameState, ent);
     577           0 :                 bestbase = base;
     578           0 :                 break;
     579             :         }
     580           0 :         if (!bestbase)  // entity outside our territory
     581             :         {
     582           0 :                 if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes())
     583           0 :                         bestbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
     584             :                 else
     585           0 :                         bestbase = PETRA.getBestBase(gameState, ent) || this.noBase;
     586           0 :                 bestbase.assignEntity(gameState, ent);
     587             :         }
     588             :         // now assign entities garrisoned inside this entity
     589           0 :         if (ent.isGarrisonHolder() && ent.garrisoned().length)
     590           0 :                 for (const id of ent.garrisoned())
     591           0 :                         bestbase.assignEntity(gameState, gameState.getEntityById(id));
     592             :         // and find something useful to do if we already have a base
     593           0 :         if (ent.position() && bestbase.ID !== this.noBase.ID)
     594             :         {
     595           0 :                 bestbase.assignRolelessUnits(gameState, [ent]);
     596           0 :                 if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER)
     597             :                 {
     598           0 :                         bestbase.reassignIdleWorkers(gameState, [ent]);
     599           0 :                         bestbase.workerObject.update(gameState, ent);
     600             :                 }
     601             :         }
     602             : };
     603             : 
     604             : /**
     605             :  * Adds the gather rates of individual bases to a shared object.
     606             :  * @param {Object} gameState
     607             :  * @param {Object} rates - The rates to add the gather rates to.
     608             :  */
     609           0 : PETRA.BasesManager.prototype.addGatherRates = function(gameState, rates)
     610             : {
     611           0 :         for (const base of this.baseManagers)
     612           0 :                 base.addGatherRates(gameState, rates);
     613             : };
     614             : 
     615             : /**
     616             :  * @param {number} territoryIndex
     617             :  * @return {number} - The ID of the base at the given territory index.
     618             :  */
     619           0 : PETRA.BasesManager.prototype.baseAtIndex = function(territoryIndex)
     620             : {
     621           0 :         return this.basesMap.map[territoryIndex];
     622             : };
     623             : 
     624             : /**
     625             :  * @param {number} territoryIndex
     626             :  */
     627           0 : PETRA.BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex)
     628             : {
     629           0 :         const baseID = this.basesMap.map[territoryIndex];
     630           0 :         if (baseID == 0)
     631           0 :                 return;
     632           0 :         const base = this.getBaseByID(baseID);
     633           0 :         if (base)
     634             :         {
     635           0 :                 const index = base.territoryIndices.indexOf(territoryIndex);
     636           0 :                 if (index != -1)
     637           0 :                         base.territoryIndices.splice(index, 1);
     638             :                 else
     639           0 :                         API3.warn(" problem in headquarters::updateTerritories for base " + baseID);
     640             :         }
     641             :         else
     642           0 :                 API3.warn(" problem in headquarters::updateTerritories without base " + baseID);
     643           0 :         this.basesMap.map[territoryIndex] = 0;
     644             : };
     645             : 
     646             : /**
     647             :  * @return {boolean} - Whether the index was added to a base.
     648             :  */
     649           0 : PETRA.BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIndex, passabilityMap)
     650             : {
     651           0 :         if (this.baseAtIndex(territoryIndex) != 0)
     652           0 :                 return false;
     653           0 :         let landPassable = false;
     654           0 :         const ind = API3.getMapIndices(territoryIndex, gameState.ai.HQ.territoryMap, passabilityMap);
     655             :         let access;
     656           0 :         for (const k of ind)
     657             :         {
     658           0 :                 if (!gameState.ai.HQ.landRegions[gameState.ai.accessibility.landPassMap[k]])
     659           0 :                         continue;
     660           0 :                 landPassable = true;
     661           0 :                 access = gameState.ai.accessibility.landPassMap[k];
     662           0 :                 break;
     663             :         }
     664           0 :         if (!landPassable)
     665           0 :                 return false;
     666           0 :         let distmin = Math.min();
     667             :         let baseID;
     668           0 :         const pos = [gameState.ai.HQ.territoryMap.cellSize * (territoryIndex % gameState.ai.HQ.territoryMap.width + 0.5), gameState.ai.HQ.territoryMap.cellSize * (Math.floor(territoryIndex / gameState.ai.HQ.territoryMap.width) + 0.5)];
     669           0 :         for (const base of this.baseManagers)
     670             :         {
     671           0 :                 if (!base.anchor || !base.anchor.position())
     672           0 :                         continue;
     673           0 :                 if (base.accessIndex != access)
     674           0 :                         continue;
     675           0 :                 const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
     676           0 :                 if (dist >= distmin)
     677           0 :                         continue;
     678           0 :                 distmin = dist;
     679           0 :                 baseID = base.ID;
     680             :         }
     681           0 :         if (!baseID)
     682           0 :                 return false;
     683           0 :         this.getBaseByID(baseID).territoryIndices.push(territoryIndex);
     684           0 :         this.basesMap.map[territoryIndex] = baseID;
     685           0 :         return true;
     686             : };
     687             : 
     688             : /** Reassign territories when a base is going to be deleted */
     689           0 : PETRA.BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
     690             : {
     691           0 :         const cellSize = territoryMap.cellSize;
     692           0 :         const width = territoryMap.width;
     693           0 :         for (let j = 0; j < territoryMap.length; ++j)
     694             :         {
     695           0 :                 if (this.basesMap.map[j] != deletedBase.ID)
     696           0 :                         continue;
     697           0 :                 if (territoryMap.getOwnerIndex(j) != PlayerID)
     698             :                 {
     699           0 :                         API3.warn("Petra reassignTerritories: should never happen");
     700           0 :                         this.basesMap.map[j] = 0;
     701           0 :                         continue;
     702             :                 }
     703             : 
     704           0 :                 let distmin = Math.min();
     705             :                 let baseID;
     706           0 :                 const pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
     707           0 :                 for (const base of this.baseManagers)
     708             :                 {
     709           0 :                         if (!base.anchor || !base.anchor.position())
     710           0 :                                 continue;
     711           0 :                         if (base.accessIndex != deletedBase.accessIndex)
     712           0 :                                 continue;
     713           0 :                         const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
     714           0 :                         if (dist >= distmin)
     715           0 :                                 continue;
     716           0 :                         distmin = dist;
     717           0 :                         baseID = base.ID;
     718             :                 }
     719           0 :                 if (baseID)
     720             :                 {
     721           0 :                         this.getBaseByID(baseID).territoryIndices.push(j);
     722           0 :                         this.basesMap.map[j] = baseID;
     723             :                 }
     724             :                 else
     725           0 :                         this.basesMap.map[j] = 0;
     726             :         }
     727             : };
     728             : 
     729             : /**
     730             :  * We will loop only on one active base per turn.
     731             :  */
     732           0 : PETRA.BasesManager.prototype.update = function(gameState, queues, events)
     733             : {
     734           0 :         Engine.ProfileStart("BasesManager update");
     735             : 
     736           0 :         this.turnCache = {};
     737           0 :         this.assignGatherers();
     738           0 :         let nbBases = this.baseManagers.length;
     739           0 :         let activeBase = false;
     740           0 :         this.noBase.update(gameState, queues, events);
     741           0 :         while (!activeBase && nbBases != 0)
     742             :         {
     743           0 :                 this.currentBase %= this.baseManagers.length;
     744           0 :                 activeBase = this.baseManagers[this.currentBase++].update(gameState, queues, events);
     745           0 :                 --nbBases;
     746             :                 // TODO what to do with this.reassignTerritories(this.baseManagers[this.currentBase]);
     747             :         }
     748             : 
     749           0 :         Engine.ProfileStop();
     750             : };
     751             : 
     752           0 : PETRA.BasesManager.prototype.Serialize = function()
     753             : {
     754           0 :         const properties = {
     755             :                 "currentBase": this.currentBase
     756             :         };
     757             : 
     758           0 :         const baseManagers = [];
     759           0 :         for (const base of this.baseManagers)
     760           0 :                 baseManagers.push(base.Serialize());
     761             : 
     762           0 :         return {
     763             :                 "properties": properties,
     764             :                 "noBase": this.noBase.Serialize(),
     765             :                 "baseManagers": baseManagers
     766             :         };
     767             : };
     768             : 
     769           0 : PETRA.BasesManager.prototype.Deserialize = function(gameState, data)
     770             : {
     771           0 :         for (const key in data.properties)
     772           0 :                 this[key] = data.properties[key];
     773             : 
     774           0 :         this.noBase = new PETRA.BaseManager(gameState, this);
     775           0 :         this.noBase.Deserialize(gameState, data.noBase);
     776           0 :         this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
     777           0 :         this.noBase.Deserialize(gameState, data.noBase);
     778             : 
     779           0 :         this.baseManagers = [];
     780           0 :         for (const basedata of data.baseManagers)
     781             :         {
     782             :                 // The first call to deserialize set the ID base needed by entitycollections.
     783           0 :                 const newbase = new PETRA.BaseManager(gameState, this);
     784           0 :                 newbase.Deserialize(gameState, basedata);
     785           0 :                 newbase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
     786           0 :                 newbase.Deserialize(gameState, basedata);
     787           0 :                 this.baseManagers.push(newbase);
     788             :         }
     789             : };

Generated by: LCOV version 1.14