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

          Line data    Source code
       1             : /**
       2             :  * Defines a construction plan, ie a building.
       3             :  * We'll try to fing a good position if non has been provided
       4             :  */
       5             : 
       6           0 : PETRA.ConstructionPlan = function(gameState, type, metadata, position)
       7             : {
       8           0 :         if (!PETRA.QueuePlan.call(this, gameState, type, metadata))
       9           0 :                 return false;
      10             : 
      11           0 :         this.position = position ? position : 0;
      12             : 
      13           0 :         this.category = "building";
      14             : 
      15           0 :         return true;
      16             : };
      17             : 
      18           0 : PETRA.ConstructionPlan.prototype = Object.create(PETRA.QueuePlan.prototype);
      19             : 
      20           0 : PETRA.ConstructionPlan.prototype.canStart = function(gameState)
      21             : {
      22           0 :         if (gameState.ai.HQ.turnCache.buildingBuilt)   // do not start another building if already one this turn
      23           0 :                 return false;
      24             : 
      25           0 :         if (!this.isGo(gameState))
      26           0 :                 return false;
      27             : 
      28           0 :         if (!this.template.available(gameState))
      29           0 :                 return false;
      30             : 
      31           0 :         return gameState.ai.HQ.buildManager.hasBuilder(this.type);
      32             : };
      33             : 
      34           0 : PETRA.ConstructionPlan.prototype.start = function(gameState)
      35             : {
      36           0 :         Engine.ProfileStart("Building construction start");
      37             : 
      38             :         // We don't care which builder we assign, since they won't actually do
      39             :         // the building themselves - all we care about is that there is at least
      40             :         // one unit that can start the foundation (should always be the case here).
      41           0 :         let builder = gameState.findBuilder(this.type);
      42           0 :         if (!builder)
      43             :         {
      44           0 :                 API3.warn("petra error: builder not found when starting construction.");
      45           0 :                 Engine.ProfileStop();
      46           0 :                 return;
      47             :         }
      48             : 
      49           0 :         let pos = this.findGoodPosition(gameState);
      50           0 :         if (!pos)
      51             :         {
      52           0 :                 gameState.ai.HQ.buildManager.setUnbuildable(gameState, this.type, 90, "room");
      53           0 :                 Engine.ProfileStop();
      54           0 :                 return;
      55             :         }
      56             : 
      57           0 :         if (this.metadata && this.metadata.expectedGain && (!this.template.hasClass("Market") ||
      58             :             gameState.getOwnEntitiesByClass("Market", true).hasEntities()))
      59             :         {
      60             :                 // Check if this Market is still worth building (others may have been built making it useless).
      61           0 :                 let tradeManager = gameState.ai.HQ.tradeManager;
      62           0 :                 tradeManager.checkRoutes(gameState);
      63           0 :                 if (!tradeManager.isNewMarketWorth(this.metadata.expectedGain))
      64             :                 {
      65           0 :                         Engine.ProfileStop();
      66           0 :                         return;
      67             :                 }
      68             :         }
      69           0 :         gameState.ai.HQ.turnCache.buildingBuilt = true;
      70             : 
      71           0 :         if (this.metadata === undefined)
      72           0 :                 this.metadata = { "base": pos.base };
      73           0 :         else if (this.metadata.base === undefined)
      74           0 :                 this.metadata.base = pos.base;
      75             : 
      76           0 :         if (pos.access)
      77           0 :                 this.metadata.access = pos.access;   // needed for Docks whose position is on water
      78             :         else
      79           0 :                 this.metadata.access = gameState.ai.accessibility.getAccessValue([pos.x, pos.z]);
      80             : 
      81           0 :         if (this.template.buildPlacementType() == "shore")
      82             :         {
      83             :                 // adjust a bit the position if needed
      84           0 :                 let cosa = Math.cos(pos.angle);
      85           0 :                 let sina = Math.sin(pos.angle);
      86           0 :                 let shiftMax = gameState.ai.HQ.territoryMap.cellSize;
      87           0 :                 for (let shift = 0; shift <= shiftMax; shift += 2)
      88             :                 {
      89           0 :                         builder.construct(this.type, pos.x-shift*sina, pos.z-shift*cosa, pos.angle, this.metadata);
      90           0 :                         if (shift > 0)
      91           0 :                                 builder.construct(this.type, pos.x+shift*sina, pos.z+shift*cosa, pos.angle, this.metadata);
      92             :                 }
      93             :         }
      94           0 :         else if (pos.xx === undefined || pos.x == pos.xx && pos.z == pos.zz)
      95           0 :                 builder.construct(this.type, pos.x, pos.z, pos.angle, this.metadata);
      96             :         else // try with the lowest, move towards us unless we're same
      97             :         {
      98           0 :                 for (let step = 0; step <= 1; step += 0.2)
      99           0 :                         builder.construct(this.type, step*pos.x + (1-step)*pos.xx, step*pos.z + (1-step)*pos.zz,
     100             :                                 pos.angle, this.metadata);
     101             :         }
     102           0 :         this.onStart(gameState);
     103           0 :         Engine.ProfileStop();
     104             : 
     105           0 :         if (this.metadata && this.metadata.proximity)
     106           0 :                 gameState.ai.HQ.navalManager.createTransportIfNeeded(gameState, this.metadata.proximity, [pos.x, pos.z], this.metadata.access);
     107             : };
     108             : 
     109           0 : PETRA.ConstructionPlan.prototype.findGoodPosition = function(gameState)
     110             : {
     111           0 :         let template = this.template;
     112             : 
     113           0 :         if (template.buildPlacementType() == "shore")
     114           0 :                 return this.findDockPosition(gameState);
     115             : 
     116           0 :         let HQ = gameState.ai.HQ;
     117           0 :         if (template.hasClass("Storehouse") && this.metadata && this.metadata.base)
     118             :         {
     119             :                 // recompute the best dropsite location in case some conditions have changed
     120           0 :                 let base = HQ.getBaseByID(this.metadata.base);
     121           0 :                 let type = this.metadata.type ? this.metadata.type : "wood";
     122           0 :                 const newpos = base.findBestDropsiteLocation(gameState, type, template._templateName);
     123           0 :                 if (newpos && newpos.quality > 0)
     124             :                 {
     125           0 :                         let pos = newpos.pos;
     126           0 :                         return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": this.metadata.base };
     127             :                 }
     128             :         }
     129             : 
     130           0 :         if (!this.position)
     131             :         {
     132           0 :                 if (template.hasClass("CivCentre"))
     133             :                 {
     134             :                         let pos;
     135           0 :                         if (this.metadata && this.metadata.resource)
     136             :                         {
     137           0 :                                 let proximity = this.metadata.proximity ? this.metadata.proximity : undefined;
     138           0 :                                 pos = HQ.findEconomicCCLocation(gameState, template, this.metadata.resource, proximity);
     139             :                         }
     140             :                         else
     141           0 :                                 pos = HQ.findStrategicCCLocation(gameState, template);
     142             : 
     143           0 :                         if (pos)
     144           0 :                                 return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": 0 };
     145             :                         // No possible location, try to build instead a dock in a not-enemy island
     146           0 :                         let templateName = gameState.applyCiv("structures/{civ}/dock");
     147           0 :                         if (gameState.ai.HQ.canBuild(gameState, templateName) && !gameState.isTemplateDisabled(templateName))
     148             :                         {
     149           0 :                                 template = gameState.getTemplate(templateName);
     150           0 :                                 if (template && gameState.getResources().canAfford(new API3.Resources(template.cost())))
     151           0 :                                         this.buildOverseaDock(gameState, template);
     152             :                         }
     153           0 :                         return false;
     154             :                 }
     155           0 :                 else if (template.hasClasses(["Tower", "Fortress", "ArmyCamp"]))
     156             :                 {
     157           0 :                         let pos = HQ.findDefensiveLocation(gameState, template);
     158           0 :                         if (pos)
     159           0 :                                 return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": pos[2] };
     160             :                         // if this fortress is our first one, just try the standard placement
     161           0 :                         if (!template.hasClass("Fortress") || gameState.getOwnEntitiesByClass("Fortress", true).hasEntities())
     162           0 :                                 return false;
     163             :                 }
     164           0 :                 else if (template.hasClass("Market")) // Docks are done before.
     165             :                 {
     166           0 :                         let pos = HQ.findMarketLocation(gameState, template);
     167           0 :                         if (pos && pos[2] > 0)
     168             :                         {
     169           0 :                                 if (!this.metadata)
     170           0 :                                         this.metadata = {};
     171           0 :                                 this.metadata.expectedGain = pos[3];
     172           0 :                                 return { "x": pos[0], "z": pos[1], "angle": 3*Math.PI/4, "base": pos[2] };
     173             :                         }
     174           0 :                         else if (!pos)
     175           0 :                                 return false;
     176             :                 }
     177             :         }
     178             : 
     179             :         // Compute each tile's closeness to friendly structures:
     180             : 
     181           0 :         let placement = new API3.Map(gameState.sharedScript, "territory");
     182           0 :         let cellSize = placement.cellSize; // size of each tile
     183             : 
     184           0 :         let alreadyHasHouses = false;
     185             : 
     186           0 :         if (this.position)      // If a position was specified then place the building as close to it as possible
     187             :         {
     188           0 :                 let x = Math.floor(this.position[0] / cellSize);
     189           0 :                 let z = Math.floor(this.position[1] / cellSize);
     190           0 :                 placement.addInfluence(x, z, 255);
     191             :         }
     192             :         else    // No position was specified so try and find a sensible place to build
     193             :         {
     194             :                 // give a small > 0 level as the result of addInfluence is constrained to be > 0
     195             :                 // if we really need houses (i.e. Phasing without enough village building), do not apply these constraints
     196           0 :                 if (this.metadata && this.metadata.base !== undefined)
     197             :                 {
     198           0 :                         let base = this.metadata.base;
     199           0 :                         for (let j = 0; j < placement.map.length; ++j)
     200           0 :                                 if (HQ.baseAtIndex(j) == base)
     201           0 :                                         placement.set(j, 45);
     202             :                 }
     203             :                 else
     204             :                 {
     205           0 :                         for (let j = 0; j < placement.map.length; ++j)
     206           0 :                                 if (HQ.baseAtIndex(j) != 0)
     207           0 :                                         placement.set(j, 45);
     208             :                 }
     209             : 
     210           0 :                 if (!HQ.requireHouses || !template.hasClass("House"))
     211             :                 {
     212           0 :                         gameState.getOwnStructures().forEach(function(ent) {
     213           0 :                                 let pos = ent.position();
     214           0 :                                 let x = Math.round(pos[0] / cellSize);
     215           0 :                                 let z = Math.round(pos[1] / cellSize);
     216             : 
     217           0 :                                 let struct = PETRA.getBuiltEntity(gameState, ent);
     218           0 :                                 if (struct.resourceDropsiteTypes() && struct.resourceDropsiteTypes().indexOf("food") != -1)
     219             :                                 {
     220           0 :                                         if (template.hasClasses(["Field", "Corral"]))
     221           0 :                                                 placement.addInfluence(x, z, 80 / cellSize, 50);
     222             :                                         else // If this is not a field add a negative influence because we want to leave this area for fields
     223           0 :                                                 placement.addInfluence(x, z, 80 / cellSize, -20);
     224             :                                 }
     225           0 :                                 else if (template.hasClass("House"))
     226             :                                 {
     227           0 :                                         if (ent.hasClass("House"))
     228             :                                         {
     229           0 :                                                 placement.addInfluence(x, z, 60 / cellSize, 40);    // houses are close to other houses
     230           0 :                                                 alreadyHasHouses = true;
     231             :                                         }
     232           0 :                                         else if (ent.hasClasses(["Gate", "!Wall"]))
     233           0 :                                                 placement.addInfluence(x, z, 60 / cellSize, -40);   // and further away from other stuffs
     234             :                                 }
     235           0 :                                 else if (template.hasClass("Farmstead") && !ent.hasClasses(["Field", "Corral"]) &&
     236             :                                         ent.hasClasses(["Gate", "!Wall"]))
     237           0 :                                         placement.addInfluence(x, z, 100 / cellSize, -25);       // move farmsteads away to make room (Wall test needed for iber)
     238           0 :                                 else if (template.hasClass("GarrisonFortress") && ent.hasClass("House"))
     239           0 :                                         placement.addInfluence(x, z, 120 / cellSize, -50);
     240           0 :                                 else if (template.hasClass("Military"))
     241           0 :                                         placement.addInfluence(x, z, 40 / cellSize, -40);
     242           0 :                                 else if (template.genericName() == "Rotary Mill" && ent.hasClass("Field"))
     243           0 :                                         placement.addInfluence(x, z, 60 / cellSize, 40);
     244             :                         });
     245             :                 }
     246           0 :                 if (template.hasClass("Farmstead"))
     247             :                 {
     248           0 :                         for (let j = 0; j < placement.map.length; ++j)
     249             :                         {
     250           0 :                                 let value = placement.map[j] - gameState.sharedScript.resourceMaps.wood.map[j]/3;
     251           0 :                                 if (HQ.borderMap.map[j] & PETRA.fullBorder_Mask)
     252           0 :                                         value /= 2;     // we need space around farmstead, so disfavor map border
     253           0 :                                 placement.set(j, value);
     254             :                         }
     255             :                 }
     256             :         }
     257             : 
     258             :         // Requires to be inside our territory, and inside our base territory if required
     259             :         // and if our first market, put it on border if possible to maximize distance with next Market.
     260           0 :         let favorBorder = template.hasClass("Market");
     261           0 :         let disfavorBorder = gameState.currentPhase() > 1 && !template.hasDefensiveFire();
     262           0 :         let favoredBase = this.metadata && (this.metadata.favoredBase ||
     263             :                          (this.metadata.militaryBase ? HQ.findBestBaseForMilitary(gameState) : undefined));
     264           0 :         if (this.metadata && this.metadata.base !== undefined)
     265             :         {
     266           0 :                 let base = this.metadata.base;
     267           0 :                 for (let j = 0; j < placement.map.length; ++j)
     268             :                 {
     269           0 :                         if (HQ.baseAtIndex(j) != base)
     270           0 :                                 placement.map[j] = 0;
     271           0 :                         else if (placement.map[j] > 0)
     272             :                         {
     273           0 :                                 if (favorBorder && HQ.borderMap.map[j] & PETRA.border_Mask)
     274           0 :                                         placement.set(j, placement.map[j] + 50);
     275           0 :                                 else if (disfavorBorder && !(HQ.borderMap.map[j] & PETRA.fullBorder_Mask))
     276           0 :                                         placement.set(j, placement.map[j] + 10);
     277             : 
     278           0 :                                 let x = (j % placement.width + 0.5) * cellSize;
     279           0 :                                 let z = (Math.floor(j / placement.width) + 0.5) * cellSize;
     280           0 :                                 if (HQ.isNearInvadingArmy([x, z]))
     281           0 :                                         placement.map[j] = 0;
     282             :                         }
     283             :                 }
     284             :         }
     285             :         else
     286             :         {
     287           0 :                 for (let j = 0; j < placement.map.length; ++j)
     288             :                 {
     289           0 :                         if (HQ.baseAtIndex(j) == 0)
     290           0 :                                 placement.map[j] = 0;
     291           0 :                         else if (placement.map[j] > 0)
     292             :                         {
     293           0 :                                 if (favorBorder && HQ.borderMap.map[j] & PETRA.border_Mask)
     294           0 :                                         placement.set(j, placement.map[j] + 50);
     295           0 :                                 else if (disfavorBorder && !(HQ.borderMap.map[j] & PETRA.fullBorder_Mask))
     296           0 :                                         placement.set(j, placement.map[j] + 10);
     297             : 
     298           0 :                                 let x = (j % placement.width + 0.5) * cellSize;
     299           0 :                                 let z = (Math.floor(j / placement.width) + 0.5) * cellSize;
     300           0 :                                 if (HQ.isNearInvadingArmy([x, z]))
     301           0 :                                         placement.map[j] = 0;
     302           0 :                                 else if (favoredBase && HQ.baseAtIndex(j) == favoredBase)
     303           0 :                                         placement.set(j, placement.map[j] + 100);
     304             :                         }
     305             :                 }
     306             :         }
     307             : 
     308             :         // Find the best non-obstructed:
     309             :         // Find target building's approximate obstruction radius, and expand by a bit to make sure we're not too close,
     310             :         // this allows room for units to walk between buildings.
     311             :         // note: not for houses and dropsites who ought to be closer to either each other or a resource.
     312             :         // also not for fields who can be stacked quite a bit
     313             : 
     314           0 :         let obstructions = PETRA.createObstructionMap(gameState, 0, template);
     315             :         // obstructions.dumpIm(template.buildPlacementType() + "_obstructions.png");
     316             : 
     317           0 :         let radius = 0;
     318           0 :         if (template.hasClasses(["Fortress", "Arsenal"]) ||
     319             :                 this.type == gameState.applyCiv("structures/{civ}/elephant_stable"))
     320           0 :                 radius = Math.floor((template.obstructionRadius().max + 8) / obstructions.cellSize);
     321           0 :         else if (template.resourceDropsiteTypes() === undefined && !template.hasClasses(["House", "Field", "Market"]))
     322           0 :                 radius = Math.ceil((template.obstructionRadius().max + 4) / obstructions.cellSize);
     323             :         else
     324           0 :                 radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize);
     325             : 
     326             :         let bestTile;
     327           0 :         if (template.hasClass("House") && !alreadyHasHouses)
     328             :         {
     329             :                 // try to get some space to place several houses first
     330           0 :                 bestTile = placement.findBestTile(3*radius, obstructions);
     331           0 :                 if (!bestTile.val)
     332           0 :                         bestTile = undefined;
     333             :         }
     334             : 
     335           0 :         if (!bestTile)
     336           0 :                 bestTile = placement.findBestTile(radius, obstructions);
     337             : 
     338           0 :         if (!bestTile.val)
     339           0 :                 return false;
     340             : 
     341           0 :         let bestIdx = bestTile.idx;
     342             : 
     343           0 :         let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
     344           0 :         let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
     345             : 
     346           0 :         let territorypos = placement.gamePosToMapPos([x, z]);
     347           0 :         let territoryIndex = territorypos[0] + territorypos[1]*placement.width;
     348             :         // default angle = 3*Math.PI/4;
     349           0 :         return { "x": x, "z": z, "angle": 3*Math.PI/4, "base": HQ.baseAtIndex(territoryIndex) };
     350             : };
     351             : 
     352             : /**
     353             :  * Placement of buildings with Dock build category
     354             :  * metadata.proximity is defined when first dock without any territory
     355             :  * => we try to minimize distance from our current point
     356             :  * metadata.oversea is defined for dock in oversea islands
     357             :  * => we try to maximize distance to our current docks (for trade)
     358             :  * otherwise standard dock on an island where we already have a cc
     359             :  * => we try not to be too far from our territory
     360             :  * In all cases, we add a bonus for nearby resources, and when a large extend of water in front ot it.
     361             :  */
     362           0 : PETRA.ConstructionPlan.prototype.findDockPosition = function(gameState)
     363             : {
     364           0 :         let template = this.template;
     365           0 :         let territoryMap = gameState.ai.HQ.territoryMap;
     366             : 
     367           0 :         let obstructions = PETRA.createObstructionMap(gameState, 0, template);
     368             :         // obstructions.dumpIm(template.buildPlacementType() + "_obstructions.png");
     369             : 
     370             :         let bestIdx;
     371             :         let bestJdx;
     372             :         let bestAngle;
     373             :         let bestLand;
     374             :         let bestWater;
     375           0 :         let bestVal = -1;
     376           0 :         let navalPassMap = gameState.ai.accessibility.navalPassMap;
     377             : 
     378           0 :         let width = gameState.ai.HQ.territoryMap.width;
     379           0 :         let cellSize = gameState.ai.HQ.territoryMap.cellSize;
     380             : 
     381           0 :         let nbShips = gameState.ai.HQ.navalManager.transportShips.length;
     382           0 :         let wantedLand = this.metadata && this.metadata.land ? this.metadata.land : null;
     383           0 :         let wantedSea = this.metadata && this.metadata.sea ? this.metadata.sea : null;
     384           0 :         let proxyAccess = this.metadata && this.metadata.proximity ? gameState.ai.accessibility.getAccessValue(this.metadata.proximity) : null;
     385           0 :         let oversea = this.metadata && this.metadata.oversea ? this.metadata.oversea : null;
     386           0 :         if (nbShips == 0 && proxyAccess && proxyAccess > 1)
     387             :         {
     388           0 :                 wantedLand = {};
     389           0 :                 wantedLand[proxyAccess] = true;
     390             :         }
     391           0 :         let dropsiteTypes = template.resourceDropsiteTypes();
     392           0 :         let radius = Math.ceil(template.obstructionRadius().max / obstructions.cellSize);
     393             : 
     394           0 :         let halfSize = 0;    // used for dock angle
     395           0 :         let halfDepth = 0;   // used by checkPlacement
     396           0 :         let halfWidth = 0;   // used by checkPlacement
     397           0 :         if (template.get("Footprint/Square"))
     398             :         {
     399           0 :                 halfSize = Math.max(+template.get("Footprint/Square/@depth"), +template.get("Footprint/Square/@width")) / 2;
     400           0 :                 halfDepth = +template.get("Footprint/Square/@depth") / 2;
     401           0 :                 halfWidth = +template.get("Footprint/Square/@width") / 2;
     402             :         }
     403           0 :         else if (template.get("Footprint/Circle"))
     404             :         {
     405           0 :                 halfSize = +template.get("Footprint/Circle/@radius");
     406           0 :                 halfDepth = halfSize;
     407           0 :                 halfWidth = halfSize;
     408             :         }
     409             : 
     410             :         // res is a measure of the amount of resources around, and maxRes is the max value taken into account
     411             :         // water is a measure of the water space around, and maxWater is the max value that can be returned by checkDockPlacement
     412           0 :         const maxRes = 10;
     413           0 :         const maxWater = 16;
     414           0 :         let ccEnts = oversea ? gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre")) : null;
     415           0 :         let docks = oversea ? gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")) : null;
     416             :         // Normalisation factors (only guessed, no attempt to optimize them)
     417           0 :         let factor = proxyAccess ? 1 : oversea ? 0.2 : 40;
     418           0 :         for (let j = 0; j < territoryMap.length; ++j)
     419             :         {
     420           0 :                 if (!this.isDockLocation(gameState, j, halfDepth, wantedLand, wantedSea))
     421           0 :                         continue;
     422           0 :                 let score = 0;
     423           0 :                 if (!proxyAccess && !oversea)
     424             :                 {
     425             :                         // if not in our (or allied) territory, we do not want it too far to be able to defend it
     426           0 :                         score = this.getFrontierProximity(gameState, j);
     427           0 :                         if (score > 4)
     428           0 :                                 continue;
     429           0 :                         score *= factor;
     430             :                 }
     431           0 :                 let i = territoryMap.getNonObstructedTile(j, radius, obstructions);
     432           0 :                 if (i < 0)
     433           0 :                         continue;
     434           0 :                 if (wantedSea && navalPassMap[i] != wantedSea)
     435           0 :                         continue;
     436             : 
     437           0 :                 let res = dropsiteTypes ? Math.min(maxRes, this.getResourcesAround(gameState, dropsiteTypes, j, 80)) : maxRes;
     438           0 :                 let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
     439             : 
     440             :                 // If proximity is given, we look for the nearest point
     441           0 :                 if (proxyAccess)
     442           0 :                         score = API3.VectorDistance(this.metadata.proximity, pos);
     443             : 
     444             :                 // Bonus for resources
     445           0 :                 score += 20 * (maxRes - res);
     446             : 
     447           0 :                 if (oversea)
     448             :                 {
     449             :                         // Not much farther to one of our cc than to enemy ones
     450             :                         let enemyDist;
     451             :                         let ownDist;
     452           0 :                         for (let cc of ccEnts.values())
     453             :                         {
     454           0 :                                 let owner = cc.owner();
     455           0 :                                 if (owner != PlayerID && !gameState.isPlayerEnemy(owner))
     456           0 :                                         continue;
     457           0 :                                 let dist = API3.SquareVectorDistance(pos, cc.position());
     458           0 :                                 if (owner == PlayerID && (!ownDist || dist < ownDist))
     459           0 :                                         ownDist = dist;
     460           0 :                                 if (gameState.isPlayerEnemy(owner) && (!enemyDist || dist < enemyDist))
     461           0 :                                         enemyDist = dist;
     462             :                         }
     463           0 :                         if (ownDist && enemyDist && enemyDist < 0.5 * ownDist)
     464           0 :                                 continue;
     465             : 
     466             :                         // And maximize distance for trade.
     467           0 :                         let dockDist = 0;
     468           0 :                         for (let dock of docks.values())
     469             :                         {
     470           0 :                                 if (PETRA.getSeaAccess(gameState, dock) != navalPassMap[i])
     471           0 :                                         continue;
     472           0 :                                 let dist = API3.SquareVectorDistance(pos, dock.position());
     473           0 :                                 if (dist > dockDist)
     474           0 :                                         dockDist = dist;
     475             :                         }
     476           0 :                         if (dockDist > 0)
     477             :                         {
     478           0 :                                 dockDist = Math.sqrt(dockDist);
     479           0 :                                 if (dockDist > width * cellSize) // Could happen only on square maps, but anyway we don't want to be too far away
     480           0 :                                         continue;
     481           0 :                                 score += factor * (width * cellSize - dockDist);
     482             :                         }
     483             :                 }
     484             : 
     485             :                 // Add a penalty if on the map border as ship movement will be difficult
     486           0 :                 if (gameState.ai.HQ.borderMap.map[j] & PETRA.fullBorder_Mask)
     487           0 :                         score += 20;
     488             : 
     489             :                 // Do a pre-selection, supposing we will have the best possible water
     490           0 :                 if (bestIdx !== undefined && score > bestVal + 5 * maxWater)
     491           0 :                         continue;
     492             : 
     493           0 :                 let x = (i % obstructions.width + 0.5) * obstructions.cellSize;
     494           0 :                 let z = (Math.floor(i / obstructions.width) + 0.5) * obstructions.cellSize;
     495           0 :                 let angle = this.getDockAngle(gameState, x, z, halfSize);
     496           0 :                 if (angle == false)
     497           0 :                         continue;
     498           0 :                 let ret = this.checkDockPlacement(gameState, x, z, halfDepth, halfWidth, angle);
     499           0 :                 if (!ret || !gameState.ai.HQ.landRegions[ret.land] || wantedLand && !wantedLand[ret.land])
     500           0 :                         continue;
     501             :                 // Final selection now that the checkDockPlacement water is known
     502           0 :                 if (bestIdx !== undefined && score + 5 * (maxWater - ret.water) > bestVal)
     503           0 :                         continue;
     504           0 :                 if (this.metadata.proximity && gameState.ai.accessibility.regionSize[ret.land] < 4000)
     505           0 :                         continue;
     506           0 :                 if (gameState.ai.HQ.isDangerousLocation(gameState, pos, halfSize))
     507           0 :                         continue;
     508             : 
     509           0 :                 bestVal = score + maxWater - ret.water;
     510           0 :                 bestIdx = i;
     511           0 :                 bestJdx = j;
     512           0 :                 bestAngle = angle;
     513           0 :                 bestLand = ret.land;
     514           0 :                 bestWater = ret.water;
     515             :         }
     516           0 :         if (bestVal < 0)
     517           0 :                 return false;
     518             : 
     519             :         // if no good place with enough water around and still in first phase, wait for expansion at the next phase
     520           0 :         if (!this.metadata.proximity && bestWater < 10 && gameState.currentPhase() == 1)
     521           0 :                 return false;
     522             : 
     523           0 :         let x = (bestIdx % obstructions.width + 0.5) * obstructions.cellSize;
     524           0 :         let z = (Math.floor(bestIdx / obstructions.width) + 0.5) * obstructions.cellSize;
     525             : 
     526             :         // Assign this dock to a base
     527           0 :         let baseIndex = gameState.ai.HQ.baseAtIndex(bestJdx);
     528           0 :         if (!baseIndex)
     529           0 :                 baseIndex = -2; // We'll do an anchorless base around it
     530             : 
     531           0 :         return { "x": x, "z": z, "angle": bestAngle, "base": baseIndex, "access": bestLand };
     532             : };
     533             : 
     534             : /**
     535             :  * Find a good island to build a dock.
     536             :  */
     537           0 : PETRA.ConstructionPlan.prototype.buildOverseaDock = function(gameState, template)
     538             : {
     539           0 :         let docks = gameState.getOwnStructures().filter(API3.Filters.byClass("Dock"));
     540           0 :         if (!docks.hasEntities())
     541           0 :                 return;
     542             : 
     543           0 :         let passabilityMap = gameState.getPassabilityMap();
     544           0 :         let cellArea = passabilityMap.cellSize * passabilityMap.cellSize;
     545           0 :         let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
     546             : 
     547           0 :         let land = {};
     548             :         let found;
     549           0 :         for (let i = 0; i < gameState.ai.accessibility.regionSize.length; ++i)
     550             :         {
     551           0 :                 if (gameState.ai.accessibility.regionType[i] != "land" ||
     552             :                     cellArea * gameState.ai.accessibility.regionSize[i] < 3600)
     553           0 :                         continue;
     554           0 :                 let keep = true;
     555           0 :                 for (let dock of docks.values())
     556             :                 {
     557           0 :                         if (PETRA.getLandAccess(gameState, dock) != i)
     558           0 :                                 continue;
     559           0 :                         keep = false;
     560           0 :                         break;
     561             :                 }
     562           0 :                 if (!keep)
     563           0 :                         continue;
     564             :                 let sea;
     565           0 :                 for (let cc of ccEnts.values())
     566             :                 {
     567           0 :                         let ccAccess = PETRA.getLandAccess(gameState, cc);
     568           0 :                         if (ccAccess != i)
     569             :                         {
     570           0 :                                 if (cc.owner() == PlayerID && !sea)
     571           0 :                                         sea = gameState.ai.HQ.getSeaBetweenIndices(gameState, ccAccess, i);
     572           0 :                                 continue;
     573             :                         }
     574             :                         // Docks on island where we have a cc are already done elsewhere
     575           0 :                         if (cc.owner() == PlayerID || gameState.isPlayerEnemy(cc.owner()))
     576             :                         {
     577           0 :                                 keep = false;
     578           0 :                                 break;
     579             :                         }
     580             :                 }
     581           0 :                 if (!keep || !sea)
     582           0 :                         continue;
     583           0 :                 land[i] = true;
     584           0 :                 found = true;
     585             :         }
     586           0 :         if (!found)
     587           0 :                 return;
     588           0 :         if (!gameState.ai.HQ.navalMap)
     589           0 :                 API3.warn("petra.findOverseaLand on a non-naval map??? we should never go there ");
     590             : 
     591           0 :         let oldTemplate = this.template;
     592           0 :         let oldMetadata = this.metadata;
     593           0 :         this.template = template;
     594             :         let pos;
     595           0 :         this.metadata = { "land": land, "oversea": true };
     596           0 :         pos = this.findDockPosition(gameState);
     597           0 :         if (pos)
     598             :         {
     599           0 :                 let type = template.templateName();
     600           0 :                 let builder = gameState.findBuilder(type);
     601           0 :                 this.metadata.base = pos.base;
     602             :                 // Adjust a bit the position if needed
     603           0 :                 let cosa = Math.cos(pos.angle);
     604           0 :                 let sina = Math.sin(pos.angle);
     605           0 :                 let shiftMax = gameState.ai.HQ.territoryMap.cellSize;
     606           0 :                 for (let shift = 0; shift <= shiftMax; shift += 2)
     607             :                 {
     608           0 :                         builder.construct(type, pos.x-shift*sina, pos.z-shift*cosa, pos.angle, this.metadata);
     609           0 :                         if (shift > 0)
     610           0 :                                 builder.construct(type, pos.x+shift*sina, pos.z+shift*cosa, pos.angle, this.metadata);
     611             :                 }
     612             :         }
     613           0 :         this.template = oldTemplate;
     614           0 :         this.metadata = oldMetadata;
     615             : };
     616             : 
     617             : /** Algorithm taken from the function GetDockAngle in simulation/helpers/Commands.js */
     618           0 : PETRA.ConstructionPlan.prototype.getDockAngle = function(gameState, x, z, size)
     619             : {
     620           0 :         let pos = gameState.ai.accessibility.gamePosToMapPos([x, z]);
     621           0 :         let k = pos[0] + pos[1]*gameState.ai.accessibility.width;
     622           0 :         let seaRef = gameState.ai.accessibility.navalPassMap[k];
     623           0 :         if (seaRef < 2)
     624           0 :                 return false;
     625           0 :         const numPoints = 16;
     626           0 :         for (let dist = 0; dist < 4; ++dist)
     627             :         {
     628           0 :                 let waterPoints = [];
     629           0 :                 for (let i = 0; i < numPoints; ++i)
     630             :                 {
     631           0 :                         let angle = 2 * Math.PI * i / numPoints;
     632           0 :                         pos = [x - (1+dist)*size*Math.sin(angle), z + (1+dist)*size*Math.cos(angle)];
     633           0 :                         pos = gameState.ai.accessibility.gamePosToMapPos(pos);
     634           0 :                         if (pos[0] < 0 || pos[0] >= gameState.ai.accessibility.width ||
     635             :                             pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
     636           0 :                                 continue;
     637           0 :                         let j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     638           0 :                         if (gameState.ai.accessibility.navalPassMap[j] == seaRef)
     639           0 :                                 waterPoints.push(i);
     640             :                 }
     641           0 :                 let length = waterPoints.length;
     642           0 :                 if (!length)
     643           0 :                         continue;
     644           0 :                 let consec = [];
     645           0 :                 for (let i = 0; i < length; ++i)
     646             :                 {
     647           0 :                         let count = 0;
     648           0 :                         for (let j = 0; j < length-1; ++j)
     649             :                         {
     650           0 :                                 if ((waterPoints[(i + j) % length]+1) % numPoints == waterPoints[(i + j + 1) % length])
     651           0 :                                         ++count;
     652             :                                 else
     653           0 :                                         break;
     654             :                         }
     655           0 :                         consec[i] = count;
     656             :                 }
     657           0 :                 let start = 0;
     658           0 :                 let count = 0;
     659           0 :                 for (let c in consec)
     660             :                 {
     661           0 :                         if (consec[c] > count)
     662             :                         {
     663           0 :                                 start = c;
     664           0 :                                 count = consec[c];
     665             :                         }
     666             :                 }
     667             : 
     668             :                 // If we've found a shoreline, stop searching
     669           0 :                 if (count != numPoints-1)
     670           0 :                         return -((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI;
     671             :         }
     672           0 :         return false;
     673             : };
     674             : 
     675             : /**
     676             :  * Algorithm taken from checkPlacement in simulation/components/BuildRestriction.js
     677             :  * to determine the special dock requirements
     678             :  * returns {"land": land index for this dock, "water": amount of water around this spot}
     679             :  */
     680           0 : PETRA.ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, halfDepth, halfWidth, angle)
     681             : {
     682           0 :         let sz = halfDepth * Math.sin(angle);
     683           0 :         let cz = halfDepth * Math.cos(angle);
     684             :         // center back position
     685           0 :         let pos = gameState.ai.accessibility.gamePosToMapPos([x - sz, z - cz]);
     686           0 :         let j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     687           0 :         let land = gameState.ai.accessibility.landPassMap[j];
     688           0 :         if (land < 2)
     689           0 :                 return null;
     690             :         // center front position
     691           0 :         pos = gameState.ai.accessibility.gamePosToMapPos([x + sz, z + cz]);
     692           0 :         j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     693           0 :         if (gameState.ai.accessibility.landPassMap[j] > 1 || gameState.ai.accessibility.navalPassMap[j] < 2)
     694           0 :                 return null;
     695             :         // additional constraints compared to BuildRestriction.js to assure we have enough place to build
     696           0 :         let sw = halfWidth * Math.cos(angle) * 3 / 4;
     697           0 :         let cw = halfWidth * Math.sin(angle) * 3 / 4;
     698           0 :         pos = gameState.ai.accessibility.gamePosToMapPos([x - sz + sw, z - cz - cw]);
     699           0 :         j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     700           0 :         if (gameState.ai.accessibility.landPassMap[j] != land)
     701           0 :                 return null;
     702           0 :         pos = gameState.ai.accessibility.gamePosToMapPos([x - sz - sw, z - cz + cw]);
     703           0 :         j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     704           0 :         if (gameState.ai.accessibility.landPassMap[j] != land)
     705           0 :                 return null;
     706           0 :         let water = 0;
     707           0 :         let sp = 15 * Math.sin(angle);
     708           0 :         let cp = 15 * Math.cos(angle);
     709           0 :         for (let i = 1; i < 5; ++i)
     710             :         {
     711           0 :                 pos = gameState.ai.accessibility.gamePosToMapPos([x + sz + i*(sp+sw), z + cz + i*(cp-cw)]);
     712           0 :                 if (pos[0] < 0 || pos[0] >= gameState.ai.accessibility.width ||
     713             :                     pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
     714           0 :                         break;
     715           0 :                 j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     716           0 :                 if (gameState.ai.accessibility.landPassMap[j] > 1 || gameState.ai.accessibility.navalPassMap[j] < 2)
     717           0 :                         break;
     718           0 :                 pos = gameState.ai.accessibility.gamePosToMapPos([x + sz + i*sp, z + cz + i*cp]);
     719           0 :                 if (pos[0] < 0 || pos[0] >= gameState.ai.accessibility.width ||
     720             :                     pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
     721           0 :                         break;
     722           0 :                 j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     723           0 :                 if (gameState.ai.accessibility.landPassMap[j] > 1 || gameState.ai.accessibility.navalPassMap[j] < 2)
     724           0 :                         break;
     725           0 :                 pos = gameState.ai.accessibility.gamePosToMapPos([x + sz + i*(sp-sw), z + cz + i*(cp+cw)]);
     726           0 :                 if (pos[0] < 0 || pos[0] >= gameState.ai.accessibility.width ||
     727             :                     pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
     728           0 :                         break;
     729           0 :                 j = pos[0] + pos[1]*gameState.ai.accessibility.width;
     730           0 :                 if (gameState.ai.accessibility.landPassMap[j] > 1 || gameState.ai.accessibility.navalPassMap[j] < 2)
     731           0 :                         break;
     732           0 :                 water += 4;
     733             :         }
     734           0 :         return { "land": land, "water": water };
     735             : };
     736             : 
     737             : /**
     738             :  * fast check if we can build a dock: returns false if nearest land is farther than the dock dimension
     739             :  * if the (object) wantedLand is given, this nearest land should have one of these accessibility
     740             :  * if wantedSea is given, this tile should be inside this sea
     741             :  */
     742           0 : PETRA.ConstructionPlan.prototype.around = [[ 1.0, 0.0], [ 0.87, 0.50], [ 0.50, 0.87], [ 0.0, 1.0], [-0.50, 0.87], [-0.87, 0.50],
     743             :                 [-1.0, 0.0], [-0.87, -0.50], [-0.50, -0.87], [ 0.0, -1.0], [ 0.50, -0.87], [ 0.87, -0.50]];
     744             : 
     745           0 : PETRA.ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wantedLand, wantedSea)
     746             : {
     747           0 :         let width = gameState.ai.HQ.territoryMap.width;
     748           0 :         let cellSize = gameState.ai.HQ.territoryMap.cellSize;
     749           0 :         let dimLand = dimension + 1.5 * cellSize;
     750           0 :         let dimSea = dimension + 2 * cellSize;
     751             : 
     752           0 :         let accessibility = gameState.ai.accessibility;
     753           0 :         let x = (j%width + 0.5) * cellSize;
     754           0 :         let z = (Math.floor(j/width) + 0.5) * cellSize;
     755           0 :         let pos = accessibility.gamePosToMapPos([x, z]);
     756           0 :         let k = pos[0] + pos[1]*accessibility.width;
     757           0 :         let landPass = accessibility.landPassMap[k];
     758           0 :         if (landPass > 1 && wantedLand && !wantedLand[landPass] ||
     759             :             landPass < 2 && accessibility.navalPassMap[k] < 2)
     760           0 :                 return false;
     761             : 
     762           0 :         for (let a of this.around)
     763             :         {
     764           0 :                 pos = accessibility.gamePosToMapPos([x + dimLand*a[0], z + dimLand*a[1]]);
     765           0 :                 if (pos[0] < 0 || pos[0] >= accessibility.width)
     766           0 :                         continue;
     767           0 :                 if (pos[1] < 0 || pos[1] >= accessibility.height)
     768           0 :                         continue;
     769           0 :                 k = pos[0] + pos[1]*accessibility.width;
     770           0 :                 landPass = accessibility.landPassMap[k];
     771           0 :                 if (landPass < 2 || wantedLand && !wantedLand[landPass])
     772           0 :                         continue;
     773           0 :                 pos = accessibility.gamePosToMapPos([x - dimSea*a[0], z - dimSea*a[1]]);
     774           0 :                 if (pos[0] < 0 || pos[0] >= accessibility.width)
     775           0 :                         continue;
     776           0 :                 if (pos[1] < 0 || pos[1] >= accessibility.height)
     777           0 :                         continue;
     778           0 :                 k = pos[0] + pos[1]*accessibility.width;
     779           0 :                 if (wantedSea && accessibility.navalPassMap[k] != wantedSea ||
     780             :                    !wantedSea && accessibility.navalPassMap[k] < 2)
     781           0 :                         continue;
     782           0 :                 return true;
     783             :         }
     784             : 
     785           0 :         return false;
     786             : };
     787             : 
     788             : /**
     789             :  * return a measure of the proximity to our frontier (including our allies)
     790             :  * 0=inside, 1=less than 24m, 2= less than 48m, 3= less than 72m, 4=less than 96m, 5=above 96m
     791             :  */
     792           0 : PETRA.ConstructionPlan.prototype.getFrontierProximity = function(gameState, j)
     793             : {
     794           0 :         let alliedVictory = gameState.getAlliedVictory();
     795           0 :         let territoryMap = gameState.ai.HQ.territoryMap;
     796           0 :         let territoryOwner = territoryMap.getOwnerIndex(j);
     797           0 :         if (territoryOwner == PlayerID || alliedVictory && gameState.isPlayerAlly(territoryOwner))
     798           0 :                 return 0;
     799             : 
     800           0 :         let borderMap = gameState.ai.HQ.borderMap;
     801           0 :         let width = territoryMap.width;
     802           0 :         let step = Math.round(24 / territoryMap.cellSize);
     803           0 :         let ix = j % width;
     804           0 :         let iz = Math.floor(j / width);
     805           0 :         let best = 5;
     806           0 :         for (let a of this.around)
     807             :         {
     808           0 :                 for (let i = 1; i < 5; ++i)
     809             :                 {
     810           0 :                         let jx = ix + Math.round(i*step*a[0]);
     811           0 :                         if (jx < 0 || jx >= width)
     812           0 :                                 continue;
     813           0 :                         let jz = iz + Math.round(i*step*a[1]);
     814           0 :                         if (jz < 0 || jz >= width)
     815           0 :                                 continue;
     816           0 :                         if (borderMap.map[jx+width*jz] & PETRA.outside_Mask)
     817           0 :                                 continue;
     818           0 :                         territoryOwner = territoryMap.getOwnerIndex(jx+width*jz);
     819           0 :                         if (alliedVictory && gameState.isPlayerAlly(territoryOwner) || territoryOwner == PlayerID)
     820             :                         {
     821           0 :                                 best = Math.min(best, i);
     822           0 :                                 break;
     823             :                         }
     824             :                 }
     825           0 :                 if (best == 1)
     826           0 :                         break;
     827             :         }
     828             : 
     829           0 :         return best;
     830             : };
     831             : 
     832             : /**
     833             :  * get the sum of the resources (except food) around, inside a given radius
     834             :  * resources have a weight (1 if dist=0 and 0 if dist=size) doubled for wood
     835             :  */
     836           0 : PETRA.ConstructionPlan.prototype.getResourcesAround = function(gameState, types, i, radius)
     837             : {
     838           0 :         let resourceMaps = gameState.sharedScript.resourceMaps;
     839           0 :         let w = resourceMaps.wood.width;
     840           0 :         let cellSize = resourceMaps.wood.cellSize;
     841           0 :         let size = Math.floor(radius / cellSize);
     842           0 :         let ix = i % w;
     843           0 :         let iy = Math.floor(i / w);
     844           0 :         let total = 0;
     845           0 :         let nbcell = 0;
     846           0 :         for (let k of types)
     847             :         {
     848           0 :                 if (k == "food" || !resourceMaps[k])
     849           0 :                         continue;
     850           0 :                 let weigh0 = k == "wood" ? 2 : 1;
     851           0 :                 for (let dy = 0; dy <= size; ++dy)
     852             :                 {
     853           0 :                         let dxmax = size - dy;
     854           0 :                         let ky = iy + dy;
     855           0 :                         if (ky >= 0 && ky < w)
     856             :                         {
     857           0 :                                 for (let dx = -dxmax; dx <= dxmax; ++dx)
     858             :                                 {
     859           0 :                                         let kx = ix + dx;
     860           0 :                                         if (kx < 0 || kx >= w)
     861           0 :                                                 continue;
     862           0 :                                         let ddx = dx > 0 ? dx : -dx;
     863           0 :                                         let weight = weigh0 * (dxmax - ddx) / size;
     864           0 :                                         total += weight * resourceMaps[k].map[kx + w * ky];
     865           0 :                                         nbcell += weight;
     866             :                                 }
     867             :                         }
     868           0 :                         if (dy == 0)
     869           0 :                                 continue;
     870           0 :                         ky = iy - dy;
     871           0 :                         if (ky >= 0 && ky < w)
     872             :                         {
     873           0 :                                 for (let dx = -dxmax; dx <= dxmax; ++dx)
     874             :                                 {
     875           0 :                                         let kx = ix + dx;
     876           0 :                                         if (kx < 0 || kx >= w)
     877           0 :                                                 continue;
     878           0 :                                         let ddx = dx > 0 ? dx : -dx;
     879           0 :                                         let weight = weigh0 * (dxmax - ddx) / size;
     880           0 :                                         total += weight * resourceMaps[k].map[kx + w * ky];
     881           0 :                                         nbcell += weight;
     882             :                                 }
     883             :                         }
     884             :                 }
     885             :         }
     886           0 :         return nbcell ? total / nbcell : 0;
     887             : };
     888             : 
     889           0 : PETRA.ConstructionPlan.prototype.isGo = function(gameState)
     890             : {
     891           0 :         if (this.goRequirement && this.goRequirement == "houseNeeded")
     892             :         {
     893           0 :                 if (!gameState.ai.HQ.canBuild(gameState, "structures/{civ}/house") &&
     894             :                     !gameState.ai.HQ.canBuild(gameState, "structures/{civ}/apartment"))
     895           0 :                         return false;
     896           0 :                 if (gameState.getPopulationMax() <= gameState.getPopulationLimit())
     897           0 :                         return false;
     898           0 :                 let freeSlots = gameState.getPopulationLimit() - gameState.getPopulation();
     899           0 :                 for (let ent of gameState.getOwnFoundations().values())
     900             :                 {
     901           0 :                         let template = gameState.getBuiltTemplate(ent.templateName());
     902           0 :                         if (template)
     903           0 :                                 freeSlots += template.getPopulationBonus();
     904             :                 }
     905             : 
     906           0 :                 if (gameState.ai.HQ.saveResources)
     907           0 :                         return freeSlots <= 10;
     908           0 :                 if (gameState.getPopulation() > 55)
     909           0 :                         return freeSlots <= 21;
     910           0 :                 if (gameState.getPopulation() > 30)
     911           0 :                         return freeSlots <= 15;
     912           0 :                 return freeSlots <= 10;
     913             :         }
     914           0 :         return true;
     915             : };
     916             : 
     917           0 : PETRA.ConstructionPlan.prototype.onStart = function(gameState)
     918             : {
     919           0 :         if (this.queueToReset)
     920           0 :                 gameState.ai.queueManager.changePriority(this.queueToReset, gameState.ai.Config.priorities[this.queueToReset]);
     921             : };
     922             : 
     923           0 : PETRA.ConstructionPlan.prototype.Serialize = function()
     924             : {
     925           0 :         return {
     926             :                 "category": this.category,
     927             :                 "type": this.type,
     928             :                 "ID": this.ID,
     929             :                 "metadata": this.metadata,
     930             :                 "cost": this.cost.Serialize(),
     931             :                 "number": this.number,
     932             :                 "position": this.position,
     933             :                 "goRequirement": this.goRequirement || undefined,
     934             :                 "queueToReset": this.queueToReset || undefined
     935             :         };
     936             : };
     937             : 
     938           0 : PETRA.ConstructionPlan.prototype.Deserialize = function(gameState, data)
     939             : {
     940           0 :         for (let key in data)
     941           0 :                 this[key] = data[key];
     942             : 
     943           0 :         this.cost = new API3.Resources();
     944           0 :         this.cost.Deserialize(data.cost);
     945             : };

Generated by: LCOV version 1.14