refactor: introduce core ECS for movement and AI

This commit is contained in:
Peter Stockings
2026-01-21 13:49:26 +11:00
parent 01124e66a7
commit 516bf6e3c9
6 changed files with 393 additions and 185 deletions

View File

@@ -1,27 +1,75 @@
import { type World, type EntityId, type Actor, type Vec2 } from "../core/types";
import { type World, type EntityId, type Actor, type Vec2, type CombatantActor } from "../core/types";
import { idx } from "./world/world-logic";
import { ECSWorld } from "./ecs/World";
import { MovementSystem } from "./ecs/MovementSystem";
import { AISystem } from "./ecs/AISystem";
export class EntityManager {
private grid: Map<number, EntityId[]> = new Map();
private actors: Map<EntityId, Actor>;
private world: World;
private lastId: number = 0;
private ecs: ECSWorld;
private movementSystem: MovementSystem;
private aiSystem: AISystem;
constructor(world: World) {
this.world = world;
this.actors = world.actors;
this.ecs = new ECSWorld();
this.movementSystem = new MovementSystem(this.ecs, this.world, this);
this.aiSystem = new AISystem(this.ecs, this.world, this);
this.lastId = Math.max(0, ...this.actors.keys());
this.ecs.setNextId(this.lastId + 1);
this.rebuildGrid();
}
get ecsWorld(): ECSWorld {
return this.ecs;
}
get movement(): MovementSystem {
return this.movementSystem;
}
get ai(): AISystem {
return this.aiSystem;
}
rebuildGrid() {
this.grid.clear();
// Also re-sync ECS if needed, though typically we do this once at start
for (const actor of this.actors.values()) {
this.syncActorToECS(actor);
this.addToGrid(actor);
}
}
private syncActorToECS(actor: Actor) {
const id = actor.id;
this.ecs.addComponent(id, "position", actor.pos);
this.ecs.addComponent(id, "name", { name: actor.id.toString() });
if (actor.category === "combatant") {
const c = actor as CombatantActor;
this.ecs.addComponent(id, "stats", c.stats);
this.ecs.addComponent(id, "energy", { current: c.energy, speed: c.speed });
this.ecs.addComponent(id, "actorType", { type: c.type });
if (c.isPlayer) {
this.ecs.addComponent(id, "player", {});
} else {
this.ecs.addComponent(id, "ai", {
state: c.aiState || "wandering",
alertedAt: c.alertedAt,
lastKnownPlayerPos: c.lastKnownPlayerPos
});
}
} else if (actor.category === "collectible") {
this.ecs.addComponent(id, "collectible", { type: "exp_orb", amount: actor.expAmount });
}
}
private addToGrid(actor: Actor) {
const i = idx(this.world, actor.pos.x, actor.pos.y);
if (!this.grid.has(i)) {
@@ -61,6 +109,13 @@ export class EntityManager {
actor.pos.x = to.x;
actor.pos.y = to.y;
// Update ECS
const posComp = this.ecs.getComponent(actorId, "position");
if (posComp) {
posComp.x = to.x;
posComp.y = to.y;
}
// Add to new position
const newIdx = idx(this.world, to.x, to.y);
if (!this.grid.has(newIdx)) this.grid.set(newIdx, []);
@@ -69,6 +124,7 @@ export class EntityManager {
addActor(actor: Actor) {
this.actors.set(actor.id, actor);
this.syncActorToECS(actor);
this.addToGrid(actor);
}
@@ -76,12 +132,11 @@ export class EntityManager {
const actor = this.actors.get(actorId);
if (actor) {
this.removeFromGrid(actor);
this.ecs.destroyEntity(actorId);
this.actors.delete(actorId);
}
}
getActorsAt(x: number, y: number): Actor[] {
const i = idx(this.world, x, y);
const ids = this.grid.get(i);