A reflected Lightning Blast ends the caster's turn on the spot (rev 28)
FAQ: 'If this gets FULLY REFLECTED, you lose the rest of your turn and cannot draw cards (in addition to damage).' When the returned bolt stuns its own caster mid-turn, their remaining actions end at once and the end-of-turn draw comes up empty (new turn.drawForbidden flag); the normal stun still costs them the next turn. The affected player sees 'Your own bolt has left you reeling' in place of the usual spent-turn slip. Older revisions played on and stored games 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
67157ad3a2
commit
1d7407cb20
@@ -167,6 +167,9 @@ export interface TurnState {
|
||||
/** SLOW: "his attacks [reduce] to every other turn". */
|
||||
attackForbidden: boolean;
|
||||
actionsEnded: boolean;
|
||||
/** A reflected LIGHTNING BLAST stuns mid-turn: no end-of-turn draw either
|
||||
* (FAQ: "you lose the rest of your turn and can't draw cards"). */
|
||||
drawForbidden?: boolean;
|
||||
}
|
||||
|
||||
export interface CastStack {
|
||||
@@ -845,6 +848,15 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
if (ctx.damageDealt <= 0 || !ctx.defender.alive) return;
|
||||
ctx.defender.lostTurns++;
|
||||
ctx.events.push({ type: "stunned", player: ctx.defender.id, turnsLost: 1 });
|
||||
// Stunned mid-own-turn (the bolt came back): FAQ — "If this gets FULLY
|
||||
// REFLECTED, you lose the rest of your turn and can't draw cards (in
|
||||
// addition to damage)." Rules rev 28; older games played on and replay so.
|
||||
const st = ctx.state;
|
||||
if ((st.config.deckRev ?? 1) >= 28 &&
|
||||
st.players[st.turn.activeIndex]!.id === ctx.defender.id) {
|
||||
st.turn.actionsEnded = true;
|
||||
st.turn.drawForbidden = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
powerthrust: { kind: "attack", requiresLos: true, baseDamage: (n) => 2 + (n ?? 0) },
|
||||
@@ -5955,7 +5967,7 @@ function doEndTurn(prev: GameState, draw: number): CommandResult {
|
||||
const events: GameEvent[] = [];
|
||||
|
||||
const room = handLimit(p) - p.hand.length;
|
||||
const count = Math.min(draw, room);
|
||||
const count = state.turn.drawForbidden ? 0 : Math.min(draw, room);
|
||||
if (count > 0) {
|
||||
const drawn: CardInstance[] = [];
|
||||
let toDraw = count;
|
||||
|
||||
@@ -1023,3 +1023,45 @@ describe("the reflected-attack window (rules rev 25)", () => {
|
||||
expect(state.players.find((p) => p.id === attacker)!.life).toBe(5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("a reflected lightning blast ends the caster's turn (rules rev 28)", () => {
|
||||
function boltRig(deckRev: number) {
|
||||
let { state } = createGame({ playerIds: ["a", "b"], seed: 42, sets: ["basic"], deckRev });
|
||||
state = toRound2(state);
|
||||
const { attacker, defender } = faceOff(state);
|
||||
const lb = giveCard(state, attacker, "lightning-blast");
|
||||
giveCard(state, attacker, "number-3", "N", 1);
|
||||
giveCard(state, defender, "full-reflection", "FR", 0);
|
||||
state = must(state, attacker, {
|
||||
type: "cast", instanceId: lb.instanceId, numberInstanceIds: ["number-3#N"],
|
||||
target: { kind: "player", playerId: defender },
|
||||
});
|
||||
state = must(state, defender, { type: "counteract", instanceId: "full-reflection#FR" });
|
||||
state = must(state, attacker, { type: "pass" });
|
||||
// rev 25+: the returned bolt opens its window; the caster declines to answer.
|
||||
if (state.stack) state = must(state, attacker, { type: "pass" });
|
||||
return { state, attacker, defender };
|
||||
}
|
||||
|
||||
it("FAQ: 'you lose the rest of your turn and can't draw cards'", () => {
|
||||
let { state, attacker } = boltRig(28);
|
||||
expect(state.players.find((p) => p.id === attacker)!.life).toBe(12);
|
||||
expect(state.turn.actionsEnded).toBe(true);
|
||||
// No more marching, and the end-of-turn draw comes up empty.
|
||||
expect(applyCommand(state, attacker, { type: "move", direction: "N" }).ok).toBe(false);
|
||||
const before = state.players.find((p) => p.id === attacker)!.hand.length;
|
||||
state = must(state, attacker, { type: "endTurn", draw: 2 });
|
||||
expect(state.players.find((p) => p.id === attacker)!.hand.length).toBe(before);
|
||||
// And the stun still costs the next turn.
|
||||
expect(state.players.find((p) => p.id === attacker)!.lostTurns
|
||||
+ (activePlayer(state).id === attacker ? 1 : 0)).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("rev 27 keeps the old behavior: the caster plays on and draws", () => {
|
||||
let { state, attacker } = boltRig(27);
|
||||
expect(state.turn.actionsEnded).toBe(false);
|
||||
const before = state.players.find((p) => p.id === attacker)!.hand.length;
|
||||
state = must(state, attacker, { type: "endTurn", draw: 2 });
|
||||
expect(state.players.find((p) => p.id === attacker)!.hand.length).toBeGreaterThan(before);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface Room {
|
||||
const rooms = new Map<string, Room>();
|
||||
|
||||
/** Rules revision new games are dealt under (stored games keep their own). */
|
||||
const RULES_REV = 27;
|
||||
const RULES_REV = 28;
|
||||
|
||||
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
|
||||
|
||||
@@ -1587,7 +1587,11 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if actionsSpent}
|
||||
<div class="slip">Your spellwork is spent for this turn — all that remains is to draw and end it.</div>
|
||||
{#if view.turn.drawForbidden}
|
||||
<div class="slip urgent">Your own bolt has left you reeling — the rest of this turn is lost, and no cards come with it.</div>
|
||||
{:else}
|
||||
<div class="slip">Your spellwork is spent for this turn — all that remains is to draw and end it.</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if view.yourAmbushes.length > 0}
|
||||
|
||||
@@ -136,7 +136,7 @@ class LocalGame {
|
||||
seed,
|
||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||
...(colors ? { colors } : {}),
|
||||
deckRev: 27,
|
||||
deckRev: 28,
|
||||
};
|
||||
const { state, events } = createGame(config);
|
||||
for (const e of events) {
|
||||
|
||||
Reference in New Issue
Block a user