Line data Source code
1 : /**
2 : * Bases Manager
3 : * Manages the list of available bases and queries information from those (e.g. resource levels).
4 : * Only one base is run every turn.
5 : */
6 :
7 0 : PETRA.BasesManager = function(Config)
8 : {
9 0 : this.Config = Config;
10 :
11 0 : this.currentBase = 0;
12 :
13 : // Cache some quantities for performance.
14 0 : this.turnCache = {};
15 :
16 : // Deals with unit/structure without base.
17 0 : this.noBase = undefined;
18 :
19 0 : this.baseManagers = [];
20 : };
21 :
22 0 : PETRA.BasesManager.prototype.init = function(gameState)
23 : {
24 : // Initialize base map. Each pixel is a base ID, or 0 if not or not accessible.
25 0 : this.basesMap = new API3.Map(gameState.sharedScript, "territory");
26 :
27 0 : this.noBase = new PETRA.BaseManager(gameState, this);
28 0 : this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
29 0 : this.noBase.accessIndex = 0;
30 :
31 0 : for (const cc of gameState.getOwnStructures().filter(API3.Filters.byClass("CivCentre")).values())
32 0 : if (cc.foundationProgress() === undefined)
33 0 : this.createBase(gameState, cc, PETRA.BaseManager.STATE_WITH_ANCHOR);
34 : else
35 0 : this.createBase(gameState, cc, PETRA.BaseManager.STATE_UNCONSTRUCTED);
36 : };
37 :
38 : /**
39 : * Initialization needed after deserialization (only called when deserialising).
40 : */
41 0 : PETRA.BasesManager.prototype.postinit = function(gameState)
42 : {
43 : // Rebuild the base maps from the territory indices of each base.
44 0 : this.basesMap = new API3.Map(gameState.sharedScript, "territory");
45 0 : for (const base of this.baseManagers)
46 0 : for (const j of base.territoryIndices)
47 0 : this.basesMap.map[j] = base.ID;
48 :
49 0 : for (const ent of gameState.getOwnEntities().values())
50 : {
51 0 : if (!ent.resourceDropsiteTypes() || !ent.hasClass("Structure"))
52 0 : continue;
53 : // Entities which have been built or have changed ownership after the last AI turn have no base.
54 : // they will be dealt with in the next checkEvents
55 0 : const baseID = ent.getMetadata(PlayerID, "base");
56 0 : if (baseID === undefined)
57 0 : continue;
58 0 : const base = this.getBaseByID(baseID);
59 0 : base.assignResourceToDropsite(gameState, ent);
60 : }
61 : };
62 :
63 : /**
64 : * Create a new base in the baseManager:
65 : * If an existing one without anchor already exist, use it.
66 : * Otherwise create a new one.
67 : * TODO when buildings, criteria should depend on distance
68 : */
69 0 : PETRA.BasesManager.prototype.createBase = function(gameState, ent, type = PETRA.BaseManager.STATE_WITH_ANCHOR)
70 : {
71 0 : const access = PETRA.getLandAccess(gameState, ent);
72 : let newbase;
73 0 : for (const base of this.baseManagers)
74 : {
75 0 : if (base.accessIndex != access)
76 0 : continue;
77 0 : if (type !== PETRA.BaseManager.STATE_ANCHORLESS && base.anchor)
78 0 : continue;
79 0 : if (type !== PETRA.BaseManager.STATE_ANCHORLESS)
80 : {
81 : // TODO we keep the first one, we should rather use the nearest if buildings
82 : // and possibly also cut on distance
83 0 : newbase = base;
84 0 : break;
85 : }
86 : else
87 : {
88 : // TODO here also test on distance instead of first
89 0 : if (newbase && !base.anchor)
90 0 : continue;
91 0 : newbase = base;
92 0 : if (newbase.anchor)
93 0 : break;
94 : }
95 : }
96 :
97 0 : if (this.Config.debug > 0)
98 : {
99 0 : API3.warn(" ----------------------------------------------------------");
100 0 : API3.warn(" BasesManager createBase entrance avec access " + access + " and type " + type);
101 0 : API3.warn(" with access " + uneval(this.baseManagers.map(base => base.accessIndex)) +
102 0 : " and base nbr " + uneval(this.baseManagers.map(base => base.ID)) +
103 0 : " and anchor " + uneval(this.baseManagers.map(base => !!base.anchor)));
104 : }
105 :
106 0 : if (!newbase)
107 : {
108 0 : newbase = new PETRA.BaseManager(gameState, this);
109 0 : newbase.init(gameState, type);
110 0 : this.baseManagers.push(newbase);
111 : }
112 : else
113 0 : newbase.reset(type);
114 :
115 0 : if (type !== PETRA.BaseManager.STATE_ANCHORLESS)
116 0 : newbase.setAnchor(gameState, ent);
117 : else
118 0 : newbase.setAnchorlessEntity(gameState, ent);
119 :
120 0 : return newbase;
121 : };
122 :
123 : /** TODO check if the new anchorless bases should be added to addBase */
124 0 : PETRA.BasesManager.prototype.checkEvents = function(gameState, events)
125 : {
126 0 : let addBase = false;
127 :
128 0 : for (const evt of events.Destroy)
129 : {
130 : // Let's check we haven't lost an important building here.
131 0 : if (evt && !evt.SuccessfulFoundation && evt.entityObj && evt.metadata && evt.metadata[PlayerID] &&
132 : evt.metadata[PlayerID].base)
133 : {
134 0 : const ent = evt.entityObj;
135 0 : if (evt?.metadata?.[PlayerID]?.assignedResource)
136 0 : this.getBaseByID(evt.metadata[PlayerID].base).removeFromAssignedDropsite(ent);
137 0 : if (ent.owner() != PlayerID)
138 0 : continue;
139 : // A new base foundation was created and destroyed on the same (AI) turn
140 0 : if (evt.metadata[PlayerID].base == -1 || evt.metadata[PlayerID].base == -2)
141 0 : continue;
142 0 : const base = this.getBaseByID(evt.metadata[PlayerID].base);
143 0 : if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
144 0 : base.removeDropsite(gameState, ent);
145 0 : if (evt.metadata[PlayerID].baseAnchor && evt.metadata[PlayerID].baseAnchor === true)
146 0 : base.anchorLost(gameState, ent);
147 : }
148 : }
149 :
150 0 : for (const evt of events.EntityRenamed)
151 : {
152 0 : const ent = gameState.getEntityById(evt.newentity);
153 0 : if (!ent || ent.owner() != PlayerID || ent.getMetadata(PlayerID, "base") === undefined)
154 0 : continue;
155 0 : const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
156 0 : if (!base.anchorId || base.anchorId != evt.entity)
157 0 : continue;
158 0 : base.anchorId = evt.newentity;
159 0 : base.anchor = ent;
160 : }
161 :
162 0 : for (const evt of events.Create)
163 : {
164 : // Let's check if we have a valuable foundation needing builders quickly
165 : // (normal foundations are taken care in baseManager.assignToFoundations)
166 0 : const ent = gameState.getEntityById(evt.entity);
167 0 : if (!ent || ent.owner() != PlayerID || ent.foundationProgress() === undefined)
168 0 : continue;
169 :
170 0 : if (ent.getMetadata(PlayerID, "base") == -1) // Standard base around a cc
171 : {
172 : // Okay so let's try to create a new base around this.
173 0 : const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
174 : // Let's get a few units from other bases there to build this.
175 0 : const builders = this.bulkPickWorkers(gameState, newbase, 10);
176 0 : if (builders !== false)
177 : {
178 0 : builders.forEach(worker => {
179 0 : worker.setMetadata(PlayerID, "base", newbase.ID);
180 0 : worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER);
181 0 : worker.setMetadata(PlayerID, "target-foundation", ent.id());
182 : });
183 : }
184 : }
185 0 : else if (ent.getMetadata(PlayerID, "base") == -2) // anchorless base around a dock
186 : {
187 0 : const newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
188 : // Let's get a few units from other bases there to build this.
189 0 : const builders = this.bulkPickWorkers(gameState, newbase, 4);
190 0 : if (builders != false)
191 : {
192 0 : builders.forEach(worker => {
193 0 : worker.setMetadata(PlayerID, "base", newbase.ID);
194 0 : worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER);
195 0 : worker.setMetadata(PlayerID, "target-foundation", ent.id());
196 : });
197 : }
198 : }
199 : }
200 :
201 0 : for (const evt of events.ConstructionFinished)
202 : {
203 0 : if (evt.newentity == evt.entity) // repaired building
204 0 : continue;
205 0 : const ent = gameState.getEntityById(evt.newentity);
206 0 : if (!ent || ent.owner() != PlayerID)
207 0 : continue;
208 0 : if (ent.getMetadata(PlayerID, "base") === undefined)
209 0 : continue;
210 0 : const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
211 0 : base.buildings.updateEnt(ent);
212 0 : if (ent.resourceDropsiteTypes())
213 0 : base.assignResourceToDropsite(gameState, ent);
214 :
215 0 : if (ent.getMetadata(PlayerID, "baseAnchor") === true)
216 : {
217 0 : if (base.constructing)
218 0 : base.constructing = false;
219 0 : addBase = true;
220 : }
221 : }
222 :
223 0 : for (const evt of events.OwnershipChanged)
224 : {
225 0 : if (evt.from == PlayerID)
226 : {
227 0 : const ent = gameState.getEntityById(evt.entity);
228 0 : if (!ent || ent.getMetadata(PlayerID, "base") === undefined)
229 0 : continue;
230 0 : const base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
231 0 : if (ent.resourceDropsiteTypes() && ent.hasClass("Structure"))
232 0 : base.removeDropsite(gameState, ent);
233 0 : if (ent.getMetadata(PlayerID, "baseAnchor") === true)
234 0 : base.anchorLost(gameState, ent);
235 : }
236 :
237 0 : if (evt.to != PlayerID)
238 0 : continue;
239 0 : const ent = gameState.getEntityById(evt.entity);
240 0 : if (!ent)
241 0 : continue;
242 0 : if (ent.hasClass("Unit"))
243 : {
244 0 : PETRA.getBestBase(gameState, ent).assignEntity(gameState, ent);
245 0 : continue;
246 : }
247 0 : if (ent.hasClass("CivCentre")) // build a new base around it
248 : {
249 : let newbase;
250 0 : if (ent.foundationProgress() !== undefined)
251 0 : newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_UNCONSTRUCTED);
252 : else
253 : {
254 0 : newbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_CAPTURED);
255 0 : addBase = true;
256 : }
257 0 : newbase.assignEntity(gameState, ent);
258 : }
259 : else
260 : {
261 : let base;
262 : // If dropsite on new island, create a base around it
263 0 : if (!ent.decaying() && ent.resourceDropsiteTypes())
264 0 : base = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
265 : else
266 0 : base = PETRA.getBestBase(gameState, ent) || this.noBase;
267 0 : base.assignEntity(gameState, ent);
268 : }
269 : }
270 :
271 0 : for (const evt of events.TrainingFinished)
272 : {
273 0 : for (const entId of evt.entities)
274 : {
275 0 : const ent = gameState.getEntityById(entId);
276 0 : if (!ent || !ent.isOwn(PlayerID))
277 0 : continue;
278 :
279 : // Assign it immediately to something useful to do.
280 0 : if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER)
281 : {
282 : let base;
283 0 : if (ent.getMetadata(PlayerID, "base") === undefined)
284 : {
285 0 : base = PETRA.getBestBase(gameState, ent);
286 0 : base.assignEntity(gameState, ent);
287 : }
288 : else
289 0 : base = this.getBaseByID(ent.getMetadata(PlayerID, "base"));
290 0 : base.reassignIdleWorkers(gameState, [ent]);
291 0 : base.workerObject.update(gameState, ent);
292 : }
293 0 : else if (ent.resourceSupplyType() && ent.position())
294 : {
295 0 : const type = ent.resourceSupplyType();
296 0 : if (!type.generic)
297 0 : continue;
298 0 : const dropsites = gameState.getOwnDropsites(type.generic);
299 0 : const pos = ent.position();
300 0 : const access = PETRA.getLandAccess(gameState, ent);
301 0 : let distmin = Math.min();
302 : let goal;
303 0 : for (const dropsite of dropsites.values())
304 : {
305 0 : if (!dropsite.position() || PETRA.getLandAccess(gameState, dropsite) != access)
306 0 : continue;
307 0 : const dist = API3.SquareVectorDistance(pos, dropsite.position());
308 0 : if (dist > distmin)
309 0 : continue;
310 0 : distmin = dist;
311 0 : goal = dropsite.position();
312 : }
313 0 : if (goal)
314 0 : ent.moveToRange(goal[0], goal[1]);
315 : }
316 : }
317 : }
318 :
319 0 : if (addBase)
320 0 : gameState.ai.HQ.handleNewBase(gameState);
321 : };
322 :
323 : /**
324 : * returns an entity collection of workers through BaseManager.pickBuilders
325 : * TODO: when same accessIndex, sort by distance
326 : */
327 0 : PETRA.BasesManager.prototype.bulkPickWorkers = function(gameState, baseRef, number)
328 : {
329 0 : const accessIndex = baseRef.accessIndex;
330 0 : if (!accessIndex)
331 0 : return false;
332 0 : const baseBest = this.baseManagers.slice();
333 : // We can also use workers without a base.
334 0 : baseBest.push(this.noBase);
335 0 : baseBest.sort((a, b) => {
336 0 : if (a.accessIndex == accessIndex && b.accessIndex != accessIndex)
337 0 : return -1;
338 0 : else if (b.accessIndex == accessIndex && a.accessIndex != accessIndex)
339 0 : return 1;
340 0 : return 0;
341 : });
342 :
343 0 : let needed = number;
344 0 : const workers = new API3.EntityCollection(gameState.sharedScript);
345 0 : for (const base of baseBest)
346 : {
347 0 : if (base.ID == baseRef.ID)
348 0 : continue;
349 0 : base.pickBuilders(gameState, workers, needed);
350 0 : if (workers.length >= number)
351 0 : break;
352 0 : needed = number - workers.length;
353 : }
354 0 : if (!workers.length)
355 0 : return false;
356 0 : return workers;
357 : };
358 :
359 : /**
360 : * @return {Object} - Resources (estimation) still gatherable in our territory.
361 : */
362 0 : PETRA.BasesManager.prototype.getTotalResourceLevel = function(gameState, resources = Resources.GetCodes(), proximity = ["nearby", "medium"])
363 : {
364 0 : const total = {};
365 0 : for (const res of resources)
366 0 : total[res] = 0;
367 0 : for (const base of this.baseManagers)
368 0 : for (const res in total)
369 0 : total[res] += base.getResourceLevel(gameState, res, proximity);
370 :
371 0 : return total;
372 : };
373 :
374 : /**
375 : * Returns the current gather rate
376 : * This is not per-se exact, it performs a few adjustments ad-hoc to account for travel distance, stuffs like that.
377 : */
378 0 : PETRA.BasesManager.prototype.GetCurrentGatherRates = function(gameState)
379 : {
380 0 : if (!this.turnCache.currentRates)
381 : {
382 0 : const currentRates = {};
383 0 : for (const res of Resources.GetCodes())
384 0 : currentRates[res] = 0.5 * this.GetTCResGatherer(res);
385 :
386 0 : this.addGatherRates(gameState, currentRates);
387 :
388 0 : for (const res of Resources.GetCodes())
389 0 : currentRates[res] = Math.max(currentRates[res], 0);
390 :
391 0 : this.turnCache.currentRates = currentRates;
392 : }
393 :
394 0 : return this.turnCache.currentRates;
395 : };
396 :
397 : /** Some functions that register that we assigned a gatherer to a resource this turn */
398 :
399 : /** Add a gatherer to the turn cache for this supply. */
400 0 : PETRA.BasesManager.prototype.AddTCGatherer = function(supplyID)
401 : {
402 0 : if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID] !== undefined)
403 0 : ++this.turnCache.resourceGatherer[supplyID];
404 : else
405 : {
406 0 : if (!this.turnCache.resourceGatherer)
407 0 : this.turnCache.resourceGatherer = {};
408 0 : this.turnCache.resourceGatherer[supplyID] = 1;
409 : }
410 : };
411 :
412 : /** Remove a gatherer from the turn cache for this supply. */
413 0 : PETRA.BasesManager.prototype.RemoveTCGatherer = function(supplyID)
414 : {
415 0 : if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
416 0 : --this.turnCache.resourceGatherer[supplyID];
417 : else
418 : {
419 0 : if (!this.turnCache.resourceGatherer)
420 0 : this.turnCache.resourceGatherer = {};
421 0 : this.turnCache.resourceGatherer[supplyID] = -1;
422 : }
423 : };
424 :
425 0 : PETRA.BasesManager.prototype.GetTCGatherer = function(supplyID)
426 : {
427 0 : if (this.turnCache.resourceGatherer && this.turnCache.resourceGatherer[supplyID])
428 0 : return this.turnCache.resourceGatherer[supplyID];
429 :
430 0 : return 0;
431 : };
432 :
433 : /** The next two are to register that we assigned a gatherer to a resource this turn. */
434 0 : PETRA.BasesManager.prototype.AddTCResGatherer = function(resource)
435 : {
436 0 : const check = "resourceGatherer-" + resource;
437 0 : if (this.turnCache[check])
438 0 : ++this.turnCache[check];
439 : else
440 0 : this.turnCache[check] = 1;
441 :
442 0 : if (this.turnCache.currentRates)
443 0 : this.turnCache.currentRates[resource] += 0.5;
444 : };
445 :
446 0 : PETRA.BasesManager.prototype.GetTCResGatherer = function(resource)
447 : {
448 0 : const check = "resourceGatherer-" + resource;
449 0 : if (this.turnCache[check])
450 0 : return this.turnCache[check];
451 :
452 0 : return 0;
453 : };
454 :
455 : /**
456 : * flag a resource as exhausted
457 : */
458 0 : PETRA.BasesManager.prototype.isResourceExhausted = function(resource)
459 : {
460 0 : const check = "exhausted-" + resource;
461 0 : if (this.turnCache[check] == undefined)
462 0 : this.turnCache[check] = this.basesManager.isResourceExhausted(resource);
463 :
464 0 : return this.turnCache[check];
465 : };
466 :
467 : /**
468 : * returns the number of bases with a cc
469 : * ActiveBases includes only those with a built cc
470 : * PotentialBases includes also those with a cc in construction
471 : */
472 0 : PETRA.BasesManager.prototype.numActiveBases = function()
473 : {
474 0 : if (!this.turnCache.base)
475 0 : this.updateBaseCache();
476 0 : return this.turnCache.base.active;
477 : };
478 :
479 0 : PETRA.BasesManager.prototype.hasActiveBase = function()
480 : {
481 0 : return !!this.numActiveBases();
482 : };
483 :
484 0 : PETRA.BasesManager.prototype.numPotentialBases = function()
485 : {
486 0 : if (!this.turnCache.base)
487 0 : this.updateBaseCache();
488 0 : return this.turnCache.base.potential;
489 : };
490 :
491 0 : PETRA.BasesManager.prototype.hasPotentialBase = function()
492 : {
493 0 : return !!this.numPotentialBases();
494 : };
495 :
496 : /**
497 : * Updates the number of active and potential bases.
498 : * .potential {number} - Bases that may or may not still be a foundation.
499 : * .active {number} - Usable bases.
500 : */
501 0 : PETRA.BasesManager.prototype.updateBaseCache = function()
502 : {
503 0 : this.turnCache.base = { "active": 0, "potential": 0 };
504 0 : for (const base of this.baseManagers)
505 : {
506 0 : if (!base.anchor)
507 0 : continue;
508 0 : ++this.turnCache.base.potential;
509 0 : if (base.anchor.foundationProgress() === undefined)
510 0 : ++this.turnCache.base.active;
511 : }
512 : };
513 :
514 0 : PETRA.BasesManager.prototype.resetBaseCache = function()
515 : {
516 0 : this.turnCache.base = undefined;
517 : };
518 :
519 0 : PETRA.BasesManager.prototype.baselessBase = function()
520 : {
521 0 : return this.noBase;
522 : };
523 :
524 : /**
525 : * @param {number} baseID
526 : * @return {Object} - The base corresponding to baseID.
527 : */
528 0 : PETRA.BasesManager.prototype.getBaseByID = function(baseID)
529 : {
530 0 : if (this.noBase.ID === baseID)
531 0 : return this.noBase;
532 0 : return this.baseManagers.find(base => base.ID === baseID);
533 : };
534 :
535 : /**
536 : * flag a resource as exhausted
537 : */
538 0 : PETRA.BasesManager.prototype.isResourceExhausted = function(resource)
539 : {
540 0 : return this.baseManagers.every(base =>
541 0 : !base.dropsiteSupplies[resource].nearby.length &&
542 : !base.dropsiteSupplies[resource].medium.length &&
543 : !base.dropsiteSupplies[resource].faraway.length);
544 : };
545 :
546 : /**
547 : * Count gatherers returning resources in the number of gatherers of resourceSupplies
548 : * to prevent the AI always reassigning idle workers to these resourceSupplies (specially in naval maps).
549 : */
550 0 : PETRA.BasesManager.prototype.assignGatherers = function()
551 : {
552 0 : for (const base of this.baseManagers)
553 0 : for (const worker of base.workers.values())
554 : {
555 0 : if (worker.unitAIState().split(".").indexOf("RETURNRESOURCE") === -1)
556 0 : continue;
557 0 : const orders = worker.unitAIOrderData();
558 0 : if (orders.length < 2 || !orders[1].target || orders[1].target != worker.getMetadata(PlayerID, "supply"))
559 0 : continue;
560 0 : this.AddTCGatherer(orders[1].target);
561 : }
562 : };
563 :
564 : /**
565 : * Assign an entity to the closest base.
566 : * Used by the starting strategy.
567 : */
568 0 : PETRA.BasesManager.prototype.assignEntity = function(gameState, ent, territoryIndex)
569 : {
570 : let bestbase;
571 0 : for (const base of this.baseManagers)
572 : {
573 0 : if ((!ent.getMetadata(PlayerID, "base") || ent.getMetadata(PlayerID, "base") != base.ID) &&
574 : base.territoryIndices.indexOf(territoryIndex) == -1)
575 0 : continue;
576 0 : base.assignEntity(gameState, ent);
577 0 : bestbase = base;
578 0 : break;
579 : }
580 0 : if (!bestbase) // entity outside our territory
581 : {
582 0 : if (ent.hasClass("Structure") && !ent.decaying() && ent.resourceDropsiteTypes())
583 0 : bestbase = this.createBase(gameState, ent, PETRA.BaseManager.STATE_ANCHORLESS);
584 : else
585 0 : bestbase = PETRA.getBestBase(gameState, ent) || this.noBase;
586 0 : bestbase.assignEntity(gameState, ent);
587 : }
588 : // now assign entities garrisoned inside this entity
589 0 : if (ent.isGarrisonHolder() && ent.garrisoned().length)
590 0 : for (const id of ent.garrisoned())
591 0 : bestbase.assignEntity(gameState, gameState.getEntityById(id));
592 : // and find something useful to do if we already have a base
593 0 : if (ent.position() && bestbase.ID !== this.noBase.ID)
594 : {
595 0 : bestbase.assignRolelessUnits(gameState, [ent]);
596 0 : if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER)
597 : {
598 0 : bestbase.reassignIdleWorkers(gameState, [ent]);
599 0 : bestbase.workerObject.update(gameState, ent);
600 : }
601 : }
602 : };
603 :
604 : /**
605 : * Adds the gather rates of individual bases to a shared object.
606 : * @param {Object} gameState
607 : * @param {Object} rates - The rates to add the gather rates to.
608 : */
609 0 : PETRA.BasesManager.prototype.addGatherRates = function(gameState, rates)
610 : {
611 0 : for (const base of this.baseManagers)
612 0 : base.addGatherRates(gameState, rates);
613 : };
614 :
615 : /**
616 : * @param {number} territoryIndex
617 : * @return {number} - The ID of the base at the given territory index.
618 : */
619 0 : PETRA.BasesManager.prototype.baseAtIndex = function(territoryIndex)
620 : {
621 0 : return this.basesMap.map[territoryIndex];
622 : };
623 :
624 : /**
625 : * @param {number} territoryIndex
626 : */
627 0 : PETRA.BasesManager.prototype.removeBaseFromTerritoryIndex = function(territoryIndex)
628 : {
629 0 : const baseID = this.basesMap.map[territoryIndex];
630 0 : if (baseID == 0)
631 0 : return;
632 0 : const base = this.getBaseByID(baseID);
633 0 : if (base)
634 : {
635 0 : const index = base.territoryIndices.indexOf(territoryIndex);
636 0 : if (index != -1)
637 0 : base.territoryIndices.splice(index, 1);
638 : else
639 0 : API3.warn(" problem in headquarters::updateTerritories for base " + baseID);
640 : }
641 : else
642 0 : API3.warn(" problem in headquarters::updateTerritories without base " + baseID);
643 0 : this.basesMap.map[territoryIndex] = 0;
644 : };
645 :
646 : /**
647 : * @return {boolean} - Whether the index was added to a base.
648 : */
649 0 : PETRA.BasesManager.prototype.addTerritoryIndexToBase = function(gameState, territoryIndex, passabilityMap)
650 : {
651 0 : if (this.baseAtIndex(territoryIndex) != 0)
652 0 : return false;
653 0 : let landPassable = false;
654 0 : const ind = API3.getMapIndices(territoryIndex, gameState.ai.HQ.territoryMap, passabilityMap);
655 : let access;
656 0 : for (const k of ind)
657 : {
658 0 : if (!gameState.ai.HQ.landRegions[gameState.ai.accessibility.landPassMap[k]])
659 0 : continue;
660 0 : landPassable = true;
661 0 : access = gameState.ai.accessibility.landPassMap[k];
662 0 : break;
663 : }
664 0 : if (!landPassable)
665 0 : return false;
666 0 : let distmin = Math.min();
667 : let baseID;
668 0 : const pos = [gameState.ai.HQ.territoryMap.cellSize * (territoryIndex % gameState.ai.HQ.territoryMap.width + 0.5), gameState.ai.HQ.territoryMap.cellSize * (Math.floor(territoryIndex / gameState.ai.HQ.territoryMap.width) + 0.5)];
669 0 : for (const base of this.baseManagers)
670 : {
671 0 : if (!base.anchor || !base.anchor.position())
672 0 : continue;
673 0 : if (base.accessIndex != access)
674 0 : continue;
675 0 : const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
676 0 : if (dist >= distmin)
677 0 : continue;
678 0 : distmin = dist;
679 0 : baseID = base.ID;
680 : }
681 0 : if (!baseID)
682 0 : return false;
683 0 : this.getBaseByID(baseID).territoryIndices.push(territoryIndex);
684 0 : this.basesMap.map[territoryIndex] = baseID;
685 0 : return true;
686 : };
687 :
688 : /** Reassign territories when a base is going to be deleted */
689 0 : PETRA.BasesManager.prototype.reassignTerritories = function(deletedBase, territoryMap)
690 : {
691 0 : const cellSize = territoryMap.cellSize;
692 0 : const width = territoryMap.width;
693 0 : for (let j = 0; j < territoryMap.length; ++j)
694 : {
695 0 : if (this.basesMap.map[j] != deletedBase.ID)
696 0 : continue;
697 0 : if (territoryMap.getOwnerIndex(j) != PlayerID)
698 : {
699 0 : API3.warn("Petra reassignTerritories: should never happen");
700 0 : this.basesMap.map[j] = 0;
701 0 : continue;
702 : }
703 :
704 0 : let distmin = Math.min();
705 : let baseID;
706 0 : const pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
707 0 : for (const base of this.baseManagers)
708 : {
709 0 : if (!base.anchor || !base.anchor.position())
710 0 : continue;
711 0 : if (base.accessIndex != deletedBase.accessIndex)
712 0 : continue;
713 0 : const dist = API3.SquareVectorDistance(base.anchor.position(), pos);
714 0 : if (dist >= distmin)
715 0 : continue;
716 0 : distmin = dist;
717 0 : baseID = base.ID;
718 : }
719 0 : if (baseID)
720 : {
721 0 : this.getBaseByID(baseID).territoryIndices.push(j);
722 0 : this.basesMap.map[j] = baseID;
723 : }
724 : else
725 0 : this.basesMap.map[j] = 0;
726 : }
727 : };
728 :
729 : /**
730 : * We will loop only on one active base per turn.
731 : */
732 0 : PETRA.BasesManager.prototype.update = function(gameState, queues, events)
733 : {
734 0 : Engine.ProfileStart("BasesManager update");
735 :
736 0 : this.turnCache = {};
737 0 : this.assignGatherers();
738 0 : let nbBases = this.baseManagers.length;
739 0 : let activeBase = false;
740 0 : this.noBase.update(gameState, queues, events);
741 0 : while (!activeBase && nbBases != 0)
742 : {
743 0 : this.currentBase %= this.baseManagers.length;
744 0 : activeBase = this.baseManagers[this.currentBase++].update(gameState, queues, events);
745 0 : --nbBases;
746 : // TODO what to do with this.reassignTerritories(this.baseManagers[this.currentBase]);
747 : }
748 :
749 0 : Engine.ProfileStop();
750 : };
751 :
752 0 : PETRA.BasesManager.prototype.Serialize = function()
753 : {
754 0 : const properties = {
755 : "currentBase": this.currentBase
756 : };
757 :
758 0 : const baseManagers = [];
759 0 : for (const base of this.baseManagers)
760 0 : baseManagers.push(base.Serialize());
761 :
762 0 : return {
763 : "properties": properties,
764 : "noBase": this.noBase.Serialize(),
765 : "baseManagers": baseManagers
766 : };
767 : };
768 :
769 0 : PETRA.BasesManager.prototype.Deserialize = function(gameState, data)
770 : {
771 0 : for (const key in data.properties)
772 0 : this[key] = data.properties[key];
773 :
774 0 : this.noBase = new PETRA.BaseManager(gameState, this);
775 0 : this.noBase.Deserialize(gameState, data.noBase);
776 0 : this.noBase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
777 0 : this.noBase.Deserialize(gameState, data.noBase);
778 :
779 0 : this.baseManagers = [];
780 0 : for (const basedata of data.baseManagers)
781 : {
782 : // The first call to deserialize set the ID base needed by entitycollections.
783 0 : const newbase = new PETRA.BaseManager(gameState, this);
784 0 : newbase.Deserialize(gameState, basedata);
785 0 : newbase.init(gameState, PETRA.BaseManager.STATE_WITH_ANCHOR);
786 0 : newbase.Deserialize(gameState, basedata);
787 0 : this.baseManagers.push(newbase);
788 : }
789 : };
|