escape/src/engine/Entity.js

64 lines
1.7 KiB
JavaScript

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.
}
}