A counter-spell aimed at the other wizard is explained, and named when it blocks
Blocked blows now say what blocked them (shield, counter-spell, dispel magic or protection from evil), the first counter-spell cast at the other wizard earns a one-time explanation that it protects its subject, and the spell's description says to cast it on yourself. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5de352db43
commit
dd6ff93606
+24
-9
@@ -200,7 +200,9 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
overlaps: [] as { name: string; spell: string; earlier: string }[],
|
||||
broken: [] as { name: string; hand: Hand }[],
|
||||
curedAndHurt: [] as { name: string; hurt: number; healed: number }[],
|
||||
newMonsterAttacked: ''
|
||||
newMonsterAttacked: '',
|
||||
/** A counter-spell aimed at the other wizard, who is then protected by it. */
|
||||
counterAimedAway: '' as string
|
||||
};
|
||||
|
||||
// ---- 1. Gestures, after last turn's enchantments have their say ----
|
||||
@@ -276,7 +278,8 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
|
||||
// ---- 2. Which completed spells are actually loosed ----
|
||||
const casts: ActiveCast[] = [];
|
||||
const shielded = new Set<TargetId>();
|
||||
/** Who is shielded this turn, and by what, so a blocked blow can name it. */
|
||||
const shielded = new Map<TargetId, string>();
|
||||
for (const id of acting) {
|
||||
const w = W[id];
|
||||
const accepted: ActiveCast[] = [];
|
||||
@@ -364,7 +367,7 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
if (dispels.length > 0) {
|
||||
log('Dispel magic sweeps the field: every other spell fails, all enchantments end, and every monster will fall after this turn.', 'spell');
|
||||
for (const c of casts) if (!c.nullified && c.spell.id !== 'dispel_magic') c.nullified = 'dispelled';
|
||||
for (const d of dispels) shielded.add(d.target);
|
||||
for (const d of dispels) shielded.set(d.target, 'dispel magic');
|
||||
for (const id of IDS) clearEnchantments(W[id]);
|
||||
for (const m of state.monsters) {
|
||||
clearMonsterEnchantments(m);
|
||||
@@ -441,11 +444,17 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
|
||||
// ---- 6. Who is shielded this turn ----
|
||||
for (const c of live()) {
|
||||
if (['shield', 'counter_spell', 'dispel_magic', 'protection_from_evil'].includes(c.spell.id)) shielded.add(c.target);
|
||||
if (c.spell.id === 'shield') shielded.set(c.target, 'shield');
|
||||
if (c.spell.id === 'counter_spell') shielded.set(c.target, 'counter-spell');
|
||||
if (c.spell.id === 'protection_from_evil') shielded.set(c.target, 'protection from evil');
|
||||
if (c.spell.id === 'counter_spell' && isWizardId(c.target) && c.target !== c.caster) {
|
||||
facts.counterAimedAway = `${W[c.caster].name}'s counter-spell on ${W[c.target].name}`;
|
||||
}
|
||||
}
|
||||
for (const id of IDS) if (W[id].protectionTurns > 0) shielded.add(id);
|
||||
for (const m of state.monsters) if (m.protectionTurns > 0) shielded.add(m.id);
|
||||
for (const id of IDS) if (W[id].protectionTurns > 0 && !shielded.has(id)) shielded.set(id, 'protection from evil');
|
||||
for (const m of state.monsters) if (m.protectionTurns > 0 && !shielded.has(m.id)) shielded.set(m.id, 'protection from evil');
|
||||
const isShielded = (id: TargetId) => shielded.has(id) && !frozenBeing(id);
|
||||
const guard = (id: TargetId) => `${beingName(state, id)}'s ${shielded.get(id) ?? 'shield'}`;
|
||||
|
||||
// ---- 7. Summons, then storms and elementals ----
|
||||
for (const c of live()) {
|
||||
@@ -731,7 +740,7 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
case 'missile':
|
||||
if (isShielded(t)) {
|
||||
facts.shieldBlocked.add(t);
|
||||
log(`${beingName(state, t)}'s shield turns the missile aside.`, 'fizzle');
|
||||
log(`${guard(t)} turns the missile aside.`, 'fizzle');
|
||||
} else hurt(t, 1, 'a missile');
|
||||
break;
|
||||
case 'finger_of_death':
|
||||
@@ -826,7 +835,7 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
}
|
||||
if (isShielded(target)) {
|
||||
facts.shieldBlocked.add(target);
|
||||
log(`${m.name}'s blows glance off ${beingName(state, target)}'s shield.`, 'fizzle');
|
||||
log(`${m.name}'s blows glance off ${guard(target)}.`, 'fizzle');
|
||||
} else if (isWizardId(target) && W[target].invisibleTurns > 0 && !frozen(target)) {
|
||||
log(`${m.name} flails at the air where ${W[target].name} used to be.`, 'fizzle');
|
||||
} else {
|
||||
@@ -846,7 +855,7 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
log(`${w.name} stabs at nothing.`, 'fizzle');
|
||||
} else if (isShielded(target)) {
|
||||
facts.shieldBlocked.add(target);
|
||||
log(`${w.name}'s knife skids off ${beingName(state, target)}'s shield.`, 'fizzle');
|
||||
log(`${w.name}'s knife skids off ${guard(target)}.`, 'fizzle');
|
||||
} else {
|
||||
hurt(target, 1, `${w.name}'s knife`);
|
||||
}
|
||||
@@ -960,6 +969,12 @@ export function resolveTurn(previous: GameState, inputs: Record<WizardId, TurnIn
|
||||
text: `A shield stops only creatures, missiles and stabs, so it did nothing against ${facts.throughShield[0].spell}. Counter-spell is the defence against other spells.`
|
||||
});
|
||||
}
|
||||
if (facts.counterAimedAway) {
|
||||
lessons.push({
|
||||
key: 'counter-aim',
|
||||
text: `A counter-spell protects its subject, not its caster: ${facts.counterAimedAway} cancelled spells aimed at them and shielded them from creatures, missiles and stabs. To stop what the other wizard casts at you, cast the counter-spell on yourself.`
|
||||
});
|
||||
}
|
||||
if (facts.fingerThroughCounter) {
|
||||
lessons.push({ key: 'counter-finger', text: 'A counter-spell cannot stop finger of death. Only dispel magic can, or an anti-spell before the eight gestures are finished.' });
|
||||
} else if (facts.counterSmothered > 0) {
|
||||
|
||||
Reference in New Issue
Block a user