Line data Source code
1 : /**
2 : * Armies used by the defense manager.
3 : * An army is a collection of own entities and enemy entities.
4 : *
5 : * Types of armies:
6 : * "default": army to counter an invading army
7 : * "capturing": army set to capture a gaia building or recover capture points to one of its own structures
8 : * It must contain only one foe (the building to capture) and never be merged
9 : */
10 0 : PETRA.DefenseArmy = function(gameState, foeEntities, type)
11 : {
12 0 : this.ID = gameState.ai.uniqueIDs.armies++;
13 0 : this.type = type || "default";
14 :
15 0 : this.Config = gameState.ai.Config;
16 0 : this.compactSize = this.Config.Defense.armyCompactSize;
17 0 : this.breakawaySize = this.Config.Defense.armyBreakawaySize;
18 :
19 : // average
20 0 : this.foePosition = [0, 0];
21 0 : this.positionLastUpdate = gameState.ai.elapsedTime;
22 :
23 : // Some caching
24 : // A list of our defenders that were tasked with attacking a particular unit
25 : // This doesn't mean that they actually are since they could move on to something else on their own.
26 0 : this.assignedAgainst = {};
27 : // who we assigned against, for quick removal.
28 0 : this.assignedTo = {};
29 :
30 0 : this.foeEntities = [];
31 0 : this.foeStrength = 0;
32 :
33 0 : this.ownEntities = [];
34 0 : this.ownStrength = 0;
35 :
36 : // actually add units
37 0 : for (let id of foeEntities)
38 0 : this.addFoe(gameState, id, true);
39 :
40 0 : this.recalculatePosition(gameState, true);
41 :
42 0 : return true;
43 : };
44 :
45 : /**
46 : * add an entity to the enemy army
47 : * Will return true if the entity was added and false otherwise.
48 : * won't recalculate our position but will dirty it.
49 : * force is true at army creation or when merging armies, so in this case we should add it even if far
50 : */
51 0 : PETRA.DefenseArmy.prototype.addFoe = function(gameState, enemyId, force)
52 : {
53 0 : if (this.foeEntities.indexOf(enemyId) !== -1)
54 0 : return false;
55 0 : let ent = gameState.getEntityById(enemyId);
56 0 : if (!ent || !ent.position())
57 0 : return false;
58 :
59 : // check distance
60 0 : if (!force && API3.SquareVectorDistance(ent.position(), this.foePosition) > this.compactSize)
61 0 : return false;
62 :
63 0 : this.foeEntities.push(enemyId);
64 0 : this.assignedAgainst[enemyId] = [];
65 0 : this.positionLastUpdate = 0;
66 0 : this.evaluateStrength(ent);
67 0 : ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
68 :
69 0 : return true;
70 : };
71 :
72 : /**
73 : * returns true if the entity was removed and false otherwise.
74 : * TODO: when there is a technology update, we should probably recompute the strengths, or weird stuffs will happen.
75 : */
76 0 : PETRA.DefenseArmy.prototype.removeFoe = function(gameState, enemyId, enemyEntity)
77 : {
78 0 : let idx = this.foeEntities.indexOf(enemyId);
79 0 : if (idx === -1)
80 0 : return false;
81 :
82 0 : this.foeEntities.splice(idx, 1);
83 :
84 0 : this.assignedAgainst[enemyId] = undefined;
85 0 : for (let to in this.assignedTo)
86 0 : if (this.assignedTo[to] == enemyId)
87 0 : this.assignedTo[to] = undefined;
88 :
89 0 : let ent = enemyEntity ? enemyEntity : gameState.getEntityById(enemyId);
90 0 : if (ent) // TODO recompute strength when no entities (could happen if capture+destroy)
91 : {
92 0 : this.evaluateStrength(ent, false, true);
93 0 : ent.setMetadata(PlayerID, "PartOfArmy", undefined);
94 : }
95 :
96 0 : return true;
97 : };
98 :
99 : /**
100 : * adds a defender but doesn't assign him yet.
101 : * force is true when merging armies, so in this case we should add it even if no position as it can be in a ship
102 : */
103 0 : PETRA.DefenseArmy.prototype.addOwn = function(gameState, id, force)
104 : {
105 0 : if (this.ownEntities.indexOf(id) !== -1)
106 0 : return false;
107 0 : let ent = gameState.getEntityById(id);
108 0 : if (!ent || !ent.position() && !force)
109 0 : return false;
110 :
111 0 : this.ownEntities.push(id);
112 0 : this.evaluateStrength(ent, true);
113 0 : ent.setMetadata(PlayerID, "PartOfArmy", this.ID);
114 0 : this.assignedTo[id] = 0;
115 :
116 0 : let plan = ent.getMetadata(PlayerID, "plan");
117 0 : if (plan !== undefined)
118 0 : ent.setMetadata(PlayerID, "plan", -2);
119 : else
120 0 : ent.setMetadata(PlayerID, "plan", -3);
121 0 : let subrole = ent.getMetadata(PlayerID, "subrole");
122 0 : if (subrole === undefined || subrole !== PETRA.Worker.SUBROLE_DEFENDER)
123 0 : ent.setMetadata(PlayerID, "formerSubrole", subrole);
124 0 : ent.setMetadata(PlayerID, "subrole", PETRA.Worker.SUBROLE_DEFENDER);
125 0 : return true;
126 : };
127 :
128 0 : PETRA.DefenseArmy.prototype.removeOwn = function(gameState, id, Entity)
129 : {
130 0 : let idx = this.ownEntities.indexOf(id);
131 0 : if (idx === -1)
132 0 : return false;
133 :
134 0 : this.ownEntities.splice(idx, 1);
135 :
136 0 : if (this.assignedTo[id] !== 0)
137 : {
138 0 : let temp = this.assignedAgainst[this.assignedTo[id]];
139 0 : if (temp)
140 0 : temp.splice(temp.indexOf(id), 1);
141 : }
142 0 : this.assignedTo[id] = undefined;
143 :
144 0 : let ent = Entity ? Entity : gameState.getEntityById(id);
145 0 : if (!ent)
146 0 : return true;
147 :
148 0 : this.evaluateStrength(ent, true, true);
149 0 : ent.setMetadata(PlayerID, "PartOfArmy", undefined);
150 0 : if (ent.getMetadata(PlayerID, "plan") === -2)
151 0 : ent.setMetadata(PlayerID, "plan", -1);
152 : else
153 0 : ent.setMetadata(PlayerID, "plan", undefined);
154 :
155 0 : let formerSubrole = ent.getMetadata(PlayerID, "formerSubrole");
156 0 : if (formerSubrole !== undefined)
157 0 : ent.setMetadata(PlayerID, "subrole", formerSubrole);
158 : else
159 0 : ent.setMetadata(PlayerID, "subrole", undefined);
160 0 : ent.setMetadata(PlayerID, "formerSubrole", undefined);
161 :
162 : // Remove from transport plan if not yet on Board
163 0 : if (ent.getMetadata(PlayerID, "transport") !== undefined)
164 : {
165 0 : let plan = gameState.ai.HQ.navalManager.getPlan(ent.getMetadata(PlayerID, "transport"));
166 0 : if (plan && plan.state === PETRA.TransportPlan.BOARDING && ent.position())
167 0 : plan.removeUnit(gameState, ent);
168 : }
169 :
170 : /*
171 : // TODO be sure that all units in the transport need the cancelation
172 : if (!ent.position()) // this unit must still be in a transport plan ... try to cancel it
173 : {
174 : let planID = ent.getMetadata(PlayerID, "transport");
175 : // no plans must mean that the unit was in a ship which was destroyed, so do nothing
176 : if (planID)
177 : {
178 : if (gameState.ai.Config.debug > 0)
179 : warn("ent from army still in transport plan: plan " + planID + " canceled");
180 : let plan = gameState.ai.HQ.navalManager.getPlan(planID);
181 : if (plan && !plan.canceled)
182 : plan.cancelTransport(gameState);
183 : }
184 : }
185 : */
186 :
187 0 : return true;
188 : };
189 :
190 : /**
191 : * resets the army properly.
192 : * assumes we already cleared dead units.
193 : */
194 0 : PETRA.DefenseArmy.prototype.clear = function(gameState)
195 : {
196 0 : while (this.foeEntities.length > 0)
197 0 : this.removeFoe(gameState, this.foeEntities[0]);
198 :
199 : // Go back to our or allied territory if needed
200 0 : let posOwn = [0, 0];
201 0 : let nOwn = 0;
202 0 : let posAlly = [0, 0];
203 0 : let nAlly = 0;
204 0 : let posOther = [0, 0];
205 0 : let nOther = 0;
206 0 : for (let entId of this.ownEntities)
207 : {
208 0 : let ent = gameState.getEntityById(entId);
209 0 : if (!ent || !ent.position())
210 0 : continue;
211 0 : let pos = ent.position();
212 0 : let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(pos);
213 0 : if (territoryOwner === PlayerID)
214 : {
215 0 : posOwn[0] += pos[0];
216 0 : posOwn[1] += pos[1];
217 0 : ++nOwn;
218 : }
219 0 : else if (gameState.isPlayerMutualAlly(territoryOwner))
220 : {
221 0 : posAlly[0] += pos[0];
222 0 : posAlly[1] += pos[1];
223 0 : ++nAlly;
224 : }
225 : else
226 : {
227 0 : posOther[0] += pos[0];
228 0 : posOther[1] += pos[1];
229 0 : ++nOther;
230 : }
231 : }
232 : let destination;
233 : let defensiveFound;
234 : let distmin;
235 0 : let radius = 0;
236 0 : if (nOwn > 0)
237 0 : destination = [posOwn[0]/nOwn, posOwn[1]/nOwn];
238 0 : else if (nAlly > 0)
239 0 : destination = [posAlly[0]/nAlly, posAlly[1]/nAlly];
240 : else
241 : {
242 0 : posOther[0] /= nOther;
243 0 : posOther[1] /= nOther;
244 0 : let armyAccess = gameState.ai.accessibility.getAccessValue(posOther);
245 0 : for (let struct of gameState.getAllyStructures().values())
246 : {
247 0 : let pos = struct.position();
248 0 : if (!pos || !gameState.isPlayerMutualAlly(gameState.ai.HQ.territoryMap.getOwner(pos)))
249 0 : continue;
250 0 : if (PETRA.getLandAccess(gameState, struct) !== armyAccess)
251 0 : continue;
252 0 : let defensiveStruct = struct.hasDefensiveFire();
253 0 : if (defensiveFound && !defensiveStruct)
254 0 : continue;
255 0 : let dist = API3.SquareVectorDistance(posOther, pos);
256 0 : if (distmin && dist > distmin && (defensiveFound || !defensiveStruct))
257 0 : continue;
258 0 : if (defensiveStruct)
259 0 : defensiveFound = true;
260 0 : distmin = dist;
261 0 : destination = pos;
262 0 : radius = struct.obstructionRadius().max;
263 : }
264 : }
265 0 : while (this.ownEntities.length > 0)
266 : {
267 0 : let entId = this.ownEntities[0];
268 0 : this.removeOwn(gameState, entId);
269 0 : let ent = gameState.getEntityById(entId);
270 0 : if (ent)
271 : {
272 0 : if (!ent.position() || ent.getMetadata(PlayerID, "transport") !== undefined ||
273 : ent.getMetadata(PlayerID, "transporter") !== undefined)
274 0 : continue;
275 0 : if (ent.healthLevel() < this.Config.garrisonHealthLevel.low &&
276 : gameState.ai.HQ.defenseManager.garrisonAttackedUnit(gameState, ent))
277 0 : continue;
278 :
279 0 : if (destination && !gameState.isPlayerMutualAlly(gameState.ai.HQ.territoryMap.getOwner(ent.position())))
280 0 : ent.moveToRange(destination[0], destination[1], radius, radius + 5);
281 : else
282 0 : ent.stopMoving();
283 : }
284 : }
285 :
286 0 : this.assignedAgainst = {};
287 0 : this.assignedTo = {};
288 :
289 0 : this.recalculateStrengths(gameState);
290 0 : this.recalculatePosition(gameState);
291 : };
292 :
293 0 : PETRA.DefenseArmy.prototype.assignUnit = function(gameState, entID)
294 : {
295 : // we'll assume this defender is ours already.
296 : // we'll also override any previous assignment
297 :
298 0 : let ent = gameState.getEntityById(entID);
299 0 : if (!ent || !ent.position())
300 0 : return false;
301 :
302 : // try to return its resources, and if any, the attack order will be queued
303 0 : let queued = PETRA.returnResources(gameState, ent);
304 :
305 : let idMin;
306 : let distMin;
307 : let idMinAll;
308 : let distMinAll;
309 0 : for (let id of this.foeEntities)
310 : {
311 0 : let eEnt = gameState.getEntityById(id);
312 0 : if (!eEnt || !eEnt.position()) // probably can't happen.
313 0 : continue;
314 :
315 0 : if (!ent.canAttackTarget(eEnt, PETRA.allowCapture(gameState, ent, eEnt)))
316 0 : continue;
317 :
318 0 : if (eEnt.hasClass("Unit") && eEnt.unitAIOrderData() && eEnt.unitAIOrderData().length &&
319 : eEnt.unitAIOrderData()[0].target && eEnt.unitAIOrderData()[0].target == entID)
320 : { // being attacked >>> target the unit
321 0 : idMin = id;
322 0 : break;
323 : }
324 :
325 : // already enough units against it
326 0 : if (this.assignedAgainst[id].length > 8 ||
327 : this.assignedAgainst[id].length > 5 && !eEnt.hasClass("Hero") && !PETRA.isSiegeUnit(eEnt))
328 0 : continue;
329 :
330 0 : let dist = API3.SquareVectorDistance(ent.position(), eEnt.position());
331 0 : if (idMinAll === undefined || dist < distMinAll)
332 : {
333 0 : idMinAll = id;
334 0 : distMinAll = dist;
335 : }
336 0 : if (this.assignedAgainst[id].length > 2)
337 0 : continue;
338 0 : if (idMin === undefined || dist < distMin)
339 : {
340 0 : idMin = id;
341 0 : distMin = dist;
342 : }
343 : }
344 :
345 : let idFoe;
346 0 : if (idMin !== undefined)
347 0 : idFoe = idMin;
348 0 : else if (idMinAll !== undefined)
349 0 : idFoe = idMinAll;
350 : else
351 0 : return false;
352 :
353 0 : let ownIndex = PETRA.getLandAccess(gameState, ent);
354 0 : let foeEnt = gameState.getEntityById(idFoe);
355 0 : let foePosition = foeEnt.position();
356 0 : let foeIndex = gameState.ai.accessibility.getAccessValue(foePosition);
357 0 : if (ownIndex == foeIndex || ent.hasClass("Ship"))
358 : {
359 0 : this.assignedTo[entID] = idFoe;
360 0 : this.assignedAgainst[idFoe].push(entID);
361 0 : ent.attack(idFoe, PETRA.allowCapture(gameState, ent, foeEnt), queued);
362 : }
363 : else
364 0 : gameState.ai.HQ.navalManager.requireTransport(gameState, ent, ownIndex, foeIndex, foePosition);
365 0 : return true;
366 : };
367 :
368 0 : PETRA.DefenseArmy.prototype.getType = function()
369 : {
370 0 : return this.type;
371 : };
372 :
373 0 : PETRA.DefenseArmy.prototype.getState = function()
374 : {
375 0 : if (!this.foeEntities.length)
376 0 : return 0;
377 0 : return 1;
378 : };
379 :
380 : /**
381 : * merge this army with another properly.
382 : * assumes units are in only one army.
383 : * also assumes that all have been properly cleaned up (no dead units).
384 : */
385 0 : PETRA.DefenseArmy.prototype.merge = function(gameState, otherArmy)
386 : {
387 : // copy over all parameters.
388 0 : for (let i in otherArmy.assignedAgainst)
389 : {
390 0 : if (this.assignedAgainst[i] === undefined)
391 0 : this.assignedAgainst[i] = otherArmy.assignedAgainst[i];
392 : else
393 0 : this.assignedAgainst[i] = this.assignedAgainst[i].concat(otherArmy.assignedAgainst[i]);
394 : }
395 0 : for (let i in otherArmy.assignedTo)
396 0 : this.assignedTo[i] = otherArmy.assignedTo[i];
397 :
398 0 : for (let id of otherArmy.foeEntities)
399 0 : this.addFoe(gameState, id, true);
400 : // TODO: reassign those ?
401 0 : for (let id of otherArmy.ownEntities)
402 0 : this.addOwn(gameState, id, true);
403 :
404 0 : this.recalculatePosition(gameState, true);
405 0 : this.recalculateStrengths(gameState);
406 :
407 0 : return true;
408 : };
409 :
410 0 : PETRA.DefenseArmy.prototype.needsDefenders = function(gameState)
411 : {
412 : let defenseRatio;
413 0 : let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(this.foePosition);
414 0 : if (territoryOwner == PlayerID)
415 0 : defenseRatio = this.Config.Defense.defenseRatio.own;
416 0 : else if (gameState.isPlayerAlly(territoryOwner))
417 : {
418 0 : defenseRatio = this.Config.Defense.defenseRatio.ally;
419 0 : let numExclusiveAllies = 0;
420 0 : for (let p = 1; p < gameState.sharedScript.playersData.length; ++p)
421 0 : if (p != territoryOwner && gameState.sharedScript.playersData[p].isAlly[territoryOwner])
422 0 : ++numExclusiveAllies;
423 0 : defenseRatio /= 1 + 0.5*Math.max(0, numExclusiveAllies-1);
424 : }
425 : else
426 0 : defenseRatio = this.Config.Defense.defenseRatio.neutral;
427 :
428 : // some preliminary checks because we don't update for tech so entStrength removed can be > entStrength added
429 0 : if (this.foeStrength <= 0 || this.ownStrength <= 0)
430 0 : this.recalculateStrengths(gameState);
431 :
432 0 : if (this.foeStrength * defenseRatio <= this.ownStrength)
433 0 : return false;
434 0 : return this.foeStrength * defenseRatio - this.ownStrength;
435 : };
436 :
437 :
438 : /** if not forced, will only recalculate if on a different turn. */
439 0 : PETRA.DefenseArmy.prototype.recalculatePosition = function(gameState, force)
440 : {
441 0 : if (!force && this.positionLastUpdate === gameState.ai.elapsedTime)
442 0 : return;
443 :
444 0 : let npos = 0;
445 0 : let pos = [0, 0];
446 0 : for (let id of this.foeEntities)
447 : {
448 0 : let ent = gameState.getEntityById(id);
449 0 : if (!ent || !ent.position())
450 0 : continue;
451 0 : npos++;
452 0 : let epos = ent.position();
453 0 : pos[0] += epos[0];
454 0 : pos[1] += epos[1];
455 : }
456 : // if npos = 0, the army must have been destroyed and will be removed next turn. keep previous position
457 0 : if (npos > 0)
458 : {
459 0 : this.foePosition[0] = pos[0]/npos;
460 0 : this.foePosition[1] = pos[1]/npos;
461 : }
462 :
463 0 : this.positionLastUpdate = gameState.ai.elapsedTime;
464 : };
465 :
466 0 : PETRA.DefenseArmy.prototype.recalculateStrengths = function(gameState)
467 : {
468 0 : this.ownStrength = 0;
469 0 : this.foeStrength = 0;
470 :
471 0 : for (let id of this.foeEntities)
472 0 : this.evaluateStrength(gameState.getEntityById(id));
473 0 : for (let id of this.ownEntities)
474 0 : this.evaluateStrength(gameState.getEntityById(id), true);
475 : };
476 :
477 : /** adds or remove the strength of the entity either to the enemy or to our units. */
478 0 : PETRA.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove)
479 : {
480 0 : if (!ent)
481 0 : return;
482 :
483 : let entStrength;
484 0 : if (ent.hasClass("Structure"))
485 : {
486 0 : if (ent.owner() !== PlayerID)
487 0 : entStrength = ent.getDefaultArrow() ? 6*ent.getDefaultArrow() : 4;
488 : else // small strength used only when we try to recover capture points
489 0 : entStrength = 2;
490 : }
491 : else
492 0 : entStrength = PETRA.getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance);
493 :
494 : // TODO adapt the getMaxStrength function for animals.
495 : // For the time being, just increase it for elephants as the returned value is too small.
496 0 : if (ent.hasClasses(["Animal+Elephant"]))
497 0 : entStrength *= 3;
498 :
499 0 : if (remove)
500 0 : entStrength *= -1;
501 :
502 0 : if (isOwn)
503 0 : this.ownStrength += entStrength;
504 : else
505 0 : this.foeStrength += entStrength;
506 : };
507 :
508 0 : PETRA.DefenseArmy.prototype.checkEvents = function(gameState, events)
509 : {
510 : // Warning the metadata is already cloned in shared.js. Futhermore, changes should be done before destroyEvents
511 : // otherwise it would remove the old entity from this army list
512 : // TODO we should may-be reevaluate the strength
513 0 : for (let evt of events.EntityRenamed) // take care of promoted and packed units
514 : {
515 0 : if (this.foeEntities.indexOf(evt.entity) !== -1)
516 : {
517 0 : let ent = gameState.getEntityById(evt.newentity);
518 0 : if (ent && ent.templateName().indexOf("resource|") !== -1) // corpse of animal killed
519 0 : continue;
520 0 : let idx = this.foeEntities.indexOf(evt.entity);
521 0 : this.foeEntities[idx] = evt.newentity;
522 0 : this.assignedAgainst[evt.newentity] = this.assignedAgainst[evt.entity];
523 0 : this.assignedAgainst[evt.entity] = undefined;
524 0 : for (let to in this.assignedTo)
525 0 : if (this.assignedTo[to] === evt.entity)
526 0 : this.assignedTo[to] = evt.newentity;
527 : }
528 0 : else if (this.ownEntities.indexOf(evt.entity) !== -1)
529 : {
530 0 : let idx = this.ownEntities.indexOf(evt.entity);
531 0 : this.ownEntities[idx] = evt.newentity;
532 0 : this.assignedTo[evt.newentity] = this.assignedTo[evt.entity];
533 0 : this.assignedTo[evt.entity] = undefined;
534 0 : for (let against in this.assignedAgainst)
535 : {
536 0 : if (!this.assignedAgainst[against])
537 0 : continue;
538 0 : if (this.assignedAgainst[against].indexOf(evt.entity) !== -1)
539 0 : this.assignedAgainst[against][this.assignedAgainst[against].indexOf(evt.entity)] = evt.newentity;
540 : }
541 : }
542 : }
543 :
544 0 : for (let evt of events.Garrison)
545 0 : this.removeFoe(gameState, evt.entity);
546 :
547 0 : for (let evt of events.OwnershipChanged) // captured
548 : {
549 0 : if (!gameState.isPlayerEnemy(evt.to))
550 0 : this.removeFoe(gameState, evt.entity);
551 0 : else if (evt.from === PlayerID)
552 0 : this.removeOwn(gameState, evt.entity);
553 : }
554 :
555 0 : for (let evt of events.Destroy)
556 : {
557 0 : let entityObj = evt.entityObj || undefined;
558 : // we may have capture+destroy, so do not trust owner and check all possibilities
559 0 : this.removeOwn(gameState, evt.entity, entityObj);
560 0 : this.removeFoe(gameState, evt.entity, entityObj);
561 : }
562 : };
563 :
564 0 : PETRA.DefenseArmy.prototype.update = function(gameState)
565 : {
566 0 : for (let entId of this.ownEntities)
567 : {
568 0 : let ent = gameState.getEntityById(entId);
569 0 : if (!ent)
570 0 : continue;
571 0 : let orderData = ent.unitAIOrderData();
572 0 : if (!orderData.length && !ent.getMetadata(PlayerID, "transport"))
573 0 : this.assignUnit(gameState, entId);
574 0 : else if (orderData.length && orderData[0].target && orderData[0].attackType && orderData[0].attackType === "Capture")
575 : {
576 0 : let target = gameState.getEntityById(orderData[0].target);
577 0 : if (target && !PETRA.allowCapture(gameState, ent, target))
578 0 : ent.attack(orderData[0].target, false);
579 : }
580 : }
581 :
582 0 : if (this.type == "capturing")
583 : {
584 0 : if (this.foeEntities.length && gameState.getEntityById(this.foeEntities[0]))
585 : {
586 : // Check if we still still some capturePoints to recover
587 : // and if not, remove this foe from the list (capture army have only one foe)
588 0 : let capture = gameState.getEntityById(this.foeEntities[0]).capturePoints();
589 0 : if (capture)
590 0 : for (let j = 0; j < capture.length; ++j)
591 0 : if (gameState.isPlayerEnemy(j) && capture[j] > 0)
592 0 : return [];
593 0 : this.removeFoe(gameState, this.foeEntities[0]);
594 : }
595 0 : return [];
596 : }
597 :
598 0 : let breakaways = [];
599 : // TODO: assign unassigned defenders, cleanup of a few things.
600 : // perhaps occasional strength recomputation
601 :
602 : // occasional update or breakaways, positions…
603 0 : if (gameState.ai.elapsedTime - this.positionLastUpdate > 5)
604 : {
605 0 : this.recalculatePosition(gameState);
606 0 : this.positionLastUpdate = gameState.ai.elapsedTime;
607 :
608 : // Check for breakaways.
609 0 : for (let i = 0; i < this.foeEntities.length; ++i)
610 : {
611 0 : let id = this.foeEntities[i];
612 0 : let ent = gameState.getEntityById(id);
613 0 : if (!ent || !ent.position())
614 0 : continue;
615 0 : if (API3.SquareVectorDistance(ent.position(), this.foePosition) > this.breakawaySize)
616 : {
617 0 : breakaways.push(id);
618 0 : if (this.removeFoe(gameState, id))
619 0 : i--;
620 : }
621 : }
622 :
623 0 : this.recalculatePosition(gameState);
624 : }
625 :
626 0 : return breakaways;
627 : };
628 :
629 0 : PETRA.DefenseArmy.prototype.Serialize = function()
630 : {
631 0 : return {
632 : "ID": this.ID,
633 : "type": this.type,
634 : "foePosition": this.foePosition,
635 : "positionLastUpdate": this.positionLastUpdate,
636 : "assignedAgainst": this.assignedAgainst,
637 : "assignedTo": this.assignedTo,
638 : "foeEntities": this.foeEntities,
639 : "foeStrength": this.foeStrength,
640 : "ownEntities": this.ownEntities,
641 : "ownStrength": this.ownStrength
642 : };
643 : };
644 :
645 0 : PETRA.DefenseArmy.prototype.Deserialize = function(data)
646 : {
647 0 : for (let key in data)
648 0 : this[key] = data[key];
649 : };
|