Flourishes third wave: the grind, the grave, and the pratfalls
The showstopper: sectors visibly move — a dashed parchment ghost rotates through its quarter-turn or slides the whole way to its new ground (corrected for rev-11 renormalization, since the event records pre-shift origins and the view holds the truth). Death sends a small soul drifting up from the token; victory bursts fireworks over the winner's home, three volleys; CHAOS spins a triple spiral over the whole maze and stands alone in its batch. And the slapstick tier: knockbacks, shoves, drags, and pit-jumps leave a motion streak; falling in a pit swallows dark with dust; ooze gets a wobbling pratfall ellipse; tacks sting in three hopping asterisks; thornbush snaps green; slime sinks its ring; a successful pit climb puffs dust. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
6da350fd46
commit
68b0da6301
+83
-6
@@ -8,9 +8,12 @@ type Cell = { x: number; y: number };
|
||||
type Side = "N" | "E" | "S" | "W";
|
||||
|
||||
type FxShape =
|
||||
| { kind: "fireball" | "bolt" | "waterbolt"; from: Cell; to: Cell }
|
||||
| { kind: "fireball" | "bolt" | "waterbolt" | "streak"; from: Cell; to: Cell }
|
||||
| { kind: "burst" | "splash" | "shimmer" | "shield" | "sparkle" | "whiff" | "hit"
|
||||
| "pow" | "claw" | "absorb" | "warp-glow"; at: Cell }
|
||||
| "pow" | "claw" | "absorb" | "warp-glow" | "soul" | "fireworks" | "chaos-swirl"
|
||||
| "pit-fall" | "ooze-slip" | "tacks-ow" | "thorn-snap" | "slime-stuck" | "dust-puff"; at: Cell }
|
||||
| { kind: "sector-spin"; origin: Cell; clockwise: boolean }
|
||||
| { kind: "sector-slide"; from: Cell; to: Cell }
|
||||
| { kind: "edge-dust"; cell: Cell; side: Side };
|
||||
export type BoardFx = FxShape & { id: number };
|
||||
|
||||
@@ -20,7 +23,9 @@ let nextId = 1;
|
||||
export function fxTtl(kind: BoardFx["kind"]): number {
|
||||
switch (kind) {
|
||||
case "fireball": case "waterbolt": return 700;
|
||||
case "bolt": return 600;
|
||||
case "bolt": case "streak": return 600;
|
||||
case "soul": case "fireworks": case "chaos-swirl":
|
||||
case "sector-spin": case "sector-slide": return 1500;
|
||||
default: return 900;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +57,11 @@ export function fxForEvents(
|
||||
for (const e of events) {
|
||||
switch (e.type) {
|
||||
case "spellCast": {
|
||||
if (e.cardId === "chaos") {
|
||||
push({ kind: "chaos-swirl", at: { x: (view.board.width - 1) / 2, y: (view.board.height - 1) / 2 } });
|
||||
beat++;
|
||||
break;
|
||||
}
|
||||
const to = e.targetCell ?? posOf(e.target);
|
||||
const projectile = PROJECTILES[e.cardId];
|
||||
if (projectile && to) {
|
||||
@@ -136,10 +146,77 @@ export function fxForEvents(
|
||||
push({ kind: "edge-dust", cell: e.edge.cell, side: e.edge.side });
|
||||
beat++;
|
||||
break;
|
||||
case "sectorRotated":
|
||||
case "sectorRelocated":
|
||||
// The whole maze moved; any effect would point at stale ground.
|
||||
case "died": {
|
||||
const at = posOf(e.player);
|
||||
if (at) { push({ kind: "soul", at }, 250); beat++; }
|
||||
break;
|
||||
}
|
||||
case "gameWon": {
|
||||
const home = view.players.find((p) => p.id === e.player)?.home;
|
||||
if (home) {
|
||||
push({ kind: "fireworks", at: home });
|
||||
push({ kind: "fireworks", at: home }, 350);
|
||||
push({ kind: "fireworks", at: home }, 700);
|
||||
beat++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "knockedBack":
|
||||
case "shoved":
|
||||
push({ kind: "streak", from: e.from, to: e.to });
|
||||
beat++;
|
||||
break;
|
||||
case "objectDragged":
|
||||
push({ kind: "streak", from: e.from, to: e.to });
|
||||
beat++;
|
||||
break;
|
||||
case "jumpedPit":
|
||||
push({ kind: "streak", from: e.from, to: e.to });
|
||||
beat++;
|
||||
break;
|
||||
case "fellInPit":
|
||||
push({ kind: "pit-fall", at: e.at });
|
||||
beat++;
|
||||
break;
|
||||
case "climbedFromPit": {
|
||||
if (e.success) {
|
||||
const at = posOf(e.player);
|
||||
if (at) { push({ kind: "dust-puff", at }); beat++; }
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "slippedInOoze":
|
||||
push({ kind: "ooze-slip", at: e.at });
|
||||
beat++;
|
||||
break;
|
||||
case "steppedOnTacks":
|
||||
push({ kind: "tacks-ow", at: e.at });
|
||||
beat++;
|
||||
break;
|
||||
case "enteredThornbush":
|
||||
push({ kind: "thorn-snap", at: e.at });
|
||||
beat++;
|
||||
break;
|
||||
case "stuckInSlime":
|
||||
push({ kind: "slime-stuck", at: e.at });
|
||||
beat++;
|
||||
break;
|
||||
case "sectorRotated": {
|
||||
const origin = view.board.placements[e.sectorIndex]?.origin;
|
||||
if (origin) push({ kind: "sector-spin", origin: { ...origin }, clockwise: e.clockwise });
|
||||
// Everything after the grind points at pre-move ground; stop here.
|
||||
return out;
|
||||
}
|
||||
case "sectorRelocated": {
|
||||
// The event records pre-normalization origins; the view holds the
|
||||
// truth. Shift the recorded start by the same correction.
|
||||
const trueTo = view.board.placements[e.sectorIndex]?.origin;
|
||||
if (trueTo) {
|
||||
const dx = trueTo.x - e.to.x, dy = trueTo.y - e.to.y;
|
||||
push({ kind: "sector-slide", from: { x: e.from.x + dx, y: e.from.y + dy }, to: { ...trueTo } });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user