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 : };
|