- Replace socket.io relay with Colyseus 0.15 authoritative server - GameRoom with GameState schema (players, units, resources) - Pure TS services: CombatResolver, EconomyService, PathfindingService, UnitManager - POST /api/create-room → 4-char invite code - React/MUI LobbyScreen: Create (shows code + START GAME) / Join by code - ColyseusClient: joinOrCreate/join by room type = invite code - Nginx: static assets direct, all else proxied to Colyseus (WS upgrade) - Content-hashed JS bundles for Cloudflare cache-busting - 1-player lobbies: START GAME button bypasses 2-player wait
90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
import EasyStar from "easystarjs";
|
|
|
|
/**
|
|
* PathfindingService — Server-side A* pathfinding service via EasyStar.js.
|
|
*
|
|
* Pure grid-based pathfinding, no Phaser dependency. Used by the
|
|
* authoritative Colyseus server to validate unit movement and compute
|
|
* tile-paths from map data.
|
|
*
|
|
* Grid semantics: 0 = walkable, 1 = blocked.
|
|
*/
|
|
export class PathfindingService {
|
|
private easystar: EasyStar.js;
|
|
private grid: number[][];
|
|
|
|
constructor(width: number, height: number) {
|
|
this.easystar = new EasyStar.js();
|
|
this.easystar.setIterationsPerCalculation(1000);
|
|
this.easystar.enableDiagonals();
|
|
this.easystar.enableCornerCutting();
|
|
|
|
// Initialize with an empty grid matching dimensions
|
|
this.grid = Array.from({ length: height }, () => Array(width).fill(0));
|
|
}
|
|
|
|
/**
|
|
* Replace the entire walkability grid and re-register acceptable tiles.
|
|
*/
|
|
setGrid(grid: number[][]): void {
|
|
this.grid = grid;
|
|
this.easystar.setGrid(grid);
|
|
this.easystar.setAcceptableTiles([0]);
|
|
}
|
|
|
|
/**
|
|
* Mark a single tile as walkable (0) or blocked (1).
|
|
* Re-registers the grid with EasyStar when the value actually changes.
|
|
*/
|
|
setWalkable(x: number, y: number, walkable: boolean): void {
|
|
if (
|
|
y < 0 ||
|
|
y >= this.grid.length ||
|
|
x < 0 ||
|
|
x >= this.grid[0].length
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const newValue = walkable ? 0 : 1;
|
|
if (this.grid[y][x] === newValue) return;
|
|
|
|
this.grid[y][x] = newValue;
|
|
this.easystar.setGrid(this.grid);
|
|
}
|
|
|
|
/**
|
|
* Asynchronously find a tile-path between two tile coordinates.
|
|
* Returns the path as an array of {x, y} positions, or null if no path exists.
|
|
*/
|
|
findPath(
|
|
from: { x: number; y: number },
|
|
to: { x: number; y: number },
|
|
): Promise<{ x: number; y: number }[] | null> {
|
|
return new Promise((resolve) => {
|
|
this.easystar.findPath(
|
|
from.x,
|
|
from.y,
|
|
to.x,
|
|
to.y,
|
|
(path: { x: number; y: number }[] | null) => {
|
|
resolve(path);
|
|
},
|
|
);
|
|
this.easystar.calculate();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate whether a move from one tile to another is possible.
|
|
* Returns true if a path exists, false otherwise.
|
|
*/
|
|
async isValidMove(
|
|
from: { x: number; y: number },
|
|
to: { x: number; y: number },
|
|
): Promise<boolean> {
|
|
const path = await this.findPath(from, to);
|
|
return path !== null;
|
|
}
|
|
}
|