64 lines
1.7 KiB
JavaScript
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.
|
|
}
|
|
}
|