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
@@ -795,7 +795,7 @@ function respond(view: GameView, style: AutomatonStyle, tier: TierTraits): Comma
|
||||
}
|
||||
|
||||
/** The best attack available against a visible target, numbers and amplify included. */
|
||||
function bestAttack(view: GameView, targetId: PlayerId, tier: TierTraits): Command | null {
|
||||
function bestAttack(view: GameView, targetId: PlayerId, tier: TierTraits): { cmd: Command; damage: number } | null {
|
||||
const numbers = numbersInHand(view);
|
||||
const biggest = numbers[numbers.length - 1];
|
||||
const biggestValue = biggest ? (cardDef(biggest.cardId).value ?? 0) : 0;
|
||||
@@ -852,7 +852,7 @@ function bestAttack(view: GameView, targetId: PlayerId, tier: TierTraits): Comma
|
||||
};
|
||||
}
|
||||
}
|
||||
return best?.cmd ?? null;
|
||||
return best;
|
||||
}
|
||||
|
||||
/** An affliction worth casting when no damage lands, mid number attached.
|
||||
@@ -1164,7 +1164,12 @@ export function automatonCommand(
|
||||
if ((!view.turn.attackUsed || (adrenalized && !view.turn.secondAttackUsed)) &&
|
||||
view.turn.round > 1) {
|
||||
const sighted = sightedCellsFor(view);
|
||||
const visible = livingEnemies(view).filter((p) => sighted.has(cellKey(p.position)));
|
||||
// A wizard under the clockwork's own BUDDY pact is off the target list:
|
||||
// attacking them would tear up the pact it just paid a card for.
|
||||
const pacted = (id: PlayerId) =>
|
||||
view.sustained.some((s) => s.cardId === "buddy" && s.casterId === you && s.targetId === id);
|
||||
const visible = livingEnemies(view)
|
||||
.filter((p) => sighted.has(cellKey(p.position)) && !pacted(p.id));
|
||||
if (visible.length > 0) {
|
||||
const target = thief && visible.some((p) => p.id === thief.id)
|
||||
? thief
|
||||
@@ -1222,22 +1227,31 @@ export function automatonCommand(
|
||||
}
|
||||
}
|
||||
const spell = bestAttack(view, target.id, tier);
|
||||
if (spell) return spell;
|
||||
if (spell) return spell.cmd;
|
||||
const misery = tier.afflictions ? bestAffliction(view, target.id, thief?.id === target.id) : null;
|
||||
if (misery) return misery;
|
||||
if (cellKey(target.position) === here && style !== "worrier") {
|
||||
return { type: "punch", targetId: target.id };
|
||||
}
|
||||
}
|
||||
// Only pacted wizards in sight? A pact is torn up for one thing: a kill.
|
||||
const pactedVisible = livingEnemies(view)
|
||||
.filter((p) => sighted.has(cellKey(p.position)) && pacted(p.id))
|
||||
.sort((a, b) => a.life - b.life);
|
||||
for (const p of pactedVisible) {
|
||||
const spell = bestAttack(view, p.id, tier);
|
||||
if (spell && spell.damage >= p.life) return spell.cmd;
|
||||
}
|
||||
// AROUND THE CORNER: an enemy tucked one bend out of sight is not safe.
|
||||
if (visible.length === 0 && tier.buffs) {
|
||||
const corner = inHand(view, "around-the-corner");
|
||||
if (corner) {
|
||||
for (const p of livingEnemies(view)) {
|
||||
if (pacted(p.id)) continue;
|
||||
if (!bentSightFor(view, self.position, p.position)) continue;
|
||||
const spell = bestAttack(view, p.id, tier);
|
||||
if (spell && spell.type === "cast") {
|
||||
return { ...spell, aroundCornerInstanceId: corner.instanceId };
|
||||
if (spell && spell.cmd.type === "cast") {
|
||||
return { ...spell.cmd, aroundCornerInstanceId: corner.instanceId };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1254,9 +1268,12 @@ export function automatonCommand(
|
||||
}
|
||||
|
||||
// BUDDY after the blow: the wounded cannot come looking for payback —
|
||||
// the pact holds unless the clockwork strikes them again. The berserker
|
||||
// skips it; it would only break its own pact tomorrow.
|
||||
if (tier.buffs && view.turn.attackUsed && style !== "berserker") {
|
||||
// the pact holds unless the clockwork strikes them again. Signed only
|
||||
// when the clockwork actually wants OUT of the fight (hauling gold,
|
||||
// bleeding, or with other enemies left to hunt); a healthy duelist keeps
|
||||
// its options, and the berserker would only break its own pact tomorrow.
|
||||
if (tier.buffs && view.turn.attackUsed && style !== "berserker" &&
|
||||
(self.carriedTreasureId != null || self.life <= 8 || livingEnemies(view).length > 1)) {
|
||||
const pact = inHand(view, "buddy");
|
||||
if (pact) {
|
||||
const sighted = sightedCellsFor(view);
|
||||
|
||||
@@ -2153,8 +2153,15 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
onResolved: (ctx) => {
|
||||
if (ctx.fullyStopped) return;
|
||||
const [mineId, theirsId] = (ctx.stack.params!.cardId ?? "").split(";");
|
||||
const mine = ctx.attacker.hand.findIndex((c) => c.cardId === mineId && cardDef(c.cardId).cardType === "object");
|
||||
const theirs = ctx.defender.hand.findIndex((c) => c.cardId === theirsId && cardDef(c.cardId).cardType === "object");
|
||||
// "Swap any two carried items": every movable object trades — daggers,
|
||||
// rocks, wands, stones (rules rev 26; earlier games matched only
|
||||
// object-typed cards and replay so).
|
||||
const tradable = (id: string) =>
|
||||
(ctx.state.config.deckRev ?? 1) >= 26
|
||||
? isMovableObject(id)
|
||||
: cardDef(id).cardType === "object";
|
||||
const mine = ctx.attacker.hand.findIndex((c) => c.cardId === mineId && tradable(c.cardId));
|
||||
const theirs = ctx.defender.hand.findIndex((c) => c.cardId === theirsId && tradable(c.cardId));
|
||||
if (mine === -1 || theirs === -1) return;
|
||||
const [a] = ctx.attacker.hand.splice(mine, 1);
|
||||
const [b] = ctx.defender.hand.splice(theirs, 1);
|
||||
|
||||
Reference in New Issue
Block a user