Deferred death: damage and destroy only mark minions; the Death Creation Step removes them when the outermost Phase ends, and Death Phases resolve Deathrattles in order of play until no new deaths occur. Deathrattle summons use the board position the Death Event remembered. Harness adds destroy(target). Expansion Drop: Twisting Nether as content only. test_02_mortally_wounded_still_triggers no longer asserts the Acolyte is still on the board: from Module 3 on it leaves at the end of the Sequence, and verify-references runs Module 2's tests against the Module 3 reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
// Card definitions as the engine sees them. The definitions themselves live in `content`.
|
|
|
|
import type { Action, Ctx } from "./queue";
|
|
|
|
/**
|
|
* A card's behaviour: given where and for whom it runs, the actions to perform.
|
|
* Module 2 writes these as functions in `content`; Module 5 turns them into data.
|
|
*/
|
|
export type EffectFn = (ctx: Ctx) => Action[];
|
|
|
|
/** What a triggered ability listens for. */
|
|
export type TriggerOn =
|
|
| "selfDamaged" // this minion takes damage
|
|
| "friendlyMinionDamaged" // a minion with the same owner (this one included) takes damage
|
|
| "anyMinionDamaged" // any minion takes damage
|
|
| "heroDamaged" // either hero takes damage
|
|
| "deathrattle"; // this minion dies (it resolves after the minion has left play)
|
|
|
|
/** The trigger's family name, as reported in `Triggered` events. */
|
|
export function triggerLabel(on: TriggerOn): string {
|
|
switch (on) {
|
|
case "selfDamaged":
|
|
case "friendlyMinionDamaged":
|
|
case "anyMinionDamaged":
|
|
case "heroDamaged":
|
|
return "damaged";
|
|
case "deathrattle":
|
|
return "deathrattle";
|
|
}
|
|
}
|
|
|
|
export interface Trigger {
|
|
on: TriggerOn;
|
|
effect: EffectFn;
|
|
}
|
|
|
|
/** What the engine needs to know about a card. */
|
|
export interface CardDef {
|
|
name: string;
|
|
attack: number;
|
|
health: number;
|
|
/** Triggered abilities, in printed order. */
|
|
triggers: Trigger[];
|
|
/** A spell's effect when cast; absent for minions. */
|
|
onCast?: EffectFn;
|
|
}
|
|
|
|
export function minion(name: string, attack: number, health: number, triggers: Trigger[] = []): CardDef {
|
|
return { name, attack, health, triggers };
|
|
}
|
|
|
|
export function spell(name: string, onCast: EffectFn): CardDef {
|
|
return { name, attack: 0, health: 0, triggers: [], onCast };
|
|
}
|
|
|
|
/** Every card the game can create, looked up by name. */
|
|
export class CardLibrary {
|
|
private readonly cards = new Map<string, CardDef>();
|
|
|
|
constructor(defs: CardDef[]) {
|
|
for (const def of defs) this.cards.set(def.name, def);
|
|
}
|
|
|
|
get(name: string): CardDef {
|
|
const def = this.cards.get(name);
|
|
if (!def) throw new Error(`unknown card "${name}": define it in src/content`);
|
|
return def;
|
|
}
|
|
}
|