Use rot-js for scheduling & path finding

This commit is contained in:
Peter Stockings
2026-01-05 15:41:27 +11:00
parent 50a922ca85
commit 43d5dce2e5
9 changed files with 237 additions and 174 deletions

View File

@@ -5,6 +5,7 @@ import { findPathAStar } from "../world/pathfinding";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { type EntityManager } from "../EntityManager";
import { FOV } from "rot-js";
import * as ROT from "rot-js";
export function applyAction(w: World, actorId: EntityId, action: Action, em?: EntityManager): SimEvent[] {
@@ -26,10 +27,7 @@ export function applyAction(w: World, actorId: EntityId, action: Action, em?: En
break;
}
// Spend energy for any action (move/wait/attack)
if (actor.category === "combatant") {
actor.energy -= GAME_CONFIG.gameplay.actionCost;
}
// Note: Energy is now managed by ROT.Scheduler, no need to deduct manually
return events;
}
@@ -380,7 +378,7 @@ export function decideEnemyAction(w: World, enemy: CombatantActor, player: Comba
}
/**
* Energy/speed scheduler: runs until it's the player's turn and the game needs input.
* Speed-based scheduler using rot-js: runs until it's the player's turn and the game needs input.
* Returns enemy events accumulated along the way.
*/
export function stepUntilPlayerTurn(w: World, playerId: EntityId, em?: EntityManager): { awaitingPlayerId: EntityId; events: SimEvent[] } {
@@ -389,26 +387,36 @@ export function stepUntilPlayerTurn(w: World, playerId: EntityId, em?: EntityMan
const events: SimEvent[] = [];
while (true) {
while (![...w.actors.values()].some(a => a.category === "combatant" && a.energy >= GAME_CONFIG.gameplay.energyThreshold)) {
for (const a of w.actors.values()) {
if (a.category === "combatant") {
a.energy += a.speed;
}
// Create scheduler and add all combatants
const scheduler = new ROT.Scheduler.Speed();
for (const actor of w.actors.values()) {
if (actor.category === "combatant") {
// ROT.Scheduler.Speed expects actors to have a getSpeed() method
// Add it dynamically if it doesn't exist
const actorWithGetSpeed = actor as any;
if (!actorWithGetSpeed.getSpeed) {
actorWithGetSpeed.getSpeed = function() { return this.speed; };
}
scheduler.add(actorWithGetSpeed, true);
}
}
while (true) {
// Get next actor from scheduler
const actor = scheduler.next() as CombatantActor | null;
if (!actor || !w.actors.has(actor.id)) {
// Actor was removed (died), continue to next
continue;
}
const ready = [...w.actors.values()].filter(a =>
a.category === "combatant" && a.energy >= GAME_CONFIG.gameplay.energyThreshold
) as CombatantActor[];
ready.sort((a, b) => (b.energy - a.energy) || (a.id - b.id));
const actor = ready[0];
if (actor.isPlayer) {
// Player's turn - return control to the user
return { awaitingPlayerId: actor.id, events };
}
// Enemy turn - decide action and apply it
const decision = decideEnemyAction(w, actor, player, em);
// Emit alert event if enemy just spotted player
@@ -429,3 +437,5 @@ export function stepUntilPlayerTurn(w: World, playerId: EntityId, em?: EntityMan
}
}
}