diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 381d35fd9..482fb47de 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -54,14 +54,16 @@ export class TradeShipExecution implements Execution { return; } - if (this.origOwner !== this.tradeShip.owner()) { + const tradeShipOwner = this.tradeShip.owner(); + const dstPortOwner = this._dstPort.owner(); + if (this.wasCaptured !== true && this.origOwner !== tradeShipOwner) { // Store as variable in case ship is recaptured by previous owner this.wasCaptured = true; } // If a player captures another player's port while trading we should delete // the ship. - if (this._dstPort.owner().id() === this.srcPort.owner().id()) { + if (dstPortOwner.id() === this.srcPort.owner().id()) { this.tradeShip.delete(false); this.active = false; return; @@ -69,15 +71,17 @@ export class TradeShipExecution implements Execution { if ( !this.wasCaptured && - (!this._dstPort.isActive() || - !this.tradeShip.owner().canTrade(this._dstPort.owner())) + (!this._dstPort.isActive() || !tradeShipOwner.canTrade(dstPortOwner)) ) { this.tradeShip.delete(false); this.active = false; return; } - if (this.wasCaptured) { + if ( + this.wasCaptured && + (tradeShipOwner !== dstPortOwner || !this._dstPort.isActive()) + ) { const ports = this.tradeShip .owner() .units(UnitType.Port) @@ -92,18 +96,18 @@ export class TradeShipExecution implements Execution { } } - const result = this.pathFinder.nextTile( - this.tradeShip.tile(), - this._dstPort.tile(), - ); + const curTile = this.tradeShip.tile(); + if (curTile === this.dstPort()) { + this.complete(); + return; + } + + const result = this.pathFinder.nextTile(curTile, this._dstPort.tile()); switch (result.type) { - case PathFindResultType.Completed: - this.complete(); - break; case PathFindResultType.Pending: // Fire unit event to rerender. - this.tradeShip.move(this.tradeShip.tile()); + this.tradeShip.move(curTile); break; case PathFindResultType.NextTile: // Update safeFromPirates status @@ -113,6 +117,9 @@ export class TradeShipExecution implements Execution { this.tradeShip.move(result.node); this.tilesTraveled++; break; + case PathFindResultType.Completed: + this.complete(); + break; case PathFindResultType.PathNotFound: console.warn("captured trade ship cannot find route"); if (this.tradeShip.isActive()) { diff --git a/tests/core/executions/TradeShipExecution.test.ts b/tests/core/executions/TradeShipExecution.test.ts new file mode 100644 index 000000000..0aab3fbce --- /dev/null +++ b/tests/core/executions/TradeShipExecution.test.ts @@ -0,0 +1,122 @@ +import { TradeShipExecution } from "../../../src/core/execution/TradeShipExecution"; +import { Game, Player, Unit } from "../../../src/core/game/Game"; +import { setup } from "../../util/Setup"; + +describe("TradeShipExecution", () => { + let game: Game; + let origOwner: Player; + let dstOwner: Player; + let pirate: Player; + let srcPort: Unit; + let piratePort: Unit; + let tradeShip: Unit; + let dstPort: Unit; + let tradeShipExecution: TradeShipExecution; + + beforeEach(async () => { + // Mock Game, Player, Unit, and required methods + + game = await setup("ocean_and_land", { + infiniteGold: true, + instantBuild: true, + }); + game.displayMessage = jest.fn(); + origOwner = { + canBuild: jest.fn(() => true), + buildUnit: jest.fn((type, spawn, opts) => tradeShip), + displayName: jest.fn(() => "Origin"), + addGold: jest.fn(), + units: jest.fn(() => [dstPort]), + unitCount: jest.fn(() => 1), + id: jest.fn(() => 1), + canTrade: jest.fn(() => true), + } as any; + + dstOwner = { + id: jest.fn(() => 2), + addGold: jest.fn(), + displayName: jest.fn(() => "Destination"), + units: jest.fn(() => [dstPort]), + unitCount: jest.fn(() => 1), + canTrade: jest.fn(() => true), + } as any; + + pirate = { + id: jest.fn(() => 3), + addGold: jest.fn(), + displayName: jest.fn(() => "Destination"), + units: jest.fn(() => [piratePort]), + unitCount: jest.fn(() => 1), + canTrade: jest.fn(() => true), + } as any; + + piratePort = { + tile: jest.fn(() => 40011), + owner: jest.fn(() => pirate), + isActive: jest.fn(() => true), + } as any; + + srcPort = { + tile: jest.fn(() => 20011), + owner: jest.fn(() => origOwner), + isActive: jest.fn(() => true), + } as any; + + dstPort = { + tile: jest.fn(() => 30015), // 15x15 + owner: jest.fn(() => dstOwner), + isActive: jest.fn(() => true), + } as any; + + tradeShip = { + isActive: jest.fn(() => true), + owner: jest.fn(() => origOwner), + move: jest.fn(), + setTargetUnit: jest.fn(), + setSafeFromPirates: jest.fn(), + delete: jest.fn(), + tile: jest.fn(() => 2001), + } as any; + + tradeShipExecution = new TradeShipExecution(origOwner, srcPort, dstPort); + tradeShipExecution.init(game, 0); + tradeShipExecution["pathFinder"] = { + nextTile: jest.fn(() => ({ type: 0, node: 2001 })), + } as any; + tradeShipExecution["tradeShip"] = tradeShip; + }); + + it("should initialize and tick without errors", () => { + tradeShipExecution.tick(1); + expect(tradeShipExecution.isActive()).toBe(true); + }); + + it("should deactivate if tradeShip is not active", () => { + tradeShip.isActive = jest.fn(() => false); + tradeShipExecution.tick(1); + expect(tradeShipExecution.isActive()).toBe(false); + }); + + it("should delete ship if port owner changes to current owner", () => { + dstPort.owner = jest.fn(() => origOwner); + tradeShipExecution.tick(1); + expect(tradeShip.delete).toHaveBeenCalledWith(false); + expect(tradeShipExecution.isActive()).toBe(false); + }); + + it("should pick another port if ship is captured", () => { + tradeShip.owner = jest.fn(() => pirate); + tradeShipExecution.tick(1); + expect(tradeShip.setTargetUnit).toHaveBeenCalledWith(piratePort); + }); + + it("should complete trade and award gold", () => { + tradeShipExecution["pathFinder"] = { + nextTile: jest.fn(() => ({ type: 2, node: 2001 })), + } as any; + tradeShipExecution.tick(1); + expect(tradeShip.delete).toHaveBeenCalledWith(false); + expect(tradeShipExecution.isActive()).toBe(false); + expect(game.displayMessage).toHaveBeenCalled(); + }); +});