From 7855e1b0e9c632e711e04ca7a66f57302f63b75d Mon Sep 17 00:00:00 2001 From: bijx Date: Tue, 24 Feb 2026 22:31:06 -0500 Subject: [PATCH] Feat: Troop transport retreats to closest owned tile v2 (#3286) If this PR fixes an issue, link it below. If not, delete these two lines. Resolves #1139 ## Description: New version of the #2789 PR that is cleaner after changes made to old pathfinding logic. Adds logic to troop transport retreat behaviour which retreats a transport to the closest owned tile instead of the source. Now if no shores are detected (you lost all your shoreline while the transport was out) we handle the return case same as if the original source was no longer your territory. image ## Video example from previous PR (works the exact same way in this PR): https://github.com/user-attachments/assets/e43a3b10-e8b0-4f23-87f3-2dc4739de880 ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: bijx --- src/core/execution/TransportShipExecution.ts | 21 ++++++++------------ tests/Disconnected.test.ts | 8 +++++++- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index fd02d3b84..b5ccdf464 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -29,6 +29,7 @@ export class TransportShipExecution implements Execution { private dst: TileRef | null; private src: TileRef | null; + private retreatDst: TileRef | false | null = null; private boat: Unit; private originalOwner: Player; @@ -156,27 +157,21 @@ export class TransportShipExecution implements Execution { } if (this.boat.retreating()) { - // Ensure retreat source is still valid for (new) owner - if (this.mg.owner(this.src!) !== this.attacker) { - // Use bestTransportShipSpawn, not canBuild because of its max boats check etc - const newSrc = this.attacker.bestTransportShipSpawn(this.dst); - if (newSrc === false) { - this.src = null; - } else { - this.src = newSrc; - } - } + // Resolve retreat destination once, based on current boat location when retreat begins. + this.retreatDst ??= this.attacker.bestTransportShipSpawn( + this.boat.tile(), + ); - if (this.src === null) { + if (this.retreatDst === false) { console.warn( - `TransportShipExecution: retreating but no src found for new attacker`, + `TransportShipExecution: retreating but no retreat destination found`, ); this.attacker.addTroops(this.boat.troops()); this.boat.delete(false); this.active = false; return; } else { - this.dst = this.src; + this.dst = this.retreatDst; if (this.boat.targetTile() !== this.dst) { this.boat.setTargetTile(this.dst); diff --git a/tests/Disconnected.test.ts b/tests/Disconnected.test.ts index c52f00911..d8ba217e4 100644 --- a/tests/Disconnected.test.ts +++ b/tests/Disconnected.test.ts @@ -373,7 +373,7 @@ describe("Disconnected", () => { expect(game.owner(enemyShoreTile)).toBe(player1); }); - test("Captured transport ship should retreat to owner's shore tile", () => { + test("Captured transport ship should retreat to closest owner shore tile", () => { player1.conquer(game.map().ref(coastX, 4)); player2.conquer(game.map().ref(coastX, 1)); @@ -397,9 +397,15 @@ describe("Disconnected", () => { expect(player2.isAlive()).toBe(false); expect(transportShip.owner()).toBe(player1); + const expectedRetreatTile = player1.bestTransportShipSpawn( + transportShip.tile(), + ); + expect(expectedRetreatTile).not.toBe(false); + transportShip.orderBoatRetreat(); executeTicks(game, 2); + expect(transportShip.targetTile()).toBe(expectedRetreatTile); expect(transportShip.targetTile()).not.toBe(enemyShoreTile); expect(game.owner(transportShip.targetTile()!)).toBe(player1); });