From 9769cf255046ba45ebc37307f37a3a5f13aa1cfb Mon Sep 17 00:00:00 2001 From: scamiv <6170744+scamiv@users.noreply.github.com> Date: Sat, 27 Dec 2025 00:05:48 +0100 Subject: [PATCH] =?UTF-8?q?Trade=20ships=20now=20use=20boatPathFromTileToS?= =?UTF-8?q?hore()=20and=20follow=20a=20cached=20path=20instead=20of=20Path?= =?UTF-8?q?Finder.Mini=20each=20tick:=20TradeShipExecution.ts=20(line=201)?= =?UTF-8?q?=20Warships=20now=20use=20boatPathFromTileToWater()=20for=20pat?= =?UTF-8?q?rol=20and=20for=20chasing=20trade=20ships=20(still=20keeps=20th?= =?UTF-8?q?e=20=E2=80=9Ccapture=20if=20within=205=E2=80=9D=20behavior):=20?= =?UTF-8?q?WarshipExecution.ts=20(line=201)=20Added=20boatPathFromTileToWa?= =?UTF-8?q?ter()=20helper:=20TransportShipUtils.ts=20(line=201)=20Fixed/up?= =?UTF-8?q?dated=20tests=20that=20previously=20monkeypatched=20the=20remov?= =?UTF-8?q?ed=20pathFinder=20field:=20TradeShipExecution.test.ts=20(line?= =?UTF-8?q?=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/execution/TradeShipExecution.ts | 56 ++++++----- src/core/execution/WarshipExecution.ts | 123 +++++++++++++++-------- src/core/game/TransportShipUtils.ts | 37 ++++++- 3 files changed, 150 insertions(+), 66 deletions(-) diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 0da513b8e..cc994ce7e 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -8,8 +8,7 @@ import { UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; -import { PathFindResultType } from "../pathfinding/AStar"; -import { PathFinder } from "../pathfinding/PathFinding"; +import { boatPathFromTileToShore } from "../game/TransportShipUtils"; import { distSortUnit } from "../Util"; export class TradeShipExecution implements Execution { @@ -17,8 +16,10 @@ export class TradeShipExecution implements Execution { private mg: Game; private tradeShip: Unit | undefined; private wasCaptured = false; - private pathFinder: PathFinder; private tilesTraveled = 0; + private path: TileRef[] = []; + private pathIndex = 0; + private pathDst: TileRef | null = null; constructor( private origOwner: Player, @@ -28,7 +29,6 @@ export class TradeShipExecution implements Execution { init(mg: Game, ticks: number): void { this.mg = mg; - this.pathFinder = PathFinder.Mini(mg, 2500); } tick(ticks: number): void { @@ -102,31 +102,39 @@ export class TradeShipExecution implements Execution { return; } - const result = this.pathFinder.nextTile(curTile, this._dstPort.tile()); - - switch (result.type) { - case PathFindResultType.Pending: - // Fire unit event to rerender. - this.tradeShip.move(curTile); - break; - case PathFindResultType.NextTile: - // Update safeFromPirates status - if (this.mg.isWater(result.node) && this.mg.isShoreline(result.node)) { - this.tradeShip.setSafeFromPirates(); - } - this.tradeShip.move(result.node); - this.tilesTraveled++; - break; - case PathFindResultType.Completed: - this.complete(); - break; - case PathFindResultType.PathNotFound: + const dst = this._dstPort.tile(); + if (this.pathDst !== dst || this.path.length === 0) { + const newPath = boatPathFromTileToShore(this.mg, curTile, dst); + if (newPath === null || newPath.length < 2) { console.warn("captured trade ship cannot find route"); if (this.tradeShip.isActive()) { this.tradeShip.delete(false); } this.active = false; - break; + return; + } + this.path = newPath; + this.pathIndex = 0; + this.pathDst = dst; + } + + const next = this.path[this.pathIndex + 1]; + if (next === undefined) { + this.complete(); + return; + } + + if (this.mg.isWater(next) && this.mg.isShoreline(next)) { + this.tradeShip.setSafeFromPirates(); + } + + this.tradeShip.move(next); + this.tilesTraveled++; + this.pathIndex++; + + if (next === dst) { + this.complete(); + return; } } diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts index 1ddb064cb..c7ed4790b 100644 --- a/src/core/execution/WarshipExecution.ts +++ b/src/core/execution/WarshipExecution.ts @@ -8,8 +8,7 @@ import { UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; -import { PathFindResultType } from "../pathfinding/AStar"; -import { PathFinder } from "../pathfinding/PathFinding"; +import { boatPathFromTileToWater } from "../game/TransportShipUtils"; import { PseudoRandom } from "../PseudoRandom"; import { ShellExecution } from "./ShellExecution"; @@ -17,17 +16,19 @@ export class WarshipExecution implements Execution { private random: PseudoRandom; private warship: Unit; private mg: Game; - private pathfinder: PathFinder; private lastShellAttack = 0; private alreadySentShell = new Set(); + private path: TileRef[] = []; + private pathIndex = 0; + private pathDst: TileRef | null = null; + constructor( private input: (UnitParams & OwnerComp) | Unit, ) {} init(mg: Game, ticks: number): void { this.mg = mg; - this.pathfinder = PathFinder.Mini(mg, 10_000, true, 100); this.random = new PseudoRandom(mg.ticks()); if (isUnit(this.input)) { this.warship = this.input; @@ -48,6 +49,9 @@ export class WarshipExecution implements Execution { this.input, ); } + this.path = []; + this.pathIndex = 0; + this.pathDst = null; } tick(ticks: number): void { @@ -177,27 +181,43 @@ export class WarshipExecution implements Execution { private huntDownTradeShip() { for (let i = 0; i < 2; i++) { // target is trade ship so capture it. - const result = this.pathfinder.nextTile( - this.warship.tile(), - this.warship.targetUnit()!.tile(), - 5, - ); - switch (result.type) { - case PathFindResultType.Completed: - this.warship.owner().captureUnit(this.warship.targetUnit()!); - this.warship.setTargetUnit(undefined); - this.warship.move(this.warship.tile()); - return; - case PathFindResultType.NextTile: - this.warship.move(result.node); - break; - case PathFindResultType.Pending: - this.warship.touch(); - break; - case PathFindResultType.PathNotFound: - console.log(`path not found to target`); - break; + const curr = this.warship.tile(); + const dst = this.warship.targetUnit()!.tile(); + + if (curr === null) { + return; } + + if (curr !== null && this.mg.manhattanDist(curr, dst) < 5) { + this.warship.owner().captureUnit(this.warship.targetUnit()!); + this.warship.setTargetUnit(undefined); + this.warship.move(this.warship.tile()); + return; + } + + if (this.pathDst !== dst || this.path.length === 0) { + const newPath = boatPathFromTileToWater(this.mg, curr, dst); + if (newPath === null || newPath.length < 2) { + console.log(`path not found to target`); + this.path = []; + this.pathIndex = 0; + this.pathDst = null; + break; + } + this.path = newPath; + this.pathIndex = 0; + this.pathDst = dst; + } + + const next = this.path[this.pathIndex + 1]; + if (next === undefined) { + this.path = []; + this.pathIndex = 0; + this.pathDst = null; + break; + } + this.warship.move(next); + this.pathIndex++; } } @@ -209,25 +229,46 @@ export class WarshipExecution implements Execution { } } - const result = this.pathfinder.nextTile( - this.warship.tile(), - this.warship.targetTile()!, - ); - switch (result.type) { - case PathFindResultType.Completed: - this.warship.setTargetTile(undefined); - this.warship.move(result.node); - break; - case PathFindResultType.NextTile: - this.warship.move(result.node); - break; - case PathFindResultType.Pending: - this.warship.touch(); - return; - case PathFindResultType.PathNotFound: + const curr = this.warship.tile(); + const dst = this.warship.targetTile()!; + + if (curr === null) return; + if (curr === dst) { + this.warship.setTargetTile(undefined); + return; + } + + if (this.pathDst !== dst || this.path.length === 0) { + const newPath = boatPathFromTileToWater(this.mg, curr, dst); + if (newPath === null || newPath.length < 2) { console.warn(`path not found to target tile`); this.warship.setTargetTile(undefined); - break; + this.path = []; + this.pathIndex = 0; + this.pathDst = null; + return; + } + this.path = newPath; + this.pathIndex = 0; + this.pathDst = dst; + } + + const next = this.path[this.pathIndex + 1]; + if (next === undefined) { + this.warship.setTargetTile(undefined); + this.path = []; + this.pathIndex = 0; + this.pathDst = null; + return; + } + this.warship.move(next); + this.pathIndex++; + + if (next === dst) { + this.warship.setTargetTile(undefined); + this.path = []; + this.pathIndex = 0; + this.pathDst = null; } } diff --git a/src/core/game/TransportShipUtils.ts b/src/core/game/TransportShipUtils.ts index d7b190e49..62ede882f 100644 --- a/src/core/game/TransportShipUtils.ts +++ b/src/core/game/TransportShipUtils.ts @@ -175,7 +175,7 @@ export function boatPathFromTileToShore( const result = bfs.findWaterPathFromSeeds(gm, seedNodes, seedOrigins, targetWater, { kingMoves: true, noCornerCutting: true, - maxVisited: 300_000, + maxVisited: 300_000, //todo: replace with a proper limit based on the map size }); if (result === null) return null; @@ -185,6 +185,41 @@ export function boatPathFromTileToShore( return [startTile, ...result.path, dstShore]; } +export function boatPathFromTileToWater( + gm: GameMap, + startTile: TileRef, + dstWater: TileRef, +): TileRef[] | null { + if (!gm.isValidRef(startTile) || !gm.isValidRef(dstWater)) return null; + if (!gm.isWater(dstWater)) return null; + + const bfs = getBoatBfs(gm); + + let seedNodes: TileRef[] = []; + let seedOrigins: TileRef[] = []; + if (gm.isWater(startTile)) { + seedNodes = [startTile]; + seedOrigins = [startTile]; + } else if (gm.isShore(startTile)) { + const adj = adjacentWaterTiles(gm, startTile); + if (adj.length === 0) return null; + seedNodes = adj; + seedOrigins = adj.map(() => startTile); + } else { + return null; + } + + const result = bfs.findWaterPathFromSeeds(gm, seedNodes, seedOrigins, [dstWater], { + kingMoves: true, + noCornerCutting: true, + maxVisited: 300_000, + }); + if (result === null) return null; + + if (gm.isWater(startTile)) return result.path; + return [startTile, ...result.path]; +} + export function bestTransportShipRoute( gm: Game, attacker: Player,