Another refactor

This commit is contained in:
Peter Stockings
2026-01-05 13:24:56 +11:00
parent ac86d612e2
commit ce68470ab1
17 changed files with 853 additions and 801 deletions

View File

@@ -1,10 +1,12 @@
import type { World, EntityId, Action, SimEvent, Actor, CombatantActor, CollectibleActor, ActorType } from "../../core/types";
import { isBlocked } from "../world/world-logic";
import { findPathAStar } from "../world/pathfinding";
import { GAME_CONFIG } from "../../core/config/GameConfig";
import { type EntityManager } from "../EntityManager";
export function applyAction(w: World, actorId: EntityId, action: Action): SimEvent[] {
export function applyAction(w: World, actorId: EntityId, action: Action, em?: EntityManager): SimEvent[] {
const actor = w.actors.get(actorId);
if (!actor) return [];
@@ -12,10 +14,10 @@ export function applyAction(w: World, actorId: EntityId, action: Action): SimEve
switch (action.type) {
case "move":
events.push(...handleMove(w, actor, action));
events.push(...handleMove(w, actor, action, em));
break;
case "attack":
events.push(...handleAttack(w, actor, action));
events.push(...handleAttack(w, actor, action, em));
break;
case "wait":
default:
@@ -31,7 +33,7 @@ export function applyAction(w: World, actorId: EntityId, action: Action): SimEve
return events;
}
function handleExpCollection(w: World, player: Actor, events: SimEvent[]) {
function handleExpCollection(w: World, player: Actor, events: SimEvent[], em?: EntityManager) {
if (player.category !== "combatant") return;
const orbs = [...w.actors.values()].filter(a =>
@@ -53,7 +55,8 @@ function handleExpCollection(w: World, player: Actor, events: SimEvent[]) {
});
checkLevelUp(player, events);
w.actors.delete(orb.id);
if (em) em.removeActor(orb.id);
else w.actors.delete(orb.id);
}
}
@@ -86,19 +89,23 @@ function checkLevelUp(player: CombatantActor, events: SimEvent[]) {
}
function handleMove(w: World, actor: Actor, action: { dx: number; dy: number }): SimEvent[] {
function handleMove(w: World, actor: Actor, action: { dx: number; dy: number }, em?: EntityManager): SimEvent[] {
const from = { ...actor.pos };
const nx = actor.pos.x + action.dx;
const ny = actor.pos.y + action.dy;
if (!isBlocked(w, nx, ny)) {
actor.pos.x = nx;
actor.pos.y = ny;
if (!isBlocked(w, nx, ny, em)) {
if (em) {
em.moveActor(actor.id, from, { x: nx, y: ny });
} else {
actor.pos.x = nx;
actor.pos.y = ny;
}
const to = { ...actor.pos };
const events: SimEvent[] = [{ type: "moved", actorId: actor.id, from, to }];
if (actor.category === "combatant" && actor.isPlayer) {
handleExpCollection(w, actor, events);
handleExpCollection(w, actor, events, em);
}
return events;
@@ -108,7 +115,8 @@ function handleMove(w: World, actor: Actor, action: { dx: number; dy: number }):
}
function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }): SimEvent[] {
function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }, em?: EntityManager): SimEvent[] {
const target = w.actors.get(action.targetId);
if (target && target.category === "combatant" && actor.category === "combatant") {
const events: SimEvent[] = [{ type: "attacked", attackerId: actor.id, targetId: action.targetId }];
@@ -183,19 +191,25 @@ function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }): S
y: target.pos.y,
victimType: target.type as ActorType
});
w.actors.delete(target.id);
if (em) em.removeActor(target.id);
else w.actors.delete(target.id);
// Spawn EXP Orb
const enemyDef = (GAME_CONFIG.enemies as any)[target.type || ""];
const expAmount = enemyDef?.expValue || 0;
const orbId = Math.max(0, ...w.actors.keys(), target.id) + 1;
w.actors.set(orbId, {
const orbId = em ? em.getNextId() : Math.max(0, ...w.actors.keys(), target.id) + 1;
const orb: CollectibleActor = {
id: orbId,
category: "collectible",
type: "exp_orb",
pos: { ...target.pos },
expAmount // Explicit member in CollectibleActor
});
expAmount
};
if (em) em.addActor(orb);
else w.actors.set(orbId, orb);
events.push({ type: "orb-spawned", orbId, x: target.pos.x, y: target.pos.y });
}
@@ -210,7 +224,7 @@ function handleAttack(w: World, actor: Actor, action: { targetId: EntityId }): S
* - if adjacent to player, attack
* - else step toward player using greedy Manhattan
*/
export function decideEnemyAction(w: World, enemy: CombatantActor, player: CombatantActor): Action {
export function decideEnemyAction(w: World, enemy: CombatantActor, player: CombatantActor, em?: EntityManager): Action {
const dx = player.pos.x - enemy.pos.x;
const dy = player.pos.y - enemy.pos.y;
const dist = Math.abs(dx) + Math.abs(dy);
@@ -219,7 +233,21 @@ export function decideEnemyAction(w: World, enemy: CombatantActor, player: Comba
return { type: "attack", targetId: player.id };
}
// Use A* for smarter pathfinding
const dummySeen = new Uint8Array(w.width * w.height).fill(1); // Enemies "know" the map
const path = findPathAStar(w, dummySeen, enemy.pos, player.pos, { ignoreBlockedTarget: true, ignoreSeen: true, em });
if (path.length >= 2) {
const next = path[1];
const adx = next.x - enemy.pos.x;
const ady = next.y - enemy.pos.y;
return { type: "move", dx: adx, dy: ady };
}
// Fallback to greedy if no path found
const options: { dx: number; dy: number }[] = [];
if (Math.abs(dx) >= Math.abs(dy)) {
options.push({ dx: Math.sign(dx), dy: 0 });
options.push({ dx: 0, dy: Math.sign(dy) });
@@ -243,7 +271,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.
* Returns enemy events accumulated along the way.
*/
export function stepUntilPlayerTurn(w: World, playerId: EntityId): { awaitingPlayerId: EntityId; events: SimEvent[] } {
export function stepUntilPlayerTurn(w: World, playerId: EntityId, em?: EntityManager): { awaitingPlayerId: EntityId; events: SimEvent[] } {
const player = w.actors.get(playerId) as CombatantActor;
if (!player || player.category !== "combatant") throw new Error("Player missing or invalid");
@@ -269,8 +297,8 @@ export function stepUntilPlayerTurn(w: World, playerId: EntityId): { awaitingPla
return { awaitingPlayerId: actor.id, events };
}
const action = decideEnemyAction(w, actor, player);
events.push(...applyAction(w, actor.id, action));
const action = decideEnemyAction(w, actor, player, em);
events.push(...applyAction(w, actor.id, action, em));
// Check if player was killed by this action
if (!w.actors.has(playerId)) {