Selecting a creature opens its card, with living stats
Tapping a creature you command now does both things at once: selects it for orders AND opens its card in the inspector corner, with a stats line pinned beneath — hits left, moves left, attack ready or spent (or barred on the turn it appears; the shadow reports itself unharmed by ordinary damage). The same stats ride along on any hold-to-peek of a creature, and they are derived live, so marching the monster updates the numbers as you spend its moves. While commanding, the hint strip carries the same line beside the hand, with a "done" to dismiss. Deselecting closes the card. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d34173df9f
commit
7ad3e7acc8
@@ -5,7 +5,7 @@
|
||||
import Help from "./Help.svelte";
|
||||
import Replay from "./Replay.svelte";
|
||||
import { local } from "./local.svelte";
|
||||
import { allCardDefs, cardDef, isNumberCard, SIDES, stepTarget, cellKey, isMovableObject, sightedCellsFor } from "@wizwar/engine";
|
||||
import { allCardDefs, cardDef, isNumberCard, SIDES, stepTarget, cellKey, isMovableObject, sightedCellsFor, type GameView } from "@wizwar/engine";
|
||||
import type { CardInstance, Side } from "@wizwar/engine";
|
||||
|
||||
net.connect();
|
||||
@@ -19,6 +19,8 @@
|
||||
let attackNoticeSeen = $state<string | null>(null);
|
||||
/** A face-up card being peeked at from the scoresheet (view-only). */
|
||||
let peekCard = $state<CardInstance | null>(null);
|
||||
/** When the peeked card is a creature on the board, its live stats ride along. */
|
||||
let peekCreatureId = $state<string | null>(null);
|
||||
let helpTab = $state<"play" | "rules" | "cards" | "about">("play");
|
||||
let hotseatCount = $state(2);
|
||||
let setupName = $state("");
|
||||
@@ -151,6 +153,7 @@
|
||||
function selectCard(card: CardInstance) {
|
||||
if (!view) return;
|
||||
peekCard = null;
|
||||
peekCreatureId = null;
|
||||
if (youMustRespond) {
|
||||
dispatch({ type: "counteract", instanceId: card.instanceId });
|
||||
return;
|
||||
@@ -390,6 +393,20 @@
|
||||
tacks: "handful-of-tacks", pit: "create-pit", safe: "safe",
|
||||
};
|
||||
|
||||
function creatureStats(c: NonNullable<GameView["creatures"]>[number]): string {
|
||||
const hp = Number.isFinite(c.maxDamage)
|
||||
? `${c.maxDamage - c.damage} of ${c.maxDamage} hits left`
|
||||
: "unharmed by ordinary damage";
|
||||
const moves = `${c.movesPerTurn - c.movementUsed} of ${c.movesPerTurn} moves`;
|
||||
const atk = c.justCreated ? "no attack the turn it appears" : c.attackUsed ? "attack spent" : "attack ready";
|
||||
return `${hp} · ${moves} · ${atk}`;
|
||||
}
|
||||
const peekNote = $derived.by(() => {
|
||||
if (!view || !peekCreatureId) return null;
|
||||
const c = view.creatures.find((c) => c.id === peekCreatureId);
|
||||
return c ? creatureStats(c) : null;
|
||||
});
|
||||
|
||||
/** Show the card behind whatever occupies this square (view-only). */
|
||||
function peekAt(cell: { x: number; y: number }): boolean {
|
||||
if (!view) return false;
|
||||
@@ -406,6 +423,7 @@
|
||||
: null;
|
||||
if (!cardId) return false;
|
||||
peekCard = { instanceId: `peek-${cardId}`, cardId };
|
||||
peekCreatureId = creature?.id ?? null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -455,7 +473,15 @@
|
||||
}
|
||||
const mine = creature.controllerId === view.you || creature.kind === "democratic-monster";
|
||||
if (mine) {
|
||||
selectedCreature = selectedCreature === creatureId ? null : creatureId;
|
||||
const selecting = selectedCreature !== creatureId;
|
||||
selectedCreature = selecting ? creatureId : null;
|
||||
if (selecting) {
|
||||
peekCard = { instanceId: `peek-${creature.kind}`, cardId: creature.kind };
|
||||
peekCreatureId = creature.id;
|
||||
} else {
|
||||
peekCard = null;
|
||||
peekCreatureId = null;
|
||||
}
|
||||
} else {
|
||||
peekAt(creature.position);
|
||||
}
|
||||
@@ -645,8 +671,9 @@
|
||||
|
||||
{#if peekCard}
|
||||
<div class="inspector" role="img" aria-label="displayed card, enlarged">
|
||||
<button class="inspector-close" onclick={() => (peekCard = null)} aria-label="hide card">×</button>
|
||||
<button class="inspector-close" onclick={() => { peekCard = null; peekCreatureId = null; }} aria-label="hide card">×</button>
|
||||
<div class="inspector-card"><Card card={peekCard} /></div>
|
||||
{#if peekNote}<div class="peek-note">{peekNote}</div>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1003,8 +1030,16 @@
|
||||
<button class="hint-cancel" onclick={clearSelection}>cancel</button>
|
||||
</div>
|
||||
{/if}
|
||||
{#if youMustRespond || selectedDef || youMustDiscard || discardMode || discardSelection.size > 0}
|
||||
{#if youMustRespond || selectedDef || selectedCreature || youMustDiscard || discardMode || discardSelection.size > 0}
|
||||
<div class="hint-strip">
|
||||
{#if selectedCreature && !selectedDef}
|
||||
{@const sc = view.creatures.find((c) => c.id === selectedCreature)}
|
||||
{#if sc}
|
||||
<span>Commanding <strong>{cardDef(sc.kind).name}</strong> — {creatureStats(sc)}.
|
||||
Tap a square beside it to march, a target in its square to attack.</span>
|
||||
<button class="hint-cancel" onclick={() => { selectedCreature = null; peekCard = null; peekCreatureId = null; }}>done</button>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if youMustRespond && view.stack}
|
||||
<span class="hint-alert">
|
||||
{#if view.stack.defenderId === view.you}
|
||||
@@ -1673,6 +1708,18 @@
|
||||
.attack-power { font-family: "Courier Prime", monospace; color: #6b5a41; font-size: 0.9rem; }
|
||||
.attack-fist { font-size: 1.1rem; color: #43331f; }
|
||||
|
||||
.peek-note {
|
||||
margin-top: 0.4rem;
|
||||
background: #2b2218;
|
||||
color: #e8dfc6;
|
||||
font-family: "Courier Prime", monospace;
|
||||
font-size: 0.72rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
max-width: 11rem;
|
||||
}
|
||||
|
||||
.inspector {
|
||||
position: fixed;
|
||||
right: 0.9rem;
|
||||
@@ -1740,6 +1787,18 @@
|
||||
.attack-power { font-family: "Courier Prime", monospace; color: #6b5a41; font-size: 0.9rem; }
|
||||
.attack-fist { font-size: 1.1rem; color: #43331f; }
|
||||
|
||||
.peek-note {
|
||||
margin-top: 0.4rem;
|
||||
background: #2b2218;
|
||||
color: #e8dfc6;
|
||||
font-family: "Courier Prime", monospace;
|
||||
font-size: 0.72rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
max-width: 11rem;
|
||||
}
|
||||
|
||||
.inspector { right: 0.5rem; bottom: 0.5rem; }
|
||||
.inspector-card { transform: scale(1.3); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user