Line data Source code
1 : /**
2 : * Handle events that are important to specific victory conditions:
3 : * in capture_the_relic, capture gaia relics and train military guards.
4 : * in regicide, train healer and military guards for the hero.
5 : * in wonder, train military guards.
6 : */
7 :
8 0 : PETRA.VictoryManager = function(Config)
9 : {
10 0 : this.Config = Config;
11 0 : this.criticalEnts = new Map();
12 : // Holds ids of all ents who are (or can be) guarding and if the ent is currently guarding
13 0 : this.guardEnts = new Map();
14 0 : this.healersPerCriticalEnt = 2 + Math.round(this.Config.personality.defensive * 2);
15 0 : this.tryCaptureGaiaRelic = false;
16 0 : this.tryCaptureGaiaRelicLapseTime = -1;
17 : // Gaia relics which we are targeting currently and have not captured yet
18 0 : this.targetedGaiaRelics = new Map();
19 : };
20 :
21 : /**
22 : * Cache the ids of any inital victory-critical entities.
23 : */
24 0 : PETRA.VictoryManager.prototype.init = function(gameState)
25 : {
26 0 : if (gameState.getVictoryConditions().has("wonder"))
27 : {
28 0 : for (let wonder of gameState.getOwnEntitiesByClass("Wonder", true).values())
29 0 : this.criticalEnts.set(wonder.id(), { "guardsAssigned": 0, "guards": new Map() });
30 : }
31 :
32 0 : if (gameState.getVictoryConditions().has("regicide"))
33 : {
34 0 : for (let hero of gameState.getOwnEntitiesByClass("Hero", true).values())
35 : {
36 0 : let defaultStance = hero.hasClass("Soldier") ? "aggressive" : "passive";
37 0 : if (hero.getStance() != defaultStance)
38 0 : hero.setStance(defaultStance);
39 0 : this.criticalEnts.set(hero.id(), {
40 : "garrisonEmergency": false,
41 : "healersAssigned": 0,
42 : "guardsAssigned": 0, // for non-healer guards
43 : "guards": new Map() // ids of ents who are currently guarding this hero
44 : });
45 : }
46 : }
47 :
48 0 : if (gameState.getVictoryConditions().has("capture_the_relic"))
49 : {
50 0 : for (let relic of gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).values())
51 : {
52 0 : if (relic.owner() == PlayerID)
53 0 : this.criticalEnts.set(relic.id(), { "guardsAssigned": 0, "guards": new Map() });
54 : }
55 : }
56 : };
57 :
58 : /**
59 : * In regicide victory condition, if the hero has less than 70% health, try to garrison it in a healing structure
60 : * If it is less than 40%, try to garrison in the closest possible structure
61 : * If the hero cannot garrison, retreat it to the closest base
62 : */
63 0 : PETRA.VictoryManager.prototype.checkEvents = function(gameState, events)
64 : {
65 0 : if (gameState.getVictoryConditions().has("wonder"))
66 : {
67 0 : for (let evt of events.Create)
68 : {
69 0 : let ent = gameState.getEntityById(evt.entity);
70 0 : if (!ent || !ent.isOwn(PlayerID) || ent.foundationProgress() === undefined ||
71 : !ent.hasClass("Wonder"))
72 0 : continue;
73 :
74 : // Let's get a few units from other bases to build the wonder.
75 0 : let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
76 0 : let builders = gameState.ai.HQ.bulkPickWorkers(gameState, base, 10);
77 0 : if (builders)
78 0 : for (let worker of builders.values())
79 : {
80 0 : worker.setMetadata(PlayerID, "base", base.ID);
81 0 : worker.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_BUILDER);
82 0 : worker.setMetadata(PlayerID, "target-foundation", ent.id());
83 : }
84 : }
85 :
86 0 : for (let evt of events.ConstructionFinished)
87 : {
88 0 : if (!evt || !evt.newentity)
89 0 : continue;
90 :
91 0 : let ent = gameState.getEntityById(evt.newentity);
92 0 : if (ent && ent.isOwn(PlayerID) && ent.hasClass("Wonder"))
93 0 : this.criticalEnts.set(ent.id(), { "guardsAssigned": 0, "guards": new Map() });
94 : }
95 : }
96 :
97 0 : if (gameState.getVictoryConditions().has("regicide"))
98 : {
99 0 : for (let evt of events.Attacked)
100 : {
101 0 : if (!this.criticalEnts.has(evt.target))
102 0 : continue;
103 :
104 0 : let target = gameState.getEntityById(evt.target);
105 0 : if (!target || !target.position() || target.healthLevel() > this.Config.garrisonHealthLevel.high)
106 0 : continue;
107 :
108 0 : let plan = target.getMetadata(PlayerID, "plan");
109 0 : let hero = this.criticalEnts.get(evt.target);
110 0 : if (plan != -2 && plan != -3)
111 : {
112 0 : target.stopMoving();
113 :
114 0 : if (plan >= 0)
115 : {
116 0 : let attackPlan = gameState.ai.HQ.attackManager.getPlan(plan);
117 0 : if (attackPlan)
118 0 : attackPlan.removeUnit(target, true);
119 : }
120 :
121 0 : if (target.getMetadata(PlayerID, "PartOfArmy"))
122 : {
123 0 : let army = gameState.ai.HQ.defenseManager.getArmy(target.getMetadata(PlayerID, "PartOfArmy"));
124 0 : if (army)
125 0 : army.removeOwn(gameState, target.id());
126 : }
127 :
128 0 : hero.garrisonEmergency = target.healthLevel() < this.Config.garrisonHealthLevel.low;
129 0 : this.pickCriticalEntRetreatLocation(gameState, target, hero.garrisonEmergency);
130 : }
131 0 : else if (target.healthLevel() < this.Config.garrisonHealthLevel.low && !hero.garrisonEmergency)
132 : {
133 : // the hero is severely wounded, try to retreat/garrison quicker
134 0 : gameState.ai.HQ.garrisonManager.cancelGarrison(target);
135 0 : this.pickCriticalEntRetreatLocation(gameState, target, true);
136 0 : hero.garrisonEmergency = true;
137 : }
138 : }
139 :
140 0 : for (let evt of events.TrainingFinished)
141 0 : for (let entId of evt.entities)
142 : {
143 0 : let ent = gameState.getEntityById(entId);
144 0 : if (ent && ent.isOwn(PlayerID) && ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER)
145 0 : this.assignGuardToCriticalEnt(gameState, ent);
146 : }
147 :
148 0 : for (let evt of events.Garrison)
149 : {
150 0 : if (!this.criticalEnts.has(evt.entity))
151 0 : continue;
152 :
153 0 : let hero = this.criticalEnts.get(evt.entity);
154 0 : if (hero.garrisonEmergency)
155 0 : hero.garrisonEmergency = false;
156 :
157 0 : let holderEnt = gameState.getEntityById(evt.holder);
158 0 : if (!holderEnt)
159 0 : continue;
160 :
161 0 : if (holderEnt.hasClass("Ship"))
162 : {
163 : // If the hero is garrisoned on a ship, remove its guards
164 0 : for (let guardId of hero.guards.keys())
165 : {
166 0 : let guardEnt = gameState.getEntityById(guardId);
167 0 : if (!guardEnt)
168 0 : continue;
169 :
170 0 : guardEnt.removeGuard();
171 0 : this.guardEnts.set(guardId, false);
172 : }
173 0 : hero.guards.clear();
174 0 : continue;
175 : }
176 :
177 : // Move the current guards to the garrison location.
178 : // TODO: try to garrison them with the critical ent.
179 0 : for (let guardId of hero.guards.keys())
180 : {
181 0 : let guardEnt = gameState.getEntityById(guardId);
182 0 : if (!guardEnt)
183 0 : continue;
184 :
185 0 : let plan = guardEnt.getMetadata(PlayerID, "plan");
186 :
187 : // Current military guards (with Soldier class) will have been assigned plan metadata, but healer guards
188 : // are not assigned a plan, and so they could be already moving to garrison somewhere due to low health.
189 0 : if (!guardEnt.hasClass("Soldier") && (plan == -2 || plan == -3))
190 0 : continue;
191 :
192 0 : let pos = holderEnt.position();
193 0 : let radius = holderEnt.obstructionRadius().max;
194 0 : if (pos)
195 0 : guardEnt.moveToRange(pos[0], pos[1], radius, radius + 5);
196 : }
197 : }
198 : }
199 :
200 0 : for (let evt of events.EntityRenamed)
201 : {
202 0 : if (!this.guardEnts.has(evt.entity))
203 0 : continue;
204 0 : for (let data of this.criticalEnts.values())
205 : {
206 0 : if (!data.guards.has(evt.entity))
207 0 : continue;
208 0 : data.guards.set(evt.newentity, data.guards.get(evt.entity));
209 0 : data.guards.delete(evt.entity);
210 0 : break;
211 : }
212 0 : this.guardEnts.set(evt.newentity, this.guardEnts.get(evt.entity));
213 0 : this.guardEnts.delete(evt.entity);
214 : }
215 :
216 : // Check if new healers/guards need to be assigned to an ent
217 0 : for (let evt of events.Destroy)
218 : {
219 0 : if (!evt.entityObj || evt.entityObj.owner() != PlayerID)
220 0 : continue;
221 :
222 0 : let entId = evt.entityObj.id();
223 0 : if (this.criticalEnts.has(entId))
224 : {
225 0 : this.removeCriticalEnt(gameState, entId);
226 0 : continue;
227 : }
228 :
229 0 : if (!this.guardEnts.has(entId))
230 0 : continue;
231 :
232 0 : for (let data of this.criticalEnts.values())
233 0 : if (data.guards.has(entId))
234 : {
235 0 : data.guards.delete(entId);
236 0 : if (evt.entityObj.hasClass("Healer"))
237 0 : --data.healersAssigned;
238 : else
239 0 : --data.guardsAssigned;
240 0 : break;
241 : }
242 :
243 0 : this.guardEnts.delete(entId);
244 : }
245 :
246 0 : for (let evt of events.UnGarrison)
247 : {
248 0 : if (!this.guardEnts.has(evt.entity) && !this.criticalEnts.has(evt.entity))
249 0 : continue;
250 :
251 0 : let ent = gameState.getEntityById(evt.entity);
252 0 : if (!ent)
253 0 : continue;
254 :
255 : // If this ent travelled to a criticalEnt's accessValue, try again to assign as a guard
256 0 : if ((ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER ||
257 : ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_GUARD) && !this.guardEnts.get(evt.entity))
258 : {
259 0 : this.assignGuardToCriticalEnt(gameState, ent, ent.getMetadata(PlayerID, "guardedEnt"));
260 0 : continue;
261 : }
262 :
263 0 : if (!this.criticalEnts.has(evt.entity))
264 0 : continue;
265 :
266 : // If this is a hero, try to assign ents that should be guarding it, but couldn't previously
267 0 : let criticalEnt = this.criticalEnts.get(evt.entity);
268 0 : for (let [id, isGuarding] of this.guardEnts)
269 : {
270 0 : if (criticalEnt.guards.size >= this.healersPerCriticalEnt)
271 0 : break;
272 :
273 0 : if (!isGuarding)
274 : {
275 0 : let guardEnt = gameState.getEntityById(id);
276 0 : if (guardEnt)
277 0 : this.assignGuardToCriticalEnt(gameState, guardEnt, evt.entity);
278 : }
279 : }
280 : }
281 :
282 0 : for (let evt of events.OwnershipChanged)
283 : {
284 0 : if (evt.from == PlayerID && this.criticalEnts.has(evt.entity))
285 : {
286 0 : this.removeCriticalEnt(gameState, evt.entity);
287 0 : continue;
288 : }
289 0 : if (evt.from == 0 && this.targetedGaiaRelics.has(evt.entity))
290 0 : this.abortCaptureGaiaRelic(gameState, evt.entity);
291 :
292 0 : if (evt.to != PlayerID)
293 0 : continue;
294 :
295 0 : let ent = gameState.getEntityById(evt.entity);
296 0 : if (ent && (gameState.getVictoryConditions().has("wonder") && ent.hasClass("Wonder") ||
297 : gameState.getVictoryConditions().has("capture_the_relic") && ent.hasClass("Relic")))
298 : {
299 0 : this.criticalEnts.set(ent.id(), { "guardsAssigned": 0, "guards": new Map() });
300 : // Move captured relics to the closest base
301 0 : if (ent.hasClass("Relic"))
302 0 : this.pickCriticalEntRetreatLocation(gameState, ent, false);
303 : }
304 : }
305 : };
306 :
307 0 : PETRA.VictoryManager.prototype.removeCriticalEnt = function(gameState, criticalEntId)
308 : {
309 0 : for (let [guardId, role] of this.criticalEnts.get(criticalEntId).guards)
310 : {
311 0 : let guardEnt = gameState.getEntityById(guardId);
312 0 : if (!guardEnt)
313 0 : continue;
314 :
315 0 : if (role == "healer")
316 0 : this.guardEnts.set(guardId, false);
317 : else
318 : {
319 0 : guardEnt.setMetadata(PlayerID, "plan", -1);
320 0 : guardEnt.setMetadata(PlayerID, "role", undefined);
321 0 : this.guardEnts.delete(guardId);
322 : }
323 :
324 0 : if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
325 0 : guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
326 : }
327 0 : this.criticalEnts.delete(criticalEntId);
328 : };
329 :
330 : /**
331 : * Train more healers to be later affected to critical entities if needed
332 : */
333 0 : PETRA.VictoryManager.prototype.manageCriticalEntHealers = function(gameState, queues)
334 : {
335 0 : if (gameState.ai.HQ.saveResources || queues.healer.hasQueuedUnits() ||
336 : !gameState.getOwnEntitiesByClass("Temple", true).hasEntities() ||
337 : this.guardEnts.size > Math.min(gameState.getPopulationMax() / 10, gameState.getPopulation() / 4))
338 0 : return;
339 :
340 0 : for (let data of this.criticalEnts.values())
341 : {
342 0 : if (data.healersAssigned === undefined || data.healersAssigned >= this.healersPerCriticalEnt)
343 0 : continue;
344 0 : let template = gameState.applyCiv("units/{civ}/support_healer_b");
345 0 : queues.healer.addPlan(new PETRA.TrainingPlan(gameState, template, { "role": PETRA.Worker.ROLE_CRITICAL_ENT_HEALER, "base": 0 }, 1, 1));
346 0 : return;
347 : }
348 : };
349 :
350 : /**
351 : * Try to keep some military units guarding any criticalEnts, if we can afford it.
352 : * If we have too low a population and require units for other needs, remove guards so they can be reassigned.
353 : * TODO: Swap citizen soldier guards with champions if they become available.
354 : */
355 0 : PETRA.VictoryManager.prototype.manageCriticalEntGuards = function(gameState)
356 : {
357 0 : let numWorkers = gameState.getOwnEntitiesByRole(PETRA.Worker.ROLE_WORKER, true).length;
358 0 : if (numWorkers < 20)
359 : {
360 0 : for (let data of this.criticalEnts.values())
361 : {
362 0 : for (let guardId of data.guards.keys())
363 : {
364 0 : let guardEnt = gameState.getEntityById(guardId);
365 0 : if (!guardEnt || !guardEnt.hasClass("CitizenSoldier") ||
366 : guardEnt.getMetadata(PlayerID, "role") !== PETRA.Worker.ROLE_CRITICAL_ENT_GUARD)
367 0 : continue;
368 :
369 0 : guardEnt.removeGuard();
370 0 : guardEnt.setMetadata(PlayerID, "plan", -1);
371 0 : guardEnt.setMetadata(PlayerID, "role", undefined);
372 0 : this.guardEnts.delete(guardId);
373 0 : --data.guardsAssigned;
374 :
375 0 : if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
376 0 : guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
377 :
378 0 : if (++numWorkers >= 20)
379 0 : break;
380 : }
381 0 : if (numWorkers >= 20)
382 0 : break;
383 : }
384 : }
385 :
386 0 : let minWorkers = 25;
387 0 : let deltaWorkers = 3;
388 0 : for (let [id, data] of this.criticalEnts)
389 : {
390 0 : let criticalEnt = gameState.getEntityById(id);
391 0 : if (!criticalEnt)
392 0 : continue;
393 :
394 0 : let militaryGuardsPerCriticalEnt = (criticalEnt.hasClass("Wonder") ? 10 : 4) +
395 : Math.round(this.Config.personality.defensive * 5);
396 :
397 0 : if (data.guardsAssigned >= militaryGuardsPerCriticalEnt)
398 0 : continue;
399 :
400 : // First try to pick guards in the criticalEnt's accessIndex, to avoid unnecessary transports
401 0 : for (let checkForSameAccess of [true, false])
402 : {
403 : // First try to assign any Champion units we might have
404 0 : for (let entity of gameState.getOwnEntitiesByClass("Champion", true).values())
405 : {
406 0 : if (!this.tryAssignMilitaryGuard(gameState, entity, criticalEnt, checkForSameAccess))
407 0 : continue;
408 0 : if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt)
409 0 : break;
410 : }
411 :
412 0 : if (data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
413 0 : break;
414 :
415 0 : for (let entity of gameState.ai.HQ.attackManager.outOfPlan.values())
416 : {
417 0 : if (!this.tryAssignMilitaryGuard(gameState, entity, criticalEnt, checkForSameAccess))
418 0 : continue;
419 0 : --numWorkers;
420 0 : if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
421 0 : break;
422 : }
423 :
424 0 : if (data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
425 0 : break;
426 :
427 0 : for (let entity of gameState.getOwnEntitiesByClass("Soldier", true).values())
428 : {
429 0 : if (!this.tryAssignMilitaryGuard(gameState, entity, criticalEnt, checkForSameAccess))
430 0 : continue;
431 0 : --numWorkers;
432 0 : if (++data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
433 0 : break;
434 : }
435 :
436 0 : if (data.guardsAssigned >= militaryGuardsPerCriticalEnt || numWorkers <= minWorkers + deltaWorkers * data.guardsAssigned)
437 0 : break;
438 : }
439 : }
440 : };
441 :
442 0 : PETRA.VictoryManager.prototype.tryAssignMilitaryGuard = function(gameState, guardEnt, criticalEnt, checkForSameAccess)
443 : {
444 0 : if (guardEnt.getMetadata(PlayerID, "plan") !== undefined ||
445 : guardEnt.getMetadata(PlayerID, "transport") !== undefined || this.criticalEnts.has(guardEnt.id()) ||
446 : checkForSameAccess && (!guardEnt.position() || !criticalEnt.position() ||
447 : PETRA.getLandAccess(gameState, criticalEnt) != PETRA.getLandAccess(gameState, guardEnt)))
448 0 : return false;
449 :
450 0 : if (!this.assignGuardToCriticalEnt(gameState, guardEnt, criticalEnt.id()))
451 0 : return false;
452 :
453 0 : guardEnt.setMetadata(PlayerID, "plan", -2);
454 0 : guardEnt.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_CRITICAL_ENT_GUARD);
455 0 : return true;
456 : };
457 :
458 0 : PETRA.VictoryManager.prototype.pickCriticalEntRetreatLocation = function(gameState, criticalEnt, emergency)
459 : {
460 0 : gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, criticalEnt, emergency);
461 0 : let plan = criticalEnt.getMetadata(PlayerID, "plan");
462 :
463 0 : if (plan == -2 || plan == -3)
464 0 : return;
465 :
466 0 : if (this.criticalEnts.get(criticalEnt.id()).garrisonEmergency)
467 0 : this.criticalEnts.get(criticalEnt.id()).garrisonEmergency = false;
468 :
469 : // Couldn't find a place to garrison, so the ent will flee from attacks
470 0 : if (!criticalEnt.hasClass("Relic") && criticalEnt.getStance() != "passive")
471 0 : criticalEnt.setStance("passive");
472 0 : let accessIndex = PETRA.getLandAccess(gameState, criticalEnt);
473 0 : let bestBase = PETRA.getBestBase(gameState, criticalEnt, true);
474 0 : if (bestBase.accessIndex == accessIndex)
475 : {
476 0 : const bestBasePos = bestBase.anchor.position();
477 0 : criticalEnt.moveToRange(bestBasePos[0], bestBasePos[1],
478 : 0, bestBase.anchor.obstructionRadius().max);
479 : }
480 : };
481 :
482 : /**
483 : * Only send the guard command if the guard's accessIndex is the same as the critical ent
484 : * and the critical ent has a position (i.e. not garrisoned).
485 : * Request a transport if the accessIndex value is different, and if a transport is needed,
486 : * the guardEnt will be given metadata describing which entity it is being sent to guard,
487 : * which will be used once its transport has finished.
488 : * Return false if the guardEnt is not a valid guard unit (i.e. cannot guard or is being transported).
489 : */
490 0 : PETRA.VictoryManager.prototype.assignGuardToCriticalEnt = function(gameState, guardEnt, criticalEntId)
491 : {
492 0 : if (guardEnt.getMetadata(PlayerID, "transport") !== undefined || !guardEnt.canGuard())
493 0 : return false;
494 :
495 0 : if (criticalEntId && !this.criticalEnts.has(criticalEntId))
496 : {
497 0 : criticalEntId = undefined;
498 0 : if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
499 0 : guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
500 : }
501 :
502 0 : if (!criticalEntId)
503 : {
504 0 : let isHealer = guardEnt.hasClass("Healer");
505 :
506 : // Assign to the critical ent with the fewest guards
507 0 : let min = Math.min();
508 0 : for (let [id, data] of this.criticalEnts)
509 : {
510 0 : if (isHealer && (data.healersAssigned === undefined || data.healersAssigned > min))
511 0 : continue;
512 0 : if (!isHealer && data.guardsAssigned > min)
513 0 : continue;
514 :
515 0 : criticalEntId = id;
516 0 : min = isHealer ? data.healersAssigned : data.guardsAssigned;
517 : }
518 0 : if (criticalEntId)
519 : {
520 0 : let data = this.criticalEnts.get(criticalEntId);
521 0 : if (isHealer)
522 0 : ++data.healersAssigned;
523 : else
524 0 : ++data.guardsAssigned;
525 : }
526 : }
527 :
528 0 : if (!criticalEntId)
529 : {
530 0 : if (guardEnt.getMetadata(PlayerID, "guardedEnt"))
531 0 : guardEnt.setMetadata(PlayerID, "guardedEnt", undefined);
532 0 : return false;
533 : }
534 :
535 0 : let criticalEnt = gameState.getEntityById(criticalEntId);
536 0 : if (!criticalEnt || !criticalEnt.position() || !guardEnt.position())
537 : {
538 0 : this.guardEnts.set(guardEnt.id(), false);
539 0 : return false;
540 : }
541 :
542 0 : if (guardEnt.getMetadata(PlayerID, "guardedEnt") != criticalEntId)
543 0 : guardEnt.setMetadata(PlayerID, "guardedEnt", criticalEntId);
544 :
545 0 : let guardEntAccess = PETRA.getLandAccess(gameState, guardEnt);
546 0 : let criticalEntAccess = PETRA.getLandAccess(gameState, criticalEnt);
547 0 : if (guardEntAccess == criticalEntAccess)
548 : {
549 0 : let queued = PETRA.returnResources(gameState, guardEnt);
550 0 : guardEnt.guard(criticalEnt, queued);
551 0 : const guardRole = guardEnt.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_CRITICAL_ENT_HEALER ? "healer" : "guard";
552 0 : this.criticalEnts.get(criticalEntId).guards.set(guardEnt.id(), guardRole);
553 :
554 : // Switch this guard ent to the criticalEnt's base
555 0 : if (criticalEnt.hasClass("Structure") && criticalEnt.getMetadata(PlayerID, "base") !== undefined)
556 0 : guardEnt.setMetadata(PlayerID, "base", criticalEnt.getMetadata(PlayerID, "base"));
557 : }
558 : else
559 0 : gameState.ai.HQ.navalManager.requireTransport(gameState, guardEnt, guardEntAccess, criticalEntAccess, criticalEnt.position());
560 :
561 0 : this.guardEnts.set(guardEnt.id(), guardEntAccess == criticalEntAccess);
562 0 : return true;
563 : };
564 :
565 0 : PETRA.VictoryManager.prototype.resetCaptureGaiaRelic = function(gameState)
566 : {
567 : // Do not capture gaia relics too frequently as the ai has access to the entire map
568 0 : this.tryCaptureGaiaRelicLapseTime = gameState.ai.elapsedTime + 240 - 30 * (this.Config.difficulty - 3);
569 0 : this.tryCaptureGaiaRelic = false;
570 : };
571 :
572 0 : PETRA.VictoryManager.prototype.update = function(gameState, events, queues)
573 : {
574 : // Wait a turn for trigger scripts to spawn any critical ents (i.e. in regicide)
575 0 : if (gameState.ai.playedTurn == 1)
576 0 : this.init(gameState);
577 :
578 0 : this.checkEvents(gameState, events);
579 :
580 0 : if (gameState.ai.playedTurn % 10 != 0 ||
581 : !gameState.getVictoryConditions().has("wonder") && !gameState.getVictoryConditions().has("regicide") &&
582 : !gameState.getVictoryConditions().has("capture_the_relic"))
583 0 : return;
584 :
585 0 : this.manageCriticalEntGuards(gameState);
586 :
587 0 : if (gameState.getVictoryConditions().has("wonder"))
588 0 : gameState.ai.HQ.buildWonder(gameState, queues, true);
589 :
590 0 : if (gameState.getVictoryConditions().has("regicide"))
591 : {
592 0 : for (let id of this.criticalEnts.keys())
593 : {
594 0 : let ent = gameState.getEntityById(id);
595 0 : if (ent && ent.healthLevel() > this.Config.garrisonHealthLevel.high && ent.hasClass("Soldier") &&
596 : ent.getStance() != "aggressive")
597 0 : ent.setStance("aggressive");
598 : }
599 0 : this.manageCriticalEntHealers(gameState, queues);
600 : }
601 :
602 0 : if (gameState.getVictoryConditions().has("capture_the_relic"))
603 : {
604 0 : if (!this.tryCaptureGaiaRelic && gameState.ai.elapsedTime > this.tryCaptureGaiaRelicLapseTime)
605 0 : this.tryCaptureGaiaRelic = true;
606 :
607 : // Reinforce (if needed) any raid currently trying to capture a gaia relic
608 0 : for (let relicId of this.targetedGaiaRelics.keys())
609 : {
610 0 : let relic = gameState.getEntityById(relicId);
611 0 : if (!relic || relic.owner() != 0)
612 0 : this.abortCaptureGaiaRelic(gameState, relicId);
613 : else
614 0 : this.captureGaiaRelic(gameState, relic);
615 : }
616 : // And look for some new gaia relics visible by any of our units
617 : // or that may be on our territory
618 0 : let allGaiaRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic")).filter(relic => relic.owner() == 0);
619 0 : for (let relic of allGaiaRelics.values())
620 : {
621 0 : let relicPosition = relic.position();
622 0 : if (!relicPosition || this.targetedGaiaRelics.has(relic.id()))
623 0 : continue;
624 0 : let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(relicPosition);
625 0 : if (territoryOwner == PlayerID)
626 : {
627 0 : this.targetedGaiaRelics.set(relic.id(), []);
628 0 : this.captureGaiaRelic(gameState, relic);
629 0 : break;
630 : }
631 :
632 0 : if (territoryOwner != 0 && gameState.isPlayerEnemy(territoryOwner))
633 0 : continue;
634 :
635 0 : for (let ent of gameState.getOwnUnits().values())
636 : {
637 0 : if (!ent.position() || !ent.visionRange())
638 0 : continue;
639 0 : if (API3.SquareVectorDistance(ent.position(), relicPosition) > Math.square(ent.visionRange()))
640 0 : continue;
641 0 : this.targetedGaiaRelics.set(relic.id(), []);
642 0 : this.captureGaiaRelic(gameState, relic);
643 0 : break;
644 : }
645 : }
646 : }
647 : };
648 :
649 : /**
650 : * Send an expedition to capture a gaia relic, or reinforce an existing one.
651 : */
652 0 : PETRA.VictoryManager.prototype.captureGaiaRelic = function(gameState, relic)
653 : {
654 0 : let capture = -relic.defaultRegenRate();
655 0 : let sumCapturePoints = relic.capturePoints().reduce((a, b) => a + b);
656 0 : let plans = this.targetedGaiaRelics.get(relic.id());
657 0 : for (let plan of plans)
658 : {
659 0 : let attack = gameState.ai.HQ.attackManager.getPlan(plan);
660 0 : if (!attack)
661 0 : continue;
662 0 : for (let ent of attack.unitCollection.values())
663 0 : capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture");
664 : }
665 : // No need to make a new attack if already enough units
666 0 : if (capture > sumCapturePoints / 50)
667 0 : return;
668 0 : let relicPosition = relic.position();
669 0 : let access = PETRA.getLandAccess(gameState, relic);
670 0 : let units = gameState.getOwnUnits().filter(ent => {
671 0 : if (!ent.position() || !ent.canCapture(relic))
672 0 : return false;
673 0 : if (ent.getMetadata(PlayerID, "transport") !== undefined)
674 0 : return false;
675 0 : if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined)
676 0 : return false;
677 0 : let plan = ent.getMetadata(PlayerID, "plan");
678 0 : if (plan == -2 || plan == -3)
679 0 : return false;
680 0 : if (plan !== undefined && plan >= 0)
681 : {
682 0 : let attack = gameState.ai.HQ.attackManager.getPlan(plan);
683 0 : if (attack && (attack.state !== PETRA.AttackPlan.STATE_UNEXECUTED || attack.type === PETRA.AttackPlan.TYPE_RAID))
684 0 : return false;
685 : }
686 0 : if (PETRA.getLandAccess(gameState, ent) != access)
687 0 : return false;
688 0 : return true;
689 : }).filterNearest(relicPosition);
690 0 : let expedition = [];
691 0 : for (let ent of units.values())
692 : {
693 0 : capture += ent.captureStrength() * PETRA.getAttackBonus(ent, relic, "Capture");
694 0 : expedition.push(ent);
695 0 : if (capture > sumCapturePoints / 25)
696 0 : break;
697 : }
698 0 : if (!expedition.length || !plans.length && capture < sumCapturePoints / 100)
699 0 : return;
700 0 : let attack = gameState.ai.HQ.attackManager.raidTargetEntity(gameState, relic);
701 0 : if (!attack)
702 0 : return;
703 0 : let plan = attack.name;
704 0 : attack.rallyPoint = undefined;
705 0 : for (let ent of expedition)
706 : {
707 0 : ent.setMetadata(PlayerID, "plan", plan);
708 0 : attack.unitCollection.updateEnt(ent);
709 0 : if (!attack.rallyPoint)
710 0 : attack.rallyPoint = ent.position();
711 : }
712 0 : attack.forceStart();
713 0 : this.targetedGaiaRelics.get(relic.id()).push(plan);
714 : };
715 :
716 0 : PETRA.VictoryManager.prototype.abortCaptureGaiaRelic = function(gameState, relicId)
717 : {
718 0 : for (let plan of this.targetedGaiaRelics.get(relicId))
719 : {
720 0 : let attack = gameState.ai.HQ.attackManager.getPlan(plan);
721 0 : if (attack)
722 0 : attack.Abort(gameState);
723 : }
724 0 : this.targetedGaiaRelics.delete(relicId);
725 : };
726 :
727 0 : PETRA.VictoryManager.prototype.Serialize = function()
728 : {
729 0 : return {
730 : "criticalEnts": this.criticalEnts,
731 : "guardEnts": this.guardEnts,
732 : "healersPerCriticalEnt": this.healersPerCriticalEnt,
733 : "tryCaptureGaiaRelic": this.tryCaptureGaiaRelic,
734 : "tryCaptureGaiaRelicLapseTime": this.tryCaptureGaiaRelicLapseTime,
735 : "targetedGaiaRelics": this.targetedGaiaRelics
736 : };
737 : };
738 :
739 0 : PETRA.VictoryManager.prototype.Deserialize = function(data)
740 : {
741 0 : for (let key in data)
742 0 : this[key] = data[key];
743 : };
|