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

          Line data    Source code
       1             : /**
       2             :  * Manage the trade
       3             :  */
       4           0 : PETRA.TradeManager = function(Config)
       5             : {
       6           0 :         this.Config = Config;
       7           0 :         this.tradeRoute = undefined;
       8           0 :         this.potentialTradeRoute = undefined;
       9           0 :         this.routeProspection = false;
      10           0 :         this.targetNumTraders = this.Config.Economy.targetNumTraders;
      11           0 :         this.warnedAllies = {};
      12             : };
      13             : 
      14           0 : PETRA.TradeManager.prototype.init = function(gameState)
      15             : {
      16           0 :         this.traders = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER));
      17           0 :         this.traders.registerUpdates();
      18           0 :         this.minimalGain = gameState.ai.HQ.navalMap ? 3 : 5;
      19             : };
      20             : 
      21           0 : PETRA.TradeManager.prototype.hasTradeRoute = function()
      22             : {
      23           0 :         return this.tradeRoute !== undefined;
      24             : };
      25             : 
      26           0 : PETRA.TradeManager.prototype.assignTrader = function(ent)
      27             : {
      28           0 :         ent.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER);
      29           0 :         this.traders.updateEnt(ent);
      30             : };
      31             : 
      32           0 : PETRA.TradeManager.prototype.trainMoreTraders = function(gameState, queues)
      33             : {
      34           0 :         if (!this.hasTradeRoute() || queues.trader.hasQueuedUnits())
      35           0 :                 return;
      36             : 
      37           0 :         let numTraders = this.traders.length;
      38           0 :         let numSeaTraders = this.traders.filter(API3.Filters.byClass("Ship")).length;
      39           0 :         let numLandTraders = numTraders - numSeaTraders;
      40             :         // add traders already in training
      41           0 :         gameState.getOwnTrainingFacilities().forEach(function(ent) {
      42           0 :                 for (let item of ent.trainingQueue())
      43             :                 {
      44           0 :                         if (!item.metadata || !item.metadata.role || item.metadata.role !== PETRA.Worker.ROLE_TRADER)
      45           0 :                                 continue;
      46           0 :                         numTraders += item.count;
      47           0 :                         if (item.metadata.sea !== undefined)
      48           0 :                                 numSeaTraders += item.count;
      49             :                         else
      50           0 :                                 numLandTraders += item.count;
      51             :                 }
      52             :         });
      53           0 :         if (numTraders >= this.targetNumTraders &&
      54             :                 (!this.tradeRoute.sea && numLandTraders >= Math.floor(this.targetNumTraders/2) ||
      55             :                   this.tradeRoute.sea && numSeaTraders >= Math.floor(this.targetNumTraders/2)))
      56           0 :                 return;
      57             : 
      58             :         let template;
      59           0 :         const metadata = { "role": PETRA.Worker.ROLE_TRADER };
      60           0 :         if (this.tradeRoute.sea)
      61             :         {
      62             :                 // if we have some merchand ships assigned to transport, try first to reassign them
      63             :                 // May-be, there were produced at an early stage when no other ship were available
      64             :                 // and the naval manager will train now more appropriate ships.
      65           0 :                 let already = false;
      66             :                 let shipToSwitch;
      67           0 :                 gameState.ai.HQ.navalManager.seaTransportShips[this.tradeRoute.sea].forEach(function(ship) {
      68           0 :                         if (already || !ship.hasClass("Trader"))
      69           0 :                                 return;
      70           0 :                         if (ship.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_SWITCH_TO_TRADER)
      71             :                         {
      72           0 :                                 already = true;
      73           0 :                                 return;
      74             :                         }
      75           0 :                         shipToSwitch = ship;
      76             :                 });
      77           0 :                 if (already)
      78           0 :                         return;
      79           0 :                 if (shipToSwitch)
      80             :                 {
      81           0 :                         if (shipToSwitch.getMetadata(PlayerID, "transporter") === undefined)
      82           0 :                                 shipToSwitch.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER);
      83             :                         else
      84           0 :                                 shipToSwitch.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_SWITCH_TO_TRADER);
      85           0 :                         return;
      86             :                 }
      87             : 
      88           0 :                 template = gameState.applyCiv("units/{civ}/ship_merchant");
      89           0 :                 metadata.sea = this.tradeRoute.sea;
      90             :         }
      91             :         else
      92             :         {
      93           0 :                 template = gameState.applyCiv("units/{civ}/support_trader");
      94           0 :                 if (!this.tradeRoute.source.hasClass("Naval"))
      95           0 :                         metadata.base = this.tradeRoute.source.getMetadata(PlayerID, "base");
      96             :                 else
      97           0 :                         metadata.base = this.tradeRoute.target.getMetadata(PlayerID, "base");
      98             :         }
      99             : 
     100           0 :         if (!gameState.getTemplate(template))
     101             :         {
     102           0 :                 if (this.Config.debug > 0)
     103           0 :                         API3.warn("Petra error: trying to train " + template + " for civ " +
     104             :                                   gameState.getPlayerCiv() + " but no template found.");
     105           0 :                 return;
     106             :         }
     107           0 :         queues.trader.addPlan(new PETRA.TrainingPlan(gameState, template, metadata, 1, 1));
     108             : };
     109             : 
     110           0 : PETRA.TradeManager.prototype.updateTrader = function(gameState, ent)
     111             : {
     112           0 :         if (ent.hasClass("Ship") && gameState.ai.playedTurn % 5 == 0 &&
     113             :             !ent.unitAIState().startsWith("INDIVIDUAL.COLLECTTREASURE") &&
     114             :             PETRA.gatherTreasure(gameState, ent, true))
     115           0 :                 return;
     116             : 
     117           0 :         if (!this.hasTradeRoute() || !ent.isIdle() || !ent.position())
     118           0 :                 return;
     119           0 :         if (ent.getMetadata(PlayerID, "transport") !== undefined)
     120           0 :                 return;
     121             : 
     122             :         // TODO if the trader is idle and has workOrders, restore them to avoid losing the current gain
     123             : 
     124           0 :         Engine.ProfileStart("Trade Manager");
     125           0 :         let access = ent.hasClass("Ship") ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent);
     126           0 :         let route = this.checkRoutes(gameState, access);
     127           0 :         if (!route)
     128             :         {
     129             :                 // TODO try to garrison land trader inside merchant ship when only sea routes available
     130           0 :                 if (this.Config.debug > 0)
     131           0 :                         API3.warn(" no available route for " + ent.genericName() + " " + ent.id());
     132           0 :                 Engine.ProfileStop();
     133           0 :                 return;
     134             :         }
     135             : 
     136           0 :         let nearerSource = true;
     137           0 :         if (API3.SquareVectorDistance(route.target.position(), ent.position()) < API3.SquareVectorDistance(route.source.position(), ent.position()))
     138           0 :                 nearerSource = false;
     139             : 
     140           0 :         if (!ent.hasClass("Ship") && route.land != access)
     141             :         {
     142           0 :                 if (nearerSource)
     143           0 :                         gameState.ai.HQ.navalManager.requireTransport(gameState, ent, access, route.land, route.source.position());
     144             :                 else
     145           0 :                         gameState.ai.HQ.navalManager.requireTransport(gameState, ent, access, route.land, route.target.position());
     146           0 :                 Engine.ProfileStop();
     147           0 :                 return;
     148             :         }
     149             : 
     150           0 :         if (nearerSource)
     151           0 :                 ent.tradeRoute(route.target, route.source);
     152             :         else
     153           0 :                 ent.tradeRoute(route.source, route.target);
     154           0 :         ent.setMetadata(PlayerID, "route", this.routeEntToId(route));
     155           0 :         Engine.ProfileStop();
     156             : };
     157             : 
     158           0 : PETRA.TradeManager.prototype.setTradingGoods = function(gameState)
     159             : {
     160           0 :         let resTradeCodes = Resources.GetTradableCodes();
     161           0 :         if (!resTradeCodes.length)
     162           0 :                 return;
     163           0 :         let tradingGoods = {};
     164           0 :         for (let res of resTradeCodes)
     165           0 :                 tradingGoods[res] = 0;
     166             :         // first, try to anticipate future needs
     167           0 :         let stocks = gameState.ai.HQ.getTotalResourceLevel(gameState);
     168           0 :         let mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState, resTradeCodes);
     169           0 :         let wantedRates = gameState.ai.HQ.GetWantedGatherRates(gameState);
     170           0 :         let remaining = 100;
     171           0 :         let targetNum = this.Config.Economy.targetNumTraders;
     172           0 :         for (let res of resTradeCodes)
     173             :         {
     174           0 :                 if (res == "food")
     175           0 :                         continue;
     176           0 :                 let wantedRate = wantedRates[res];
     177           0 :                 if (stocks[res] < 200)
     178             :                 {
     179           0 :                         tradingGoods[res] = wantedRate > 0 ? 20 : 10;
     180           0 :                         targetNum += Math.min(5, 3 + Math.ceil(wantedRate/30));
     181             :                 }
     182           0 :                 else if (stocks[res] < 500)
     183             :                 {
     184           0 :                         tradingGoods[res] = wantedRate > 0 ? 15 : 10;
     185           0 :                         targetNum += 2;
     186             :                 }
     187           0 :                 else if (stocks[res] < 1000)
     188             :                 {
     189           0 :                         tradingGoods[res] = 10;
     190           0 :                         targetNum += 1;
     191             :                 }
     192           0 :                 remaining -= tradingGoods[res];
     193             :         }
     194           0 :         this.targetNumTraders = Math.round(this.Config.popScaling * targetNum);
     195             : 
     196             : 
     197             :         // then add what is needed now
     198           0 :         let mainNeed = Math.floor(remaining * 70 / 100);
     199           0 :         let nextNeed = remaining - mainNeed;
     200             : 
     201           0 :         tradingGoods[mostNeeded[0].type] += mainNeed;
     202           0 :         if (mostNeeded[1] && mostNeeded[1].wanted > 0)
     203           0 :                 tradingGoods[mostNeeded[1].type] += nextNeed;
     204             :         else
     205           0 :                 tradingGoods[mostNeeded[0].type] += nextNeed;
     206           0 :         Engine.PostCommand(PlayerID, { "type": "set-trading-goods", "tradingGoods": tradingGoods });
     207           0 :         if (this.Config.debug > 2)
     208           0 :                 API3.warn(" trading goods set to " + uneval(tradingGoods));
     209             : };
     210             : 
     211             : /**
     212             :  * Try to barter unneeded resources for needed resources.
     213             :  * only once per turn because the info is not updated within a turn
     214             :  */
     215           0 : PETRA.TradeManager.prototype.performBarter = function(gameState)
     216             : {
     217           0 :         let barterers = gameState.getOwnEntitiesByClass("Barter", true).filter(API3.Filters.isBuilt()).toEntityArray();
     218           0 :         if (barterers.length == 0)
     219           0 :                 return false;
     220           0 :         let resBarterCodes = Resources.GetBarterableCodes();
     221           0 :         if (!resBarterCodes.length)
     222           0 :                 return false;
     223             : 
     224             :         // Available resources after account substraction
     225           0 :         let available = gameState.ai.queueManager.getAvailableResources(gameState);
     226           0 :         let needs = gameState.ai.queueManager.currentNeeds(gameState);
     227             : 
     228           0 :         let rates = gameState.ai.HQ.GetCurrentGatherRates(gameState);
     229             : 
     230           0 :         let barterPrices = gameState.getBarterPrices();
     231             :         // calculates conversion rates
     232           0 :         let getBarterRate = (prices, buy, sell) => Math.round(100 * prices.sell[sell] / prices.buy[buy]);
     233             : 
     234             :         // loop through each missing resource checking if we could barter and help finishing a queue quickly.
     235           0 :         for (let buy of resBarterCodes)
     236             :         {
     237             :                 // Check if our rate allows to gather it fast enough
     238           0 :                 if (needs[buy] == 0 || needs[buy] < rates[buy] * 30)
     239           0 :                         continue;
     240             : 
     241             :                 // Pick the best resource to barter.
     242             :                 let bestToSell;
     243           0 :                 let bestRate = 0;
     244           0 :                 for (let sell of resBarterCodes)
     245             :                 {
     246           0 :                         if (sell == buy)
     247           0 :                                 continue;
     248             :                         // Do not sell if we need it or do not have enough buffer
     249           0 :                         if (needs[sell] > 0 || available[sell] < 500)
     250           0 :                                 continue;
     251             : 
     252             :                         let barterRateMin;
     253           0 :                         if (sell == "food")
     254             :                         {
     255           0 :                                 barterRateMin = 30;
     256           0 :                                 if (available[sell] > 40000)
     257           0 :                                         barterRateMin = 0;
     258           0 :                                 else if (available[sell] > 15000)
     259           0 :                                         barterRateMin = 5;
     260           0 :                                 else if (available[sell] > 1000)
     261           0 :                                         barterRateMin = 10;
     262             :                         }
     263             :                         else
     264             :                         {
     265           0 :                                 barterRateMin = 70;
     266           0 :                                 if (available[sell] > 5000)
     267           0 :                                         barterRateMin = 30;
     268           0 :                                 else if (available[sell] > 1000)
     269           0 :                                         barterRateMin = 50;
     270           0 :                                 if (buy == "food")
     271           0 :                                         barterRateMin += 20;
     272             :                         }
     273             : 
     274           0 :                         let barterRate = getBarterRate(barterPrices, buy, sell);
     275           0 :                         if (barterRate > bestRate && barterRate > barterRateMin)
     276             :                         {
     277           0 :                                 bestRate = barterRate;
     278           0 :                                 bestToSell = sell;
     279             :                         }
     280             :                 }
     281           0 :                 if (bestToSell !== undefined)
     282             :                 {
     283           0 :                         let amount = available[bestToSell] > 5000 ? 500 : 100;
     284           0 :                         barterers[0].barter(buy, bestToSell, amount);
     285           0 :                         if (this.Config.debug > 2)
     286           0 :                                 API3.warn("Necessity bartering: sold " + bestToSell +" for " + buy +
     287             :                                           " >> need sell " + needs[bestToSell] + " need buy " + needs[buy] +
     288             :                                           " rate buy " + rates[buy] + " available sell " + available[bestToSell] +
     289             :                                           " available buy " + available[buy] + " barterRate " + bestRate +
     290             :                                           " amount " + amount);
     291           0 :                         return true;
     292             :                 }
     293             :         }
     294             : 
     295             :         // now do contingency bartering, selling food to buy finite resources (and annoy our ennemies by increasing prices)
     296           0 :         if (available.food < 1000 || needs.food > 0 || resBarterCodes.indexOf("food") == -1)
     297           0 :                 return false;
     298             :         let bestToBuy;
     299           0 :         let bestChoice = 0;
     300           0 :         for (let buy of resBarterCodes)
     301             :         {
     302           0 :                 if (buy == "food")
     303           0 :                         continue;
     304           0 :                 let barterRateMin = 80;
     305           0 :                 if (available[buy] < 5000 && available.food > 5000)
     306           0 :                         barterRateMin -= 20 - Math.floor(available[buy]/250);
     307           0 :                 let barterRate = getBarterRate(barterPrices, buy, "food");
     308           0 :                 if (barterRate < barterRateMin)
     309           0 :                         continue;
     310           0 :                 let choice = barterRate / (100 + available[buy]);
     311           0 :                 if (choice > bestChoice)
     312             :                 {
     313           0 :                         bestChoice = choice;
     314           0 :                         bestToBuy = buy;
     315             :                 }
     316             :         }
     317           0 :         if (bestToBuy !== undefined)
     318             :         {
     319           0 :                 let amount = available.food > 5000 ? 500 : 100;
     320           0 :                 barterers[0].barter(bestToBuy, "food", amount);
     321           0 :                 if (this.Config.debug > 2)
     322           0 :                         API3.warn("Contingency bartering: sold food for " + bestToBuy +
     323             :                                   " available sell " + available.food + " available buy " + available[bestToBuy] +
     324             :                                   " barterRate " + getBarterRate(barterPrices, bestToBuy, "food") +
     325             :                                   " amount " + amount);
     326           0 :                 return true;
     327             :         }
     328             : 
     329           0 :         return false;
     330             : };
     331             : 
     332           0 : PETRA.TradeManager.prototype.checkEvents = function(gameState, events)
     333             : {
     334             :         // check if one market from a traderoute is renamed, change the route accordingly
     335           0 :         for (let evt of events.EntityRenamed)
     336             :         {
     337           0 :                 let ent = gameState.getEntityById(evt.newentity);
     338           0 :                 if (!ent || !ent.hasClass("Trade"))
     339           0 :                         continue;
     340           0 :                 for (let trader of this.traders.values())
     341             :                 {
     342           0 :                         let route = trader.getMetadata(PlayerID, "route");
     343           0 :                         if (!route)
     344           0 :                                 continue;
     345           0 :                         if (route.source == evt.entity)
     346           0 :                                 route.source = evt.newentity;
     347           0 :                         else if (route.target == evt.entity)
     348           0 :                                 route.target = evt.newentity;
     349             :                         else
     350           0 :                                 continue;
     351           0 :                         trader.setMetadata(PlayerID, "route", route);
     352             :                 }
     353             :         }
     354             : 
     355             :         // if one market (or market-foundation) is destroyed, we should look for a better route
     356           0 :         for (let evt of events.Destroy)
     357             :         {
     358           0 :                 if (!evt.entityObj)
     359           0 :                         continue;
     360           0 :                 let ent = evt.entityObj;
     361           0 :                 if (!ent || !ent.hasClass("Trade") || !gameState.isPlayerAlly(ent.owner()))
     362           0 :                         continue;
     363           0 :                 this.activateProspection(gameState);
     364           0 :                 return true;
     365             :         }
     366             : 
     367             :         // same thing if one market is built
     368           0 :         for (let evt of events.Create)
     369             :         {
     370           0 :                 let ent = gameState.getEntityById(evt.entity);
     371           0 :                 if (!ent || ent.foundationProgress() !== undefined || !ent.hasClass("Trade") ||
     372             :                     !gameState.isPlayerAlly(ent.owner()))
     373           0 :                         continue;
     374           0 :                 this.activateProspection(gameState);
     375           0 :                 return true;
     376             :         }
     377             : 
     378             : 
     379             :         // and same thing for captured markets
     380           0 :         for (let evt of events.OwnershipChanged)
     381             :         {
     382           0 :                 if (!gameState.isPlayerAlly(evt.from) && !gameState.isPlayerAlly(evt.to))
     383           0 :                         continue;
     384           0 :                 let ent = gameState.getEntityById(evt.entity);
     385           0 :                 if (!ent || ent.foundationProgress() !== undefined || !ent.hasClass("Trade"))
     386           0 :                         continue;
     387           0 :                 this.activateProspection(gameState);
     388           0 :                 return true;
     389             :         }
     390             : 
     391             :         // or if diplomacy changed
     392           0 :         if (events.DiplomacyChanged.length)
     393             :         {
     394           0 :                 this.activateProspection(gameState);
     395           0 :                 return true;
     396             :         }
     397             : 
     398           0 :         return false;
     399             : };
     400             : 
     401           0 : PETRA.TradeManager.prototype.activateProspection = function(gameState)
     402             : {
     403           0 :         this.routeProspection = true;
     404           0 :         gameState.ai.HQ.buildManager.setBuildable(gameState.applyCiv("structures/{civ}/market"));
     405           0 :         gameState.ai.HQ.buildManager.setBuildable(gameState.applyCiv("structures/{civ}/dock"));
     406             : };
     407             : 
     408             : /**
     409             :  * fills the best trade route in this.tradeRoute and the best potential route in this.potentialTradeRoute
     410             :  * If an index is given, it returns the best route with this index or the best land route if index is a land index
     411             :  */
     412           0 : PETRA.TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
     413             : {
     414             :         // If we cannot trade, do not bother checking routes.
     415           0 :         if (!Resources.GetTradableCodes().length)
     416             :         {
     417           0 :                 this.tradeRoute = undefined;
     418           0 :                 this.potentialTradeRoute = undefined;
     419           0 :                 return false;
     420             :         }
     421             : 
     422           0 :         let market1 = gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Trade"), gameState.getOwnStructures());
     423           0 :         let market2 = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities());
     424           0 :         if (market1.length + market2.length < 2)  // We have to wait  ... markets will be built soon
     425             :         {
     426           0 :                 this.tradeRoute = undefined;
     427           0 :                 this.potentialTradeRoute = undefined;
     428           0 :                 return false;
     429             :         }
     430             : 
     431           0 :         let onlyOurs = !market2.hasEntities();
     432           0 :         if (onlyOurs)
     433           0 :                 market2 = market1;
     434           0 :         let candidate = { "gain": 0 };
     435           0 :         let potential = { "gain": 0 };
     436           0 :         let bestIndex = { "gain": 0 };
     437           0 :         let bestLand = { "gain": 0 };
     438             : 
     439           0 :         let mapSize = gameState.sharedScript.mapSize;
     440           0 :         let traderTemplatesGains = gameState.getTraderTemplatesGains();
     441             : 
     442           0 :         for (let m1 of market1.values())
     443             :         {
     444           0 :                 if (!m1.position())
     445           0 :                         continue;
     446           0 :                 let access1 = PETRA.getLandAccess(gameState, m1);
     447           0 :                 let sea1 = m1.hasClass("Naval") ? PETRA.getSeaAccess(gameState, m1) : undefined;
     448           0 :                 for (let m2 of market2.values())
     449             :                 {
     450           0 :                         if (onlyOurs && m1.id() >= m2.id())
     451           0 :                                 continue;
     452           0 :                         if (!m2.position())
     453           0 :                                 continue;
     454           0 :                         let access2 = PETRA.getLandAccess(gameState, m2);
     455           0 :                         let sea2 = m2.hasClass("Naval") ? PETRA.getSeaAccess(gameState, m2) : undefined;
     456           0 :                         let land = access1 == access2 ? access1 : undefined;
     457           0 :                         let sea = sea1 && sea1 == sea2 ? sea1 : undefined;
     458           0 :                         if (!land && !sea)
     459           0 :                                 continue;
     460           0 :                         if (land && PETRA.isLineInsideEnemyTerritory(gameState, m1.position(), m2.position()))
     461           0 :                                 continue;
     462             :                         let gainMultiplier;
     463           0 :                         if (land && traderTemplatesGains.landGainMultiplier)
     464           0 :                                 gainMultiplier = traderTemplatesGains.landGainMultiplier;
     465           0 :                         else if (sea && traderTemplatesGains.navalGainMultiplier)
     466           0 :                                 gainMultiplier = traderTemplatesGains.navalGainMultiplier;
     467             :                         else
     468           0 :                                 continue;
     469           0 :                         let gain = Math.round(gainMultiplier * TradeGain(API3.SquareVectorDistance(m1.position(), m2.position()), mapSize));
     470           0 :                         if (gain < this.minimalGain)
     471           0 :                                 continue;
     472           0 :                         if (m1.foundationProgress() === undefined && m2.foundationProgress() === undefined)
     473             :                         {
     474           0 :                                 if (accessIndex)
     475             :                                 {
     476           0 :                                         if (gameState.ai.accessibility.regionType[accessIndex] == "water" && sea == accessIndex)
     477             :                                         {
     478           0 :                                                 if (gain < bestIndex.gain)
     479           0 :                                                         continue;
     480           0 :                                                 bestIndex = { "source": m1, "target": m2, "gain": gain, "land": land, "sea": sea };
     481             :                                         }
     482           0 :                                         else if (gameState.ai.accessibility.regionType[accessIndex] == "land" && land == accessIndex)
     483             :                                         {
     484           0 :                                                 if (gain < bestIndex.gain)
     485           0 :                                                         continue;
     486           0 :                                                 bestIndex = { "source": m1, "target": m2, "gain": gain, "land": land, "sea": sea };
     487             :                                         }
     488           0 :                                         else if (gameState.ai.accessibility.regionType[accessIndex] == "land")
     489             :                                         {
     490           0 :                                                 if (gain < bestLand.gain)
     491           0 :                                                         continue;
     492           0 :                                                 bestLand = { "source": m1, "target": m2, "gain": gain, "land": land, "sea": sea };
     493             :                                         }
     494             :                                 }
     495           0 :                                 if (gain < candidate.gain)
     496           0 :                                         continue;
     497           0 :                                 candidate = { "source": m1, "target": m2, "gain": gain, "land": land, "sea": sea };
     498             :                         }
     499           0 :                         if (gain < potential.gain)
     500           0 :                                 continue;
     501           0 :                         potential = { "source": m1, "target": m2, "gain": gain, "land": land, "sea": sea };
     502             :                 }
     503             :         }
     504             : 
     505           0 :         if (potential.gain < 1)
     506           0 :                 this.potentialTradeRoute = undefined;
     507             :         else
     508           0 :                 this.potentialTradeRoute = potential;
     509             : 
     510           0 :         if (candidate.gain < 1)
     511             :         {
     512           0 :                 if (this.Config.debug > 2)
     513           0 :                         API3.warn("no better trade route possible");
     514           0 :                 this.tradeRoute = undefined;
     515           0 :                 return false;
     516             :         }
     517             : 
     518           0 :         if (this.Config.debug > 1 && this.tradeRoute)
     519             :         {
     520           0 :                 if (candidate.gain > this.tradeRoute.gain)
     521           0 :                         API3.warn("one better trade route set with gain " + candidate.gain + " instead of " + this.tradeRoute.gain);
     522             :         }
     523           0 :         else if (this.Config.debug > 1)
     524           0 :                 API3.warn("one trade route set with gain " + candidate.gain);
     525           0 :         this.tradeRoute = candidate;
     526             : 
     527           0 :         if (this.Config.chat)
     528             :         {
     529           0 :                 let owner = this.tradeRoute.source.owner();
     530           0 :                 if (owner == PlayerID)
     531           0 :                         owner = this.tradeRoute.target.owner();
     532           0 :                 if (owner != PlayerID && !this.warnedAllies[owner])
     533             :                 {       // Warn an ally that we have a trade route with him
     534           0 :                         PETRA.chatNewTradeRoute(gameState, owner);
     535           0 :                         this.warnedAllies[owner] = true;
     536             :                 }
     537             :         }
     538             : 
     539           0 :         if (accessIndex)
     540             :         {
     541           0 :                 if (bestIndex.gain > 0)
     542           0 :                         return bestIndex;
     543           0 :                 else if (gameState.ai.accessibility.regionType[accessIndex] == "land" && bestLand.gain > 0)
     544           0 :                         return bestLand;
     545           0 :                 return false;
     546             :         }
     547           0 :         return true;
     548             : };
     549             : 
     550             : /** Called when a market was built or destroyed, and checks if trader orders should be changed */
     551           0 : PETRA.TradeManager.prototype.checkTrader = function(gameState, ent)
     552             : {
     553           0 :         let presentRoute = ent.getMetadata(PlayerID, "route");
     554           0 :         if (!presentRoute)
     555           0 :                 return;
     556             : 
     557           0 :         if (!ent.position())
     558             :         {
     559             :                 // This trader is garrisoned, we will decide later (when ungarrisoning) what to do
     560           0 :                 ent.setMetadata(PlayerID, "route", undefined);
     561           0 :                 return;
     562             :         }
     563             : 
     564           0 :         let access = ent.hasClass("Ship") ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent);
     565           0 :         let possibleRoute = this.checkRoutes(gameState, access);
     566             :         // Warning:  presentRoute is from metadata, so contains entity ids
     567           0 :         if (!possibleRoute ||
     568             :             possibleRoute.source.id() != presentRoute.source && possibleRoute.source.id() != presentRoute.target ||
     569             :             possibleRoute.target.id() != presentRoute.source && possibleRoute.target.id() != presentRoute.target)
     570             :         {
     571             :                 // Trader will be assigned in updateTrader
     572           0 :                 ent.setMetadata(PlayerID, "route", undefined);
     573           0 :                 if (!possibleRoute && !ent.hasClass("Ship"))
     574             :                 {
     575           0 :                         let closestBase = PETRA.getBestBase(gameState, ent, true);
     576           0 :                         if (closestBase.accessIndex == access)
     577             :                         {
     578           0 :                                 let closestBasePos = closestBase.anchor.position();
     579           0 :                                 ent.moveToRange(closestBasePos[0], closestBasePos[1], 0, 15);
     580           0 :                                 return;
     581             :                         }
     582             :                 }
     583           0 :                 ent.stopMoving();
     584             :         }
     585             : };
     586             : 
     587           0 : PETRA.TradeManager.prototype.prospectForNewMarket = function(gameState, queues)
     588             : {
     589           0 :         if (queues.economicBuilding.hasQueuedUnitsWithClass("Trade") || queues.dock.hasQueuedUnitsWithClass("Trade"))
     590           0 :                 return;
     591           0 :         if (!gameState.ai.HQ.canBuild(gameState, "structures/{civ}/market"))
     592           0 :                 return;
     593           0 :         if (!gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Trade"), gameState.getOwnStructures()).hasEntities() &&
     594             :             !gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Trade"), gameState.getExclusiveAllyEntities()).hasEntities())
     595           0 :                 return;
     596           0 :         let template = gameState.getTemplate(gameState.applyCiv("structures/{civ}/market"));
     597           0 :         if (!template)
     598           0 :                 return;
     599           0 :         this.checkRoutes(gameState);
     600           0 :         let marketPos = gameState.ai.HQ.findMarketLocation(gameState, template);
     601           0 :         if (!marketPos || marketPos[3] == 0)   // marketPos[3] is the expected gain
     602             :         {       // no position found
     603           0 :                 if (gameState.getOwnEntitiesByClass("Market", true).hasEntities())
     604           0 :                         gameState.ai.HQ.buildManager.setUnbuildable(gameState, gameState.applyCiv("structures/{civ}/market"));
     605             :                 else
     606           0 :                         this.routeProspection = false;
     607           0 :                 return;
     608             :         }
     609           0 :         this.routeProspection = false;
     610           0 :         if (!this.isNewMarketWorth(marketPos[3]))
     611           0 :                 return; // position found, but not enough gain compared to our present route
     612             : 
     613           0 :         if (this.Config.debug > 1)
     614             :         {
     615           0 :                 if (this.potentialTradeRoute)
     616           0 :                         API3.warn("turn " + gameState.ai.playedTurn + "we could have a new route with gain " +
     617             :                                 marketPos[3] + " instead of the present " + this.potentialTradeRoute.gain);
     618             :                 else
     619           0 :                         API3.warn("turn " + gameState.ai.playedTurn + "we could have a first route with gain " +
     620             :                                 marketPos[3]);
     621             :         }
     622             : 
     623           0 :         if (!this.tradeRoute)
     624           0 :                 gameState.ai.queueManager.changePriority("economicBuilding", 2 * this.Config.priorities.economicBuilding);
     625           0 :         let plan = new PETRA.ConstructionPlan(gameState, "structures/{civ}/market");
     626           0 :         if (!this.tradeRoute)
     627           0 :                 plan.queueToReset = "economicBuilding";
     628           0 :         queues.economicBuilding.addPlan(plan);
     629             : };
     630             : 
     631           0 : PETRA.TradeManager.prototype.isNewMarketWorth = function(expectedGain)
     632             : {
     633           0 :         if (!Resources.GetTradableCodes().length)
     634           0 :                 return false;
     635           0 :         if (expectedGain < this.minimalGain)
     636           0 :                 return false;
     637           0 :         if (this.potentialTradeRoute && expectedGain < 2*this.potentialTradeRoute.gain &&
     638             :                 expectedGain < this.potentialTradeRoute.gain + 20)
     639           0 :                 return false;
     640           0 :         return true;
     641             : };
     642             : 
     643           0 : PETRA.TradeManager.prototype.update = function(gameState, events, queues)
     644             : {
     645           0 :         if (gameState.ai.HQ.canBarter && Resources.GetBarterableCodes().length)
     646           0 :                 this.performBarter(gameState);
     647             : 
     648           0 :         if (this.Config.difficulty <= PETRA.DIFFICULTY_VERY_EASY)
     649           0 :                 return;
     650             : 
     651           0 :         if (this.checkEvents(gameState, events))  // true if one market was built or destroyed
     652             :         {
     653           0 :                 this.traders.forEach(ent => { this.checkTrader(gameState, ent); });
     654           0 :                 this.checkRoutes(gameState);
     655             :         }
     656             : 
     657           0 :         if (this.tradeRoute)
     658             :         {
     659           0 :                 this.traders.forEach(ent => { this.updateTrader(gameState, ent); });
     660           0 :                 if (gameState.ai.playedTurn % 5 == 0)
     661           0 :                         this.trainMoreTraders(gameState, queues);
     662           0 :                 if (gameState.ai.playedTurn % 20 == 0 && this.traders.length >= 2)
     663           0 :                         gameState.ai.HQ.researchManager.researchTradeBonus(gameState, queues);
     664           0 :                 if (gameState.ai.playedTurn % 60 == 0)
     665           0 :                         this.setTradingGoods(gameState);
     666             :         }
     667             : 
     668           0 :         if (this.routeProspection)
     669           0 :                 this.prospectForNewMarket(gameState, queues);
     670             : };
     671             : 
     672           0 : PETRA.TradeManager.prototype.routeEntToId = function(route)
     673             : {
     674           0 :         if (!route)
     675           0 :                 return undefined;
     676             : 
     677           0 :         let ret = {};
     678           0 :         for (let key in route)
     679             :         {
     680           0 :                 if (key == "source" || key == "target")
     681             :                 {
     682           0 :                         if (!route[key])
     683           0 :                                 return undefined;
     684           0 :                         ret[key] = route[key].id();
     685             :                 }
     686             :                 else
     687           0 :                         ret[key] = route[key];
     688             :         }
     689           0 :         return ret;
     690             : };
     691             : 
     692           0 : PETRA.TradeManager.prototype.routeIdToEnt = function(gameState, route)
     693             : {
     694           0 :         if (!route)
     695           0 :                 return undefined;
     696             : 
     697           0 :         let ret = {};
     698           0 :         for (let key in route)
     699             :         {
     700           0 :                 if (key == "source" || key == "target")
     701             :                 {
     702           0 :                         ret[key] = gameState.getEntityById(route[key]);
     703           0 :                         if (!ret[key])
     704           0 :                                 return undefined;
     705             :                 }
     706             :                 else
     707           0 :                         ret[key] = route[key];
     708             :         }
     709           0 :         return ret;
     710             : };
     711             : 
     712           0 : PETRA.TradeManager.prototype.Serialize = function()
     713             : {
     714           0 :         return {
     715             :                 "tradeRoute": this.routeEntToId(this.tradeRoute),
     716             :                 "potentialTradeRoute": this.routeEntToId(this.potentialTradeRoute),
     717             :                 "routeProspection": this.routeProspection,
     718             :                 "targetNumTraders": this.targetNumTraders,
     719             :                 "warnedAllies": this.warnedAllies
     720             :         };
     721             : };
     722             : 
     723           0 : PETRA.TradeManager.prototype.Deserialize = function(gameState, data)
     724             : {
     725           0 :         for (let key in data)
     726             :         {
     727           0 :                 if (key == "tradeRoute" || key == "potentialTradeRoute")
     728           0 :                         this[key] = this.routeIdToEnt(gameState, data[key]);
     729             :                 else
     730           0 :                         this[key] = data[key];
     731             :         }
     732             : };

Generated by: LCOV version 1.14