Dispel Creation reaches every creation, and edge hints tell the truth
An opponent's wraith refused to be dispelled — the engine was willing all along (any controller's creature, sight required), but creature clicks never routed cell-target spells, so the cast never left the client. Clicking a creature with a cell spell selected now lands on its square. The dimming aid told the same lie twice over: it lit every created square including ones out of sight (a caster's own thornbush beyond the walls) and never lit creatures at all. It now mirrors the engine — anything created, whoever made it, in sight. And "click a wall line" stops standing in for every edge card: each now says what the click actually is — a locked door for the keys, a corridor line for the fire, the creation itself for the dispel — and attacks no longer show two hints for one wall. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ee6a08f25c
commit
9493873b64
@@ -284,7 +284,14 @@ export function eligibleCellsFor(view: GameView, cardId: string): Set<string> |
|
|||||||
return new Set(cells.filter((k) => view.squareContents[k]?.kind === "stone"));
|
return new Set(cells.filter((k) => view.squareContents[k]?.kind === "stone"));
|
||||||
}
|
}
|
||||||
if (cardId === "dispel-creation") {
|
if (cardId === "dispel-creation") {
|
||||||
return new Set(cells.filter((k) => view.squareContents[k] != null));
|
// Anything created — terrain or creature, whoever made it — in sight.
|
||||||
|
const out = new Set<string>();
|
||||||
|
for (const k of cells) {
|
||||||
|
const created = view.squareContents[k] != null ||
|
||||||
|
view.creatures.some((c) => key(c.position.x, c.position.y) === k);
|
||||||
|
if (created && sighted.has(k)) out.add(k);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
|||||||
import { applyCommand, activePlayer, boardView, gameLos } from "../src/game";
|
import { applyCommand, activePlayer, boardView, gameLos } from "../src/game";
|
||||||
import { cellKey, SIDES, stepTarget, type Cell } from "../src/board";
|
import { cellKey, SIDES, stepTarget, type Cell } from "../src/board";
|
||||||
import type { CardInstance } from "../src/cards";
|
import type { CardInstance } from "../src/cards";
|
||||||
import { eligibleCellsFor, viewFor } from "../src/view";
|
import { eligibleCellsFor, sightedCellsFor, viewFor } from "../src/view";
|
||||||
import { newExpansionGame as newGame, must, giveCard, emptyNeighborCell } from "./helpers";
|
import { newExpansionGame as newGame, must, giveCard, emptyNeighborCell } from "./helpers";
|
||||||
|
|
||||||
describe("expansion terrain", () => {
|
describe("expansion terrain", () => {
|
||||||
@@ -191,4 +191,24 @@ describe("eligibility dimming mirrors the engine", () => {
|
|||||||
expect(held).toBe(true);
|
expect(held).toBe(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("dispel dimming lights creatures and demands sight, whoever made them", () => {
|
||||||
|
let { state } = newGame();
|
||||||
|
const caster = activePlayer(state);
|
||||||
|
const other = state.players.find((p) => p.id !== caster.id)!;
|
||||||
|
// An enemy wraith beside the caster: created by the OTHER wizard.
|
||||||
|
state.creatures.push({
|
||||||
|
id: "w1", kind: "wraith", controllerId: other.id,
|
||||||
|
position: { x: caster.position.x, y: caster.position.y },
|
||||||
|
damage: 0, maxDamage: Infinity, movesPerTurn: 3, movementUsed: 0,
|
||||||
|
attackUsed: false, justCreated: false,
|
||||||
|
wallPassesPerTurn: 1, wallPassUsed: 0, scorchedThisTurn: [],
|
||||||
|
});
|
||||||
|
const lit = eligibleCellsFor(viewFor(state, caster.id), "dispel-creation")!;
|
||||||
|
expect(lit.has(cellKey(caster.position))).toBe(true);
|
||||||
|
// Everything lit is created AND sighted.
|
||||||
|
const view = viewFor(state, caster.id);
|
||||||
|
const seen = sightedCellsFor(view);
|
||||||
|
for (const k of lit) expect(seen.has(k)).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -205,6 +205,22 @@
|
|||||||
|
|
||||||
const selectedDef = $derived(selectedCard ? cardDef(selectedCard.cardId) : null);
|
const selectedDef = $derived(selectedCard ? cardDef(selectedCard.cardId) : null);
|
||||||
const WAND_IDS = new Set(["blaster-wand", "sticky-wand", "shift-wand", "warp-wand"]);
|
const WAND_IDS = new Set(["blaster-wand", "sticky-wand", "shift-wand", "warp-wand"]);
|
||||||
|
/** What the edge click actually is, per card — "wall line" fits few. */
|
||||||
|
const EDGE_HINTS: Record<string, string> = {
|
||||||
|
"create-wall": "click an open corridor line to wall it",
|
||||||
|
"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",
|
||||||
|
"pick-lock": "click a locked door",
|
||||||
|
"master-key": "click a locked door",
|
||||||
|
"jam-lock": "click a door to jam its lock solid",
|
||||||
|
"remove-lock": "click a door to strip its lock for good",
|
||||||
|
"create-door": "click a wall for the new door",
|
||||||
|
"warp-wand": "click a wall to warp it open",
|
||||||
|
"stone-to-water": "click a stone wall, or a stone-filled square",
|
||||||
|
"dispel-creation": "click the creation — a square, a creature, or a conjured wall",
|
||||||
|
};
|
||||||
const EDGE_CARDS = new Set([
|
const EDGE_CARDS = new Set([
|
||||||
"create-wall", "destroy-wall", "illusion-wall", "wall-of-fire", "waterwall",
|
"create-wall", "destroy-wall", "illusion-wall", "wall-of-fire", "waterwall",
|
||||||
"pick-lock", "jam-lock", "remove-lock", "master-key", "dispel-creation",
|
"pick-lock", "jam-lock", "remove-lock", "master-key", "dispel-creation",
|
||||||
@@ -664,6 +680,12 @@
|
|||||||
const creature = view.creatures.find((c) => c.id === creatureId);
|
const creature = view.creatures.find((c) => c.id === creatureId);
|
||||||
if (!creature) return;
|
if (!creature) return;
|
||||||
if (!isYourTurn) { peekAt(creature.position); return; }
|
if (!isYourTurn) { peekAt(creature.position); return; }
|
||||||
|
if (selectedCard && CELL_CARDS.has(selectedCard.cardId)) {
|
||||||
|
// Cell-target spells aimed at a creature (DISPEL CREATION on a wraith)
|
||||||
|
// land on its square — the engine finds the creature there.
|
||||||
|
clickCell(creature.position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (selectedCard && (CREATURE_TARGET_CARDS.has(selectedCard.cardId) || cardDef(selectedCard.cardId).cardType === "attack")) {
|
if (selectedCard && (CREATURE_TARGET_CARDS.has(selectedCard.cardId) || cardDef(selectedCard.cardId).cardType === "attack")) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "cast", instanceId: selectedCard.instanceId,
|
type: "cast", instanceId: selectedCard.instanceId,
|
||||||
@@ -1441,7 +1463,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if selectedDef}
|
{#if selectedDef}
|
||||||
<strong class="hint-name">{selectedDef.name}</strong>
|
<strong class="hint-name">{selectedDef.name}</strong>
|
||||||
{#if edgeSelectMode}<span>— click a wall line</span>{/if}
|
{#if selectedCard && EDGE_CARDS.has(selectedCard.cardId)}
|
||||||
|
<span>— {EDGE_HINTS[selectedCard.cardId] ?? "click a wall line"}</span>
|
||||||
|
{/if}
|
||||||
{#if selectedCard && WAND_IDS.has(selectedCard.cardId) && view.wandCharges[selectedCard.instanceId] == null && !attachedNumber}
|
{#if selectedCard && WAND_IDS.has(selectedCard.cardId) && view.wandCharges[selectedCard.instanceId] == null && !attachedNumber}
|
||||||
<span>— first use: tap a NUMBER card to set the wand's charges, then click a target</span>
|
<span>— first use: tap a NUMBER card to set the wand's charges, then click a target</span>
|
||||||
{:else if selectedDef.cardType === "attack" && !cellSelectMode && !EDGE_CARDS.has(selectedCard?.cardId ?? "")}
|
{:else if selectedDef.cardType === "attack" && !cellSelectMode && !EDGE_CARDS.has(selectedCard?.cardId ?? "")}
|
||||||
|
|||||||
Reference in New Issue
Block a user