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

          Line data    Source code
       1             : /**
       2             :  * Naval Manager
       3             :  * Will deal with anything ships.
       4             :  * -Basically trade over water (with fleets and goals commissioned by the economy manager)
       5             :  * -Defense over water (commissioned by the defense manager)
       6             :  * -Transport of units over water (a few units).
       7             :  * -Scouting, ultimately.
       8             :  * Also deals with handling docks, making sure we have access and stuffs like that.
       9             :  */
      10           0 : PETRA.NavalManager = function(Config)
      11             : {
      12           0 :         this.Config = Config;
      13             : 
      14             :         // ship subCollections. Also exist for land zones, idem, not caring.
      15           0 :         this.seaShips = [];
      16           0 :         this.seaTransportShips = [];
      17           0 :         this.seaWarShips = [];
      18           0 :         this.seaFishShips = [];
      19             : 
      20             :         // wanted NB per zone.
      21           0 :         this.wantedTransportShips = [];
      22           0 :         this.wantedWarShips = [];
      23           0 :         this.wantedFishShips = [];
      24             :         // needed NB per zone.
      25           0 :         this.neededTransportShips = [];
      26           0 :         this.neededWarShips = [];
      27             : 
      28           0 :         this.transportPlans = [];
      29             : 
      30             :         // shore-line regions where we can load and unload units
      31           0 :         this.landingZones = {};
      32             : };
      33             : 
      34             : /** More initialisation for stuff that needs the gameState */
      35           0 : PETRA.NavalManager.prototype.init = function(gameState, deserializing)
      36             : {
      37             :         // docks
      38           0 :         this.docks = gameState.getOwnStructures().filter(API3.Filters.byClasses(["Dock", "Shipyard"]));
      39           0 :         this.docks.registerUpdates();
      40             : 
      41           0 :         this.ships = gameState.getOwnUnits().filter(API3.Filters.and(API3.Filters.byClass("Ship"), API3.Filters.not(API3.Filters.byMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER))));
      42             :         // note: those two can overlap (some transport ships are warships too and vice-versa).
      43           0 :         this.transportShips = this.ships.filter(API3.Filters.and(API3.Filters.byCanGarrison(), API3.Filters.not(API3.Filters.byClass("FishingBoat"))));
      44           0 :         this.warShips = this.ships.filter(API3.Filters.byClass("Warship"));
      45           0 :         this.fishShips = this.ships.filter(API3.Filters.byClass("FishingBoat"));
      46             : 
      47           0 :         this.ships.registerUpdates();
      48           0 :         this.transportShips.registerUpdates();
      49           0 :         this.warShips.registerUpdates();
      50           0 :         this.fishShips.registerUpdates();
      51             : 
      52           0 :         let availableFishes = {};
      53           0 :         for (let fish of gameState.getFishableSupplies().values())
      54             :         {
      55           0 :                 let sea = this.getFishSea(gameState, fish);
      56           0 :                 if (sea && availableFishes[sea])
      57           0 :                         availableFishes[sea] += fish.resourceSupplyAmount();
      58           0 :                 else if (sea)
      59           0 :                         availableFishes[sea] = fish.resourceSupplyAmount();
      60             :         }
      61             : 
      62           0 :         for (let i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
      63             :         {
      64           0 :                 if (!gameState.ai.HQ.navalRegions[i])
      65             :                 {
      66             :                         // push dummies
      67           0 :                         this.seaShips.push(undefined);
      68           0 :                         this.seaTransportShips.push(undefined);
      69           0 :                         this.seaWarShips.push(undefined);
      70           0 :                         this.seaFishShips.push(undefined);
      71           0 :                         this.wantedTransportShips.push(0);
      72           0 :                         this.wantedWarShips.push(0);
      73           0 :                         this.wantedFishShips.push(0);
      74           0 :                         this.neededTransportShips.push(0);
      75           0 :                         this.neededWarShips.push(0);
      76             :                 }
      77             :                 else
      78             :                 {
      79           0 :                         let collec = this.ships.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
      80           0 :                         collec.registerUpdates();
      81           0 :                         this.seaShips.push(collec);
      82           0 :                         collec = this.transportShips.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
      83           0 :                         collec.registerUpdates();
      84           0 :                         this.seaTransportShips.push(collec);
      85           0 :                         collec = this.warShips.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
      86           0 :                         collec.registerUpdates();
      87           0 :                         this.seaWarShips.push(collec);
      88           0 :                         collec = this.fishShips.filter(API3.Filters.byMetadata(PlayerID, "sea", i));
      89           0 :                         collec.registerUpdates();
      90           0 :                         this.seaFishShips.push(collec);
      91           0 :                         this.wantedTransportShips.push(0);
      92           0 :                         this.wantedWarShips.push(0);
      93           0 :                         if (availableFishes[i] && availableFishes[i] > 1000)
      94           0 :                                 this.wantedFishShips.push(this.Config.Economy.targetNumFishers);
      95             :                         else
      96           0 :                                 this.wantedFishShips.push(0);
      97           0 :                         this.neededTransportShips.push(0);
      98           0 :                         this.neededWarShips.push(0);
      99             :                 }
     100             :         }
     101             : 
     102           0 :         if (deserializing)
     103           0 :                 return;
     104             : 
     105             :         // determination of the possible landing zones
     106           0 :         let width = gameState.getPassabilityMap().width;
     107           0 :         let length = width * gameState.getPassabilityMap().height;
     108           0 :         for (let i = 0; i < length; ++i)
     109             :         {
     110           0 :                 let land = gameState.ai.accessibility.landPassMap[i];
     111           0 :                 if (land < 2)
     112           0 :                         continue;
     113           0 :                 let naval = gameState.ai.accessibility.navalPassMap[i];
     114           0 :                 if (naval < 2)
     115           0 :                         continue;
     116           0 :                 if (!this.landingZones[land])
     117           0 :                         this.landingZones[land] = {};
     118           0 :                 if (!this.landingZones[land][naval])
     119           0 :                     this.landingZones[land][naval] = new Set();
     120           0 :                 this.landingZones[land][naval].add(i);
     121             :         }
     122             :         // and keep only thoses with enough room around when possible
     123           0 :         for (let land in this.landingZones)
     124             :         {
     125           0 :                 for (let sea in this.landingZones[land])
     126             :                 {
     127           0 :                         let landing = this.landingZones[land][sea];
     128           0 :                         let nbaround = {};
     129           0 :                         let nbcut = 0;
     130           0 :                         for (let i of landing)
     131             :                         {
     132           0 :                                 let nb = 0;
     133           0 :                                 if (landing.has(i-1))
     134           0 :                                         nb++;
     135           0 :                                 if (landing.has(i+1))
     136           0 :                                         nb++;
     137           0 :                                 if (landing.has(i+width))
     138           0 :                                         nb++;
     139           0 :                                 if (landing.has(i-width))
     140           0 :                                         nb++;
     141           0 :                                 nbaround[i] = nb;
     142           0 :                                 nbcut = Math.max(nb, nbcut);
     143             :                         }
     144           0 :                         nbcut = Math.min(2, nbcut);
     145           0 :                         for (let i of landing)
     146             :                         {
     147           0 :                                 if (nbaround[i] < nbcut)
     148           0 :                                         landing.delete(i);
     149             :                         }
     150             :                 }
     151             :         }
     152             : 
     153             :         // Assign our initial docks and ships
     154           0 :         for (let ship of this.ships.values())
     155           0 :                 PETRA.setSeaAccess(gameState, ship);
     156           0 :         for (let dock of this.docks.values())
     157           0 :                 PETRA.setSeaAccess(gameState, dock);
     158             : };
     159             : 
     160           0 : PETRA.NavalManager.prototype.updateFishingBoats = function(sea, num)
     161             : {
     162           0 :         if (this.wantedFishShips[sea])
     163           0 :                 this.wantedFishShips[sea] = num;
     164             : };
     165             : 
     166           0 : PETRA.NavalManager.prototype.resetFishingBoats = function(gameState, sea)
     167             : {
     168           0 :         if (sea !== undefined)
     169           0 :                 this.wantedFishShips[sea] = 0;
     170             :         else
     171           0 :                 this.wantedFishShips.fill(0);
     172             : };
     173             : 
     174             : /** Get the sea, cache it if not yet done and check if in opensea */
     175           0 : PETRA.NavalManager.prototype.getFishSea = function(gameState, fish)
     176             : {
     177           0 :         let sea = fish.getMetadata(PlayerID, "sea");
     178           0 :         if (sea)
     179           0 :                 return sea;
     180           0 :         const ntry = 4;
     181           0 :         const around = [[-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0]];
     182           0 :         let pos = gameState.ai.accessibility.gamePosToMapPos(fish.position());
     183           0 :         let width = gameState.ai.accessibility.width;
     184           0 :         let k = pos[0] + pos[1]*width;
     185           0 :         sea = gameState.ai.accessibility.navalPassMap[k];
     186           0 :         fish.setMetadata(PlayerID, "sea", sea);
     187           0 :         let radius = 120 / gameState.ai.accessibility.cellSize / ntry;
     188           0 :         if (around.every(a => {
     189           0 :                 for (let t = 0; t < ntry; ++t)
     190             :                 {
     191           0 :                         let i = pos[0] + Math.round(a[0]*radius*(ntry-t));
     192           0 :                         let j = pos[1] + Math.round(a[1]*radius*(ntry-t));
     193           0 :                         if (i < 0 || i >= width || j < 0 || j >= width)
     194           0 :                                 continue;
     195           0 :                         if (gameState.ai.accessibility.landPassMap[i + j*width] === 1)
     196             :                         {
     197           0 :                                 let navalPass = gameState.ai.accessibility.navalPassMap[i + j*width];
     198           0 :                                 if (navalPass == sea)
     199           0 :                                         return true;
     200           0 :                                 else if (navalPass == 1)  // we could be outside the map
     201           0 :                                         continue;
     202             :                         }
     203           0 :                         return false;
     204             :                 }
     205           0 :                 return true;
     206             :         }))
     207           0 :                 fish.setMetadata(PlayerID, "opensea", true);
     208           0 :         return sea;
     209             : };
     210             : 
     211             : /** check if we can safely fish at the fish position */
     212           0 : PETRA.NavalManager.prototype.canFishSafely = function(gameState, fish)
     213             : {
     214           0 :         if (fish.getMetadata(PlayerID, "opensea"))
     215           0 :                 return true;
     216           0 :         const ntry = 2;
     217           0 :         const around = [[-0.7, 0.7], [0, 1], [0.7, 0.7], [1, 0], [0.7, -0.7], [0, -1], [-0.7, -0.7], [-1, 0]];
     218           0 :         let territoryMap = gameState.ai.HQ.territoryMap;
     219           0 :         let width = territoryMap.width;
     220           0 :         let radius = 120 / territoryMap.cellSize / ntry;
     221           0 :         let pos = territoryMap.gamePosToMapPos(fish.position());
     222           0 :         return around.every(a => {
     223           0 :                 for (let t = 0; t < ntry; ++t)
     224             :                 {
     225           0 :                         let i = pos[0] + Math.round(a[0]*radius*(ntry-t));
     226           0 :                         let j = pos[1] + Math.round(a[1]*radius*(ntry-t));
     227           0 :                         if (i < 0 || i >= width || j < 0 || j >= width)
     228           0 :                                 continue;
     229           0 :                         let owner = territoryMap.getOwnerIndex(i + j*width);
     230           0 :                         if (owner != 0 && gameState.isPlayerEnemy(owner))
     231           0 :                                 return false;
     232             :                 }
     233           0 :                 return true;
     234             :         });
     235             : };
     236             : 
     237             : /** get the list of seas (or lands) around this region not connected by a dock */
     238           0 : PETRA.NavalManager.prototype.getUnconnectedSeas = function(gameState, region)
     239             : {
     240           0 :         let seas = gameState.ai.accessibility.regionLinks[region].slice();
     241           0 :         this.docks.forEach(dock => {
     242           0 :                 if (!dock.hasClass("Dock") || PETRA.getLandAccess(gameState, dock) != region)
     243           0 :                         return;
     244           0 :                 let i = seas.indexOf(PETRA.getSeaAccess(gameState, dock));
     245           0 :                 if (i != -1)
     246           0 :                         seas.splice(i--, 1);
     247             :         });
     248           0 :         return seas;
     249             : };
     250             : 
     251           0 : PETRA.NavalManager.prototype.checkEvents = function(gameState, queues, events)
     252             : {
     253           0 :         for (let evt of events.Create)
     254             :         {
     255           0 :                 if (!evt.entity)
     256           0 :                         continue;
     257           0 :                 let ent = gameState.getEntityById(evt.entity);
     258           0 :                 if (ent && ent.isOwn(PlayerID) && ent.foundationProgress() !== undefined && ent.hasClasses(["Dock", "Shipyard"]))
     259           0 :                         PETRA.setSeaAccess(gameState, ent);
     260             :         }
     261             : 
     262           0 :         for (let evt of events.TrainingFinished)
     263             :         {
     264           0 :                 if (!evt.entities)
     265           0 :                         continue;
     266           0 :                 for (let entId of evt.entities)
     267             :                 {
     268           0 :                         let ent = gameState.getEntityById(entId);
     269           0 :                         if (!ent || !ent.hasClass("Ship") || !ent.isOwn(PlayerID))
     270           0 :                                 continue;
     271           0 :                         PETRA.setSeaAccess(gameState, ent);
     272             :                 }
     273             :         }
     274             : 
     275           0 :         for (let evt of events.Destroy)
     276             :         {
     277           0 :                 if (!evt.entityObj || evt.entityObj.owner() !== PlayerID || !evt.metadata || !evt.metadata[PlayerID])
     278           0 :                         continue;
     279           0 :                 if (!evt.entityObj.hasClass("Ship") || !evt.metadata[PlayerID].transporter)
     280           0 :                         continue;
     281           0 :                 let plan = this.getPlan(evt.metadata[PlayerID].transporter);
     282           0 :                 if (!plan)
     283           0 :                         continue;
     284             : 
     285           0 :                 let shipId = evt.entityObj.id();
     286           0 :                 if (this.Config.debug > 1)
     287           0 :                         API3.warn("one ship " + shipId + " from plan " + plan.ID + " destroyed during " + plan.state);
     288           0 :                 if (plan.state === PETRA.TransportPlan.BOARDING)
     289             :                 {
     290             :                         // just reset the units onBoard metadata and wait for a new ship to be assigned to this plan
     291           0 :                         plan.units.forEach(ent => {
     292           0 :                                 if (ent.getMetadata(PlayerID, "onBoard") == "onBoard" && ent.position() ||
     293             :                                     ent.getMetadata(PlayerID, "onBoard") == shipId)
     294           0 :                                         ent.setMetadata(PlayerID, "onBoard", undefined);
     295             :                         });
     296           0 :                         plan.needTransportShips = !plan.transportShips.hasEntities();
     297             :                 }
     298           0 :                 else if (plan.state === PETRA.TransportPlan.SAILING)
     299             :                 {
     300           0 :                         let endIndex = plan.endIndex;
     301           0 :                         for (let ent of plan.units.values())
     302             :                         {
     303           0 :                                 if (!ent.position())  // unit from another ship of this plan ... do nothing
     304           0 :                                         continue;
     305           0 :                                 let access = PETRA.getLandAccess(gameState, ent);
     306           0 :                                 let endPos = ent.getMetadata(PlayerID, "endPos");
     307           0 :                                 ent.setMetadata(PlayerID, "transport", undefined);
     308           0 :                                 ent.setMetadata(PlayerID, "onBoard", undefined);
     309           0 :                                 ent.setMetadata(PlayerID, "endPos", undefined);
     310             :                                 // nothing else to do if access = endIndex as already at destination
     311             :                                 // otherwise, we should require another transport
     312             :                                 // TODO if attacking and no more ships available, remove the units from the attack
     313             :                                 // to avoid delaying it too much
     314           0 :                                 if (access != endIndex)
     315           0 :                                         this.requireTransport(gameState, ent, access, endIndex, endPos);
     316             :                         }
     317             :                 }
     318             :         }
     319             : 
     320           0 :         for (let evt of events.OwnershipChanged)        // capture events
     321             :         {
     322           0 :                 if (evt.to !== PlayerID)
     323           0 :                         continue;
     324           0 :                 let ent = gameState.getEntityById(evt.entity);
     325           0 :                 if (ent && ent.hasClasses(["Dock", "Shipyard"]))
     326           0 :                         PETRA.setSeaAccess(gameState, ent);
     327             :         }
     328             : };
     329             : 
     330             : 
     331           0 : PETRA.NavalManager.prototype.getPlan = function(ID)
     332             : {
     333           0 :         for (let plan of this.transportPlans)
     334           0 :                 if (plan.ID === ID)
     335           0 :                         return plan;
     336           0 :         return undefined;
     337             : };
     338             : 
     339           0 : PETRA.NavalManager.prototype.addPlan = function(plan)
     340             : {
     341           0 :         this.transportPlans.push(plan);
     342             : };
     343             : 
     344             : /**
     345             :  * complete already existing plan or create a new one for this requirement
     346             :  * (many units can then call this separately and end up in the same plan)
     347             :  * TODO  check garrison classes
     348             :  */
     349           0 : PETRA.NavalManager.prototype.requireTransport = function(gameState, ent, startIndex, endIndex, endPos)
     350             : {
     351           0 :         if (!ent.canGarrison())
     352           0 :                 return false;
     353             : 
     354           0 :         if (ent.getMetadata(PlayerID, "transport") !== undefined)
     355             :         {
     356           0 :                 if (this.Config.debug > 0)
     357           0 :                         API3.warn("Petra naval manager error: unit " + ent.id() + " has already required a transport");
     358           0 :                 return false;
     359             :         }
     360             : 
     361           0 :         let plans = [];
     362           0 :         for (let plan of this.transportPlans)
     363             :         {
     364           0 :                 if (plan.startIndex != startIndex || plan.endIndex != endIndex || plan.state !== PETRA.TransportPlan.BOARDING)
     365           0 :                         continue;
     366             :                 // Limit the number of siege units per transport to avoid problems when ungarrisoning
     367           0 :                 if (PETRA.isSiegeUnit(ent) && plan.units.filter(unit => PETRA.isSiegeUnit(unit)).length > 3)
     368           0 :                         continue;
     369           0 :                 plans.push(plan);
     370             :         }
     371             : 
     372           0 :         if (plans.length)
     373             :         {
     374           0 :                 plans.sort(plan => plan.units.length);
     375           0 :                 plans[0].addUnit(ent, endPos);
     376           0 :                 return true;
     377             :         }
     378             : 
     379           0 :         let plan = new PETRA.TransportPlan(gameState, [ent], startIndex, endIndex, endPos);
     380           0 :         if (plan.failed)
     381             :         {
     382           0 :                 if (this.Config.debug > 1)
     383           0 :                         API3.warn(">>>> transport plan aborted <<<<");
     384           0 :                 return false;
     385             :         }
     386           0 :         plan.init(gameState);
     387           0 :         this.transportPlans.push(plan);
     388           0 :         return true;
     389             : };
     390             : 
     391             : /** split a transport plan in two, moving all entities not yet affected to a ship in the new plan */
     392           0 : PETRA.NavalManager.prototype.splitTransport = function(gameState, plan)
     393             : {
     394           0 :         if (this.Config.debug > 1)
     395           0 :                 API3.warn(">>>> split of transport plan started <<<<");
     396           0 :         let newplan = new PETRA.TransportPlan(gameState, [], plan.startIndex, plan.endIndex, plan.endPos);
     397           0 :         if (newplan.failed)
     398             :         {
     399           0 :                 if (this.Config.debug > 1)
     400           0 :                         API3.warn(">>>> split of transport plan aborted <<<<");
     401           0 :                 return false;
     402             :         }
     403           0 :         newplan.init(gameState);
     404             : 
     405           0 :         for (let ent of plan.needSplit)
     406             :         {
     407           0 :                 if (ent.getMetadata(PlayerID, "onBoard"))     // Should never happen.
     408           0 :                         continue;
     409           0 :                 newplan.addUnit(ent, ent.getMetadata(PlayerID, "endPos"));
     410           0 :                 plan.units.updateEnt(ent);
     411             :         }
     412             : 
     413           0 :         if (newplan.units.length)
     414           0 :                 this.transportPlans.push(newplan);
     415           0 :         return newplan.units.length != 0;
     416             : };
     417             : 
     418             : /**
     419             :  * create a transport from a garrisoned ship to a land location
     420             :  * needed at start game when starting with a garrisoned ship
     421             :  */
     422           0 : PETRA.NavalManager.prototype.createTransportIfNeeded = function(gameState, fromPos, toPos, toAccess)
     423             : {
     424           0 :         let fromAccess = gameState.ai.accessibility.getAccessValue(fromPos);
     425           0 :         if (fromAccess !== 1)
     426           0 :                 return;
     427           0 :         if (toAccess < 2)
     428           0 :                 return;
     429             : 
     430           0 :         for (let ship of this.ships.values())
     431             :         {
     432           0 :                 if (!ship.isGarrisonHolder() || !ship.garrisoned().length)
     433           0 :                         continue;
     434           0 :                 if (ship.getMetadata(PlayerID, "transporter") !== undefined)
     435           0 :                         continue;
     436           0 :                 let units = [];
     437           0 :                 for (let entId of ship.garrisoned())
     438           0 :                         units.push(gameState.getEntityById(entId));
     439             :                 // TODO check that the garrisoned units have not another purpose
     440           0 :                 let plan = new PETRA.TransportPlan(gameState, units, fromAccess, toAccess, toPos, ship);
     441           0 :                 if (plan.failed)
     442           0 :                         continue;
     443           0 :                 plan.init(gameState);
     444           0 :                 this.transportPlans.push(plan);
     445             :         }
     446             : };
     447             : 
     448             : // set minimal number of needed ships when a new event (new base or new attack plan)
     449           0 : PETRA.NavalManager.prototype.setMinimalTransportShips = function(gameState, sea, number)
     450             : {
     451           0 :         if (!sea)
     452           0 :                 return;
     453           0 :         if (this.wantedTransportShips[sea] < number)
     454           0 :                 this.wantedTransportShips[sea] = number;
     455             : };
     456             : 
     457             : // bumps up the number of ships we want if we need more.
     458           0 : PETRA.NavalManager.prototype.checkLevels = function(gameState, queues)
     459             : {
     460           0 :         if (queues.ships.hasQueuedUnits())
     461           0 :                 return;
     462             : 
     463           0 :         for (let sea = 0; sea < this.neededTransportShips.length; sea++)
     464           0 :                 this.neededTransportShips[sea] = 0;
     465             : 
     466           0 :         for (let plan of this.transportPlans)
     467             :         {
     468           0 :                 if (!plan.needTransportShips || plan.units.length < 2)
     469           0 :                         continue;
     470           0 :                 let sea = plan.sea;
     471           0 :                 if (gameState.countOwnQueuedEntitiesWithMetadata("sea", sea) > 0 ||
     472             :                         this.seaTransportShips[sea].length < this.wantedTransportShips[sea])
     473           0 :                         continue;
     474           0 :                 ++this.neededTransportShips[sea];
     475           0 :                 if (this.wantedTransportShips[sea] === 0 || this.seaTransportShips[sea].length < plan.transportShips.length + 2)
     476             :                 {
     477           0 :                         ++this.wantedTransportShips[sea];
     478           0 :                         return;
     479             :                 }
     480             :         }
     481             : 
     482           0 :         for (let sea = 0; sea < this.neededTransportShips.length; sea++)
     483           0 :                 if (this.neededTransportShips[sea] > 2)
     484           0 :                         ++this.wantedTransportShips[sea];
     485             : };
     486             : 
     487           0 : PETRA.NavalManager.prototype.maintainFleet = function(gameState, queues)
     488             : {
     489           0 :         if (queues.ships.hasQueuedUnits())
     490           0 :                 return;
     491           0 :         if (!this.docks.filter(API3.Filters.isBuilt()).hasEntities())
     492           0 :                 return;
     493             :         // check if we have enough transport ships per region.
     494           0 :         for (let sea = 0; sea < this.seaShips.length; ++sea)
     495             :         {
     496           0 :                 if (this.seaShips[sea] === undefined)
     497           0 :                         continue;
     498           0 :                 if (gameState.countOwnQueuedEntitiesWithMetadata("sea", sea) > 0)
     499           0 :                         continue;
     500             : 
     501           0 :                 if (this.seaTransportShips[sea].length < this.wantedTransportShips[sea])
     502             :                 {
     503           0 :                         let template = this.getBestShip(gameState, sea, "transport");
     504           0 :                         if (template)
     505             :                         {
     506           0 :                                 queues.ships.addPlan(new PETRA.TrainingPlan(gameState, template, { "sea": sea }, 1, 1));
     507           0 :                                 continue;
     508             :                         }
     509             :                 }
     510             : 
     511             : 
     512           0 :                 if (this.seaFishShips[sea].length < this.wantedFishShips[sea])
     513             :                 {
     514           0 :                         let template = this.getBestShip(gameState, sea, "fishing");
     515           0 :                         if (template)
     516             :                         {
     517           0 :                                 queues.ships.addPlan(new PETRA.TrainingPlan(gameState, template, { "base": 0, "role": PETRA.Worker.ROLE_WORKER, "sea": sea }, 1, 1));
     518           0 :                                 continue;
     519             :                         }
     520             :                 }
     521             :         }
     522             : };
     523             : 
     524             : /** assigns free ships to plans that need some */
     525           0 : PETRA.NavalManager.prototype.assignShipsToPlans = function(gameState)
     526             : {
     527           0 :         for (let plan of this.transportPlans)
     528           0 :                 if (plan.needTransportShips)
     529           0 :                         plan.assignShip(gameState);
     530             : };
     531             : 
     532             : /** Return true if this ship is likeky (un)garrisoning units */
     533           0 : PETRA.NavalManager.prototype.isShipBoarding = function(ship)
     534             : {
     535           0 :         if (!ship.position())
     536           0 :                 return false;
     537           0 :         let plan = this.getPlan(ship.getMetadata(PlayerID, "transporter"));
     538           0 :         if (!plan || !plan.boardingPos[ship.id()])
     539           0 :                 return false;
     540           0 :         return API3.SquareVectorDistance(plan.boardingPos[ship.id()], ship.position()) < plan.boardingRange;
     541             : };
     542             : 
     543             : /** let blocking ships move apart from active ships (waiting for a better pathfinder)
     544             :  * TODO Ships entity collections are currently in two parts as the trader ships are dealt with
     545             :  * in the tradeManager. That should be modified to avoid dupplicating all the code here.
     546             :  */
     547           0 : PETRA.NavalManager.prototype.moveApart = function(gameState)
     548             : {
     549           0 :         let blockedShips = [];
     550           0 :         let blockedIds = [];
     551             : 
     552           0 :         for (let ship of this.ships.values())
     553             :         {
     554           0 :                 let shipPosition = ship.position();
     555           0 :                 if (!shipPosition)
     556           0 :                         continue;
     557           0 :                 if (ship.getMetadata(PlayerID, "transporter") !== undefined && this.isShipBoarding(ship))
     558           0 :                         continue;
     559             : 
     560           0 :                 let unitAIState = ship.unitAIState();
     561           0 :                 if (ship.getMetadata(PlayerID, "transporter") !== undefined ||
     562             :                     unitAIState == "INDIVIDUAL.GATHER.APPROACHING" ||
     563             :                     unitAIState == "INDIVIDUAL.GATHER.RETURNINGRESOURCE.APPROACHING")
     564             :                 {
     565           0 :                         let previousPosition = ship.getMetadata(PlayerID, "previousPosition");
     566           0 :                         if (!previousPosition || previousPosition[0] != shipPosition[0] ||
     567             :                                                  previousPosition[1] != shipPosition[1])
     568             :                         {
     569           0 :                                 ship.setMetadata(PlayerID, "previousPosition", shipPosition);
     570           0 :                                 ship.setMetadata(PlayerID, "turnPreviousPosition", gameState.ai.playedTurn);
     571           0 :                                 continue;
     572             :                         }
     573             :                         // New transport ships receive boarding commands only on the following turn.
     574           0 :                         if (gameState.ai.playedTurn < ship.getMetadata(PlayerID, "turnPreviousPosition") + 2)
     575           0 :                                 continue;
     576           0 :                         ship.moveToRange(shipPosition[0] + randFloat(-1, 1), shipPosition[1] + randFloat(-1, 1), 30, 35);
     577           0 :                         blockedShips.push(ship);
     578           0 :                         blockedIds.push(ship.id());
     579             :                 }
     580           0 :                 else if (ship.isIdle())
     581             :                 {
     582           0 :                         let previousIdlePosition = ship.getMetadata(PlayerID, "previousIdlePosition");
     583           0 :                         if (!previousIdlePosition || previousIdlePosition[0] != shipPosition[0] ||
     584             :                                                      previousIdlePosition[1] != shipPosition[1])
     585             :                         {
     586           0 :                                 ship.setMetadata(PlayerID, "previousIdlePosition", shipPosition);
     587           0 :                                 ship.setMetadata(PlayerID, "stationnary", undefined);
     588           0 :                                 continue;
     589             :                         }
     590           0 :                         if (ship.getMetadata(PlayerID, "stationnary"))
     591           0 :                                 continue;
     592           0 :                         ship.setMetadata(PlayerID, "stationnary", true);
     593             :                         // Check if there are some treasure around
     594           0 :                         if (PETRA.gatherTreasure(gameState, ship, true))
     595           0 :                                 continue;
     596             :                         // Do not stay idle near a dock to not disturb other ships
     597           0 :                         let sea = ship.getMetadata(PlayerID, "sea");
     598           0 :                         for (let dock of gameState.getAllyStructures().filter(API3.Filters.byClass("Dock")).values())
     599             :                         {
     600           0 :                                 if (PETRA.getSeaAccess(gameState, dock) != sea)
     601           0 :                                         continue;
     602           0 :                                 if (API3.SquareVectorDistance(shipPosition, dock.position()) > 4900)
     603           0 :                                         continue;
     604           0 :                                 ship.moveToRange(dock.position()[0], dock.position()[1], 70, 75);
     605             :                         }
     606             : 
     607             :                 }
     608             :         }
     609             : 
     610           0 :         for (let ship of gameState.ai.HQ.tradeManager.traders.filter(API3.Filters.byClass("Ship")).values())
     611             :         {
     612           0 :                 let shipPosition = ship.position();
     613           0 :                 if (!shipPosition)
     614           0 :                         continue;
     615           0 :                 let role = ship.getMetadata(PlayerID, "role");
     616           0 :                 if (role === undefined || role !== PETRA.Worker.ROLE_TRADER)    // already accounted before
     617           0 :                         continue;
     618             : 
     619           0 :                 let unitAIState = ship.unitAIState();
     620           0 :                 if (unitAIState == "INDIVIDUAL.TRADE.APPROACHINGMARKET")
     621             :                 {
     622           0 :                         let previousPosition = ship.getMetadata(PlayerID, "previousPosition");
     623           0 :                         if (!previousPosition || previousPosition[0] != shipPosition[0] ||
     624             :                                                  previousPosition[1] != shipPosition[1])
     625             :                         {
     626           0 :                                 ship.setMetadata(PlayerID, "previousPosition", shipPosition);
     627           0 :                                 ship.setMetadata(PlayerID, "turnPreviousPosition", gameState.ai.playedTurn);
     628           0 :                                 continue;
     629             :                         }
     630             :                         // New transport ships receives boarding commands only on the following turn.
     631           0 :                         if (gameState.ai.playedTurn < ship.getMetadata(PlayerID, "turnPreviousPosition") + 2)
     632           0 :                                 continue;
     633           0 :                         ship.moveToRange(shipPosition[0] + randFloat(-1, 1), shipPosition[1] + randFloat(-1, 1), 30, 35);
     634           0 :                         blockedShips.push(ship);
     635           0 :                         blockedIds.push(ship.id());
     636             :                 }
     637           0 :                 else if (ship.isIdle())
     638             :                 {
     639           0 :                         let previousIdlePosition = ship.getMetadata(PlayerID, "previousIdlePosition");
     640           0 :                         if (!previousIdlePosition || previousIdlePosition[0] != shipPosition[0] ||
     641             :                                                      previousIdlePosition[1] != shipPosition[1])
     642             :                         {
     643           0 :                                 ship.setMetadata(PlayerID, "previousIdlePosition", shipPosition);
     644           0 :                                 ship.setMetadata(PlayerID, "stationnary", undefined);
     645           0 :                                 continue;
     646             :                         }
     647           0 :                         if (ship.getMetadata(PlayerID, "stationnary"))
     648           0 :                                 continue;
     649           0 :                         ship.setMetadata(PlayerID, "stationnary", true);
     650             :                         // Check if there are some treasure around
     651           0 :                         if (PETRA.gatherTreasure(gameState, ship, true))
     652           0 :                                 continue;
     653             :                         // Do not stay idle near a dock to not disturb other ships
     654           0 :                         let sea = ship.getMetadata(PlayerID, "sea");
     655           0 :                         for (let dock of gameState.getAllyStructures().filter(API3.Filters.byClass("Dock")).values())
     656             :                         {
     657           0 :                                 if (PETRA.getSeaAccess(gameState, dock) != sea)
     658           0 :                                         continue;
     659           0 :                                 if (API3.SquareVectorDistance(shipPosition, dock.position()) > 4900)
     660           0 :                                         continue;
     661           0 :                                 ship.moveToRange(dock.position()[0], dock.position()[1], 70, 75);
     662             :                         }
     663             :                 }
     664             :         }
     665             : 
     666           0 :         for (let ship of blockedShips)
     667             :         {
     668           0 :                 let shipPosition = ship.position();
     669           0 :                 let sea = ship.getMetadata(PlayerID, "sea");
     670           0 :                 for (let blockingShip of this.seaShips[sea].values())
     671             :                 {
     672           0 :                         if (blockedIds.indexOf(blockingShip.id()) != -1 || !blockingShip.position())
     673           0 :                                 continue;
     674           0 :                         let distSquare = API3.SquareVectorDistance(shipPosition, blockingShip.position());
     675           0 :                         let unitAIState = blockingShip.unitAIState();
     676           0 :                         if (blockingShip.getMetadata(PlayerID, "transporter") === undefined &&
     677             :                             unitAIState != "INDIVIDUAL.GATHER.APPROACHING" &&
     678             :                             unitAIState != "INDIVIDUAL.GATHER.RETURNINGRESOURCE.APPROACHING")
     679             :                         {
     680           0 :                                 if (distSquare < 1600)
     681           0 :                                         blockingShip.moveToRange(shipPosition[0], shipPosition[1], 40, 45);
     682             :                         }
     683           0 :                         else if (distSquare < 900)
     684           0 :                                 blockingShip.moveToRange(shipPosition[0], shipPosition[1], 30, 35);
     685             :                 }
     686             : 
     687           0 :                 for (let blockingShip of gameState.ai.HQ.tradeManager.traders.filter(API3.Filters.byClass("Ship")).values())
     688             :                 {
     689           0 :                         if (blockingShip.getMetadata(PlayerID, "sea") != sea)
     690           0 :                                 continue;
     691           0 :                         if (blockedIds.indexOf(blockingShip.id()) != -1 || !blockingShip.position())
     692           0 :                                 continue;
     693           0 :                         let role = blockingShip.getMetadata(PlayerID, "role");
     694           0 :                         if (role === undefined || role !== PETRA.Worker.ROLE_TRADER)    // already accounted before
     695           0 :                                 continue;
     696           0 :                         let distSquare = API3.SquareVectorDistance(shipPosition, blockingShip.position());
     697           0 :                         let unitAIState = blockingShip.unitAIState();
     698           0 :                         if (unitAIState != "INDIVIDUAL.TRADE.APPROACHINGMARKET")
     699             :                         {
     700           0 :                                 if (distSquare < 1600)
     701           0 :                                         blockingShip.moveToRange(shipPosition[0], shipPosition[1], 40, 45);
     702             :                         }
     703           0 :                         else if (distSquare < 900)
     704           0 :                                 blockingShip.moveToRange(shipPosition[0], shipPosition[1], 30, 35);
     705             :                 }
     706             :         }
     707             : };
     708             : 
     709           0 : PETRA.NavalManager.prototype.buildNavalStructures = function(gameState, queues)
     710             : {
     711           0 :         if (!gameState.ai.HQ.navalMap || !gameState.ai.HQ.hasPotentialBase())
     712           0 :                 return;
     713             : 
     714           0 :         if (gameState.ai.HQ.getAccountedPopulation(gameState) > this.Config.Economy.popForDock)
     715             :         {
     716           0 :                 if (queues.dock.countQueuedUnitsWithClass("Dock") === 0 &&
     717             :                         !gameState.getOwnStructures().filter(API3.Filters.and(API3.Filters.byClass("Dock"), API3.Filters.isFoundation())).hasEntities() &&
     718             :                         gameState.ai.HQ.canBuild(gameState, "structures/{civ}/dock"))
     719             :                 {
     720           0 :                         let dockStarted = false;
     721           0 :                         for (const base of gameState.ai.HQ.baseManagers())
     722             :                         {
     723           0 :                                 if (dockStarted)
     724           0 :                                         break;
     725           0 :                                 if (!base.anchor || base.constructing)
     726           0 :                                         continue;
     727           0 :                                 let remaining = this.getUnconnectedSeas(gameState, base.accessIndex);
     728           0 :                                 for (let sea of remaining)
     729             :                                 {
     730           0 :                                         if (!gameState.ai.HQ.navalRegions[sea])
     731           0 :                                                 continue;
     732           0 :                                         let wantedLand = {};
     733           0 :                                         wantedLand[base.accessIndex] = true;
     734           0 :                                         queues.dock.addPlan(new PETRA.ConstructionPlan(gameState, "structures/{civ}/dock", { "land": wantedLand, "sea": sea }));
     735           0 :                                         dockStarted = true;
     736           0 :                                         break;
     737             :                                 }
     738             :                         }
     739             :                 }
     740             :         }
     741             : 
     742           0 :         if (gameState.currentPhase() < 2 || gameState.ai.HQ.getAccountedPopulation(gameState) < this.Config.Economy.popPhase2 + 15 ||
     743             :             queues.militaryBuilding.hasQueuedUnits())
     744           0 :                 return;
     745           0 :         if (!this.docks.filter(API3.Filters.byClass("Dock")).hasEntities() ||
     746             :              this.docks.filter(API3.Filters.byClass("Shipyard")).hasEntities())
     747           0 :                 return;
     748             :         // Use in priority resources to build a Market.
     749           0 :         if (!gameState.getOwnEntitiesByClass("Market", true).hasEntities() &&
     750             :             gameState.ai.HQ.canBuild(gameState, "structures/{civ}/market"))
     751           0 :                 return;
     752             :         let template;
     753           0 :         if (gameState.ai.HQ.canBuild(gameState, "structures/{civ}/super_dock"))
     754           0 :                 template = "structures/{civ}/super_dock";
     755           0 :         else if (gameState.ai.HQ.canBuild(gameState, "structures/{civ}/shipyard"))
     756           0 :                 template = "structures/{civ}/shipyard";
     757             :         else
     758           0 :                 return;
     759           0 :         let wantedLand = {};
     760           0 :         for (const base of gameState.ai.HQ.baseManagers())
     761           0 :                 if (base.anchor)
     762           0 :                         wantedLand[base.accessIndex] = true;
     763           0 :         let sea = this.docks.toEntityArray()[0].getMetadata(PlayerID, "sea");
     764           0 :         queues.militaryBuilding.addPlan(new PETRA.ConstructionPlan(gameState, template, { "land": wantedLand, "sea": sea }));
     765             : };
     766             : 
     767             : /** goal can be either attack (choose ship with best arrowCount) or transport (choose ship with best capacity) */
     768           0 : PETRA.NavalManager.prototype.getBestShip = function(gameState, sea, goal)
     769             : {
     770           0 :         let civ = gameState.getPlayerCiv();
     771           0 :         let trainableShips = [];
     772           0 :         gameState.getOwnTrainingFacilities().filter(API3.Filters.byMetadata(PlayerID, "sea", sea)).forEach(function(ent) {
     773           0 :                 let trainables = ent.trainableEntities(civ);
     774           0 :                 for (let trainable of trainables)
     775             :                 {
     776           0 :                         if (gameState.isTemplateDisabled(trainable))
     777           0 :                                 continue;
     778           0 :                         let template = gameState.getTemplate(trainable);
     779           0 :                         if (template && template.hasClass("Ship") && trainableShips.indexOf(trainable) === -1)
     780           0 :                                 trainableShips.push(trainable);
     781             :                 }
     782             :         });
     783             : 
     784           0 :         let best = 0;
     785             :         let bestShip;
     786           0 :         let limits = gameState.getEntityLimits();
     787           0 :         let current = gameState.getEntityCounts();
     788           0 :         for (let trainable of trainableShips)
     789             :         {
     790           0 :                 let template = gameState.getTemplate(trainable);
     791           0 :                 if (!template.available(gameState))
     792           0 :                         continue;
     793             : 
     794           0 :                 let category = template.trainingCategory();
     795           0 :                 if (category && limits[category] && current[category] >= limits[category])
     796           0 :                         continue;
     797             : 
     798           0 :                 let arrows = +(template.getDefaultArrow() || 0);
     799           0 :                 if (goal === "attack")    // choose the maximum default arrows
     800             :                 {
     801           0 :                         if (best > arrows)
     802           0 :                                 continue;
     803           0 :                         best = arrows;
     804             :                 }
     805           0 :                 else if (goal === "transport")   // choose the maximum capacity, with a bonus if arrows or if siege transport
     806             :                 {
     807           0 :                         let capacity = +(template.garrisonMax() || 0);
     808           0 :                         if (capacity < 2)
     809           0 :                                 continue;
     810           0 :                         capacity += 10*arrows;
     811           0 :                         if (MatchesClassList(template.garrisonableClasses(), "Siege"))
     812           0 :                                 capacity += 50;
     813           0 :                         if (best > capacity)
     814           0 :                                 continue;
     815           0 :                         best = capacity;
     816             :                 }
     817           0 :                 else if (goal === "fishing")
     818           0 :                         if (!template.hasClass("FishingBoat"))
     819           0 :                                 continue;
     820           0 :                 bestShip = trainable;
     821             :         }
     822           0 :         return bestShip;
     823             : };
     824             : 
     825           0 : PETRA.NavalManager.prototype.update = function(gameState, queues, events)
     826             : {
     827           0 :         Engine.ProfileStart("Naval Manager update");
     828             : 
     829             :         // close previous transport plans if finished
     830           0 :         for (let i = 0; i < this.transportPlans.length; ++i)
     831             :         {
     832           0 :                 let remaining = this.transportPlans[i].update(gameState);
     833           0 :                 if (remaining)
     834           0 :                         continue;
     835           0 :                 if (this.Config.debug > 1)
     836           0 :                         API3.warn("no more units on transport plan " + this.transportPlans[i].ID);
     837           0 :                 this.transportPlans[i].releaseAll();
     838           0 :                 this.transportPlans.splice(i--, 1);
     839             :         }
     840             :         // assign free ships to plans which need them
     841           0 :         this.assignShipsToPlans(gameState);
     842             : 
     843             :         // and require for more ships/structures if needed
     844           0 :         if (gameState.ai.playedTurn % 3 === 0)
     845             :         {
     846           0 :                 this.checkLevels(gameState, queues);
     847           0 :                 this.maintainFleet(gameState, queues);
     848           0 :                 this.buildNavalStructures(gameState, queues);
     849             :         }
     850             :         // let inactive ships move apart from active ones (waiting for a better pathfinder)
     851           0 :         this.moveApart(gameState);
     852             : 
     853           0 :         Engine.ProfileStop();
     854             : };
     855             : 
     856           0 : PETRA.NavalManager.prototype.Serialize = function()
     857             : {
     858           0 :         let properties = {
     859             :                 "wantedTransportShips": this.wantedTransportShips,
     860             :                 "wantedWarShips": this.wantedWarShips,
     861             :                 "wantedFishShips": this.wantedFishShips,
     862             :                 "neededTransportShips": this.neededTransportShips,
     863             :                 "neededWarShips": this.neededWarShips,
     864             :                 "landingZones": this.landingZones
     865             :         };
     866             : 
     867           0 :         let transports = {};
     868           0 :         for (let plan in this.transportPlans)
     869           0 :                 transports[plan] = this.transportPlans[plan].Serialize();
     870             : 
     871           0 :         return { "properties": properties, "transports": transports };
     872             : };
     873             : 
     874           0 : PETRA.NavalManager.prototype.Deserialize = function(gameState, data)
     875             : {
     876           0 :         for (let key in data.properties)
     877           0 :                 this[key] = data.properties[key];
     878             : 
     879           0 :         this.transportPlans = [];
     880           0 :         for (let i in data.transports)
     881             :         {
     882           0 :                 let dataPlan = data.transports[i];
     883           0 :                 let plan = new PETRA.TransportPlan(gameState, [], dataPlan.startIndex, dataPlan.endIndex, dataPlan.endPos);
     884           0 :                 plan.Deserialize(dataPlan);
     885           0 :                 plan.init(gameState);
     886           0 :                 this.transportPlans.push(plan);
     887             :         }
     888             : };

Generated by: LCOV version 1.14