Line data Source code
1 : /**
2 : * Attack Manager
3 : */
4 0 : PETRA.AttackManager = function(Config)
5 : {
6 0 : this.Config = Config;
7 :
8 0 : this.totalNumber = 0;
9 0 : this.attackNumber = 0;
10 0 : this.rushNumber = 0;
11 0 : this.raidNumber = 0;
12 0 : this.upcomingAttacks = {
13 : [PETRA.AttackPlan.TYPE_RUSH]: [],
14 : [PETRA.AttackPlan.TYPE_RAID]: [],
15 : [PETRA.AttackPlan.TYPE_DEFAULT]: [],
16 : [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: []
17 : };
18 0 : this.startedAttacks = {
19 : [PETRA.AttackPlan.TYPE_RUSH]: [],
20 : [PETRA.AttackPlan.TYPE_RAID]: [],
21 : [PETRA.AttackPlan.TYPE_DEFAULT]: [],
22 : [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: []
23 : };
24 0 : this.bombingAttacks = new Map();// Temporary attacks for siege units while waiting their current attack to start
25 0 : this.debugTime = 0;
26 0 : this.maxRushes = 0;
27 0 : this.rushSize = [];
28 0 : this.currentEnemyPlayer = undefined; // enemy player we are currently targeting
29 0 : this.defeated = {};
30 : };
31 :
32 : /** More initialisation for stuff that needs the gameState */
33 0 : PETRA.AttackManager.prototype.init = function(gameState)
34 : {
35 0 : this.outOfPlan = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "plan", -1));
36 0 : this.outOfPlan.registerUpdates();
37 : };
38 :
39 0 : PETRA.AttackManager.prototype.setRushes = function(allowed)
40 : {
41 0 : if (this.Config.personality.aggressive > this.Config.personalityCut.strong && allowed > 2)
42 : {
43 0 : this.maxRushes = 3;
44 0 : this.rushSize = [ 16, 20, 24 ];
45 : }
46 0 : else if (this.Config.personality.aggressive > this.Config.personalityCut.medium && allowed > 1)
47 : {
48 0 : this.maxRushes = 2;
49 0 : this.rushSize = [ 18, 22 ];
50 : }
51 0 : else if (this.Config.personality.aggressive > this.Config.personalityCut.weak && allowed > 0)
52 : {
53 0 : this.maxRushes = 1;
54 0 : this.rushSize = [ 20 ];
55 : }
56 : };
57 :
58 0 : PETRA.AttackManager.prototype.checkEvents = function(gameState, events)
59 : {
60 0 : for (let evt of events.PlayerDefeated)
61 0 : this.defeated[evt.playerId] = true;
62 :
63 0 : let answer = "decline";
64 : let other;
65 : let targetPlayer;
66 0 : for (let evt of events.AttackRequest)
67 : {
68 0 : if (evt.source === PlayerID || !gameState.isPlayerAlly(evt.source) || !gameState.isPlayerEnemy(evt.player))
69 0 : continue;
70 0 : targetPlayer = evt.player;
71 0 : let available = 0;
72 0 : for (let attackType in this.upcomingAttacks)
73 : {
74 0 : for (let attack of this.upcomingAttacks[attackType])
75 : {
76 0 : if (attack.state === PETRA.AttackPlan.STATE_COMPLETING)
77 : {
78 0 : if (attack.targetPlayer === targetPlayer)
79 0 : available += attack.unitCollection.length;
80 0 : else if (attack.targetPlayer !== undefined && attack.targetPlayer !== targetPlayer)
81 0 : other = attack.targetPlayer;
82 0 : continue;
83 : }
84 :
85 0 : attack.targetPlayer = targetPlayer;
86 :
87 0 : if (attack.unitCollection.length > 2)
88 0 : available += attack.unitCollection.length;
89 : }
90 : }
91 :
92 0 : if (available > 12) // launch the attack immediately
93 : {
94 0 : for (let attackType in this.upcomingAttacks)
95 : {
96 0 : for (let attack of this.upcomingAttacks[attackType])
97 : {
98 0 : if (attack.state === PETRA.AttackPlan.STATE_COMPLETING ||
99 : attack.targetPlayer !== targetPlayer ||
100 : attack.unitCollection.length < 3)
101 0 : continue;
102 0 : attack.forceStart();
103 0 : attack.requested = true;
104 : }
105 : }
106 0 : answer = "join";
107 : }
108 0 : else if (other !== undefined)
109 0 : answer = "other";
110 0 : break; // take only the first attack request into account
111 : }
112 0 : if (targetPlayer !== undefined)
113 0 : PETRA.chatAnswerRequestAttack(gameState, targetPlayer, answer, other);
114 :
115 0 : for (let evt of events.EntityRenamed) // take care of packing units in bombing attacks
116 : {
117 0 : for (let [targetId, unitIds] of this.bombingAttacks)
118 : {
119 0 : if (targetId == evt.entity)
120 : {
121 0 : this.bombingAttacks.set(evt.newentity, unitIds);
122 0 : this.bombingAttacks.delete(evt.entity);
123 : }
124 0 : else if (unitIds.has(evt.entity))
125 : {
126 0 : unitIds.add(evt.newentity);
127 0 : unitIds.delete(evt.entity);
128 : }
129 : }
130 : }
131 : };
132 :
133 : /**
134 : * Check for any structure in range from within our territory, and bomb it
135 : */
136 0 : PETRA.AttackManager.prototype.assignBombers = function(gameState)
137 : {
138 : // First some cleaning of current bombing attacks
139 0 : for (let [targetId, unitIds] of this.bombingAttacks)
140 : {
141 0 : let target = gameState.getEntityById(targetId);
142 0 : if (!target || !gameState.isPlayerEnemy(target.owner()))
143 0 : this.bombingAttacks.delete(targetId);
144 : else
145 : {
146 0 : for (let entId of unitIds.values())
147 : {
148 0 : let ent = gameState.getEntityById(entId);
149 0 : if (ent && ent.owner() == PlayerID)
150 : {
151 0 : let plan = ent.getMetadata(PlayerID, "plan");
152 0 : let orders = ent.unitAIOrderData();
153 0 : let lastOrder = orders && orders.length ? orders[orders.length-1] : null;
154 0 : if (lastOrder && lastOrder.target && lastOrder.target == targetId && plan != -2 && plan != -3)
155 0 : continue;
156 : }
157 0 : unitIds.delete(entId);
158 : }
159 0 : if (!unitIds.size)
160 0 : this.bombingAttacks.delete(targetId);
161 : }
162 : }
163 :
164 0 : const bombers = gameState.updatingCollection("bombers", API3.Filters.byClasses(["BoltShooter", "StoneThrower"]), gameState.getOwnUnits());
165 0 : for (let ent of bombers.values())
166 : {
167 0 : if (!ent.position() || !ent.isIdle() || !ent.attackRange("Ranged"))
168 0 : continue;
169 0 : if (ent.getMetadata(PlayerID, "plan") == -2 || ent.getMetadata(PlayerID, "plan") == -3)
170 0 : continue;
171 0 : if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") != -1)
172 : {
173 0 : let subrole = ent.getMetadata(PlayerID, "subrole");
174 0 : if (subrole && (subrole === PETRA.Worker.SUBROLE_COMPLETING || subrole === PETRA.Worker.SUBROLE_WALKING || subrole === PETRA.Worker.SUBROLE_ATTACKING))
175 0 : continue;
176 : }
177 0 : let alreadyBombing = false;
178 0 : for (let unitIds of this.bombingAttacks.values())
179 : {
180 0 : if (!unitIds.has(ent.id()))
181 0 : continue;
182 0 : alreadyBombing = true;
183 0 : break;
184 : }
185 0 : if (alreadyBombing)
186 0 : break;
187 :
188 0 : let range = ent.attackRange("Ranged").max;
189 0 : let entPos = ent.position();
190 0 : let access = PETRA.getLandAccess(gameState, ent);
191 0 : for (let struct of gameState.getEnemyStructures().values())
192 : {
193 0 : if (!ent.canAttackTarget(struct, PETRA.allowCapture(gameState, ent, struct)))
194 0 : continue;
195 :
196 0 : let structPos = struct.position();
197 : let x;
198 : let z;
199 0 : if (struct.hasClass("Field"))
200 : {
201 0 : if (!struct.resourceSupplyNumGatherers() ||
202 : !gameState.isPlayerEnemy(gameState.ai.HQ.territoryMap.getOwner(structPos)))
203 0 : continue;
204 : }
205 0 : let dist = API3.VectorDistance(entPos, structPos);
206 0 : if (dist > range)
207 : {
208 0 : let safety = struct.footprintRadius() + 30;
209 0 : x = structPos[0] + (entPos[0] - structPos[0]) * safety / dist;
210 0 : z = structPos[1] + (entPos[1] - structPos[1]) * safety / dist;
211 0 : let owner = gameState.ai.HQ.territoryMap.getOwner([x, z]);
212 0 : if (owner != 0 && gameState.isPlayerEnemy(owner))
213 0 : continue;
214 0 : x = structPos[0] + (entPos[0] - structPos[0]) * range / dist;
215 0 : z = structPos[1] + (entPos[1] - structPos[1]) * range / dist;
216 0 : if (gameState.ai.HQ.territoryMap.getOwner([x, z]) != PlayerID ||
217 : gameState.ai.accessibility.getAccessValue([x, z]) != access)
218 0 : continue;
219 : }
220 : let attackingUnits;
221 0 : for (let [targetId, unitIds] of this.bombingAttacks)
222 : {
223 0 : if (targetId != struct.id())
224 0 : continue;
225 0 : attackingUnits = unitIds;
226 0 : break;
227 : }
228 0 : if (attackingUnits && attackingUnits.size > 4)
229 0 : continue; // already enough units against that target
230 0 : if (!attackingUnits)
231 : {
232 0 : attackingUnits = new Set();
233 0 : this.bombingAttacks.set(struct.id(), attackingUnits);
234 : }
235 0 : attackingUnits.add(ent.id());
236 0 : if (dist > range)
237 0 : ent.move(x, z);
238 0 : ent.attack(struct.id(), false, dist > range);
239 0 : break;
240 : }
241 : }
242 : };
243 :
244 : /**
245 : * Some functions are run every turn
246 : * Others once in a while
247 : */
248 0 : PETRA.AttackManager.prototype.update = function(gameState, queues, events)
249 : {
250 0 : if (this.Config.debug > 2 && gameState.ai.elapsedTime > this.debugTime + 60)
251 : {
252 0 : this.debugTime = gameState.ai.elapsedTime;
253 0 : API3.warn(" upcoming attacks =================");
254 0 : for (let attackType in this.upcomingAttacks)
255 0 : for (let attack of this.upcomingAttacks[attackType])
256 0 : API3.warn(" plan " + attack.name + " type " + attackType + " state " + attack.state + " units " + attack.unitCollection.length);
257 0 : API3.warn(" started attacks ==================");
258 0 : for (let attackType in this.startedAttacks)
259 0 : for (let attack of this.startedAttacks[attackType])
260 0 : API3.warn(" plan " + attack.name + " type " + attackType + " state " + attack.state + " units " + attack.unitCollection.length);
261 0 : API3.warn(" ==================================");
262 : }
263 :
264 0 : this.checkEvents(gameState, events);
265 0 : const unexecutedAttacks = {
266 : [PETRA.AttackPlan.TYPE_RUSH]: 0,
267 : [PETRA.AttackPlan.TYPE_RAID]: 0,
268 : [PETRA.AttackPlan.TYPE_DEFAULT]: 0,
269 : [PETRA.AttackPlan.TYPE_HUGE_ATTACK]: 0
270 : };
271 0 : for (let attackType in this.upcomingAttacks)
272 : {
273 0 : for (let i = 0; i < this.upcomingAttacks[attackType].length; ++i)
274 : {
275 0 : let attack = this.upcomingAttacks[attackType][i];
276 0 : attack.checkEvents(gameState, events);
277 :
278 0 : if (attack.isStarted())
279 0 : API3.warn("Petra problem in attackManager: attack in preparation has already started ???");
280 :
281 0 : let updateStep = attack.updatePreparation(gameState);
282 : // now we're gonna check if the preparation time is over
283 0 : if (updateStep === PETRA.AttackPlan.PREPARATION_KEEP_GOING || attack.isPaused())
284 : {
285 : // just chillin'
286 0 : if (attack.state === PETRA.AttackPlan.STATE_UNEXECUTED)
287 0 : ++unexecutedAttacks[attackType];
288 : }
289 0 : else if (updateStep === PETRA.AttackPlan.PREPARATION_FAILED)
290 : {
291 0 : if (this.Config.debug > 1)
292 0 : API3.warn("Attack Manager: " + attack.getType() + " plan " + attack.getName() + " aborted.");
293 0 : attack.Abort(gameState);
294 0 : this.upcomingAttacks[attackType].splice(i--, 1);
295 : }
296 0 : else if (updateStep === PETRA.AttackPlan.PREPARATION_START)
297 : {
298 0 : if (attack.StartAttack(gameState))
299 : {
300 0 : if (this.Config.debug > 1)
301 0 : API3.warn("Attack Manager: Starting " + attack.getType() + " plan " + attack.getName());
302 0 : if (this.Config.chat)
303 0 : PETRA.chatLaunchAttack(gameState, attack.targetPlayer, attack.getType());
304 0 : this.startedAttacks[attackType].push(attack);
305 : }
306 : else
307 0 : attack.Abort(gameState);
308 0 : this.upcomingAttacks[attackType].splice(i--, 1);
309 : }
310 : }
311 : }
312 :
313 0 : for (let attackType in this.startedAttacks)
314 : {
315 0 : for (let i = 0; i < this.startedAttacks[attackType].length; ++i)
316 : {
317 0 : let attack = this.startedAttacks[attackType][i];
318 0 : attack.checkEvents(gameState, events);
319 : // okay so then we'll update the attack.
320 0 : if (attack.isPaused())
321 0 : continue;
322 0 : let remaining = attack.update(gameState, events);
323 0 : if (!remaining)
324 : {
325 0 : if (this.Config.debug > 1)
326 0 : API3.warn("Military Manager: " + attack.getType() + " plan " + attack.getName() + " is finished with remaining " + remaining);
327 0 : attack.Abort(gameState);
328 0 : this.startedAttacks[attackType].splice(i--, 1);
329 : }
330 : }
331 : }
332 :
333 : // creating plans after updating because an aborted plan might be reused in that case.
334 :
335 0 : let barracksNb = gameState.getOwnEntitiesByClass("Barracks", true).filter(API3.Filters.isBuilt()).length;
336 0 : if (this.rushNumber < this.maxRushes && barracksNb >= 1)
337 : {
338 0 : if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RUSH] === 0)
339 : {
340 : // we have a barracks and we want to rush, rush.
341 0 : let data = { "targetSize": this.rushSize[this.rushNumber] };
342 0 : const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RUSH, data);
343 0 : if (!attackPlan.failed)
344 : {
345 0 : if (this.Config.debug > 1)
346 0 : API3.warn("Military Manager: Rushing plan " + this.totalNumber + " with maxRushes " + this.maxRushes);
347 0 : this.totalNumber++;
348 0 : attackPlan.init(gameState);
349 0 : this.upcomingAttacks[PETRA.AttackPlan.TYPE_RUSH].push(attackPlan);
350 : }
351 0 : this.rushNumber++;
352 : }
353 : }
354 0 : else if (unexecutedAttacks[PETRA.AttackPlan.TYPE_DEFAULT] == 0 && unexecutedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK] == 0 &&
355 : this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length <
356 : Math.min(2, 1 + Math.round(gameState.getPopulationMax() / 100)) &&
357 : (this.startedAttacks[PETRA.AttackPlan.TYPE_DEFAULT].length + this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length == 0 ||
358 : gameState.getPopulationMax() - gameState.getPopulation() > 12))
359 : {
360 0 : if (barracksNb >= 1 && (gameState.currentPhase() > 1 || gameState.isResearching(gameState.getPhaseName(2))) ||
361 : !gameState.ai.HQ.hasPotentialBase()) // if we have no base ... nothing else to do than attack
362 : {
363 0 : const type = this.attackNumber < 2 || this.startedAttacks[PETRA.AttackPlan.TYPE_HUGE_ATTACK].length > 0 ? PETRA.AttackPlan.TYPE_DEFAULT : PETRA.AttackPlan.TYPE_HUGE_ATTACK;
364 0 : let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, type);
365 0 : if (attackPlan.failed)
366 0 : this.attackPlansEncounteredWater = true; // hack
367 : else
368 : {
369 0 : if (this.Config.debug > 1)
370 0 : API3.warn("Military Manager: Creating the plan " + type + " " + this.totalNumber);
371 0 : this.totalNumber++;
372 0 : attackPlan.init(gameState);
373 0 : this.upcomingAttacks[type].push(attackPlan);
374 : }
375 0 : this.attackNumber++;
376 : }
377 : }
378 :
379 0 : if (unexecutedAttacks[PETRA.AttackPlan.TYPE_RAID] === 0 && gameState.ai.HQ.defenseManager.targetList.length)
380 : {
381 : let target;
382 0 : for (let targetId of gameState.ai.HQ.defenseManager.targetList)
383 : {
384 0 : target = gameState.getEntityById(targetId);
385 0 : if (!target)
386 0 : continue;
387 0 : if (gameState.isPlayerEnemy(target.owner()))
388 0 : break;
389 0 : target = undefined;
390 : }
391 0 : if (target) // prepare a raid against this target
392 0 : this.raidTargetEntity(gameState, target);
393 : }
394 :
395 : // Check if we have some unused ranged siege unit which could do something useful while waiting
396 0 : if (this.Config.difficulty > PETRA.DIFFICULTY_VERY_EASY && gameState.ai.playedTurn % 5 == 0)
397 0 : this.assignBombers(gameState);
398 : };
399 :
400 0 : PETRA.AttackManager.prototype.getPlan = function(planName)
401 : {
402 0 : for (let attackType in this.upcomingAttacks)
403 : {
404 0 : for (let attack of this.upcomingAttacks[attackType])
405 0 : if (attack.getName() == planName)
406 0 : return attack;
407 : }
408 0 : for (let attackType in this.startedAttacks)
409 : {
410 0 : for (let attack of this.startedAttacks[attackType])
411 0 : if (attack.getName() == planName)
412 0 : return attack;
413 : }
414 0 : return undefined;
415 : };
416 :
417 0 : PETRA.AttackManager.prototype.pausePlan = function(planName)
418 : {
419 0 : let attack = this.getPlan(planName);
420 0 : if (attack)
421 0 : attack.setPaused(true);
422 : };
423 :
424 0 : PETRA.AttackManager.prototype.unpausePlan = function(planName)
425 : {
426 0 : let attack = this.getPlan(planName);
427 0 : if (attack)
428 0 : attack.setPaused(false);
429 : };
430 :
431 0 : PETRA.AttackManager.prototype.pauseAllPlans = function()
432 : {
433 0 : for (let attackType in this.upcomingAttacks)
434 0 : for (let attack of this.upcomingAttacks[attackType])
435 0 : attack.setPaused(true);
436 :
437 0 : for (let attackType in this.startedAttacks)
438 0 : for (let attack of this.startedAttacks[attackType])
439 0 : attack.setPaused(true);
440 : };
441 :
442 0 : PETRA.AttackManager.prototype.unpauseAllPlans = function()
443 : {
444 0 : for (let attackType in this.upcomingAttacks)
445 0 : for (let attack of this.upcomingAttacks[attackType])
446 0 : attack.setPaused(false);
447 :
448 0 : for (let attackType in this.startedAttacks)
449 0 : for (let attack of this.startedAttacks[attackType])
450 0 : attack.setPaused(false);
451 : };
452 :
453 0 : PETRA.AttackManager.prototype.getAttackInPreparation = function(type)
454 : {
455 0 : return this.upcomingAttacks[type].length ? this.upcomingAttacks[type][0] : undefined;
456 : };
457 :
458 : /**
459 : * Determine which player should be attacked: when called when starting the attack,
460 : * attack.targetPlayer is undefined and in that case, we keep track of the chosen target
461 : * for future attacks.
462 : */
463 0 : PETRA.AttackManager.prototype.getEnemyPlayer = function(gameState, attack)
464 : {
465 : let enemyPlayer;
466 :
467 : // First check if there is a preferred enemy based on our victory conditions.
468 : // If both wonder and relic, choose randomly between them TODO should combine decisions
469 :
470 0 : if (gameState.getVictoryConditions().has("wonder"))
471 0 : enemyPlayer = this.getWonderEnemyPlayer(gameState, attack);
472 :
473 0 : if (gameState.getVictoryConditions().has("capture_the_relic"))
474 0 : if (!enemyPlayer || randBool())
475 0 : enemyPlayer = this.getRelicEnemyPlayer(gameState, attack) || enemyPlayer;
476 :
477 0 : if (enemyPlayer)
478 0 : return enemyPlayer;
479 :
480 0 : let veto = {};
481 0 : for (let i in this.defeated)
482 0 : veto[i] = true;
483 : // No rush if enemy too well defended (i.e. iberians)
484 0 : if (attack.type === PETRA.AttackPlan.TYPE_RUSH)
485 : {
486 0 : for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
487 : {
488 0 : if (!gameState.isPlayerEnemy(i) || veto[i])
489 0 : continue;
490 0 : if (this.defeated[i])
491 0 : continue;
492 0 : let enemyDefense = 0;
493 0 : for (let ent of gameState.getEnemyStructures(i).values())
494 0 : if (ent.hasClasses(["Tower", "WallTower", "Fortress"]))
495 0 : enemyDefense++;
496 0 : if (enemyDefense > 6)
497 0 : veto[i] = true;
498 : }
499 : }
500 :
501 : // then if not a huge attack, continue attacking our previous target as long as it has some entities,
502 : // otherwise target the most accessible one
503 0 : if (attack.type !== PETRA.AttackPlan.TYPE_HUGE_ATTACK)
504 : {
505 0 : if (attack.targetPlayer === undefined && this.currentEnemyPlayer !== undefined &&
506 : !this.defeated[this.currentEnemyPlayer] &&
507 : gameState.isPlayerEnemy(this.currentEnemyPlayer) &&
508 : gameState.getEntities(this.currentEnemyPlayer).hasEntities())
509 0 : return this.currentEnemyPlayer;
510 :
511 : let distmin;
512 : let ccmin;
513 0 : let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
514 0 : for (let ourcc of ccEnts.values())
515 : {
516 0 : if (ourcc.owner() != PlayerID)
517 0 : continue;
518 0 : let ourPos = ourcc.position();
519 0 : let access = PETRA.getLandAccess(gameState, ourcc);
520 0 : for (let enemycc of ccEnts.values())
521 : {
522 0 : if (veto[enemycc.owner()])
523 0 : continue;
524 0 : if (!gameState.isPlayerEnemy(enemycc.owner()))
525 0 : continue;
526 0 : if (access !== PETRA.getLandAccess(gameState, enemycc))
527 0 : continue;
528 0 : let dist = API3.SquareVectorDistance(ourPos, enemycc.position());
529 0 : if (distmin && dist > distmin)
530 0 : continue;
531 0 : ccmin = enemycc;
532 0 : distmin = dist;
533 : }
534 : }
535 0 : if (ccmin)
536 : {
537 0 : enemyPlayer = ccmin.owner();
538 0 : if (attack.targetPlayer === undefined)
539 0 : this.currentEnemyPlayer = enemyPlayer;
540 0 : return enemyPlayer;
541 : }
542 : }
543 :
544 : // then let's target our strongest enemy (basically counting enemies units)
545 : // with priority to enemies with civ center
546 0 : let max = 0;
547 0 : for (let i = 1; i < gameState.sharedScript.playersData.length; ++i)
548 : {
549 0 : if (veto[i])
550 0 : continue;
551 0 : if (!gameState.isPlayerEnemy(i))
552 0 : continue;
553 0 : let enemyCount = 0;
554 0 : let enemyCivCentre = false;
555 0 : for (let ent of gameState.getEntities(i).values())
556 : {
557 0 : enemyCount++;
558 0 : if (ent.hasClass("CivCentre"))
559 0 : enemyCivCentre = true;
560 : }
561 0 : if (enemyCivCentre)
562 0 : enemyCount += 500;
563 0 : if (!enemyCount || enemyCount < max)
564 0 : continue;
565 0 : max = enemyCount;
566 0 : enemyPlayer = i;
567 : }
568 0 : if (attack.targetPlayer === undefined)
569 0 : this.currentEnemyPlayer = enemyPlayer;
570 0 : return enemyPlayer;
571 : };
572 :
573 : /**
574 : * Target the player with the most advanced wonder.
575 : * TODO currently the first built wonder is kept, should chek on the minimum wonderDuration left instead.
576 : */
577 0 : PETRA.AttackManager.prototype.getWonderEnemyPlayer = function(gameState, attack)
578 : {
579 : let enemyPlayer;
580 : let enemyWonder;
581 : let moreAdvanced;
582 0 : for (let wonder of gameState.getEnemyStructures().filter(API3.Filters.byClass("Wonder")).values())
583 : {
584 0 : if (wonder.owner() == 0)
585 0 : continue;
586 0 : let progress = wonder.foundationProgress();
587 0 : if (progress === undefined)
588 : {
589 0 : enemyWonder = wonder;
590 0 : break;
591 : }
592 0 : if (enemyWonder && moreAdvanced > progress)
593 0 : continue;
594 0 : enemyWonder = wonder;
595 0 : moreAdvanced = progress;
596 : }
597 0 : if (enemyWonder)
598 : {
599 0 : enemyPlayer = enemyWonder.owner();
600 0 : if (attack.targetPlayer === undefined)
601 0 : this.currentEnemyPlayer = enemyPlayer;
602 : }
603 0 : return enemyPlayer;
604 : };
605 :
606 : /**
607 : * Target the player with the most relics (including gaia).
608 : */
609 0 : PETRA.AttackManager.prototype.getRelicEnemyPlayer = function(gameState, attack)
610 : {
611 : let enemyPlayer;
612 0 : let allRelics = gameState.updatingGlobalCollection("allRelics", API3.Filters.byClass("Relic"));
613 0 : let maxRelicsOwned = 0;
614 0 : for (let i = 0; i < gameState.sharedScript.playersData.length; ++i)
615 : {
616 0 : if (!gameState.isPlayerEnemy(i) || this.defeated[i] ||
617 : i == 0 && !gameState.ai.HQ.victoryManager.tryCaptureGaiaRelic)
618 0 : continue;
619 :
620 0 : let relicsCount = allRelics.filter(relic => relic.owner() == i).length;
621 0 : if (relicsCount <= maxRelicsOwned)
622 0 : continue;
623 0 : maxRelicsOwned = relicsCount;
624 0 : enemyPlayer = i;
625 : }
626 0 : if (enemyPlayer !== undefined)
627 : {
628 0 : if (attack.targetPlayer === undefined)
629 0 : this.currentEnemyPlayer = enemyPlayer;
630 0 : if (enemyPlayer == 0)
631 0 : gameState.ai.HQ.victoryManager.resetCaptureGaiaRelic(gameState);
632 : }
633 0 : return enemyPlayer;
634 : };
635 :
636 : /** f.e. if we have changed diplomacy with another player. */
637 0 : PETRA.AttackManager.prototype.cancelAttacksAgainstPlayer = function(gameState, player)
638 : {
639 0 : for (let attackType in this.upcomingAttacks)
640 0 : for (let attack of this.upcomingAttacks[attackType])
641 0 : if (attack.targetPlayer === player)
642 0 : attack.targetPlayer = undefined;
643 :
644 0 : for (let attackType in this.startedAttacks)
645 0 : for (let i = 0; i < this.startedAttacks[attackType].length; ++i)
646 : {
647 0 : let attack = this.startedAttacks[attackType][i];
648 0 : if (attack.targetPlayer === player)
649 : {
650 0 : attack.Abort(gameState);
651 0 : this.startedAttacks[attackType].splice(i--, 1);
652 : }
653 : }
654 : };
655 :
656 0 : PETRA.AttackManager.prototype.raidTargetEntity = function(gameState, ent)
657 : {
658 0 : let data = { "target": ent };
659 0 : const attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, PETRA.AttackPlan.TYPE_RAID, data);
660 0 : if (attackPlan.failed)
661 0 : return null;
662 0 : if (this.Config.debug > 1)
663 0 : API3.warn("Military Manager: Raiding plan " + this.totalNumber);
664 0 : this.raidNumber++;
665 0 : this.totalNumber++;
666 0 : attackPlan.init(gameState);
667 0 : this.upcomingAttacks[PETRA.AttackPlan.TYPE_RAID].push(attackPlan);
668 0 : return attackPlan;
669 : };
670 :
671 : /**
672 : * Return the number of units from any of our attacking armies around this position
673 : */
674 0 : PETRA.AttackManager.prototype.numAttackingUnitsAround = function(pos, dist)
675 : {
676 0 : let num = 0;
677 0 : for (let attackType in this.startedAttacks)
678 0 : for (let attack of this.startedAttacks[attackType])
679 : {
680 0 : if (!attack.position) // this attack may be inside a transport
681 0 : continue;
682 0 : if (API3.SquareVectorDistance(pos, attack.position) < dist*dist)
683 0 : num += attack.unitCollection.length;
684 : }
685 0 : return num;
686 : };
687 :
688 : /**
689 : * Switch defense armies into an attack one against the given target
690 : * data.range: transform all defense armies inside range of the target into a new attack
691 : * data.armyID: transform only the defense army ID into a new attack
692 : * data.uniqueTarget: the attack will stop when the target is destroyed or captured
693 : */
694 0 : PETRA.AttackManager.prototype.switchDefenseToAttack = function(gameState, target, data)
695 : {
696 0 : if (!target || !target.position())
697 0 : return false;
698 0 : if (!data.range && !data.armyID)
699 : {
700 0 : API3.warn(" attackManager.switchDefenseToAttack inconsistent data " + uneval(data));
701 0 : return false;
702 : }
703 0 : let attackData = data.uniqueTarget ? { "uniqueTargetId": target.id() } : undefined;
704 0 : let pos = target.position();
705 0 : const attackType = PETRA.AttackPlan.TYPE_DEFAULT;
706 0 : let attackPlan = new PETRA.AttackPlan(gameState, this.Config, this.totalNumber, attackType, attackData);
707 0 : if (attackPlan.failed)
708 0 : return false;
709 0 : this.totalNumber++;
710 0 : attackPlan.init(gameState);
711 0 : this.startedAttacks[attackType].push(attackPlan);
712 :
713 0 : let targetAccess = PETRA.getLandAccess(gameState, target);
714 0 : for (let army of gameState.ai.HQ.defenseManager.armies)
715 : {
716 0 : if (data.range)
717 : {
718 0 : army.recalculatePosition(gameState);
719 0 : if (API3.SquareVectorDistance(pos, army.foePosition) > data.range * data.range)
720 0 : continue;
721 : }
722 0 : else if (army.ID != +data.armyID)
723 0 : continue;
724 :
725 0 : while (army.foeEntities.length > 0)
726 0 : army.removeFoe(gameState, army.foeEntities[0]);
727 0 : while (army.ownEntities.length > 0)
728 : {
729 0 : let unitId = army.ownEntities[0];
730 0 : army.removeOwn(gameState, unitId);
731 0 : let unit = gameState.getEntityById(unitId);
732 0 : let accessOk = unit.getMetadata(PlayerID, "transport") !== undefined ||
733 : unit.position() && PETRA.getLandAccess(gameState, unit) == targetAccess;
734 0 : if (unit && accessOk && attackPlan.isAvailableUnit(gameState, unit))
735 : {
736 0 : unit.setMetadata(PlayerID, "plan", attackPlan.name);
737 0 : unit.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK);
738 0 : attackPlan.unitCollection.updateEnt(unit);
739 : }
740 : }
741 : }
742 0 : if (!attackPlan.unitCollection.hasEntities())
743 : {
744 0 : attackPlan.Abort(gameState);
745 0 : return false;
746 : }
747 0 : for (let unit of attackPlan.unitCollection.values())
748 0 : unit.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_ATTACK);
749 0 : attackPlan.targetPlayer = target.owner();
750 0 : attackPlan.targetPos = pos;
751 0 : attackPlan.target = target;
752 0 : attackPlan.state = PETRA.AttackPlan.STATE_ARRIVED;
753 0 : return true;
754 : };
755 :
756 0 : PETRA.AttackManager.prototype.Serialize = function()
757 : {
758 0 : let properties = {
759 : "totalNumber": this.totalNumber,
760 : "attackNumber": this.attackNumber,
761 : "rushNumber": this.rushNumber,
762 : "raidNumber": this.raidNumber,
763 : "debugTime": this.debugTime,
764 : "maxRushes": this.maxRushes,
765 : "rushSize": this.rushSize,
766 : "currentEnemyPlayer": this.currentEnemyPlayer,
767 : "defeated": this.defeated
768 : };
769 :
770 0 : let upcomingAttacks = {};
771 0 : for (let key in this.upcomingAttacks)
772 : {
773 0 : upcomingAttacks[key] = [];
774 0 : for (let attack of this.upcomingAttacks[key])
775 0 : upcomingAttacks[key].push(attack.Serialize());
776 : }
777 :
778 0 : let startedAttacks = {};
779 0 : for (let key in this.startedAttacks)
780 : {
781 0 : startedAttacks[key] = [];
782 0 : for (let attack of this.startedAttacks[key])
783 0 : startedAttacks[key].push(attack.Serialize());
784 : }
785 :
786 0 : return { "properties": properties, "upcomingAttacks": upcomingAttacks, "startedAttacks": startedAttacks };
787 : };
788 :
789 0 : PETRA.AttackManager.prototype.Deserialize = function(gameState, data)
790 : {
791 0 : for (let key in data.properties)
792 0 : this[key] = data.properties[key];
793 :
794 0 : this.upcomingAttacks = {};
795 0 : for (let key in data.upcomingAttacks)
796 : {
797 0 : this.upcomingAttacks[key] = [];
798 0 : for (let dataAttack of data.upcomingAttacks[key])
799 : {
800 0 : let attack = new PETRA.AttackPlan(gameState, this.Config, dataAttack.properties.name);
801 0 : attack.Deserialize(gameState, dataAttack);
802 0 : attack.init(gameState);
803 0 : this.upcomingAttacks[key].push(attack);
804 : }
805 : }
806 :
807 0 : this.startedAttacks = {};
808 0 : for (let key in data.startedAttacks)
809 : {
810 0 : this.startedAttacks[key] = [];
811 0 : for (let dataAttack of data.startedAttacks[key])
812 : {
813 0 : let attack = new PETRA.AttackPlan(gameState, this.Config, dataAttack.properties.name);
814 0 : attack.Deserialize(gameState, dataAttack);
815 0 : attack.init(gameState);
816 0 : this.startedAttacks[key].push(attack);
817 : }
818 : }
819 : };
|