diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 9f5e661..ac61596 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -512,7 +512,7 @@ "destroy-wall": "click a wall or door to destroy it", "illusion-wall": "click an open corridor line for the false wall", "wall-of-fire": "click a corridor line for the fire", - "waterwall": "click a corridor line — the wave collapses at once", + "waterwall": "click a corridor line, or a slime to wash it away — the wave collapses at once", "pick-lock": "click a locked door", "master-key": "click a locked door — or Display it once and walk through every lock", "jam-lock": "click a door to jam its lock solid", @@ -1050,10 +1050,25 @@ clearSelection(); return; } + // "A WATERWALL ... will wash the slime away": the card wants a corridor + // line, so a tap on the slime itself picks the square's face nearest the + // caster and the wave breaks against the gel. Sight is still the engine's + // to judge. + if (selectedCard?.cardId === "waterwall" && filled === "slime") { + dispatch(withMods({ + type: "cast", instanceId: selectedCard.instanceId, + target: { kind: "edge", cell, side: slimeFace(view.board, cell, me.position) }, + })); + clearSelection(); + return; + } // 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, a bush, or a slime — put the card down to walk`); + const name = cardDef(selectedCard.cardId).name; + net.flash(EDGE_CARDS.has(selectedCard.cardId) + ? `${name} is aimed at a line between squares: ${EDGE_HINTS[selectedCard.cardId] ?? "tap a lit line"}` + : `${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 @@ -1191,6 +1206,17 @@ } } + /** The face of a slime square a waterwall breaks against when the square + * itself is tapped. The wave washes two squares out from both sides of + * its line, so the line runs across the caster's approach rather than + * along it: a tap means "wash the slime", not "wash me". Walls and the + * board's rim are never chosen while another face is free. */ + function slimeFace(board: GameView["board"], slime: { x: number; y: number }, from: { x: number; y: number }): Side { + const across: Side[] = Math.abs(from.x - slime.x) >= Math.abs(from.y - slime.y) ? ["N", "S", "W", "E"] : ["W", "E", "N", "S"]; + const free = across.filter((side) => { const t = stepTarget(board, slime, side); return t.kind !== "blocked" || t.by !== "wall"; }); + return free[0] ?? across[0]!; + } + function clickEdge(cell: { x: number; y: number }, side: Side) { if (punchWallMode) { dispatch({ type: "punchWall", cell, side });