Flourishes second wave: reflections, warps, claws, and ongoing looks

The reflected spell turns in the air and flies home wearing its own
colors, striking its caster with the hit it meant for someone else.
Warp travel glows green at both mouths. Punches land as a proper
burst star; creature blows rake three red gashes; an absorbed spell
is a little card swallowed into the absorber, spinning as it shrinks.

And ongoing spells now wear their look between events: firewalls lick
and glow, the invisible and misted breathe at a fraction of opacity,
Medusa's victims gray to stone, and the webbed wear their webs. The
pulses stop under reduced motion; the informative looks stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-17 01:02:37 -04:00
co-authored by Claude Fable 5
parent 70d7b4babb
commit 6da350fd46
2 changed files with 139 additions and 5 deletions
+46 -5
View File
@@ -9,7 +9,8 @@ type Side = "N" | "E" | "S" | "W";
type FxShape =
| { kind: "fireball" | "bolt" | "waterbolt"; from: Cell; to: Cell }
| { kind: "burst" | "splash" | "shimmer" | "shield" | "sparkle" | "whiff" | "hit"; at: Cell }
| { kind: "burst" | "splash" | "shimmer" | "shield" | "sparkle" | "whiff" | "hit"
| "pow" | "claw" | "absorb" | "warp-glow"; at: Cell }
| { kind: "edge-dust"; cell: Cell; side: Side };
export type BoardFx = FxShape & { id: number };
@@ -65,9 +66,29 @@ export function fxForEvents(
break;
}
case "punched":
push({ kind: "hit", at: e.at });
push({ kind: "pow", at: e.at });
beat++;
break;
case "creatureAttacked": {
// The target may be a wizard or a fellow creature.
const at = posOf(typeof e.target === "string" ? e.target : null) ??
(() => {
const c = view.creatures.find((c) => c.id === e.target);
return c ? { ...c.position } : null;
})();
if (at) { push({ kind: "claw", at }); beat++; }
break;
}
case "creatureTouched": {
const at = posOf(e.player);
if (at) { push({ kind: "claw", at }); beat++; }
break;
}
case "attackAbsorbedIntoHand": {
const at = posOf(e.player);
if (at) { push({ kind: "absorb", at }); beat++; }
break;
}
case "damaged": {
const at = posOf(e.player);
if (at) { push({ kind: "hit", at }); beat++; }
@@ -79,9 +100,17 @@ export function fxForEvents(
break;
}
case "attackResolved": {
if (e.fullyStopped) {
const at = posOf(e.defender);
if (at) { push({ kind: "shield", at }); beat++; }
const back = posOf(e.attacker);
const stand = posOf(e.defender);
if ((e.redirected || e.reflectedDamage > 0) && back && stand) {
// The spell turns in the air and goes home.
const kind = (e.attackCardId && PROJECTILES[e.attackCardId]) || "bolt";
push({ kind, from: stand, to: back });
push({ kind: "hit", at: back }, 380);
beat++;
} else if (e.fullyStopped && stand) {
push({ kind: "shield", at: stand });
beat++;
}
break;
}
@@ -90,6 +119,18 @@ export function fxForEvents(
push({ kind: "shimmer", at: e.to }, 200);
beat++;
break;
case "moved":
if (e.via === "warp") {
push({ kind: "warp-glow", at: e.from });
push({ kind: "warp-glow", at: e.to }, 150);
beat++;
}
break;
case "warpStepped":
push({ kind: "warp-glow", at: e.from });
push({ kind: "warp-glow", at: e.to }, 150);
beat++;
break;
case "wallCreated":
case "wallDestroyed":
push({ kind: "edge-dust", cell: e.edge.cell, side: e.edge.side });