import { Registry } from "../../core/registry.js";
import { events } from "../../core/events.js";
import { ORGANIC, FIRE_RESISTANT } from "../../core/flags.js";
import {
BLACK,
BLUE,
LIGHTBLUE,
NONE,
PALEVIOLETRED,
WHITE,
colorByName,
} from "../../core/color.js";
import { Symbol } from "../../core/symbol.js";
import { BaseSerializer } from "../../core/serializer.js";
import { stateFromString } from "../../core/state.js";
import { AbstractAgent } from "./creatures.js";
// ── Combat stats for NPCs ─────────────────────────────────────────────────────
const CTBH = {
ARCHER: 30,
COMMONER: 50,
NOBLE: 20,
RIFLEMAN: 30,
WIZARD: 40,
};
const DAMAGE = {
ARCHER: 20,
COMMONER: 5,
NOBLE: 20,
RIFLEMAN: 20,
WIZARD: 15,
};
// ── NPC base ──────────────────────────────────────────────────────────────────
/**
* Base class for human/NPC agents. NPCs can be friendly (off) or hostile (on).
* In friendly mode they wander; in hostile mode they attack.
* Quest NPCs can assign and complete quests by firing color events.
*/
class NPC extends AbstractAgent {
constructor(
name,
state,
color,
flags,
message,
questColor,
doneColor,
inQuestMsg,
flag,
chanceToHit,
damage,
glyph,
) {
const sym = state.isOn()
? Symbol.of(glyph, PALEVIOLETRED)
: questColor != null && questColor !== NONE && doneColor == null
? Symbol.of(glyph, LIGHTBLUE, null, BLUE, null)
: Symbol.of(glyph, WHITE, null, BLACK, null);
super(name, flags, color, chanceToHit, sym);
this._state = state;
this._message = message;
this._questColor = questColor ?? null;
this._doneColor = doneColor ?? null;
this._inQuestMsg = inQuestMsg ?? null;
this._flag = flag ?? null;
this._damage = damage;
}
onHit(event, _attackerLoc, agentLoc, agent) {
if (this._state.isOn()) {
agent.changeHealth(this._damage);
}
}
onHitBy(event, agentLoc, agent, dir) {
if (this._state.isOff()) {
// Friendly: talk to the player when bumped into
this._talk(event, agentLoc.getAdjacentCell?.(dir));
}
}
_talk(event, cell) {
const board = event.board;
const player = event.player;
if (this._questColor && this._questColor !== NONE) {
board?.fireColorEvent(event, this._questColor, cell);
} else if (
this._doneColor &&
this._doneColor !== NONE &&
player?.matchesFlagOrItem?.(this._flag)
) {
board?.fireColorEvent(event, this._doneColor, cell);
} else if (this._message) {
events.fireModalMessage(this._message);
}
}
}
// ── NPC factory helpers ───────────────────────────────────────────────────────
function makeNpc(args, Cls) {
if (args.length >= 7) {
return new Cls(
stateFromString(args[0]),
colorByName(args[1]) ?? NONE,
args[2],
colorByName(args[3]) ?? NONE,
colorByName(args[4]) ?? NONE,
args[5],
args[6],
);
}
return new Cls(
stateFromString(args[0]),
colorByName(args[1]) ?? NONE,
args[2],
);
}
// ── NPC serializer helper ────────────────────────────────────────────────────
function makeNpcSerializer(typeId, Cls, tag = "People") {
return new (class extends BaseSerializer {
create(args) {
return makeNpc(args, Cls);
}
store(n) {
const q = n._questColor,
d = n._doneColor,
iq = n._inQuestMsg,
f = n._flag;
if (q != null && d != null && iq != null && f != null) {
return `${typeId}|${n._state}|${n.color.name}|${n._message}|${q.name}|${d.name}|${iq}|${f}`;
}
return `${typeId}|${n._state}|${n.color.name}|${n._message}`;
}
example() {
return makeNpc(["off", "White", "Hello"], Cls);
}
template(_id) {
return `${typeId}|{state}|{color}|{message}|{questColor?}|{doneColor?}|{inQuestMsg?}|{flag?}`;
}
tag() {
return tag;
}
})();
}
// ── Commoner ──────────────────────────────────────────────────────────────────
class Commoner extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Commoner",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.COMMONER,
DAMAGE.COMMONER,
"A",
);
}
static SERIALIZER = makeNpcSerializer("Commoner", Commoner);
}
// ── Noble ─────────────────────────────────────────────────────────────────────
class Noble extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Noble",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.NOBLE,
DAMAGE.NOBLE,
"\u00C4",
); // Ä
}
static SERIALIZER = makeNpcSerializer("Noble", Noble);
}
// ── Archer ────────────────────────────────────────────────────────────────────
class Archer extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Archer",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.ARCHER,
DAMAGE.ARCHER,
"\u00C5",
); // Å
}
static SERIALIZER = makeNpcSerializer("Archer", Archer);
}
// ── Rifleman ──────────────────────────────────────────────────────────────────
class Rifleman extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Rifleman",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.RIFLEMAN,
DAMAGE.RIFLEMAN,
"\u00C2",
); // Â
}
static SERIALIZER = makeNpcSerializer("Rifleman", Rifleman);
}
// ── Wizard ────────────────────────────────────────────────────────────────────
class Wizard extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Wizard",
state,
color,
ORGANIC | FIRE_RESISTANT,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.WIZARD,
DAMAGE.WIZARD,
"\u00C3",
); // Ã
}
static SERIALIZER = makeNpcSerializer("Wizard", Wizard);
}
// ── Malloc variants (hostile faction) ────────────────────────────────────────
class MallocCommoner extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Malloc Grunt",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.COMMONER,
DAMAGE.COMMONER,
"O",
);
}
static SERIALIZER = makeNpcSerializer(
"MallocCommoner",
MallocCommoner,
"Mallocs",
);
}
class MallocNoble extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Malloc Lord",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.NOBLE,
DAMAGE.NOBLE,
"\u00D6",
); // Ö
}
static SERIALIZER = makeNpcSerializer("MallocNoble", MallocNoble, "Mallocs");
}
class MallocArcher extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Malloc Archer",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.ARCHER,
DAMAGE.ARCHER,
"\u00D3",
); // Ó
}
static SERIALIZER = makeNpcSerializer(
"MallocArcher",
MallocArcher,
"Mallocs",
);
}
class MallocRifleman extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Malloc Carbiner",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.RIFLEMAN,
DAMAGE.RIFLEMAN,
"\u00D4",
); // Ô
}
static SERIALIZER = makeNpcSerializer(
"MallocRifleman",
MallocRifleman,
"Mallocs",
);
}
class MallocWizard extends NPC {
constructor(state, color, message, questColor, doneColor, inQuestMsg, flag) {
super(
"Malloc Shaman",
state,
color,
ORGANIC,
message,
questColor,
doneColor,
inQuestMsg,
flag,
CTBH.WIZARD,
DAMAGE.WIZARD,
"\u00D5",
); // Õ
}
static SERIALIZER = makeNpcSerializer(
"MallocWizard",
MallocWizard,
"Mallocs",
);
}
// ── Registry ──────────────────────────────────────────────────────────────────
export function registerNPCs() {
Registry.register("Commoner", Commoner.SERIALIZER);
Registry.register("Noble", Noble.SERIALIZER);
Registry.register("Archer", Archer.SERIALIZER);
Registry.register("Rifleman", Rifleman.SERIALIZER);
Registry.register("Wizard", Wizard.SERIALIZER);
Registry.register("MallocCommoner", MallocCommoner.SERIALIZER);
Registry.register("MallocNoble", MallocNoble.SERIALIZER);
Registry.register("MallocArcher", MallocArcher.SERIALIZER);
Registry.register("MallocRifleman", MallocRifleman.SERIALIZER);
Registry.register("MallocWizard", MallocWizard.SERIALIZER);
}