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 = new Map(); private actors: Map; 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)) { this.grid.set(i, []); } this.grid.get(i)!.push(actor.id); } private removeFromGrid(actor: Actor) { const i = idx(this.world, actor.pos.x, actor.pos.y); const ids = this.grid.get(i); if (ids) { const index = ids.indexOf(actor.id); if (index !== -1) { ids.splice(index, 1); } if (ids.length === 0) { this.grid.delete(i); } } } moveActor(actorId: EntityId, from: Vec2, to: Vec2) { const actor = this.actors.get(actorId); if (!actor) return; // Remove from old position const oldIdx = idx(this.world, from.x, from.y); const ids = this.grid.get(oldIdx); if (ids) { const index = ids.indexOf(actorId); if (index !== -1) ids.splice(index, 1); if (ids.length === 0) this.grid.delete(oldIdx); } // Update position 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, []); this.grid.get(newIdx)!.push(actorId); } addActor(actor: Actor) { this.actors.set(actor.id, actor); this.syncActorToECS(actor); this.addToGrid(actor); } removeActor(actorId: EntityId) { 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); if (!ids) return []; return ids.map(id => this.actors.get(id)!).filter(Boolean); } isOccupied(x: number, y: number, ignoreType?: string): boolean { const actors = this.getActorsAt(x, y); if (ignoreType) { return actors.some(a => a.type !== ignoreType); } return actors.length > 0; } getNextId(): EntityId { this.lastId++; return this.lastId; } }