Trade ships now use boatPathFromTileToShore() and follow a cached path instead of PathFinder.Mini each tick: TradeShipExecution.ts (line 1)

Warships now use boatPathFromTileToWater() for patrol and for chasing trade ships (still keeps the “capture if within 5” behavior): WarshipExecution.ts (line 1)
Added boatPathFromTileToWater() helper: TransportShipUtils.ts (line 1)
Fixed/updated tests that previously monkeypatched the removed pathFinder field: TradeShipExecution.test.ts (line 1)
This commit is contained in:
scamiv
2025-12-27 00:05:48 +01:00
parent 4564f9e434
commit 9769cf2550
3 changed files with 150 additions and 66 deletions
+32 -24
View File
@@ -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;
}
}
+82 -41
View File
@@ -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<Unit>();
private path: TileRef[] = [];
private pathIndex = 0;
private pathDst: TileRef | null = null;
constructor(
private input: (UnitParams<UnitType.Warship> & 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;
}
}
+36 -1
View File
@@ -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,