diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index efabcd3..afe60c0 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -57,6 +57,23 @@ /** A mind YOU read: the stolen glimpse arrives once, in an event — hold * it up until dismissed, or it is lost to the scrollback. */ let mindRead = $state<{ player: string; cards: CardInstance[] } | null>(null); + /** An easy-to-regret play awaiting its "are you sure?" (see prefs.cautions). */ + let caution = $state<{ reasons: string[]; go: () => void } | null>(null); + function withCaution(reasons: string[], go: () => void) { + if (!prefs.cautions || reasons.length === 0) go(); + else caution = { reasons, go }; + } + /** A move with the delivery check: stepping off your home still laden. */ + function tryMove(direction: Side, over?: boolean) { + const reasons: string[] = []; + if (view && me && me.carriedTreasureId && cellKey(me.position) === cellKey(me.home)) { + const t = view.treasures.find((t) => t.id === me!.carriedTreasureId); + reasons.push(t && t.owner !== view.you + ? `you stand on your home with ${t.owner}'s treasure in your arms — drop it first and it scores` + : "you stand on your home with your own treasure in your arms — drop it first and it is safe"); + } + withCaution(reasons, () => dispatch({ type: "move", direction, ...(over ? { over: true } : {}) })); + } function playFx(events: Parameters[0]) { if (!view) return; for (const e of events) { @@ -104,7 +121,7 @@ /** Leafing through the face-up discard pile. */ let showDiscards = $state(false); let chatDraft = $state(""); - let botTier = $state("adept"); + let botTier = $state(prefs.botTier); /** Card whose official FAQ rulings are open. */ let faqCardId = $state(null); /** A discard-pile card enlarged above the pile. */ @@ -669,7 +686,7 @@ for (const side of SIDES) { const t = stepTarget(view.board, me.position, side); if (t.kind !== "blocked" && cellKey(t.to) === cellKey(cell)) { - dispatch({ type: "move", direction: side }); + tryMove(side); return; } } @@ -685,7 +702,7 @@ illusionPrompt = illusionEdgeParts(k); return; } - dispatch({ type: "move", direction: side }); + tryMove(side); return; } } @@ -699,7 +716,7 @@ const hazard = view.squareContents[cellKey(mid)]?.kind; if (cellKey(far) === cellKey(cell) && (hazard === "pit" || hazard === "tacks" || hazard === "ooze")) { - dispatch({ type: "move", direction: side, over: true }); + tryMove(side, true); return; } } @@ -769,7 +786,7 @@ if (creature && creature.position.x === cell.x && creature.position.y === cell.y) { dispatch({ type: "moveCreature", creatureId: creature.id, direction: side }); } else if (me.position.x === cell.x && me.position.y === cell.y) { - dispatch({ type: "move", direction: side }); + tryMove(side); } else { clickCell(cell); } @@ -885,14 +902,34 @@ function endTurn() { clearSelection(); - // Preference: a lone grabbable treasure underfoot is picked up on the - // way out (pickup ends actions; ending the turn is still legal after). - if (prefs.autoGrab && view && me && !me.carriedTreasureId && - !view.turn.actionsEnded && treasuresHere.length === 1 && - cellKey(me.position) !== cellKey(me.home)) { - dispatch({ type: "pickUpTreasure" }); + const reasons: string[] = []; + if (view && me) { + const left = view.turn.movementAllowance - view.turn.movementUsed; + if (left > 0) reasons.push(`${left} movement point${left === 1 ? "" : "s"} unspent`); + const limit = me.displayed.some((c) => c.cardId === "brainstone") ? 9 : 7; + const room = limit - view.yourHand.length; + if (drawCount > room) { + reasons.push(room <= 0 + ? "your hand is full — the draw will bring nothing" + : `your hand only has room for ${room} of the ${drawCount}-card draw`); + } + if (me.carriedTreasureId && cellKey(me.position) === cellKey(me.home)) { + const t = view.treasures.find((t) => t.id === me!.carriedTreasureId); + reasons.push(t && t.owner !== view.you + ? `${t.owner}'s treasure is still in your arms — drop it here and it scores` + : "your own treasure is still in your arms — drop it here and it is safe"); + } } - dispatch({ type: "endTurn", draw: drawCount }); + withCaution(reasons, () => { + // Preference: a lone grabbable treasure underfoot is picked up on the + // way out (pickup ends actions; ending the turn is still legal after). + if (prefs.autoGrab && view && me && !me.carriedTreasureId && + !view.turn.actionsEnded && treasuresHere.length === 1 && + cellKey(me.position) !== cellKey(me.home)) { + dispatch({ type: "pickUpTreasure" }); + } + dispatch({ type: "endTurn", draw: drawCount }); + }); } // SWAP MEET: pick one of your carried items, then one of theirs you can @@ -1273,6 +1310,18 @@ onchange={(e) => setPref("flourishes", e.currentTarget.checked)} /> Spell flourishes (the animated effects) + +
+ Automatons default to + {#each ["apprentice", "adept", "archmage"] as t (t)} + + {/each} +