import { getEntityDef } from './constants.js'; /** * A world entity: enemy or pickup. Holds a stub state machine good enough to * validate sprites + shooting now. Full enemy AI (patrol/chase/attack) plugs * into `update()` and the `state` transitions later. */ export class Entity { constructor(spawn) { this.x = spawn.x; this.y = spawn.y; this.type = spawn.type; this.def = getEntityDef(spawn.type) ?? { type: spawn.type, name: spawn.type, color: 0xff00ff, isEnemy: false, health: 0 }; this.health = this.def.health; this.state = 'idle'; // 'idle' | 'hurt' | 'dead' | 'collected' /** Ms remaining in a transient state (e.g. the hurt flinch flash). */ this.stateTimer = 0; } get isEnemy() { return this.def.isEnemy; } /** Still rendered and interactable? Dead enemies and collected items aren't. */ get active() { return this.state !== 'dead' && this.state !== 'collected'; } /** Stub billboard color, brightened briefly while flinching from a hit. */ get renderColor() { if (this.state === 'hurt') return 0xffffff; return this.def.color; } /** Apply damage (enemies only). Returns true if this shot killed it. */ damage(amount) { if (!this.isEnemy || !this.active) return false; this.health -= amount; if (this.health <= 0) { this.state = 'dead'; return true; } this.state = 'hurt'; this.stateTimer = 120; return false; } collect() { if (!this.isEnemy) this.state = 'collected'; } update(deltaMs) { if (this.stateTimer > 0) { this.stateTimer -= deltaMs; if (this.stateTimer <= 0 && this.state === 'hurt') { this.state = 'idle'; } } // TODO: enemy AI (patrol/chase/attack/pathfinding) hooks in here. } }