Attacks aimed at a bush or the ooze: five points clear the square

A THORNBUSH or ROSEBUSH "will be destroyed" by five points of damage,
and the FAQ lets only fire hurt a KILLER OOZE; the table refused every
attack aimed at one. An attack tapped onto a bush or ooze now lands on
it, the damage accumulating across attackers and turns, and the
fifth point clears the square. A WIZARDBLADE is swung from the square
beside it, as its text asks of a target that fills a square.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-16 22:35:01 -04:00
co-authored by Claude Fable 5.1
parent 23b4a7675e
commit bc99ee3eb7
4 changed files with 130 additions and 3 deletions
+11 -3
View File
@@ -876,14 +876,22 @@
return;
}
// A SAFE takes the cast itself: a key turns its combination, and an
// attack batters the box (fifteen points bursts it).
if (selectedCard && view.squareContents[cellKey(cell)]?.kind === "safe" &&
// attack batters the box (fifteen points bursts it). Bushes and the
// ooze take attacks the same way; five points clear the square.
const filled = view.squareContents[cellKey(cell)]?.kind;
if (selectedCard && filled === "safe" &&
(selectedCard.cardId === "pick-lock" || selectedCard.cardId === "master-key" ||
cardDef(selectedCard.cardId).cardType === "attack")) {
dispatch(withMods({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell } }));
clearSelection();
return;
}
if (selectedCard && (filled === "thornbush" || filled === "rosebush" || filled === "ooze") &&
cardDef(selectedCard.cardId).cardType === "attack") {
dispatch(withMods({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell } }));
clearSelection();
return;
}
if (selectedCard?.cardId === "relocate-sector") {
// Any board click (re-)picks the sector; the landing is chosen from the
// dashed ghost slots beyond the maze, since every on-board slot is taken.
@@ -992,7 +1000,7 @@
// A card in hand is aimed, not walked with: a tap on a bare square
// while holding one is a miss, never a stride into whatever is there.
if (selectedCard) {
net.flash(`${cardDef(selectedCard.cardId).name} is aimed by tapping a wizard, a creature, or a slime — put the card down to walk`);
net.flash(`${cardDef(selectedCard.cardId).name} is aimed by tapping a wizard, a creature, a bush, or a slime — put the card down to walk`);
return;
}
// A cell click is a move if the cell is one legal step away (the server
+2
View File
@@ -90,6 +90,8 @@ export function humanize(e: GameEvent): string | null {
case "safeOpened": return `${e.player}'s ${cardDef(e.withCardId).name} clicks the safe open — until turn's end.`;
case "safeDamaged": return `${e.attacker} batters the safe — ${e.amount} damage (${e.total}/15).`;
case "safeSmashed": return `💥 The safe BURSTS open under ${e.attacker}'s assault!`;
case "squareContentDamaged": return `${e.attacker} ${e.kind === "ooze" ? "burns" : "batters"} the ${e.kind}${e.amount} damage (${e.total}/${e.needed}).`;
case "squareContentDestroyed": return e.kind === "ooze" ? `🔥 The ooze burns away under ${e.attacker}'s fire!` : `🌿 The ${e.kind} is torn apart by ${e.attacker}'s attack!`;
case "slowDeathWindow": return `Slow Death bites ${e.player} for ${e.points} — a counter hovers over the wound…`;
case "slowDeathCountered": return `${e.player}'s ${cardDef(e.cardId).name} blunts the rot — ${e.remaining} point${e.remaining === 1 ? "" : "s"} still coming.`;
case "spellSustained": return `${spellName(e.cardId)} settles over ${e.target} (${e.turns} turn${e.turns === 1 ? "" : "s"}).`;