feat: replacement effects in Module 4 (Armor absorption, Bolf Ramshield redirect)
Applying a Damage to one target now runs DAMAGE_STEPS = [redirect, armor]
first. A damageRedirect aura (Bolf Ramshield, fetched from the wiki) sends
damage meant for its owner's hero to the oldest such minion; Armor absorbs
damage to a hero before Health, logged as the new ArmorLost{player, amount}
just before a full-amount Damaged. Four new Module 4 tests; propagated to the
references of Modules 4-10 in both tracks; card schema, catalog, card JSON,
harness.md, Lessons 2, 4 and 5 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
97c64ebee8
commit
372c99c306
113 files changed
+1925
-231
No files matched your search
@@ -12,8 +12,12 @@
|
||||
// Module 4: a queued trigger may activate more than once. How many times is
|
||||
// decided by one routine, `activations` in `aura.ts`, from aura data on cards.
|
||||
// Nothing here knows which cards multiply which triggers.
|
||||
//
|
||||
// Module 4, second interceptor: replacement. Before a Damage changes the world,
|
||||
// the steps in `DAMAGE_STEPS` may rewrite it, in that order: a redirect changes
|
||||
// who takes it, then Armor takes what it can of damage to a hero.
|
||||
|
||||
import { activations, auraUpdateOther } from "./aura";
|
||||
import { activations, auraUpdateOther, redirectFor } from "./aura";
|
||||
import type { Trigger, TriggerOn } from "./card";
|
||||
import { deathCreationStep } from "./death";
|
||||
import type { Event } from "./event";
|
||||
@@ -34,6 +38,21 @@ export type Action =
|
||||
/** Play a minion from its owner's hand: it enters the right end of the board, then its Battlecry resolves. */
|
||||
| { type: "play"; id: EntityId };
|
||||
|
||||
/**
|
||||
* A step that may rewrite one Damage to one target before the world changes:
|
||||
* - `"redirect"`: damage to a hero whose owner has a `damageRedirect` aura in force
|
||||
* goes to that aura's minion instead (the oldest one's, if several);
|
||||
* - `"armor"`: damage to a hero lowers its owner's Armor first, then Health.
|
||||
*/
|
||||
export type DamageStep = "redirect" | "armor";
|
||||
|
||||
/**
|
||||
* The order the steps run in. Each runs once per Damage, so a rewritten Damage is
|
||||
* never offered to a redirect again. A redirect comes first: Armor protects only
|
||||
* the hero that actually takes the damage.
|
||||
*/
|
||||
export const DAMAGE_STEPS: readonly DamageStep[] = ["redirect", "armor"];
|
||||
|
||||
/** What an effect may look at when it runs. */
|
||||
export class Ctx {
|
||||
constructor(
|
||||
@@ -101,10 +120,7 @@ function perform(world: World, action: Action): void {
|
||||
// Area damage: every Damage Event is created, in order of play, before any reaction.
|
||||
const targets = [...action.targets].sort((a, b) => world.orderOfPlay(a)! - world.orderOfPlay(b)!);
|
||||
const events: Event[] = [];
|
||||
for (const target of targets) {
|
||||
world.damage(target, action.amount);
|
||||
events.push({ type: "Damaged", target, amount: action.amount });
|
||||
}
|
||||
for (const target of targets) events.push(applyDamage(world, target, action.amount));
|
||||
react(world, events);
|
||||
break;
|
||||
}
|
||||
@@ -143,6 +159,31 @@ function perform(world: World, action: Action): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deal one Damage to one target, after the steps of `DAMAGE_STEPS` have rewritten it.
|
||||
* Returns its Damage Event, which names whoever took it.
|
||||
*/
|
||||
function applyDamage(world: World, target: EntityId, amount: number): Event {
|
||||
let taker = target;
|
||||
let absorbed = 0;
|
||||
for (const step of DAMAGE_STEPS) {
|
||||
switch (step) {
|
||||
case "redirect":
|
||||
if (world.isHero(taker)) taker = redirectFor(world, world.ownerOf(taker)) ?? taker;
|
||||
break;
|
||||
case "armor":
|
||||
if (world.isHero(taker)) {
|
||||
const player = world.ownerOf(taker);
|
||||
absorbed = Math.min(world.armor(player), amount);
|
||||
if (absorbed > 0) world.loseArmor(player, absorbed);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
world.damage(taker, amount, absorbed);
|
||||
return { type: "Damaged", target: taker, amount };
|
||||
}
|
||||
|
||||
/** A Phase: queue every trigger that responds to `events`, then resolve them one by one. */
|
||||
function react(world: World, events: Event[]): void {
|
||||
// Fill the Queue: for each event (already in order of play), each listener in order of play.
|
||||
|
||||
Reference in new issue
Block a user