mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 18:46:40 +00:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user