diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index afa6c9c39..776644aa2 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -29,6 +29,7 @@ export class TransportShipExecution implements Execution { // TODO make private public path: TileRef[]; private dst: TileRef | null; + private dstShore: TileRef | null; private boat: Unit; @@ -103,8 +104,8 @@ export class TransportShipExecution implements Execution { this.startTroops = Math.min(this.startTroops, this.attacker.troops()); - this.dst = targetTransportTile(this.mg, this.ref); - if (this.dst === null) { + this.dstShore = targetTransportTile(this.mg, this.ref); + if (this.dstShore === null) { console.warn( `${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`, ); @@ -112,9 +113,18 @@ export class TransportShipExecution implements Execution { return; } + this.dst = this.adjacentWater(this.dstShore); + if (this.dst === null) { + console.warn( + `${this.attacker} cannot find water tile adjacent to destination`, + ); + this.active = false; + return; + } + const closestTileSrc = this.attacker.canBuild( UnitType.TransportShip, - this.dst, + this.dstShore, ); if (closestTileSrc === false) { console.warn(`can't build transport ship`); @@ -143,6 +153,22 @@ export class TransportShipExecution implements Execution { targetTile: this.dst ?? undefined, }); + // Move boat from shore to adjacent water for pathfinding + const spawnWater = this.adjacentWater(this.src); + if (spawnWater === null) { + console.warn(`No adjacent water for transport ship spawn`); + this.boat.delete(false); + this.active = false; + return; + } + this.boat.move(spawnWater); + + if (this.dstShore !== null) { + this.boat.setTargetTile(this.dstShore); + } else { + this.boat.setTargetTile(undefined); + } + // Notify the target player about the incoming naval invasion if (this.targetID && this.targetID !== mg.terraNullius().id()) { mg.displayIncomingUnit( @@ -194,6 +220,7 @@ export class TransportShipExecution implements Execution { 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 { @@ -210,10 +237,19 @@ export class TransportShipExecution implements Execution { this.active = false; return; } else { - this.dst = this.src; + this.dstShore = this.src; + const retreatWater = this.adjacentWater(this.src); + if (retreatWater === null) { + console.warn(`No adjacent water for retreat destination`); + this.attacker.addTroops(this.boat.troops()); + this.boat.delete(false); + this.active = false; + return; + } + this.dst = retreatWater; - if (this.boat.targetTile() !== this.dst) { - this.boat.setTargetTile(this.dst); + if (this.boat.targetTile() !== this.dstShore) { + this.boat.setTargetTile(this.dstShore!); } } } @@ -221,7 +257,7 @@ export class TransportShipExecution implements Execution { const result = this.pathFinder.next(this.boat.tile(), this.dst); switch (result.status) { case PathStatus.COMPLETE: - if (this.mg.owner(this.dst) === this.attacker) { + if (this.mg.owner(this.dstShore!) === this.attacker) { const deaths = this.boat.troops() * (malusForRetreat / 100); const survivors = this.boat.troops() - deaths; this.attacker.addTroops(survivors); @@ -241,7 +277,7 @@ export class TransportShipExecution implements Execution { } return; } - this.attacker.conquer(this.dst); + this.attacker.conquer(this.dstShore!); if (this.target.isPlayer() && this.attacker.isFriendly(this.target)) { this.attacker.addTroops(this.boat.troops()); } else { @@ -250,7 +286,7 @@ export class TransportShipExecution implements Execution { this.boat.troops(), this.attacker, this.targetID, - this.dst, + this.dstShore!, false, ), ); @@ -285,4 +321,17 @@ export class TransportShipExecution implements Execution { isActive(): boolean { return this.active; } + + private adjacentWater(tile: TileRef): TileRef | null { + if (this.mg.isWater(tile)) { + return tile; + } + + for (const neighbor of this.mg.neighbors(tile)) { + if (this.mg.isWater(neighbor)) { + return neighbor; + } + } + return null; + } } diff --git a/tests/Disconnected.test.ts b/tests/Disconnected.test.ts index ca3cfc45b..693cbdf86 100644 --- a/tests/Disconnected.test.ts +++ b/tests/Disconnected.test.ts @@ -383,7 +383,7 @@ describe("Disconnected", () => { player1.conquer(game.map().ref(coastX, 4)); player2.conquer(game.map().ref(coastX, 1)); - const enemyShoreTile = game.map().ref(coastX, 8); + const enemyShoreTile = game.map().ref(coastX, 15); game.addExecution( new TransportShipExecution(