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

          Line data    Source code
       1             : /**
       2             :  * Describes a transport plan
       3             :  * Constructor assign units (units is an ID array), a destination (position).
       4             :  * The naval manager will try to deal with it accordingly.
       5             :  *
       6             :  * By this I mean that the naval manager will find how to go from access point 1 to access point 2
       7             :  * and then carry units from there.
       8             :  *
       9             :  * Note: only assign it units currently over land, or it won't work.
      10             :  * Also: destination should probably be land, otherwise the units will be lost at sea.
      11             :  *
      12             :  * metadata for units:
      13             :  *   transport = this.ID
      14             :  *   onBoard = ship.id() when affected to a ship but not yet garrisoned
      15             :  *           = "onBoard" when garrisoned in a ship
      16             :  *           = undefined otherwise
      17             :  *   endPos  = position of destination
      18             :  *
      19             :  *   metadata for ships
      20             :  *   transporter = this.ID
      21             :  */
      22             : 
      23           0 : PETRA.TransportPlan = function(gameState, units, startIndex, endIndex, endPos, ship)
      24             : {
      25           0 :         this.ID = gameState.ai.uniqueIDs.transports++;
      26           0 :         this.debug = gameState.ai.Config.debug;
      27           0 :         this.flotilla = false;   // when false, only one ship per transport ... not yet tested when true
      28             : 
      29           0 :         this.endPos = endPos;
      30           0 :         this.endIndex = endIndex;
      31           0 :         this.startIndex = startIndex;
      32             :         // TODO only cases with land-sea-land are allowed for the moment
      33             :         // we could also have land-sea-land-sea-land
      34           0 :         if (startIndex == 1)
      35             :         {
      36             :                 // special transport from already garrisoned ship
      37           0 :                 if (!ship)
      38             :                 {
      39           0 :                         this.failed = true;
      40           0 :                         return false;
      41             :                 }
      42           0 :                 this.sea = ship.getMetadata(PlayerID, "sea");
      43           0 :                 ship.setMetadata(PlayerID, "transporter", this.ID);
      44           0 :                 ship.setStance("none");
      45           0 :                 for (let ent of units)
      46           0 :                         ent.setMetadata(PlayerID, "onBoard", "onBoard");
      47             :         }
      48             :         else
      49             :         {
      50           0 :                 this.sea = gameState.ai.HQ.getSeaBetweenIndices(gameState, startIndex, endIndex);
      51           0 :                 if (!this.sea)
      52             :                 {
      53           0 :                         this.failed = true;
      54           0 :                         if (this.debug > 1)
      55           0 :                                 API3.warn("transport plan with bad path: startIndex " + startIndex + " endIndex " + endIndex);
      56           0 :                         return false;
      57             :                 }
      58             :         }
      59             : 
      60           0 :         for (let ent of units)
      61             :         {
      62           0 :                 ent.setMetadata(PlayerID, "transport", this.ID);
      63           0 :                 ent.setMetadata(PlayerID, "endPos", endPos);
      64             :         }
      65             : 
      66           0 :         if (this.debug > 1)
      67           0 :                 API3.warn("Starting a new transport plan with ID " + this.ID +
      68             :                         " to index " + endIndex + " with units length " + units.length);
      69             : 
      70           0 :         this.state = PETRA.TransportPlan.BOARDING;
      71           0 :         this.boardingPos = {};
      72           0 :         this.needTransportShips = ship === undefined;
      73           0 :         this.nTry = {};
      74           0 :         return true;
      75             : };
      76             : 
      77             : /**
      78             :  * We're trying to board units onto our ships.
      79             :  */
      80           0 : PETRA.TransportPlan.BOARDING = "boarding";
      81             : /**
      82             :  * We're moving ships and eventually unload units.
      83             :  */
      84           0 : PETRA.TransportPlan.SAILING = "sailing";
      85             : 
      86           0 : PETRA.TransportPlan.prototype.init = function(gameState)
      87             : {
      88           0 :         this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "transport", this.ID));
      89           0 :         this.ships = gameState.ai.HQ.navalManager.ships.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID));
      90           0 :         this.transportShips = gameState.ai.HQ.navalManager.transportShips.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID));
      91             : 
      92           0 :         this.units.registerUpdates();
      93           0 :         this.ships.registerUpdates();
      94           0 :         this.transportShips.registerUpdates();
      95             : 
      96           0 :         this.boardingRange = 18*18;     // TODO compute it from the ship clearance and garrison range
      97             : };
      98             : 
      99             : /** count available slots */
     100           0 : PETRA.TransportPlan.prototype.countFreeSlots = function()
     101             : {
     102           0 :         let slots = 0;
     103           0 :         for (let ship of this.transportShips.values())
     104           0 :                 slots += this.countFreeSlotsOnShip(ship);
     105           0 :         return slots;
     106             : };
     107             : 
     108           0 : PETRA.TransportPlan.prototype.countFreeSlotsOnShip = function(ship)
     109             : {
     110           0 :         if (ship.hitpoints() < ship.garrisonEjectHealth() * ship.maxHitpoints())
     111           0 :                 return 0;
     112           0 :         let occupied = ship.garrisoned().length +
     113             :                 this.units.filter(API3.Filters.byMetadata(PlayerID, "onBoard", ship.id())).length;
     114           0 :         return Math.max(ship.garrisonMax() - occupied, 0);
     115             : };
     116             : 
     117           0 : PETRA.TransportPlan.prototype.assignUnitToShip = function(gameState, ent)
     118             : {
     119           0 :         if (this.needTransportShips)
     120           0 :                 return;
     121             : 
     122           0 :         for (let ship of this.transportShips.values())
     123             :         {
     124           0 :                 if (this.countFreeSlotsOnShip(ship) == 0)
     125           0 :                         continue;
     126           0 :                 ent.setMetadata(PlayerID, "onBoard", ship.id());
     127           0 :                 if (this.debug > 1)
     128             :                 {
     129           0 :                         if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_ATTACK)
     130           0 :                                 Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [2, 0, 0] });
     131             :                         else
     132           0 :                                 Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [0, 2, 0] });
     133             :                 }
     134           0 :                 return;
     135             :         }
     136             : 
     137           0 :         if (this.flotilla)
     138             :         {
     139           0 :                 this.needTransportShips = true;
     140           0 :                 return;
     141             :         }
     142             : 
     143           0 :         if (!this.needSplit)
     144           0 :                 this.needSplit = [ent];
     145             :         else
     146           0 :                 this.needSplit.push(ent);
     147             : };
     148             : 
     149           0 : PETRA.TransportPlan.prototype.assignShip = function(gameState)
     150             : {
     151             :         let pos;
     152             :         // choose a unit of this plan not yet assigned to a ship
     153           0 :         for (let ent of this.units.values())
     154             :         {
     155           0 :                 if (!ent.position() || ent.getMetadata(PlayerID, "onBoard") !== undefined)
     156           0 :                         continue;
     157           0 :                 pos = ent.position();
     158           0 :                 break;
     159             :         }
     160             :         // and choose the nearest available ship from this unit
     161           0 :         let distmin = Math.min();
     162             :         let nearest;
     163           0 :         gameState.ai.HQ.navalManager.seaTransportShips[this.sea].forEach(ship => {
     164           0 :                 if (ship.getMetadata(PlayerID, "transporter"))
     165           0 :                         return;
     166           0 :                 if (pos)
     167             :                 {
     168           0 :                         let dist = API3.SquareVectorDistance(pos, ship.position());
     169           0 :                         if (dist > distmin)
     170           0 :                                 return;
     171           0 :                         distmin = dist;
     172           0 :                         nearest = ship;
     173             :                 }
     174           0 :                 else if (!nearest)
     175           0 :                         nearest = ship;
     176             :         });
     177           0 :         if (!nearest)
     178           0 :                 return false;
     179             : 
     180           0 :         nearest.setMetadata(PlayerID, "transporter", this.ID);
     181           0 :         nearest.setStance("none");
     182           0 :         this.ships.updateEnt(nearest);
     183           0 :         this.transportShips.updateEnt(nearest);
     184           0 :         this.needTransportShips = false;
     185           0 :         return true;
     186             : };
     187             : 
     188             : /** add a unit to this plan */
     189           0 : PETRA.TransportPlan.prototype.addUnit = function(unit, endPos)
     190             : {
     191           0 :         unit.setMetadata(PlayerID, "transport", this.ID);
     192           0 :         unit.setMetadata(PlayerID, "endPos", endPos);
     193           0 :         this.units.updateEnt(unit);
     194             : };
     195             : 
     196             : /** remove a unit from this plan, if not yet on board */
     197           0 : PETRA.TransportPlan.prototype.removeUnit = function(gameState, unit)
     198             : {
     199           0 :         let shipId = unit.getMetadata(PlayerID, "onBoard");
     200           0 :         if (shipId == "onBoard")
     201           0 :                 return;                 // too late, already onBoard
     202           0 :         else if (shipId !== undefined)
     203           0 :                 unit.stopMoving();      // cancel the garrison order
     204           0 :         unit.setMetadata(PlayerID, "transport", undefined);
     205           0 :         unit.setMetadata(PlayerID, "endPos", undefined);
     206           0 :         this.units.updateEnt(unit);
     207           0 :         if (shipId)
     208             :         {
     209           0 :                 unit.setMetadata(PlayerID, "onBoard", undefined);
     210           0 :                 let ship = gameState.getEntityById(shipId);
     211           0 :                 if (ship && !ship.garrisoned().length &&
     212             :                             !this.units.filter(API3.Filters.byMetadata(PlayerID, "onBoard", shipId)).length)
     213             :                 {
     214           0 :                         this.releaseShip(ship);
     215           0 :                         this.ships.updateEnt(ship);
     216           0 :                         this.transportShips.updateEnt(ship);
     217             :                 }
     218             :         }
     219             : };
     220             : 
     221           0 : PETRA.TransportPlan.prototype.releaseShip = function(ship)
     222             : {
     223           0 :         if (ship.getMetadata(PlayerID, "transporter") != this.ID)
     224             :         {
     225           0 :                 API3.warn(" Petra: try removing a transporter ship with " + ship.getMetadata(PlayerID, "transporter") +
     226             :                           " from " + this.ID + " and stance " + ship.getStance());
     227           0 :                 return;
     228             :         }
     229             : 
     230           0 :         let defaultStance = ship.get("UnitAI/DefaultStance");
     231           0 :         if (defaultStance)
     232           0 :                 ship.setStance(defaultStance);
     233             : 
     234           0 :         ship.setMetadata(PlayerID, "transporter", undefined);
     235           0 :         if (ship.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_SWITCH_TO_TRADER)
     236           0 :                 ship.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER);
     237             : };
     238             : 
     239           0 : PETRA.TransportPlan.prototype.releaseAll = function()
     240             : {
     241           0 :         for (let ship of this.ships.values())
     242           0 :                 this.releaseShip(ship);
     243             : 
     244           0 :         for (let ent of this.units.values())
     245             :         {
     246           0 :                 ent.setMetadata(PlayerID, "endPos", undefined);
     247           0 :                 ent.setMetadata(PlayerID, "onBoard", undefined);
     248           0 :                 ent.setMetadata(PlayerID, "transport", undefined);
     249             :                 // TODO if the index of the endPos of the entity is !=,
     250             :                 // require again another transport (we could need land-sea-land-sea-land)
     251             :         }
     252             : 
     253           0 :         this.transportShips.unregister();
     254           0 :         this.ships.unregister();
     255           0 :         this.units.unregister();
     256             : };
     257             : 
     258             : /** TODO not currently used ... to be fixed */
     259           0 : PETRA.TransportPlan.prototype.cancelTransport = function(gameState)
     260             : {
     261           0 :         let ent = this.units.toEntityArray()[0];
     262           0 :         let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
     263           0 :         if (!base.anchor || !base.anchor.position())
     264             :         {
     265           0 :                 for (const newbase of gameState.ai.HQ.baseManagers())
     266             :                 {
     267           0 :                         if (!newbase.anchor || !newbase.anchor.position())
     268           0 :                                 continue;
     269           0 :                         ent.setMetadata(PlayerID, "base", newbase.ID);
     270           0 :                         base = newbase;
     271           0 :                         break;
     272             :                 }
     273           0 :                 if (!base.anchor || !base.anchor.position())
     274           0 :                         return false;
     275           0 :                 this.units.forEach(unit => { unit.setMetadata(PlayerID, "base", base.ID); });
     276             :         }
     277           0 :         this.endIndex = this.startIndex;
     278           0 :         this.endPos = base.anchor.position();
     279           0 :         this.canceled = true;
     280           0 :         return true;
     281             : };
     282             : 
     283             : 
     284             : /**
     285             :  * Try to move on and then clear the plan.
     286             :  */
     287           0 : PETRA.TransportPlan.prototype.update = function(gameState)
     288             : {
     289           0 :         if (this.state === PETRA.TransportPlan.BOARDING)
     290           0 :                 this.onBoarding(gameState);
     291           0 :         else if (this.state === PETRA.TransportPlan.SAILING)
     292           0 :                 this.onSailing(gameState);
     293             : 
     294           0 :         return this.units.length;
     295             : };
     296             : 
     297           0 : PETRA.TransportPlan.prototype.onBoarding = function(gameState)
     298             : {
     299           0 :         let ready = true;
     300           0 :         let time = gameState.ai.elapsedTime;
     301           0 :         let shipTested = {};
     302             : 
     303           0 :         for (let ent of this.units.values())
     304             :         {
     305           0 :                 if (!ent.getMetadata(PlayerID, "onBoard"))
     306             :                 {
     307           0 :                         ready = false;
     308           0 :                         this.assignUnitToShip(gameState, ent);
     309           0 :                         if (ent.getMetadata(PlayerID, "onBoard"))
     310             :                         {
     311           0 :                                 let shipId = ent.getMetadata(PlayerID, "onBoard");
     312           0 :                                 let ship = gameState.getEntityById(shipId);
     313           0 :                                 if (!this.boardingPos[shipId])
     314             :                                 {
     315           0 :                                         this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.startIndex, this.sea, ent.position(), false);
     316           0 :                                         ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
     317           0 :                                         ship.setMetadata(PlayerID, "timeGarrison", time);
     318             :                                 }
     319           0 :                                 ent.garrison(ship);
     320           0 :                                 ent.setMetadata(PlayerID, "timeGarrison", time);
     321           0 :                                 ent.setMetadata(PlayerID, "posGarrison", ent.position());
     322             :                         }
     323             :                 }
     324           0 :                 else if (ent.getMetadata(PlayerID, "onBoard") != "onBoard" && !this.isOnBoard(ent))
     325             :                 {
     326           0 :                         ready = false;
     327           0 :                         let shipId = ent.getMetadata(PlayerID, "onBoard");
     328           0 :                         let ship = gameState.getEntityById(shipId);
     329           0 :                         if (!ship)    // the ship must have been destroyed
     330             :                         {
     331           0 :                                 ent.setMetadata(PlayerID, "onBoard", undefined);
     332           0 :                                 continue;
     333             :                         }
     334           0 :                         let distShip = API3.SquareVectorDistance(this.boardingPos[shipId], ship.position());
     335           0 :                         if (!shipTested[shipId] && distShip > this.boardingRange)
     336             :                         {
     337           0 :                                 shipTested[shipId] = true;
     338           0 :                                 let retry = false;
     339           0 :                                 let unitAIState = ship.unitAIState();
     340           0 :                                 if (unitAIState == "INDIVIDUAL.WALKING" ||
     341             :                                     unitAIState == "INDIVIDUAL.PICKUP.APPROACHING")
     342             :                                 {
     343           0 :                                         if (time - ship.getMetadata(PlayerID, "timeGarrison") > 2)
     344             :                                         {
     345           0 :                                                 let oldPos = ent.getMetadata(PlayerID, "posGarrison");
     346           0 :                                                 let newPos = ent.position();
     347           0 :                                                 if (oldPos[0] == newPos[0] && oldPos[1] == newPos[1])
     348           0 :                                                         retry = true;
     349           0 :                                                 ent.setMetadata(PlayerID, "posGarrison", newPos);
     350           0 :                                                 ent.setMetadata(PlayerID, "timeGarrison", time);
     351             :                                         }
     352             :                                 }
     353             : 
     354           0 :                                 else if (unitAIState != "INDIVIDUAL.PICKUP.LOADING" &&
     355             :                                          time - ship.getMetadata(PlayerID, "timeGarrison") > 5 ||
     356             :                                          time - ship.getMetadata(PlayerID, "timeGarrison") > 8)
     357             :                                 {
     358           0 :                                         retry = true;
     359           0 :                                         ent.setMetadata(PlayerID, "timeGarrison", time);
     360             :                                 }
     361             : 
     362           0 :                                 if (retry)
     363             :                                 {
     364           0 :                                         if (!this.nTry[shipId])
     365           0 :                                                 this.nTry[shipId] = 1;
     366             :                                         else
     367           0 :                                                 ++this.nTry[shipId];
     368           0 :                                         if (this.nTry[shipId] > 1)   // we must have been blocked by something ... try with another boarding point
     369             :                                         {
     370           0 :                                                 this.nTry[shipId] = 0;
     371           0 :                                                 if (this.debug > 1)
     372           0 :                                                         API3.warn("ship " + shipId + " new attempt for a landing point ");
     373           0 :                                                 this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.startIndex, this.sea, undefined, false);
     374             :                                         }
     375           0 :                                         ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
     376           0 :                                         ship.setMetadata(PlayerID, "timeGarrison", time);
     377             :                                 }
     378             :                         }
     379             : 
     380           0 :                         if (time - ent.getMetadata(PlayerID, "timeGarrison") > 2)
     381             :                         {
     382           0 :                                 let oldPos = ent.getMetadata(PlayerID, "posGarrison");
     383           0 :                                 let newPos = ent.position();
     384           0 :                                 if (oldPos[0] == newPos[0] && oldPos[1] == newPos[1])
     385             :                                 {
     386           0 :                                         if (distShip < this.boardingRange)   // looks like we are blocked ... try to go out of this trap
     387             :                                         {
     388           0 :                                                 if (!this.nTry[ent.id()])
     389           0 :                                                         this.nTry[ent.id()] = 1;
     390             :                                                 else
     391           0 :                                                         ++this.nTry[ent.id()];
     392           0 :                                                 if (this.nTry[ent.id()] > 5)
     393             :                                                 {
     394           0 :                                                         if (this.debug > 1)
     395           0 :                                                                 API3.warn("unit blocked, but no ways out of the trap ... destroy it");
     396           0 :                                                         this.resetUnit(gameState, ent);
     397           0 :                                                         ent.destroy();
     398           0 :                                                         continue;
     399             :                                                 }
     400           0 :                                                 if (this.nTry[ent.id()] > 1)
     401           0 :                                                         ent.moveToRange(newPos[0], newPos[1], 30, 35);
     402           0 :                                                 ent.garrison(ship, true);
     403             :                                         }
     404           0 :                                         else if (API3.SquareVectorDistance(this.boardingPos[shipId], newPos) > 225)
     405           0 :                                                 ent.moveToRange(this.boardingPos[shipId][0], this.boardingPos[shipId][1], 0, 15);
     406             :                                 }
     407             :                                 else
     408           0 :                                         this.nTry[ent.id()] = 0;
     409           0 :                                 ent.setMetadata(PlayerID, "timeGarrison", time);
     410           0 :                                 ent.setMetadata(PlayerID, "posGarrison", ent.position());
     411             :                         }
     412             :                 }
     413             :         }
     414             : 
     415           0 :         if (this.needSplit)
     416             :         {
     417           0 :                 gameState.ai.HQ.navalManager.splitTransport(gameState, this);
     418           0 :                 this.needSplit = undefined;
     419             :         }
     420             : 
     421           0 :         if (!ready)
     422           0 :                 return;
     423             : 
     424           0 :         for (let ship of this.ships.values())
     425             :         {
     426           0 :                 this.boardingPos[ship.id()] = undefined;
     427           0 :                 this.boardingPos[ship.id()] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, this.endPos, true);
     428           0 :                 ship.move(this.boardingPos[ship.id()][0], this.boardingPos[ship.id()][1]);
     429             :         }
     430           0 :         this.state = PETRA.TransportPlan.SAILING;
     431           0 :         this.nTry = {};
     432           0 :         this.unloaded = [];
     433           0 :         this.recovered = [];
     434             : };
     435             : 
     436             : /** tell if a unit is garrisoned in one of the ships of this plan, and update its metadata if yes */
     437           0 : PETRA.TransportPlan.prototype.isOnBoard = function(ent)
     438             : {
     439           0 :         for (let ship of this.transportShips.values())
     440             :         {
     441           0 :                 if (ship.garrisoned().indexOf(ent.id()) == -1)
     442           0 :                         continue;
     443           0 :                 ent.setMetadata(PlayerID, "onBoard", "onBoard");
     444           0 :                 return true;
     445             :         }
     446           0 :         return false;
     447             : };
     448             : 
     449             : /** when avoidEnnemy is true, we try to not board/unboard in ennemy territory */
     450           0 : PETRA.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, seaIndex, destination, avoidEnnemy)
     451             : {
     452           0 :         if (!gameState.ai.HQ.navalManager.landingZones[landIndex])
     453             :         {
     454           0 :                 API3.warn(" >>> no landing zone for land " + landIndex);
     455           0 :                 return destination;
     456             :         }
     457           0 :         else if (!gameState.ai.HQ.navalManager.landingZones[landIndex][seaIndex])
     458             :         {
     459           0 :                 API3.warn(" >>> no landing zone for land " + landIndex + " and sea " + seaIndex);
     460           0 :                 return destination;
     461             :         }
     462             : 
     463           0 :         let startPos = ship.position();
     464           0 :         let distmin = Math.min();
     465           0 :         let posmin = destination;
     466           0 :         let width = gameState.getPassabilityMap().width;
     467           0 :         let cell = gameState.getPassabilityMap().cellSize;
     468           0 :         let alliedDocks = gameState.getAllyStructures().filter(API3.Filters.and(
     469             :                 API3.Filters.byClass("Dock"), API3.Filters.byMetadata(PlayerID, "sea", seaIndex))).toEntityArray();
     470           0 :         for (let i of gameState.ai.HQ.navalManager.landingZones[landIndex][seaIndex])
     471             :         {
     472           0 :                 let pos = [i%width+0.5, Math.floor(i/width)+0.5];
     473           0 :                 pos = [cell*pos[0], cell*pos[1]];
     474           0 :                 let dist = API3.VectorDistance(startPos, pos);
     475           0 :                 if (destination)
     476           0 :                         dist += API3.VectorDistance(pos, destination);
     477           0 :                 if (avoidEnnemy)
     478             :                 {
     479           0 :                         let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(pos);
     480           0 :                         if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner))
     481           0 :                                 dist += 100000000;
     482             :                 }
     483             :                 // require a small distance between all ships of the transport plan to avoid path finder problems
     484             :                 // this is also used when the ship is blocked and we want to find a new boarding point
     485           0 :                 for (let shipId in this.boardingPos)
     486           0 :                         if (this.boardingPos[shipId] !== undefined &&
     487             :                             API3.SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange)
     488           0 :                                 dist += 1000000;
     489             :                 // and not too near our allied docks to not disturb naval traffic
     490             :                 let distSquare;
     491           0 :                 for (let dock of alliedDocks)
     492             :                 {
     493           0 :                         if (dock.foundationProgress() !== undefined)
     494           0 :                                 distSquare = 900;
     495             :                         else
     496           0 :                                 distSquare = 4900;
     497           0 :                         let dockDist = API3.SquareVectorDistance(dock.position(), pos);
     498           0 :                         if (dockDist < distSquare)
     499           0 :                                 dist += 100000 * (distSquare - dockDist) / distSquare;
     500             :                 }
     501           0 :                 if (dist > distmin)
     502           0 :                         continue;
     503           0 :                 distmin = dist;
     504           0 :                 posmin = pos;
     505             :         }
     506             :         // We should always have either destination or the previous boardingPos defined
     507             :         // so let's return this value if everything failed
     508           0 :         if (!posmin && this.boardingPos[ship.id()])
     509           0 :                 posmin = this.boardingPos[ship.id()];
     510           0 :         return posmin;
     511             : };
     512             : 
     513           0 : PETRA.TransportPlan.prototype.onSailing = function(gameState)
     514             : {
     515             :         // Check that the units recovered on the previous turn have been reloaded
     516           0 :         for (let recov of this.recovered)
     517             :         {
     518           0 :                 let ent = gameState.getEntityById(recov.entId);
     519           0 :                 if (!ent)  // entity destroyed
     520           0 :                         continue;
     521           0 :                 if (!ent.position())  // reloading succeeded ... move a bit the ship before trying again
     522             :                 {
     523           0 :                         let ship = gameState.getEntityById(recov.shipId);
     524           0 :                         if (ship)
     525           0 :                                 ship.moveApart(recov.entPos, 15);
     526           0 :                         continue;
     527             :                 }
     528           0 :                 if (this.debug > 1)
     529           0 :                         API3.warn(">>> transport " + this.ID + " reloading failed ... <<<");
     530             :                 // destroy the unit if inaccessible otherwise leave it there
     531           0 :                 let index = PETRA.getLandAccess(gameState, ent);
     532           0 :                 if (gameState.ai.HQ.landRegions[index])
     533             :                 {
     534           0 :                         if (this.debug > 1)
     535           0 :                                 API3.warn(" recovered entity kept " + ent.id());
     536           0 :                         this.resetUnit(gameState, ent);
     537             :                         // TODO we should not destroy it, but now the unit could still be reloaded on the next turn
     538             :                         // and mess everything
     539           0 :                         ent.destroy();
     540             :                 }
     541             :                 else
     542             :                 {
     543           0 :                         if (this.debug > 1)
     544           0 :                                 API3.warn("recovered entity destroyed " + ent.id());
     545           0 :                         this.resetUnit(gameState, ent);
     546           0 :                         ent.destroy();
     547             :                 }
     548             :         }
     549           0 :         this.recovered = [];
     550             : 
     551             :         // Check that the units unloaded on the previous turn have been really unloaded and in the right position
     552           0 :         let shipsToMove = {};
     553           0 :         for (let entId of this.unloaded)
     554             :         {
     555           0 :                 let ent = gameState.getEntityById(entId);
     556           0 :                 if (!ent)  // entity destroyed
     557           0 :                         continue;
     558           0 :                 else if (!ent.position())  // unloading failed
     559             :                 {
     560           0 :                         let ship = gameState.getEntityById(ent.getMetadata(PlayerID, "onBoard"));
     561           0 :                         if (ship)
     562             :                         {
     563           0 :                                 if (ship.garrisoned().indexOf(entId) != -1)
     564           0 :                                         ent.setMetadata(PlayerID, "onBoard", "onBoard");
     565             :                                 else
     566             :                                 {
     567           0 :                                         API3.warn("Petra transportPlan problem: unit not on ship without position ???");
     568           0 :                                         this.resetUnit(gameState, ent);
     569           0 :                                         ent.destroy();
     570             :                                 }
     571             :                         }
     572             :                         else
     573             :                         {
     574           0 :                                 API3.warn("Petra transportPlan problem: unit on ship, but no ship ???");
     575           0 :                                 this.resetUnit(gameState, ent);
     576           0 :                                 ent.destroy();
     577             :                         }
     578             :                 }
     579           0 :                 else if (PETRA.getLandAccess(gameState, ent) != this.endIndex)
     580             :                 {
     581             :                         // unit unloaded on a wrong region - try to regarrison it and move a bit the ship
     582           0 :                         if (this.debug > 1)
     583           0 :                                 API3.warn(">>> unit unloaded on a wrong region ! try to garrison it again <<<");
     584           0 :                         let ship = gameState.getEntityById(ent.getMetadata(PlayerID, "onBoard"));
     585           0 :                         if (ship && !this.canceled)
     586             :                         {
     587           0 :                                 shipsToMove[ship.id()] = ship;
     588           0 :                                 this.recovered.push({ "entId": ent.id(), "entPos": ent.position(), "shipId": ship.id() });
     589           0 :                                 ent.garrison(ship);
     590           0 :                                 ent.setMetadata(PlayerID, "onBoard", "onBoard");
     591             :                         }
     592             :                         else
     593             :                         {
     594           0 :                                 if (this.debug > 1)
     595           0 :                                         API3.warn("no way ... we destroy it");
     596           0 :                                 this.resetUnit(gameState, ent);
     597           0 :                                 ent.destroy();
     598             :                         }
     599             :                 }
     600             :                 else
     601             :                 {
     602             :                         // And make some room for other units
     603           0 :                         let pos = ent.position();
     604           0 :                         let goal = ent.getMetadata(PlayerID, "endPos");
     605           0 :                         let dist = goal ? API3.VectorDistance(pos, goal) : 0;
     606           0 :                         if (dist > 30)
     607           0 :                                 ent.moveToRange(goal[0], goal[1], dist-25, dist-20);
     608             :                         else
     609           0 :                                 ent.moveToRange(pos[0], pos[1], 20, 25);
     610           0 :                         ent.setMetadata(PlayerID, "transport", undefined);
     611           0 :                         ent.setMetadata(PlayerID, "onBoard", undefined);
     612           0 :                         ent.setMetadata(PlayerID, "endPos", undefined);
     613             :                 }
     614             :         }
     615           0 :         for (let shipId in shipsToMove)
     616             :         {
     617           0 :                 this.boardingPos[shipId] = this.getBoardingPos(gameState, shipsToMove[shipId], this.endIndex, this.sea, this.endPos, true);
     618           0 :                 shipsToMove[shipId].move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
     619             :         }
     620           0 :         this.unloaded = [];
     621             : 
     622           0 :         if (this.canceled)
     623             :         {
     624           0 :                 for (let ship of this.ships.values())
     625             :                 {
     626           0 :                         this.boardingPos[ship.id()] = undefined;
     627           0 :                         this.boardingPos[ship.id()] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, this.endPos, true);
     628           0 :                         ship.move(this.boardingPos[ship.id()][0], this.boardingPos[ship.id()][1]);
     629             :                 }
     630           0 :                 this.canceled = undefined;
     631             :         }
     632             : 
     633           0 :         for (let ship of this.transportShips.values())
     634             :         {
     635           0 :                 if (ship.unitAIState() == "INDIVIDUAL.WALKING")
     636           0 :                         continue;
     637           0 :                 let shipId = ship.id();
     638           0 :                 let dist = API3.SquareVectorDistance(ship.position(), this.boardingPos[shipId]);
     639           0 :                 let remaining = 0;
     640           0 :                 for (let entId of ship.garrisoned())
     641             :                 {
     642           0 :                         let ent = gameState.getEntityById(entId);
     643           0 :                         if (!ent.getMetadata(PlayerID, "transport"))
     644           0 :                                 continue;
     645           0 :                         remaining++;
     646           0 :                         if (dist < 625)
     647             :                         {
     648           0 :                                 ship.unload(entId);
     649           0 :                                 this.unloaded.push(entId);
     650           0 :                                 ent.setMetadata(PlayerID, "onBoard", shipId);
     651             :                         }
     652             :                 }
     653             : 
     654           0 :                 let recovering = 0;
     655           0 :                 for (let recov of this.recovered)
     656           0 :                         if (recov.shipId == shipId)
     657           0 :                                 recovering++;
     658             : 
     659           0 :                 if (!remaining && !recovering)   // when empty, release the ship and move apart to leave room for other ships. TODO fight
     660             :                 {
     661           0 :                         ship.moveApart(this.boardingPos[shipId], 30);
     662           0 :                         this.releaseShip(ship);
     663           0 :                         continue;
     664             :                 }
     665           0 :                 if (dist > this.boardingRange)
     666             :                 {
     667           0 :                         if (!this.nTry[shipId])
     668           0 :                                 this.nTry[shipId] = 1;
     669             :                         else
     670           0 :                                 ++this.nTry[shipId];
     671           0 :                         if (this.nTry[shipId] > 2)   // we must have been blocked by something ... try with another boarding point
     672             :                         {
     673           0 :                                 this.nTry[shipId] = 0;
     674           0 :                                 if (this.debug > 1)
     675           0 :                                         API3.warn(shipId + " new attempt for a landing point ");
     676           0 :                                 this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, undefined, true);
     677             :                         }
     678           0 :                         ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
     679             :                 }
     680             :         }
     681             : };
     682             : 
     683           0 : PETRA.TransportPlan.prototype.resetUnit = function(gameState, ent)
     684             : {
     685           0 :         ent.setMetadata(PlayerID, "transport", undefined);
     686           0 :         ent.setMetadata(PlayerID, "onBoard", undefined);
     687           0 :         ent.setMetadata(PlayerID, "endPos", undefined);
     688             :         // if from an army or attack, remove it
     689           0 :         if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") >= 0)
     690             :         {
     691           0 :                 let attackPlan = gameState.ai.HQ.attackManager.getPlan(ent.getMetadata(PlayerID, "plan"));
     692           0 :                 if (attackPlan)
     693           0 :                         attackPlan.removeUnit(ent, true);
     694             :         }
     695           0 :         if (ent.getMetadata(PlayerID, "PartOfArmy"))
     696             :         {
     697           0 :                 let army = gameState.ai.HQ.defenseManager.getArmy(ent.getMetadata(PlayerID, "PartOfArmy"));
     698           0 :                 if (army)
     699           0 :                         army.removeOwn(gameState, ent.id());
     700             :         }
     701             : };
     702             : 
     703           0 : PETRA.TransportPlan.prototype.Serialize = function()
     704             : {
     705           0 :         return {
     706             :                 "ID": this.ID,
     707             :                 "flotilla": this.flotilla,
     708             :                 "endPos": this.endPos,
     709             :                 "endIndex": this.endIndex,
     710             :                 "startIndex": this.startIndex,
     711             :                 "sea": this.sea,
     712             :                 "state": this.state,
     713             :                 "boardingPos": this.boardingPos,
     714             :                 "needTransportShips": this.needTransportShips,
     715             :                 "nTry": this.nTry,
     716             :                 "canceled": this.canceled,
     717             :                 "unloaded": this.unloaded,
     718             :                 "recovered": this.recovered
     719             :         };
     720             : };
     721             : 
     722           0 : PETRA.TransportPlan.prototype.Deserialize = function(data)
     723             : {
     724           0 :         for (let key in data)
     725           0 :                 this[key] = data[key];
     726             : 
     727           0 :         this.failed = false;
     728             : };

Generated by: LCOV version 1.14