Pacts are honored; Swap Meet trades items, not card names (rev 26)
The pact fix: a clockwork no longer attacks a wizard it holds a BUDDY pact on — Eric watched one sign the pact and punch him the next turn. A pact is torn up for exactly one thing: a kill. And the post-attack pact is signed only by a wizard who wants OUT of the fight (hauling gold, bleeding, or with other enemies left) — a healthy duelist keeps its options, and mutual-pact stalemates stay out of bot wars. Swap Meet drops the 'name a card' prompt for a real trade picker: click the other trader, choose one of your carried items, then claim one of their displayed items. Rules rev 26 widens the engine's match from object-typed cards to every movable object (daggers, rocks, wands, stones); older games matched narrowly and replay so. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
co-authored by
Claude Fable 5
parent
06faea0c79
commit
00517e08b1
@@ -290,7 +290,7 @@
|
||||
const CREATURE_TARGET_CARDS = new Set(["mega-monster"]);
|
||||
const MODIFIER_CARDS = new Set(["amplify", "add", "extend", "around-the-corner"]);
|
||||
|
||||
const NAMED_CARDS = new Set(["card-erasure", "drop-object", "deja-vu", "thief", "swap-meet", "remove-curse", "swarthmores-enchantment", "illusionary-attack"]);
|
||||
const NAMED_CARDS = new Set(["card-erasure", "drop-object", "deja-vu", "thief", "remove-curse", "swarthmores-enchantment", "illusionary-attack"]);
|
||||
const attackVsWall = $derived(
|
||||
selectedCard != null && cardDef(selectedCard.cardId).cardType === "attack" && !EDGE_CARDS.has(selectedCard.cardId),
|
||||
);
|
||||
@@ -309,6 +309,8 @@
|
||||
|
||||
function clearSelection() {
|
||||
punchWallMode = false;
|
||||
swapMeetTarget = null;
|
||||
swapMine = null;
|
||||
ambushVia = null;
|
||||
ambushTrigger = null;
|
||||
ambushSpell = null;
|
||||
@@ -797,6 +799,10 @@
|
||||
pendingCellFor = playerId;
|
||||
return; // next: click the destination cell
|
||||
}
|
||||
if (selectedCard.cardId === "swap-meet") {
|
||||
swapMeetTarget = playerId;
|
||||
return; // next: pick your item, then theirs
|
||||
}
|
||||
const cmd: Parameters<typeof net.command>[0] = {
|
||||
type: "cast",
|
||||
instanceId: selectedCard.instanceId,
|
||||
@@ -827,6 +833,26 @@
|
||||
dispatch({ type: "endTurn", draw: drawCount });
|
||||
}
|
||||
|
||||
// SWAP MEET: pick one of your carried items, then one of theirs you can
|
||||
// see (their displayed items — hidden hand cards are not on the table).
|
||||
let swapMeetTarget = $state<string | null>(null);
|
||||
let swapMine = $state<string | null>(null);
|
||||
const myTradables = $derived(view ? view.yourHand.filter((c) => isMovableObject(c.cardId)) : []);
|
||||
const theirTradables = $derived.by(() => {
|
||||
if (!view || !swapMeetTarget) return [];
|
||||
const t = view.players.find((p) => p.id === swapMeetTarget);
|
||||
return t ? t.displayed.filter((c) => isMovableObject(c.cardId)) : [];
|
||||
});
|
||||
function castSwapMeet(theirs: string) {
|
||||
if (!selectedCard || !swapMeetTarget || !swapMine) return;
|
||||
dispatch({
|
||||
type: "cast", instanceId: selectedCard.instanceId,
|
||||
target: { kind: "player", playerId: swapMeetTarget },
|
||||
params: { cardId: `${swapMine};${theirs}` },
|
||||
});
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
let pickChoice = $state(false);
|
||||
function pickUp() {
|
||||
if (treasuresHere.length > 1) { pickChoice = true; return; }
|
||||
@@ -1673,6 +1699,25 @@
|
||||
<span>— then click the target</span>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if selectedCard?.cardId === "swap-meet"}
|
||||
{#if !swapMeetTarget}
|
||||
<span>— click the other trader</span>
|
||||
{:else if myTradables.length === 0}
|
||||
<span>— you carry no items to trade</span>
|
||||
{:else if !swapMine}
|
||||
<span>— trade away your:</span>
|
||||
{#each myTradables as c (c.instanceId)}
|
||||
<button class="stamp tiny" onclick={() => (swapMine = c.cardId)}>{cardDef(c.cardId).name}</button>
|
||||
{/each}
|
||||
{:else if theirTradables.length === 0}
|
||||
<span>— they show no item you could claim</span>
|
||||
{:else}
|
||||
<span>— and take their:</span>
|
||||
{#each theirTradables as c (c.instanceId)}
|
||||
<button class="stamp tiny" onclick={() => castSwapMeet(c.cardId)}>{cardDef(c.cardId).name}</button>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
{#if selectedCard?.cardId === "waterbolt"}
|
||||
<label class="inline">damage <input class="num-input" type="number" min="0" max={numberTotal} bind:value={wbDamage} /></label>
|
||||
<span>(knockback {numberTotal - wbDamage})</span>
|
||||
|
||||
@@ -136,7 +136,7 @@ class LocalGame {
|
||||
seed,
|
||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||
...(colors ? { colors } : {}),
|
||||
deckRev: 25,
|
||||
deckRev: 26,
|
||||
};
|
||||
const { state, events } = createGame(config);
|
||||
for (const e of events) {
|
||||
|
||||
Reference in New Issue
Block a user