diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts index bd29e55211..fdb7fec501 100644 --- a/src/core/execution/WarshipExecution.ts +++ b/src/core/execution/WarshipExecution.ts @@ -240,7 +240,7 @@ export class WarshipExecution implements Execution { ); // Trade-ship-specific state, lazily computed. - let hasPort: boolean | undefined; + let hasReachablePort: boolean | undefined; let patrolTile: number | undefined; let patrolRangeSquared: number | undefined; let warshipComponent: number | null | undefined = undefined; @@ -264,13 +264,23 @@ export class WarshipExecution implements Execution { const type = unit.type(); if (includeTradeShips && type === UnitType.TradeShip) { - if (hasPort === undefined) { - hasPort = owner.unitCount(UnitType.Port) > 0; + if (warshipComponent === undefined) { + warshipComponent = mg.getWaterComponent(this.warship.tile()); + } + if (hasReachablePort === undefined && warshipComponent !== null) { + hasReachablePort = false; + for (const port of owner.units(UnitType.Port)) { + hasReachablePort = mg.hasWaterComponent( + port.tile(), + warshipComponent, + ); + if (hasReachablePort) break; + } patrolTile = this.warship.warshipState().patrolTile; patrolRangeSquared = config.warshipPatrolRange() ** 2; } if ( - !hasPort || + !hasReachablePort || patrolTile === undefined || unit.isSafeFromPirates() || unit.targetUnit()?.owner() === owner || @@ -278,9 +288,6 @@ export class WarshipExecution implements Execution { ) { continue; } - if (warshipComponent === undefined) { - warshipComponent = mg.getWaterComponent(this.warship.tile()); - } if ( warshipComponent !== null && !mg.hasWaterComponent(unit.tile(), warshipComponent) diff --git a/tests/Warship.test.ts b/tests/Warship.test.ts index ea2dcd0511..10ec8e2be5 100644 --- a/tests/Warship.test.ts +++ b/tests/Warship.test.ts @@ -34,7 +34,9 @@ describe("Warship", () => { // Advance past the manualMoveRetreatDisabledDuration window. executeTicks(game, 50); }); - + afterEach(() => { + vi.restoreAllMocks(); + }); test("Warship heals only if player has port", async () => { const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth; if (typeof maxHealth !== "number") { @@ -119,6 +121,41 @@ describe("Warship", () => { expect(tradeShip.owner().id()).toBe(player2.id()); }); + test("Warship doesn't capture trade if there is no port on it's water component", async () => { + const portTile = game.ref(coastX, 10); + player1.buildUnit(UnitType.Port, portTile, {}); + const warship = player1.buildUnit( + UnitType.Warship, + game.ref(coastX + 1, 11), + { + patrolTile: game.ref(coastX + 1, 11), + }, + ); + game.addExecution(new WarshipExecution(warship)); + + const tradeShip = player2.buildUnit( + UnitType.TradeShip, + game.ref(coastX + 1, 11), + { + targetUnit: player1.buildUnit(UnitType.Port, game.ref(coastX, 11), {}), + }, + ); + + const warshipTile = warship.tile(); + vi.spyOn(game, "getWaterComponent").mockImplementation((tile) => + tile === warshipTile ? 1 : 2, + ); + vi.spyOn(game, "hasWaterComponent").mockReturnValue(false); + + expect(tradeShip.owner().id()).toBe(player2.id()); + // Let plenty of time for warship to potentially capture trade ship + for (let i = 0; i < 10; i++) { + game.executeNextTick(); + } + + expect(tradeShip.owner().id()).toBe(player2.id()); + }); + test("Warship does not target trade ships that are safe from pirates", async () => { // build port so warship can target trade ships player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});