A slime shows how many spells wait in it, and a peek names them
Every cast into the gel was seen at the table, so the count is public: a badge on the slime, and the peek lists each spell with its caster. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
16fac5e6ca
commit
44c7ff8379
@@ -60,6 +60,8 @@ export interface GameView {
|
||||
/** Duration spells in play (public knowledge). */
|
||||
sustained: SustainedEffect[];
|
||||
squareContents: Record<string, SquareContent>;
|
||||
/** Spells lodged in slime, oldest first (public: each cast was seen). */
|
||||
slimeTraps: Record<string, { cardId: string; casterId: PlayerId }[]>;
|
||||
/** Cells whose contents are glued down (public: the cast was seen). */
|
||||
gluedCells: Record<string, true>;
|
||||
/** Safes standing open (their combination entered this turn). */
|
||||
@@ -156,6 +158,9 @@ export function viewFor(state: GameState, playerId: PlayerId): GameView {
|
||||
pendingDiscard: state.pendingDiscard,
|
||||
sustained: state.sustained.map((s) => ({ ...s })),
|
||||
squareContents: { ...state.squareContents },
|
||||
slimeTraps: Object.fromEntries(
|
||||
Object.entries(state.slimeTraps).map(([k, traps]) => [k, traps.map((t) => ({ cardId: t.card.cardId, casterId: t.casterId }))]),
|
||||
),
|
||||
gluedCells: { ...state.gluedCells },
|
||||
openSafes: [...state.openSafes],
|
||||
groundObjects: Object.fromEntries(
|
||||
|
||||
@@ -1003,10 +1003,21 @@
|
||||
const atk = c.justCreated ? "no attack the turn it appears" : c.attackUsed ? "attack spent" : "attack ready";
|
||||
return `${hp} · ${moves} · ${atk}`;
|
||||
}
|
||||
/** The square a peek is looking at, for notes the card alone cannot carry. */
|
||||
let peekCell = $state<{ x: number; y: number } | null>(null);
|
||||
const peekNote = $derived.by(() => {
|
||||
if (!view || !peekCreatureId) return null;
|
||||
const c = view.creatures.find((c) => c.id === peekCreatureId);
|
||||
return c ? creatureStats(c) : null;
|
||||
if (!view) return null;
|
||||
if (peekCreatureId) {
|
||||
const c = view.creatures.find((c) => c.id === peekCreatureId);
|
||||
return c ? creatureStats(c) : null;
|
||||
}
|
||||
if (peekCell && view.squareContents[cellKey(peekCell)]?.kind === "slime") {
|
||||
const waiting = view.slimeTraps?.[cellKey(peekCell)] ?? [];
|
||||
if (waiting.length > 0) {
|
||||
return `Waiting in the gel for the next wizard in: ${waiting.map((t) => `${cardDef(t.cardId).name} (${t.casterId})`).join(", ")}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
/** Show the card behind whatever occupies this square (view-only). */
|
||||
@@ -1026,6 +1037,7 @@
|
||||
if (!cardId) return false;
|
||||
peekCard = { instanceId: `peek-${cardId}`, cardId };
|
||||
peekCreatureId = creature?.id ?? null;
|
||||
peekCell = { ...cell };
|
||||
peekToken =
|
||||
creature && CREATURE_ART[creature.kind] ? tokenArt(CREATURE_ART[creature.kind]!, "creatures")
|
||||
: content && TERRAIN_ART[content.kind] ? tokenArt(TERRAIN_ART[content.kind]!, "terrain")
|
||||
|
||||
@@ -373,6 +373,13 @@
|
||||
{:else if content.kind === "safe"}
|
||||
<rect x={sx * CELL + 10} y={sy * CELL + 12} width={CELL - 20} height={CELL - 22} rx="3" class="safe" />
|
||||
{/if}
|
||||
{#if content.kind === "slime" && (view.slimeTraps?.[key]?.length ?? 0) > 0}
|
||||
<!-- spells waiting in the gel: everyone saw them cast -->
|
||||
<g class="slime-charge" transform={`translate(${sx * CELL + CELL - 9}, ${sy * CELL + 9})`}>
|
||||
<circle r="7" />
|
||||
<text y="3.5">{view.slimeTraps[key]!.length}</text>
|
||||
</g>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<!-- dimensional warp tokens -->
|
||||
@@ -765,6 +772,9 @@
|
||||
.rose { fill: #2e7d32; stroke: #b0245a; stroke-width: 3; }
|
||||
.ooze { fill: #58a12b; opacity: 0.85; }
|
||||
.slime { fill: #8ec52e; opacity: 0.85; }
|
||||
.slime-charge { pointer-events: none; }
|
||||
.slime-charge circle { fill: #3b2a6b; stroke: #f2cf5b; stroke-width: 1.5; }
|
||||
.slime-charge text { fill: #f7ecc8; font-size: 10px; font-weight: 700; text-anchor: middle; font-family: "Oswald", sans-serif; }
|
||||
.dust { fill: #9b9184; opacity: 0.75; }
|
||||
.pit { fill: #171512; }
|
||||
.tacks { font-size: 15px; text-anchor: middle; fill: #444; }
|
||||
|
||||
Reference in New Issue
Block a user