Line data Source code
1 : /**
2 : * Describes a transport plan
3 : * Constructor assign units (units is an ID array), a destination (position).
4 : * The naval manager will try to deal with it accordingly.
5 : *
6 : * By this I mean that the naval manager will find how to go from access point 1 to access point 2
7 : * and then carry units from there.
8 : *
9 : * Note: only assign it units currently over land, or it won't work.
10 : * Also: destination should probably be land, otherwise the units will be lost at sea.
11 : *
12 : * metadata for units:
13 : * transport = this.ID
14 : * onBoard = ship.id() when affected to a ship but not yet garrisoned
15 : * = "onBoard" when garrisoned in a ship
16 : * = undefined otherwise
17 : * endPos = position of destination
18 : *
19 : * metadata for ships
20 : * transporter = this.ID
21 : */
22 :
23 0 : PETRA.TransportPlan = function(gameState, units, startIndex, endIndex, endPos, ship)
24 : {
25 0 : this.ID = gameState.ai.uniqueIDs.transports++;
26 0 : this.debug = gameState.ai.Config.debug;
27 0 : this.flotilla = false; // when false, only one ship per transport ... not yet tested when true
28 :
29 0 : this.endPos = endPos;
30 0 : this.endIndex = endIndex;
31 0 : this.startIndex = startIndex;
32 : // TODO only cases with land-sea-land are allowed for the moment
33 : // we could also have land-sea-land-sea-land
34 0 : if (startIndex == 1)
35 : {
36 : // special transport from already garrisoned ship
37 0 : if (!ship)
38 : {
39 0 : this.failed = true;
40 0 : return false;
41 : }
42 0 : this.sea = ship.getMetadata(PlayerID, "sea");
43 0 : ship.setMetadata(PlayerID, "transporter", this.ID);
44 0 : ship.setStance("none");
45 0 : for (let ent of units)
46 0 : ent.setMetadata(PlayerID, "onBoard", "onBoard");
47 : }
48 : else
49 : {
50 0 : this.sea = gameState.ai.HQ.getSeaBetweenIndices(gameState, startIndex, endIndex);
51 0 : if (!this.sea)
52 : {
53 0 : this.failed = true;
54 0 : if (this.debug > 1)
55 0 : API3.warn("transport plan with bad path: startIndex " + startIndex + " endIndex " + endIndex);
56 0 : return false;
57 : }
58 : }
59 :
60 0 : for (let ent of units)
61 : {
62 0 : ent.setMetadata(PlayerID, "transport", this.ID);
63 0 : ent.setMetadata(PlayerID, "endPos", endPos);
64 : }
65 :
66 0 : if (this.debug > 1)
67 0 : API3.warn("Starting a new transport plan with ID " + this.ID +
68 : " to index " + endIndex + " with units length " + units.length);
69 :
70 0 : this.state = PETRA.TransportPlan.BOARDING;
71 0 : this.boardingPos = {};
72 0 : this.needTransportShips = ship === undefined;
73 0 : this.nTry = {};
74 0 : return true;
75 : };
76 :
77 : /**
78 : * We're trying to board units onto our ships.
79 : */
80 0 : PETRA.TransportPlan.BOARDING = "boarding";
81 : /**
82 : * We're moving ships and eventually unload units.
83 : */
84 0 : PETRA.TransportPlan.SAILING = "sailing";
85 :
86 0 : PETRA.TransportPlan.prototype.init = function(gameState)
87 : {
88 0 : this.units = gameState.getOwnUnits().filter(API3.Filters.byMetadata(PlayerID, "transport", this.ID));
89 0 : this.ships = gameState.ai.HQ.navalManager.ships.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID));
90 0 : this.transportShips = gameState.ai.HQ.navalManager.transportShips.filter(API3.Filters.byMetadata(PlayerID, "transporter", this.ID));
91 :
92 0 : this.units.registerUpdates();
93 0 : this.ships.registerUpdates();
94 0 : this.transportShips.registerUpdates();
95 :
96 0 : this.boardingRange = 18*18; // TODO compute it from the ship clearance and garrison range
97 : };
98 :
99 : /** count available slots */
100 0 : PETRA.TransportPlan.prototype.countFreeSlots = function()
101 : {
102 0 : let slots = 0;
103 0 : for (let ship of this.transportShips.values())
104 0 : slots += this.countFreeSlotsOnShip(ship);
105 0 : return slots;
106 : };
107 :
108 0 : PETRA.TransportPlan.prototype.countFreeSlotsOnShip = function(ship)
109 : {
110 0 : if (ship.hitpoints() < ship.garrisonEjectHealth() * ship.maxHitpoints())
111 0 : return 0;
112 0 : let occupied = ship.garrisoned().length +
113 : this.units.filter(API3.Filters.byMetadata(PlayerID, "onBoard", ship.id())).length;
114 0 : return Math.max(ship.garrisonMax() - occupied, 0);
115 : };
116 :
117 0 : PETRA.TransportPlan.prototype.assignUnitToShip = function(gameState, ent)
118 : {
119 0 : if (this.needTransportShips)
120 0 : return;
121 :
122 0 : for (let ship of this.transportShips.values())
123 : {
124 0 : if (this.countFreeSlotsOnShip(ship) == 0)
125 0 : continue;
126 0 : ent.setMetadata(PlayerID, "onBoard", ship.id());
127 0 : if (this.debug > 1)
128 : {
129 0 : if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_ATTACK)
130 0 : Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [2, 0, 0] });
131 : else
132 0 : Engine.PostCommand(PlayerID, { "type": "set-shading-color", "entities": [ent.id()], "rgb": [0, 2, 0] });
133 : }
134 0 : return;
135 : }
136 :
137 0 : if (this.flotilla)
138 : {
139 0 : this.needTransportShips = true;
140 0 : return;
141 : }
142 :
143 0 : if (!this.needSplit)
144 0 : this.needSplit = [ent];
145 : else
146 0 : this.needSplit.push(ent);
147 : };
148 :
149 0 : PETRA.TransportPlan.prototype.assignShip = function(gameState)
150 : {
151 : let pos;
152 : // choose a unit of this plan not yet assigned to a ship
153 0 : for (let ent of this.units.values())
154 : {
155 0 : if (!ent.position() || ent.getMetadata(PlayerID, "onBoard") !== undefined)
156 0 : continue;
157 0 : pos = ent.position();
158 0 : break;
159 : }
160 : // and choose the nearest available ship from this unit
161 0 : let distmin = Math.min();
162 : let nearest;
163 0 : gameState.ai.HQ.navalManager.seaTransportShips[this.sea].forEach(ship => {
164 0 : if (ship.getMetadata(PlayerID, "transporter"))
165 0 : return;
166 0 : if (pos)
167 : {
168 0 : let dist = API3.SquareVectorDistance(pos, ship.position());
169 0 : if (dist > distmin)
170 0 : return;
171 0 : distmin = dist;
172 0 : nearest = ship;
173 : }
174 0 : else if (!nearest)
175 0 : nearest = ship;
176 : });
177 0 : if (!nearest)
178 0 : return false;
179 :
180 0 : nearest.setMetadata(PlayerID, "transporter", this.ID);
181 0 : nearest.setStance("none");
182 0 : this.ships.updateEnt(nearest);
183 0 : this.transportShips.updateEnt(nearest);
184 0 : this.needTransportShips = false;
185 0 : return true;
186 : };
187 :
188 : /** add a unit to this plan */
189 0 : PETRA.TransportPlan.prototype.addUnit = function(unit, endPos)
190 : {
191 0 : unit.setMetadata(PlayerID, "transport", this.ID);
192 0 : unit.setMetadata(PlayerID, "endPos", endPos);
193 0 : this.units.updateEnt(unit);
194 : };
195 :
196 : /** remove a unit from this plan, if not yet on board */
197 0 : PETRA.TransportPlan.prototype.removeUnit = function(gameState, unit)
198 : {
199 0 : let shipId = unit.getMetadata(PlayerID, "onBoard");
200 0 : if (shipId == "onBoard")
201 0 : return; // too late, already onBoard
202 0 : else if (shipId !== undefined)
203 0 : unit.stopMoving(); // cancel the garrison order
204 0 : unit.setMetadata(PlayerID, "transport", undefined);
205 0 : unit.setMetadata(PlayerID, "endPos", undefined);
206 0 : this.units.updateEnt(unit);
207 0 : if (shipId)
208 : {
209 0 : unit.setMetadata(PlayerID, "onBoard", undefined);
210 0 : let ship = gameState.getEntityById(shipId);
211 0 : if (ship && !ship.garrisoned().length &&
212 : !this.units.filter(API3.Filters.byMetadata(PlayerID, "onBoard", shipId)).length)
213 : {
214 0 : this.releaseShip(ship);
215 0 : this.ships.updateEnt(ship);
216 0 : this.transportShips.updateEnt(ship);
217 : }
218 : }
219 : };
220 :
221 0 : PETRA.TransportPlan.prototype.releaseShip = function(ship)
222 : {
223 0 : if (ship.getMetadata(PlayerID, "transporter") != this.ID)
224 : {
225 0 : API3.warn(" Petra: try removing a transporter ship with " + ship.getMetadata(PlayerID, "transporter") +
226 : " from " + this.ID + " and stance " + ship.getStance());
227 0 : return;
228 : }
229 :
230 0 : let defaultStance = ship.get("UnitAI/DefaultStance");
231 0 : if (defaultStance)
232 0 : ship.setStance(defaultStance);
233 :
234 0 : ship.setMetadata(PlayerID, "transporter", undefined);
235 0 : if (ship.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_SWITCH_TO_TRADER)
236 0 : ship.setMetadata(PlayerID, "role", PETRA.Worker.ROLE_TRADER);
237 : };
238 :
239 0 : PETRA.TransportPlan.prototype.releaseAll = function()
240 : {
241 0 : for (let ship of this.ships.values())
242 0 : this.releaseShip(ship);
243 :
244 0 : for (let ent of this.units.values())
245 : {
246 0 : ent.setMetadata(PlayerID, "endPos", undefined);
247 0 : ent.setMetadata(PlayerID, "onBoard", undefined);
248 0 : ent.setMetadata(PlayerID, "transport", undefined);
249 : // TODO if the index of the endPos of the entity is !=,
250 : // require again another transport (we could need land-sea-land-sea-land)
251 : }
252 :
253 0 : this.transportShips.unregister();
254 0 : this.ships.unregister();
255 0 : this.units.unregister();
256 : };
257 :
258 : /** TODO not currently used ... to be fixed */
259 0 : PETRA.TransportPlan.prototype.cancelTransport = function(gameState)
260 : {
261 0 : let ent = this.units.toEntityArray()[0];
262 0 : let base = gameState.ai.HQ.getBaseByID(ent.getMetadata(PlayerID, "base"));
263 0 : if (!base.anchor || !base.anchor.position())
264 : {
265 0 : for (const newbase of gameState.ai.HQ.baseManagers())
266 : {
267 0 : if (!newbase.anchor || !newbase.anchor.position())
268 0 : continue;
269 0 : ent.setMetadata(PlayerID, "base", newbase.ID);
270 0 : base = newbase;
271 0 : break;
272 : }
273 0 : if (!base.anchor || !base.anchor.position())
274 0 : return false;
275 0 : this.units.forEach(unit => { unit.setMetadata(PlayerID, "base", base.ID); });
276 : }
277 0 : this.endIndex = this.startIndex;
278 0 : this.endPos = base.anchor.position();
279 0 : this.canceled = true;
280 0 : return true;
281 : };
282 :
283 :
284 : /**
285 : * Try to move on and then clear the plan.
286 : */
287 0 : PETRA.TransportPlan.prototype.update = function(gameState)
288 : {
289 0 : if (this.state === PETRA.TransportPlan.BOARDING)
290 0 : this.onBoarding(gameState);
291 0 : else if (this.state === PETRA.TransportPlan.SAILING)
292 0 : this.onSailing(gameState);
293 :
294 0 : return this.units.length;
295 : };
296 :
297 0 : PETRA.TransportPlan.prototype.onBoarding = function(gameState)
298 : {
299 0 : let ready = true;
300 0 : let time = gameState.ai.elapsedTime;
301 0 : let shipTested = {};
302 :
303 0 : for (let ent of this.units.values())
304 : {
305 0 : if (!ent.getMetadata(PlayerID, "onBoard"))
306 : {
307 0 : ready = false;
308 0 : this.assignUnitToShip(gameState, ent);
309 0 : if (ent.getMetadata(PlayerID, "onBoard"))
310 : {
311 0 : let shipId = ent.getMetadata(PlayerID, "onBoard");
312 0 : let ship = gameState.getEntityById(shipId);
313 0 : if (!this.boardingPos[shipId])
314 : {
315 0 : this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.startIndex, this.sea, ent.position(), false);
316 0 : ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
317 0 : ship.setMetadata(PlayerID, "timeGarrison", time);
318 : }
319 0 : ent.garrison(ship);
320 0 : ent.setMetadata(PlayerID, "timeGarrison", time);
321 0 : ent.setMetadata(PlayerID, "posGarrison", ent.position());
322 : }
323 : }
324 0 : else if (ent.getMetadata(PlayerID, "onBoard") != "onBoard" && !this.isOnBoard(ent))
325 : {
326 0 : ready = false;
327 0 : let shipId = ent.getMetadata(PlayerID, "onBoard");
328 0 : let ship = gameState.getEntityById(shipId);
329 0 : if (!ship) // the ship must have been destroyed
330 : {
331 0 : ent.setMetadata(PlayerID, "onBoard", undefined);
332 0 : continue;
333 : }
334 0 : let distShip = API3.SquareVectorDistance(this.boardingPos[shipId], ship.position());
335 0 : if (!shipTested[shipId] && distShip > this.boardingRange)
336 : {
337 0 : shipTested[shipId] = true;
338 0 : let retry = false;
339 0 : let unitAIState = ship.unitAIState();
340 0 : if (unitAIState == "INDIVIDUAL.WALKING" ||
341 : unitAIState == "INDIVIDUAL.PICKUP.APPROACHING")
342 : {
343 0 : if (time - ship.getMetadata(PlayerID, "timeGarrison") > 2)
344 : {
345 0 : let oldPos = ent.getMetadata(PlayerID, "posGarrison");
346 0 : let newPos = ent.position();
347 0 : if (oldPos[0] == newPos[0] && oldPos[1] == newPos[1])
348 0 : retry = true;
349 0 : ent.setMetadata(PlayerID, "posGarrison", newPos);
350 0 : ent.setMetadata(PlayerID, "timeGarrison", time);
351 : }
352 : }
353 :
354 0 : else if (unitAIState != "INDIVIDUAL.PICKUP.LOADING" &&
355 : time - ship.getMetadata(PlayerID, "timeGarrison") > 5 ||
356 : time - ship.getMetadata(PlayerID, "timeGarrison") > 8)
357 : {
358 0 : retry = true;
359 0 : ent.setMetadata(PlayerID, "timeGarrison", time);
360 : }
361 :
362 0 : if (retry)
363 : {
364 0 : if (!this.nTry[shipId])
365 0 : this.nTry[shipId] = 1;
366 : else
367 0 : ++this.nTry[shipId];
368 0 : if (this.nTry[shipId] > 1) // we must have been blocked by something ... try with another boarding point
369 : {
370 0 : this.nTry[shipId] = 0;
371 0 : if (this.debug > 1)
372 0 : API3.warn("ship " + shipId + " new attempt for a landing point ");
373 0 : this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.startIndex, this.sea, undefined, false);
374 : }
375 0 : ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
376 0 : ship.setMetadata(PlayerID, "timeGarrison", time);
377 : }
378 : }
379 :
380 0 : if (time - ent.getMetadata(PlayerID, "timeGarrison") > 2)
381 : {
382 0 : let oldPos = ent.getMetadata(PlayerID, "posGarrison");
383 0 : let newPos = ent.position();
384 0 : if (oldPos[0] == newPos[0] && oldPos[1] == newPos[1])
385 : {
386 0 : if (distShip < this.boardingRange) // looks like we are blocked ... try to go out of this trap
387 : {
388 0 : if (!this.nTry[ent.id()])
389 0 : this.nTry[ent.id()] = 1;
390 : else
391 0 : ++this.nTry[ent.id()];
392 0 : if (this.nTry[ent.id()] > 5)
393 : {
394 0 : if (this.debug > 1)
395 0 : API3.warn("unit blocked, but no ways out of the trap ... destroy it");
396 0 : this.resetUnit(gameState, ent);
397 0 : ent.destroy();
398 0 : continue;
399 : }
400 0 : if (this.nTry[ent.id()] > 1)
401 0 : ent.moveToRange(newPos[0], newPos[1], 30, 35);
402 0 : ent.garrison(ship, true);
403 : }
404 0 : else if (API3.SquareVectorDistance(this.boardingPos[shipId], newPos) > 225)
405 0 : ent.moveToRange(this.boardingPos[shipId][0], this.boardingPos[shipId][1], 0, 15);
406 : }
407 : else
408 0 : this.nTry[ent.id()] = 0;
409 0 : ent.setMetadata(PlayerID, "timeGarrison", time);
410 0 : ent.setMetadata(PlayerID, "posGarrison", ent.position());
411 : }
412 : }
413 : }
414 :
415 0 : if (this.needSplit)
416 : {
417 0 : gameState.ai.HQ.navalManager.splitTransport(gameState, this);
418 0 : this.needSplit = undefined;
419 : }
420 :
421 0 : if (!ready)
422 0 : return;
423 :
424 0 : for (let ship of this.ships.values())
425 : {
426 0 : this.boardingPos[ship.id()] = undefined;
427 0 : this.boardingPos[ship.id()] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, this.endPos, true);
428 0 : ship.move(this.boardingPos[ship.id()][0], this.boardingPos[ship.id()][1]);
429 : }
430 0 : this.state = PETRA.TransportPlan.SAILING;
431 0 : this.nTry = {};
432 0 : this.unloaded = [];
433 0 : this.recovered = [];
434 : };
435 :
436 : /** tell if a unit is garrisoned in one of the ships of this plan, and update its metadata if yes */
437 0 : PETRA.TransportPlan.prototype.isOnBoard = function(ent)
438 : {
439 0 : for (let ship of this.transportShips.values())
440 : {
441 0 : if (ship.garrisoned().indexOf(ent.id()) == -1)
442 0 : continue;
443 0 : ent.setMetadata(PlayerID, "onBoard", "onBoard");
444 0 : return true;
445 : }
446 0 : return false;
447 : };
448 :
449 : /** when avoidEnnemy is true, we try to not board/unboard in ennemy territory */
450 0 : PETRA.TransportPlan.prototype.getBoardingPos = function(gameState, ship, landIndex, seaIndex, destination, avoidEnnemy)
451 : {
452 0 : if (!gameState.ai.HQ.navalManager.landingZones[landIndex])
453 : {
454 0 : API3.warn(" >>> no landing zone for land " + landIndex);
455 0 : return destination;
456 : }
457 0 : else if (!gameState.ai.HQ.navalManager.landingZones[landIndex][seaIndex])
458 : {
459 0 : API3.warn(" >>> no landing zone for land " + landIndex + " and sea " + seaIndex);
460 0 : return destination;
461 : }
462 :
463 0 : let startPos = ship.position();
464 0 : let distmin = Math.min();
465 0 : let posmin = destination;
466 0 : let width = gameState.getPassabilityMap().width;
467 0 : let cell = gameState.getPassabilityMap().cellSize;
468 0 : let alliedDocks = gameState.getAllyStructures().filter(API3.Filters.and(
469 : API3.Filters.byClass("Dock"), API3.Filters.byMetadata(PlayerID, "sea", seaIndex))).toEntityArray();
470 0 : for (let i of gameState.ai.HQ.navalManager.landingZones[landIndex][seaIndex])
471 : {
472 0 : let pos = [i%width+0.5, Math.floor(i/width)+0.5];
473 0 : pos = [cell*pos[0], cell*pos[1]];
474 0 : let dist = API3.VectorDistance(startPos, pos);
475 0 : if (destination)
476 0 : dist += API3.VectorDistance(pos, destination);
477 0 : if (avoidEnnemy)
478 : {
479 0 : let territoryOwner = gameState.ai.HQ.territoryMap.getOwner(pos);
480 0 : if (territoryOwner != 0 && !gameState.isPlayerAlly(territoryOwner))
481 0 : dist += 100000000;
482 : }
483 : // require a small distance between all ships of the transport plan to avoid path finder problems
484 : // this is also used when the ship is blocked and we want to find a new boarding point
485 0 : for (let shipId in this.boardingPos)
486 0 : if (this.boardingPos[shipId] !== undefined &&
487 : API3.SquareVectorDistance(this.boardingPos[shipId], pos) < this.boardingRange)
488 0 : dist += 1000000;
489 : // and not too near our allied docks to not disturb naval traffic
490 : let distSquare;
491 0 : for (let dock of alliedDocks)
492 : {
493 0 : if (dock.foundationProgress() !== undefined)
494 0 : distSquare = 900;
495 : else
496 0 : distSquare = 4900;
497 0 : let dockDist = API3.SquareVectorDistance(dock.position(), pos);
498 0 : if (dockDist < distSquare)
499 0 : dist += 100000 * (distSquare - dockDist) / distSquare;
500 : }
501 0 : if (dist > distmin)
502 0 : continue;
503 0 : distmin = dist;
504 0 : posmin = pos;
505 : }
506 : // We should always have either destination or the previous boardingPos defined
507 : // so let's return this value if everything failed
508 0 : if (!posmin && this.boardingPos[ship.id()])
509 0 : posmin = this.boardingPos[ship.id()];
510 0 : return posmin;
511 : };
512 :
513 0 : PETRA.TransportPlan.prototype.onSailing = function(gameState)
514 : {
515 : // Check that the units recovered on the previous turn have been reloaded
516 0 : for (let recov of this.recovered)
517 : {
518 0 : let ent = gameState.getEntityById(recov.entId);
519 0 : if (!ent) // entity destroyed
520 0 : continue;
521 0 : if (!ent.position()) // reloading succeeded ... move a bit the ship before trying again
522 : {
523 0 : let ship = gameState.getEntityById(recov.shipId);
524 0 : if (ship)
525 0 : ship.moveApart(recov.entPos, 15);
526 0 : continue;
527 : }
528 0 : if (this.debug > 1)
529 0 : API3.warn(">>> transport " + this.ID + " reloading failed ... <<<");
530 : // destroy the unit if inaccessible otherwise leave it there
531 0 : let index = PETRA.getLandAccess(gameState, ent);
532 0 : if (gameState.ai.HQ.landRegions[index])
533 : {
534 0 : if (this.debug > 1)
535 0 : API3.warn(" recovered entity kept " + ent.id());
536 0 : this.resetUnit(gameState, ent);
537 : // TODO we should not destroy it, but now the unit could still be reloaded on the next turn
538 : // and mess everything
539 0 : ent.destroy();
540 : }
541 : else
542 : {
543 0 : if (this.debug > 1)
544 0 : API3.warn("recovered entity destroyed " + ent.id());
545 0 : this.resetUnit(gameState, ent);
546 0 : ent.destroy();
547 : }
548 : }
549 0 : this.recovered = [];
550 :
551 : // Check that the units unloaded on the previous turn have been really unloaded and in the right position
552 0 : let shipsToMove = {};
553 0 : for (let entId of this.unloaded)
554 : {
555 0 : let ent = gameState.getEntityById(entId);
556 0 : if (!ent) // entity destroyed
557 0 : continue;
558 0 : else if (!ent.position()) // unloading failed
559 : {
560 0 : let ship = gameState.getEntityById(ent.getMetadata(PlayerID, "onBoard"));
561 0 : if (ship)
562 : {
563 0 : if (ship.garrisoned().indexOf(entId) != -1)
564 0 : ent.setMetadata(PlayerID, "onBoard", "onBoard");
565 : else
566 : {
567 0 : API3.warn("Petra transportPlan problem: unit not on ship without position ???");
568 0 : this.resetUnit(gameState, ent);
569 0 : ent.destroy();
570 : }
571 : }
572 : else
573 : {
574 0 : API3.warn("Petra transportPlan problem: unit on ship, but no ship ???");
575 0 : this.resetUnit(gameState, ent);
576 0 : ent.destroy();
577 : }
578 : }
579 0 : else if (PETRA.getLandAccess(gameState, ent) != this.endIndex)
580 : {
581 : // unit unloaded on a wrong region - try to regarrison it and move a bit the ship
582 0 : if (this.debug > 1)
583 0 : API3.warn(">>> unit unloaded on a wrong region ! try to garrison it again <<<");
584 0 : let ship = gameState.getEntityById(ent.getMetadata(PlayerID, "onBoard"));
585 0 : if (ship && !this.canceled)
586 : {
587 0 : shipsToMove[ship.id()] = ship;
588 0 : this.recovered.push({ "entId": ent.id(), "entPos": ent.position(), "shipId": ship.id() });
589 0 : ent.garrison(ship);
590 0 : ent.setMetadata(PlayerID, "onBoard", "onBoard");
591 : }
592 : else
593 : {
594 0 : if (this.debug > 1)
595 0 : API3.warn("no way ... we destroy it");
596 0 : this.resetUnit(gameState, ent);
597 0 : ent.destroy();
598 : }
599 : }
600 : else
601 : {
602 : // And make some room for other units
603 0 : let pos = ent.position();
604 0 : let goal = ent.getMetadata(PlayerID, "endPos");
605 0 : let dist = goal ? API3.VectorDistance(pos, goal) : 0;
606 0 : if (dist > 30)
607 0 : ent.moveToRange(goal[0], goal[1], dist-25, dist-20);
608 : else
609 0 : ent.moveToRange(pos[0], pos[1], 20, 25);
610 0 : ent.setMetadata(PlayerID, "transport", undefined);
611 0 : ent.setMetadata(PlayerID, "onBoard", undefined);
612 0 : ent.setMetadata(PlayerID, "endPos", undefined);
613 : }
614 : }
615 0 : for (let shipId in shipsToMove)
616 : {
617 0 : this.boardingPos[shipId] = this.getBoardingPos(gameState, shipsToMove[shipId], this.endIndex, this.sea, this.endPos, true);
618 0 : shipsToMove[shipId].move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
619 : }
620 0 : this.unloaded = [];
621 :
622 0 : if (this.canceled)
623 : {
624 0 : for (let ship of this.ships.values())
625 : {
626 0 : this.boardingPos[ship.id()] = undefined;
627 0 : this.boardingPos[ship.id()] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, this.endPos, true);
628 0 : ship.move(this.boardingPos[ship.id()][0], this.boardingPos[ship.id()][1]);
629 : }
630 0 : this.canceled = undefined;
631 : }
632 :
633 0 : for (let ship of this.transportShips.values())
634 : {
635 0 : if (ship.unitAIState() == "INDIVIDUAL.WALKING")
636 0 : continue;
637 0 : let shipId = ship.id();
638 0 : let dist = API3.SquareVectorDistance(ship.position(), this.boardingPos[shipId]);
639 0 : let remaining = 0;
640 0 : for (let entId of ship.garrisoned())
641 : {
642 0 : let ent = gameState.getEntityById(entId);
643 0 : if (!ent.getMetadata(PlayerID, "transport"))
644 0 : continue;
645 0 : remaining++;
646 0 : if (dist < 625)
647 : {
648 0 : ship.unload(entId);
649 0 : this.unloaded.push(entId);
650 0 : ent.setMetadata(PlayerID, "onBoard", shipId);
651 : }
652 : }
653 :
654 0 : let recovering = 0;
655 0 : for (let recov of this.recovered)
656 0 : if (recov.shipId == shipId)
657 0 : recovering++;
658 :
659 0 : if (!remaining && !recovering) // when empty, release the ship and move apart to leave room for other ships. TODO fight
660 : {
661 0 : ship.moveApart(this.boardingPos[shipId], 30);
662 0 : this.releaseShip(ship);
663 0 : continue;
664 : }
665 0 : if (dist > this.boardingRange)
666 : {
667 0 : if (!this.nTry[shipId])
668 0 : this.nTry[shipId] = 1;
669 : else
670 0 : ++this.nTry[shipId];
671 0 : if (this.nTry[shipId] > 2) // we must have been blocked by something ... try with another boarding point
672 : {
673 0 : this.nTry[shipId] = 0;
674 0 : if (this.debug > 1)
675 0 : API3.warn(shipId + " new attempt for a landing point ");
676 0 : this.boardingPos[shipId] = this.getBoardingPos(gameState, ship, this.endIndex, this.sea, undefined, true);
677 : }
678 0 : ship.move(this.boardingPos[shipId][0], this.boardingPos[shipId][1]);
679 : }
680 : }
681 : };
682 :
683 0 : PETRA.TransportPlan.prototype.resetUnit = function(gameState, ent)
684 : {
685 0 : ent.setMetadata(PlayerID, "transport", undefined);
686 0 : ent.setMetadata(PlayerID, "onBoard", undefined);
687 0 : ent.setMetadata(PlayerID, "endPos", undefined);
688 : // if from an army or attack, remove it
689 0 : if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") >= 0)
690 : {
691 0 : let attackPlan = gameState.ai.HQ.attackManager.getPlan(ent.getMetadata(PlayerID, "plan"));
692 0 : if (attackPlan)
693 0 : attackPlan.removeUnit(ent, true);
694 : }
695 0 : if (ent.getMetadata(PlayerID, "PartOfArmy"))
696 : {
697 0 : let army = gameState.ai.HQ.defenseManager.getArmy(ent.getMetadata(PlayerID, "PartOfArmy"));
698 0 : if (army)
699 0 : army.removeOwn(gameState, ent.id());
700 : }
701 : };
702 :
703 0 : PETRA.TransportPlan.prototype.Serialize = function()
704 : {
705 0 : return {
706 : "ID": this.ID,
707 : "flotilla": this.flotilla,
708 : "endPos": this.endPos,
709 : "endIndex": this.endIndex,
710 : "startIndex": this.startIndex,
711 : "sea": this.sea,
712 : "state": this.state,
713 : "boardingPos": this.boardingPos,
714 : "needTransportShips": this.needTransportShips,
715 : "nTry": this.nTry,
716 : "canceled": this.canceled,
717 : "unloaded": this.unloaded,
718 : "recovered": this.recovered
719 : };
720 : };
721 :
722 0 : PETRA.TransportPlan.prototype.Deserialize = function(data)
723 : {
724 0 : for (let key in data)
725 0 : this[key] = data[key];
726 :
727 0 : this.failed = false;
728 : };
|