Expansion wave 4: 27 combat, curse, and utility cards
Fortune: GIFT FROM ABOVE (+3, no ceiling), GIFT FROM BELOW (a trap in the deck — 3 damage on the draw, harmless in the opening deal). Modifiers: POWER ATTACK burns life into any damage spell. Curses: WEAKNESS (drop your treasure, take double, carry nothing), STRENGTH (double physical dealt; the two cancel), WALKING DEAD (half a point per space walked, forever), DISEASE (the victim becomes a carrier who infects everyone in squares they enter), IDIOT (the victim shambles toward their nearest own treasure, able only to counteract, until they stand on it and ask "What am I doing here?"). Defense: EMPATHY (attacks bite their caster too), FORCE FIELD (spell-stopping counteraction). Mischief: MENTAL SWAP (trade whole hands), MENTAL FORCE (march someone three spaces), BUTT-HEAD (become a goat, ram for the distance charged), HEAVE-HO (throw your carried treasure as a weapon), THIEF and SWAP-MEET (item larceny), CHAOS (all hands in a pile, shuffled, redealt), ILLUSIONARY ATTACK (a fake spell that hurts if believed), WARD (a trapped treasure bites its thief), REMOVE CURSE (strip any duration spell, rolling to hit the shrunken or invisible), SWARTHMORE'S ENCHANTMENT (+1 on an enchanted object). Space-time: DIMENSIONAL WARP (paired step-through tokens), REDIRECTION (swap two outer exits' wraparounds), BIG MAN (fills the corridor: no entry, no punches, no casting past him), FEAR (nobody approaches within 3), and the out-of-turn pair — INTERRUPT and OPPORTUNITY FIRE — which open a one-action window in another player's turn. Only THUMB OF GOD remains, awaiting its digital redesign. 117 tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3a35922a40
commit
06eea72ded
@@ -55,13 +55,15 @@
|
||||
"troll", "skeleton", "wraith", "fire-imp", "democratic-monster", "shadow",
|
||||
"killer-ooze", "rosebush", "dust-cloud", "fill-square-with-slime", "create-pit",
|
||||
"handful-of-tacks", "glue", "safe", "trader", "stone-to-water", "boobytrap",
|
||||
"dimensional-warp", "redirection",
|
||||
]);
|
||||
const TWO_CELL_CARDS = new Set(["trader", "dimensional-warp", "redirection"]);
|
||||
const CREATURE_TARGET_CARDS = new Set(["mega-monster"]);
|
||||
const MODIFIER_CARDS = new Set(["amplify", "add", "extend", "around-the-corner"]);
|
||||
const SELF_CARDS = new Set([
|
||||
"invisible", "shrink", "mist-body",
|
||||
"invisible", "shrink", "mist-body", "strength", "empathy", "big-man", "fear", "adrenaline",
|
||||
]);
|
||||
const NAMED_CARDS = new Set(["card-erasure", "drop-object", "deja-vu"]);
|
||||
const NAMED_CARDS = new Set(["card-erasure", "drop-object", "deja-vu", "thief", "swap-meet", "remove-curse", "swarthmores-enchantment", "illusionary-attack"]);
|
||||
const edgeSelectMode = $derived(selectedCard != null && EDGE_CARDS.has(selectedCard.cardId));
|
||||
const cellSelectMode = $derived(
|
||||
(selectedCard != null && CELL_CARDS.has(selectedCard.cardId)) || pendingCellFor !== null,
|
||||
@@ -142,6 +144,7 @@
|
||||
// duration self-spells wait so a number card can be attached.
|
||||
const INSTANT = new Set([
|
||||
"speed", "pass-through-wall", "reuse-spell", "ugly", "alter-ego", "lifesaver", "mad-dash",
|
||||
"gift-from-above", "chaos", "interrupt", "opportunity-fire",
|
||||
"bloodstone", "brainstone", "powerstone", "shadowstone",
|
||||
"shieldstone", "soulstone", "speedstone", "visionstone",
|
||||
]);
|
||||
@@ -197,7 +200,7 @@
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectedCard?.cardId === "trader") {
|
||||
if (selectedCard && TWO_CELL_CARDS.has(selectedCard.cardId)) {
|
||||
if (!tradeFrom) { tradeFrom = cell; return; }
|
||||
net.command({
|
||||
type: "cast", instanceId: selectedCard.instanceId,
|
||||
@@ -485,8 +488,11 @@
|
||||
{#if selectedCard?.cardId === "boobytrap"}
|
||||
— place 4 tokens ({trapCells.length}/4; the FIRST is the real trap)
|
||||
{/if}
|
||||
{#if selectedCard?.cardId === "trader"}
|
||||
{tradeFrom ? "— now the second item square" : "— click the first item square"}
|
||||
{#if selectedCard && TWO_CELL_CARDS.has(selectedCard.cardId)}
|
||||
{tradeFrom ? "— now the second square" : "— click the first square"}
|
||||
{/if}
|
||||
{#if selectedCard?.cardId === "power-attack"}
|
||||
(select an attack first, then attach Power Attack via number input)
|
||||
{/if}
|
||||
{#if selectedCard?.cardId === "relocate-sector"}
|
||||
{#if pendingSectorFrom}
|
||||
@@ -543,6 +549,17 @@
|
||||
{#if discardSelection.size > 0}
|
||||
<button onclick={doDiscard}>Discard {discardSelection.size} selected</button>
|
||||
{/if}
|
||||
{#if isYourTurn && view.dimWarps.some((w) => {
|
||||
const me = view.players.find((p) => p.id === view.you)!;
|
||||
return (w.a.x === me.position.x && w.a.y === me.position.y) ||
|
||||
(w.b.x === me.position.x && w.b.y === me.position.y);
|
||||
})}
|
||||
<button onclick={() => net.command({ type: "warpStep" })}>Step through the warp</button>
|
||||
{/if}
|
||||
{#if view.outOfTurnWindow?.playerId === view.you}
|
||||
<div class="banner respond">Your interruption! Cast your spell (or pass).</div>
|
||||
<button onclick={pass}>Pass (waste it)</button>
|
||||
{/if}
|
||||
{#if isYourTurn}
|
||||
<button onclick={pickUp}>Pick up treasure</button>
|
||||
<button onclick={drop}>Drop treasure</button>
|
||||
|
||||
Reference in New Issue
Block a user