Rules rev 4: a creature's blow can be counteracted
The WRAITH card assumes the window exists — "REFLECTIONs used on the
wraith's touch will damage the wraith" — but creature damage applied
instantly, so BLUNT had nothing to catch, as playtesting found. Under
revision 4 every creature blow against a wizard opens the same
counteraction stack a spell does: the wraith's entry touch, the
democratic monster's claw, and commanded troll/skeleton/shadow
attacks. The blow's damage rides the stack; BLUNT halves it (round
up); reflections work by name against creatures — half back for
REFLECTION, the whole blow for FULL REFLECTION — landing on the
creature, not its controller; FULL SHIELD correctly does nothing
("does not stop any physical attack"); the wraith's card theft is a
secondary effect that lands only if damage does. No aim-miss rolls
against invisible or shrunk defenders — the creature is already in
the square. The attack fanfare shows the creature's own card.
Earlier revisions keep the instant touch so every stored game — and
the live one that found this — replays unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
3f3e6d9853
commit
d1eace9530
@@ -182,6 +182,10 @@ export interface CastStack {
|
|||||||
waitingOn: PlayerId;
|
waitingOn: PlayerId;
|
||||||
/** CHAOS only: the defender's FULL SHIELD sat them out rather than stopping it. */
|
/** CHAOS only: the defender's FULL SHIELD sat them out rather than stopping it. */
|
||||||
defenderShielded?: boolean;
|
defenderShielded?: boolean;
|
||||||
|
/** Set when a creature, not a wizard, delivers the attack. */
|
||||||
|
creatureId?: string;
|
||||||
|
/** The wraith's touch also steals a random card if damage lands. */
|
||||||
|
creatureTouch?: "wraith" | "claw";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CastParams {
|
export interface CastParams {
|
||||||
@@ -207,7 +211,8 @@ export interface GameConfig {
|
|||||||
* Rules revision, frozen per game so stored games replay unchanged.
|
* Rules revision, frozen per game so stored games replay unchanged.
|
||||||
* Absent = original. Rev 2: LIFESAVER leaves two-player decks ("Not
|
* Absent = original. Rev 2: LIFESAVER leaves two-player decks ("Not
|
||||||
* applicable in a 2-player game."). Rev 3: WARD springs only when armed,
|
* applicable in a 2-player game."). Rev 3: WARD springs only when armed,
|
||||||
* and CHAOS honors FULL SHIELD sit-outs and refuses REFLECTIONS.
|
* and CHAOS honors FULL SHIELD sit-outs and refuses REFLECTIONS. Rev 4:
|
||||||
|
* creature blows open a counteraction window like any attack.
|
||||||
*/
|
*/
|
||||||
deckRev?: number;
|
deckRev?: number;
|
||||||
}
|
}
|
||||||
@@ -2527,12 +2532,19 @@ function doMoveCreature(prev: GameState, creatureId: string, direction: Side): C
|
|||||||
events.push({ type: "creatureMoved", creatureId: creature.id, from, to: creature.position, direction, by: active.id });
|
events.push({ type: "creatureMoved", creatureId: creature.id, from, to: creature.position, direction, by: active.id });
|
||||||
|
|
||||||
// Touch effects on entering a player's square.
|
// Touch effects on entering a player's square.
|
||||||
|
const rev4 = (state.config.deckRev ?? 1) >= 4;
|
||||||
for (const p of state.players) {
|
for (const p of state.players) {
|
||||||
if (!p.alive || cellKey(p.position) !== cellKey(creature.position)) continue;
|
if (!p.alive || cellKey(p.position) !== cellKey(creature.position)) continue;
|
||||||
if (p.id === creature.controllerId && creature.kind !== "democratic-monster") continue; // won't hurt creator
|
if (p.id === creature.controllerId && creature.kind !== "democratic-monster") continue; // won't hurt creator
|
||||||
if (creature.kind === "wraith" && !creature.attackUsed) {
|
if (creature.kind === "wraith" && !creature.attackUsed) {
|
||||||
creature.attackUsed = true;
|
creature.attackUsed = true;
|
||||||
events.push({ type: "creatureTouched", creatureId: creature.id, player: p.id });
|
events.push({ type: "creatureTouched", creatureId: creature.id, player: p.id });
|
||||||
|
if (rev4) {
|
||||||
|
// The victim may counteract ("REFLECTIONs used on the wraith's touch
|
||||||
|
// will damage the wraith" — the card assumes exactly this window).
|
||||||
|
openCreatureStack(state, creature, p, 2, "wraith");
|
||||||
|
return { ok: true, state, events };
|
||||||
|
}
|
||||||
applyDamage(state, events, p, 2, "wraith's touch", null);
|
applyDamage(state, events, p, 2, "wraith's touch", null);
|
||||||
if (p.alive && p.hand.length > 0) {
|
if (p.alive && p.hand.length > 0) {
|
||||||
const [idx, rngNext] = nextInt(state.rng, p.hand.length);
|
const [idx, rngNext] = nextInt(state.rng, p.hand.length);
|
||||||
@@ -2546,6 +2558,10 @@ function doMoveCreature(prev: GameState, creatureId: string, direction: Side): C
|
|||||||
if (creature.kind === "democratic-monster" && !creature.attackUsed) {
|
if (creature.kind === "democratic-monster" && !creature.attackUsed) {
|
||||||
creature.attackUsed = true; // "may attack only one player per round of turns"
|
creature.attackUsed = true; // "may attack only one player per round of turns"
|
||||||
events.push({ type: "creatureTouched", creatureId: creature.id, player: p.id });
|
events.push({ type: "creatureTouched", creatureId: creature.id, player: p.id });
|
||||||
|
if (rev4) {
|
||||||
|
openCreatureStack(state, creature, p, 2, "claw");
|
||||||
|
return { ok: true, state, events };
|
||||||
|
}
|
||||||
applyDamage(state, events, p, 2, "clawing monster", null);
|
applyDamage(state, events, p, 2, "clawing monster", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2553,6 +2569,31 @@ function doMoveCreature(prev: GameState, creatureId: string, direction: Side): C
|
|||||||
return { ok: true, state, events };
|
return { ok: true, state, events };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Rules rev 4: a creature's blow opens a counteraction window like any attack. */
|
||||||
|
function openCreatureStack(
|
||||||
|
state: GameState,
|
||||||
|
creature: CreatureState,
|
||||||
|
victim: PlayerState,
|
||||||
|
damage: number,
|
||||||
|
touch?: "wraith" | "claw",
|
||||||
|
): void {
|
||||||
|
state.stack = {
|
||||||
|
attackerId: creature.controllerId,
|
||||||
|
defenderId: victim.id,
|
||||||
|
attackCard: null,
|
||||||
|
numberValue: null,
|
||||||
|
amplifyFactor: 1,
|
||||||
|
extendFactor: 1,
|
||||||
|
powerAttackPoints: 0,
|
||||||
|
params: { damage },
|
||||||
|
kind: "physical",
|
||||||
|
counters: [],
|
||||||
|
waitingOn: victim.id,
|
||||||
|
creatureId: creature.id,
|
||||||
|
...(touch ? { creatureTouch: touch } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function doCreatureAttack(prev: GameState, creatureId: string, targetId: string): CommandResult {
|
function doCreatureAttack(prev: GameState, creatureId: string, targetId: string): CommandResult {
|
||||||
const blocked = requireActionsAvailable(prev);
|
const blocked = requireActionsAvailable(prev);
|
||||||
if (blocked) return err(blocked);
|
if (blocked) return err(blocked);
|
||||||
@@ -2593,6 +2634,10 @@ function doCreatureAttack(prev: GameState, creatureId: string, targetId: string)
|
|||||||
}
|
}
|
||||||
events.push({ type: "creatureAttacked", creatureId: creature.id, target: targetId, dieRoll: roll });
|
events.push({ type: "creatureAttacked", creatureId: creature.id, target: targetId, dieRoll: roll });
|
||||||
if (targetPlayer) {
|
if (targetPlayer) {
|
||||||
|
if ((state.config.deckRev ?? 1) >= 4) {
|
||||||
|
openCreatureStack(state, creature, targetPlayer, amount);
|
||||||
|
return { ok: true, state, events };
|
||||||
|
}
|
||||||
applyDamage(state, events, targetPlayer, amount, `${creature.kind}'s blow`, null, "physical");
|
applyDamage(state, events, targetPlayer, amount, `${creature.kind}'s blow`, null, "physical");
|
||||||
} else {
|
} else {
|
||||||
damageCreature(state, events, targetCreature!, amount, `${creature.kind}'s blow`);
|
damageCreature(state, events, targetCreature!, amount, `${creature.kind}'s blow`);
|
||||||
@@ -4447,8 +4492,9 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
|
|||||||
const effect = attackId ? (CARD_EFFECTS[attackId] as AttackEffect) : null;
|
const effect = attackId ? (CARD_EFFECTS[attackId] as AttackEffect) : null;
|
||||||
|
|
||||||
// Hit rolls against INVISIBLE (attacker guesses a direction: 1-in-4) and
|
// Hit rolls against INVISIBLE (attacker guesses a direction: 1-in-4) and
|
||||||
// SHRINK (50% miss). "If a spell misses, it dissipates harmlessly."
|
// SHRINK (50% miss). "If a spell misses, it dissipates harmlessly." A
|
||||||
if (sustainedOn(state, defender.id, "invisible").length > 0) {
|
// creature sharing the square has nothing to aim: no miss rolls.
|
||||||
|
if (!stack.creatureId && sustainedOn(state, defender.id, "invisible").length > 0) {
|
||||||
const [roll, rngNext] = rollDie(state.rng);
|
const [roll, rngNext] = rollDie(state.rng);
|
||||||
state.rng = rngNext;
|
state.rng = rngNext;
|
||||||
if (roll !== 1) {
|
if (roll !== 1) {
|
||||||
@@ -4457,7 +4503,7 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sustainedOn(state, defender.id, "shrink").length > 0) {
|
if (!stack.creatureId && sustainedOn(state, defender.id, "shrink").length > 0) {
|
||||||
const [roll, rngNext] = rollDie(state.rng);
|
const [roll, rngNext] = rollDie(state.rng);
|
||||||
state.rng = rngNext;
|
state.rng = rngNext;
|
||||||
if (roll > 2) {
|
if (roll > 2) {
|
||||||
@@ -4467,7 +4513,11 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let base = effect ? effect.baseDamage(stack.numberValue, stack.params) : 1; // punch = 1
|
let base = effect
|
||||||
|
? effect.baseDamage(stack.numberValue, stack.params)
|
||||||
|
: stack.creatureId
|
||||||
|
? (stack.params?.damage ?? 1) // a creature's blow
|
||||||
|
: 1; // a punch
|
||||||
// Webs burn: "Any fire damage done to player in webs causes two extra points."
|
// Webs burn: "Any fire damage done to player in webs causes two extra points."
|
||||||
if (attackId === "fireball" && sustainedOn(state, defender.id, "sticky-web").length > 0) {
|
if (attackId === "fireball" && sustainedOn(state, defender.id, "sticky-web").length > 0) {
|
||||||
base += 2;
|
base += 2;
|
||||||
@@ -4532,14 +4582,33 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
|
|||||||
pipe.fullyStopped = true;
|
pipe.fullyStopped = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (stack.creatureId &&
|
||||||
|
(counter.card.cardId === "reflection" || counter.card.cardId === "full-reflection")) {
|
||||||
|
// "REFLECTIONs used on the wraith's touch will damage the wraith."
|
||||||
|
if (counter.card.cardId === "reflection") {
|
||||||
|
const half = Math.ceil(pipe.damage / 2);
|
||||||
|
pipe.reflectedDamage += half;
|
||||||
|
pipe.damage = half;
|
||||||
|
} else {
|
||||||
|
pipe.redirected = true; // the whole blow turns back (damage rides pipe.damage)
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const ce = CARD_EFFECTS[counter.card.cardId];
|
const ce = CARD_EFFECTS[counter.card.cardId];
|
||||||
if (ce && ce.kind === "counter") ce.apply(pipe);
|
if (ce && ce.kind === "counter") ce.apply(pipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
let damageDealt = 0;
|
let damageDealt = 0;
|
||||||
|
const attackingCreature = stack.creatureId
|
||||||
|
? state.creatures.find((c) => c.id === stack.creatureId)
|
||||||
|
: undefined;
|
||||||
if (pipe.redirected) {
|
if (pipe.redirected) {
|
||||||
if (pipe.damage > 0) {
|
if (pipe.damage > 0) {
|
||||||
applyDamage(state, events, attacker, pipe.damage, `${attackId} (reflected)`, defender.id);
|
if (attackingCreature) {
|
||||||
|
damageCreature(state, events, attackingCreature, pipe.damage, "reflected touch");
|
||||||
|
} else {
|
||||||
|
applyDamage(state, events, attacker, pipe.damage, `${attackId} (reflected)`, defender.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (effect?.sustains && pipe.duration > 0) {
|
if (effect?.sustains && pipe.duration > 0) {
|
||||||
attachSustained(state, events, attackId!, defender.id, attacker.id, pipe.duration);
|
attachSustained(state, events, attackId!, defender.id, attacker.id, pipe.duration);
|
||||||
@@ -4550,11 +4619,27 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
|
|||||||
events.push({ type: "lifeGained", player: defender.id, amount: pipe.damage, source: `${attackId} (reversed)`, lifeAfter: defender.life });
|
events.push({ type: "lifeGained", player: defender.id, amount: pipe.damage, source: `${attackId} (reversed)`, lifeAfter: defender.life });
|
||||||
damageDealt = pipe.damage; // secondary effects still take effect
|
damageDealt = pipe.damage; // secondary effects still take effect
|
||||||
} else if (pipe.damage > 0) {
|
} else if (pipe.damage > 0) {
|
||||||
applyDamage(state, events, defender, pipe.damage, attackId ?? `punch from ${attacker.id}`, attacker.id, pipe.kind);
|
const source = attackId ??
|
||||||
|
(attackingCreature ? `${attackingCreature.kind}'s blow` : `punch from ${attacker.id}`);
|
||||||
|
applyDamage(state, events, defender, pipe.damage, source, attacker.id, pipe.kind);
|
||||||
damageDealt = pipe.damage;
|
damageDealt = pipe.damage;
|
||||||
}
|
}
|
||||||
|
// "he takes 2 points of damage and loses a random card" — the theft is a
|
||||||
|
// secondary effect, stopped only when all the damage is.
|
||||||
|
if (stack.creatureTouch === "wraith" && damageDealt > 0 && defender.alive && defender.hand.length > 0) {
|
||||||
|
const [idx, rngNext] = nextInt(state.rng, defender.hand.length);
|
||||||
|
state.rng = rngNext;
|
||||||
|
const [card] = defender.hand.splice(idx, 1);
|
||||||
|
defender.displayed = defender.displayed.filter((id) => id !== card!.instanceId);
|
||||||
|
state.discard.push(card!);
|
||||||
|
events.push({ type: "cardsDiscarded", player: defender.id, cards: [card!] });
|
||||||
|
}
|
||||||
if (pipe.reflectedDamage > 0) {
|
if (pipe.reflectedDamage > 0) {
|
||||||
applyDamage(state, events, attacker, pipe.reflectedDamage, `${attackId} (reflection)`, defender.id);
|
if (attackingCreature) {
|
||||||
|
damageCreature(state, events, attackingCreature, pipe.reflectedDamage, "reflected touch");
|
||||||
|
} else {
|
||||||
|
applyDamage(state, events, attacker, pipe.reflectedDamage, `${attackId} (reflection)`, defender.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// EMPATHY: "Any attack done in any form against you acts against both
|
// EMPATHY: "Any attack done in any form against you acts against both
|
||||||
// you and the caster of the spell."
|
// you and the caster of the spell."
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { applyCommand, activePlayer, boardView, creatureAt, type GameState, type PlayerId } from "../src/game";
|
import { applyCommand, activePlayer, boardView, creatureAt, type GameState, type PlayerId, createGame } from "../src/game";
|
||||||
import { cellKey, SIDES, stepTarget, type Side } from "../src/board";
|
import { cellKey, SIDES, stepTarget, type Side } from "../src/board";
|
||||||
import type { CardInstance } from "../src/cards";
|
import type { CardInstance } from "../src/cards";
|
||||||
import { newExpansionGame as newGame, must, giveCard, toRound2, emptyNeighborCell } from "./helpers";
|
import { newExpansionGame as newGame, must, giveCard, toRound2, emptyNeighborCell } from "./helpers";
|
||||||
@@ -282,3 +282,62 @@ describe("expansion support cards", () => {
|
|||||||
expect(state.creatures.length).toBe(0);
|
expect(state.creatures.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("counteracting a creature's blow (rules rev 4)", () => {
|
||||||
|
function rev4() {
|
||||||
|
return createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev: 4 });
|
||||||
|
}
|
||||||
|
function wraithOnVictim(state: GameState) {
|
||||||
|
state = toRound2(state);
|
||||||
|
const controller = activePlayer(state);
|
||||||
|
const victim = state.players.find((p) => p.id !== controller.id)!;
|
||||||
|
const spot = emptyNeighborCell(state, controller.position);
|
||||||
|
state.creatures.push({
|
||||||
|
id: "w1", kind: "wraith", controllerId: controller.id, position: { ...controller.position },
|
||||||
|
damage: 0, maxDamage: 4, movesPerTurn: 3, movementUsed: 0,
|
||||||
|
wallPassesPerTurn: 1, wallPassUsed: 0, attackUsed: false, justCreated: false, scorchedThisTurn: [],
|
||||||
|
});
|
||||||
|
victim.position = { ...spot.cell };
|
||||||
|
return { state, controller: controller.id, victim: victim.id, side: spot.side };
|
||||||
|
}
|
||||||
|
|
||||||
|
it("BLUNT halves the wraith's touch; the card theft still lands", () => {
|
||||||
|
let { state } = rev4();
|
||||||
|
const rig = wraithOnVictim(state);
|
||||||
|
state = rig.state;
|
||||||
|
giveCard(state, rig.victim, "blunt", "B", 0);
|
||||||
|
const handBefore = state.players.find((p) => p.id === rig.victim)!.hand.filter(Boolean).length;
|
||||||
|
state = must(state, rig.controller, { type: "moveCreature", creatureId: "w1", direction: rig.side });
|
||||||
|
expect(state.stack?.creatureId).toBe("w1");
|
||||||
|
state = must(state, rig.victim, { type: "counteract", instanceId: "blunt#B" });
|
||||||
|
state = must(state, rig.controller, { type: "pass" });
|
||||||
|
state = must(state, rig.victim, { type: "pass" });
|
||||||
|
const v = state.players.find((p) => p.id === rig.victim)!;
|
||||||
|
expect(v.life).toBe(14); // 2 halved up to 1
|
||||||
|
expect(v.hand.length).toBe(handBefore - 2); // blunt spent + a card stolen
|
||||||
|
});
|
||||||
|
|
||||||
|
it("FULL REFLECTION turns the touch back on the wraith, theft and all", () => {
|
||||||
|
let { state } = rev4();
|
||||||
|
const rig = wraithOnVictim(state);
|
||||||
|
state = rig.state;
|
||||||
|
giveCard(state, rig.victim, "full-reflection", "FR", 0);
|
||||||
|
state = must(state, rig.controller, { type: "moveCreature", creatureId: "w1", direction: rig.side });
|
||||||
|
state = must(state, rig.victim, { type: "counteract", instanceId: "full-reflection#FR" });
|
||||||
|
state = must(state, rig.controller, { type: "pass" });
|
||||||
|
state = must(state, rig.victim, { type: "pass" });
|
||||||
|
const v = state.players.find((p) => p.id === rig.victim)!;
|
||||||
|
expect(v.life).toBe(15);
|
||||||
|
const wraith = state.creatures.find((c) => c.id === "w1")!;
|
||||||
|
expect(wraith.damage).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("legacy games (rev < 4) keep the instant touch", () => {
|
||||||
|
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"] });
|
||||||
|
const rig = wraithOnVictim(state);
|
||||||
|
state = rig.state;
|
||||||
|
state = must(state, rig.controller, { type: "moveCreature", creatureId: "w1", direction: rig.side });
|
||||||
|
expect(state.stack).toBeNull();
|
||||||
|
expect(state.players.find((p) => p.id === rig.victim)!.life).toBe(13);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export interface Room {
|
|||||||
const rooms = new Map<string, Room>();
|
const rooms = new Map<string, Room>();
|
||||||
|
|
||||||
/** Rules revision new games are dealt under (stored games keep their own). */
|
/** Rules revision new games are dealt under (stored games keep their own). */
|
||||||
const RULES_REV = 3;
|
const RULES_REV = 4;
|
||||||
|
|
||||||
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
|
||||||
|
|||||||
@@ -83,9 +83,12 @@
|
|||||||
const youMustRespond = $derived(view?.stack != null && view.stack.waitingOn === view.you);
|
const youMustRespond = $derived(view?.stack != null && view.stack.waitingOn === view.you);
|
||||||
const attackNoticeKey = $derived(
|
const attackNoticeKey = $derived(
|
||||||
youMustRespond && view?.stack
|
youMustRespond && view?.stack
|
||||||
? `${view.stack.attackerId}:${view.stack.attackCard?.instanceId ?? "punch"}:${view.stack.counters.length}`
|
? `${view.stack.attackerId}:${view.stack.attackCard?.instanceId ?? view.stack.creatureId ?? "punch"}:${view.stack.counters.length}`
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
const attackingCreature = $derived(
|
||||||
|
view?.stack?.creatureId ? view.creatures.find((c) => c.id === view!.stack!.creatureId) ?? null : null,
|
||||||
|
);
|
||||||
const youMustDiscard = $derived(view != null && view.pendingDiscard === view.you);
|
const youMustDiscard = $derived(view != null && view.pendingDiscard === view.you);
|
||||||
const youMustShield = $derived(view?.chaosPending?.queue[0] === view?.you && view != null);
|
const youMustShield = $derived(view?.chaosPending?.queue[0] === view?.you && view != null);
|
||||||
const holdingWard = $derived(view?.yourHand.some((c) => c.cardId === "ward") ?? false);
|
const holdingWard = $derived(view?.yourHand.some((c) => c.cardId === "ward") ?? false);
|
||||||
@@ -746,13 +749,17 @@
|
|||||||
<div class="scrim attack-scrim" role="alertdialog" aria-label="you are under attack">
|
<div class="scrim attack-scrim" role="alertdialog" aria-label="you are under attack">
|
||||||
<div class="attack-notice">
|
<div class="attack-notice">
|
||||||
<div class="attack-headline">
|
<div class="attack-headline">
|
||||||
{#if view.stack.defenderId === view.you}
|
{#if view.stack.defenderId === view.you && attackingCreature}
|
||||||
|
<strong>{view.stack.attackerId}</strong>'s {cardDef(attackingCreature.kind).name} attacks you!
|
||||||
|
{:else if view.stack.defenderId === view.you}
|
||||||
<strong>{view.stack.attackerId}</strong> attacks you!
|
<strong>{view.stack.attackerId}</strong> attacks you!
|
||||||
{:else}
|
{:else}
|
||||||
<strong>{view.stack.counters[view.stack.counters.length - 1]?.player}</strong> counters your spell!
|
<strong>{view.stack.counters[view.stack.counters.length - 1]?.player}</strong> counters your spell!
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if view.stack.defenderId === view.you && view.stack.attackCard}
|
{#if view.stack.defenderId === view.you && attackingCreature}
|
||||||
|
<div class="attack-card"><Card card={{ instanceId: `atk-${attackingCreature.id}`, cardId: attackingCreature.kind }} /></div>
|
||||||
|
{:else if view.stack.defenderId === view.you && view.stack.attackCard}
|
||||||
<div class="attack-card"><Card card={view.stack.attackCard} /></div>
|
<div class="attack-card"><Card card={view.stack.attackCard} /></div>
|
||||||
{#if view.stack.numberValue != null}
|
{#if view.stack.numberValue != null}
|
||||||
<div class="attack-power">powered by a {view.stack.numberValue}</div>
|
<div class="attack-power">powered by a {view.stack.numberValue}</div>
|
||||||
@@ -1131,7 +1138,7 @@
|
|||||||
<span class="hint-alert">
|
<span class="hint-alert">
|
||||||
{#if view.stack.defenderId === view.you}
|
{#if view.stack.defenderId === view.you}
|
||||||
{view.stack.attackerId} attacks with
|
{view.stack.attackerId} attacks with
|
||||||
{view.stack.attackCard ? cardDef(view.stack.attackCard.cardId).name : "a punch"} —
|
{attackingCreature ? `the ${cardDef(attackingCreature.kind).name}` : view.stack.attackCard ? cardDef(view.stack.attackCard.cardId).name : "a punch"} —
|
||||||
tap a counteraction card, or
|
tap a counteraction card, or
|
||||||
{:else}
|
{:else}
|
||||||
{view.stack.waitingOn === view.you ? "They counter your spell — counter back, or" : ""}
|
{view.stack.waitingOn === view.you ? "They counter your spell — counter back, or" : ""}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class LocalGame {
|
|||||||
seed,
|
seed,
|
||||||
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
sets: expansion ? ["basic", "expansion1"] : ["basic"],
|
||||||
...(colors ? { colors } : {}),
|
...(colors ? { colors } : {}),
|
||||||
deckRev: 3,
|
deckRev: 4,
|
||||||
};
|
};
|
||||||
const { state, events } = createGame(config);
|
const { state, events } = createGame(config);
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|||||||
Reference in New Issue
Block a user