Combat: GM concentration flag on combatants so the save prompt actually fires
The post-damage concentration save was keyed only off Character.concentration, which is set just when a seated player casts via their own panel — so a GM running a monster/NPC (or any unseated PC) never got the reminder, defeating the feature. Combatants now carry an optional concentrating flag with a one-click toggle in the row; noteConcentrationCheck keys off it (falling back to the linked Character). Verified in-app: toggle + 10 damage → 'roll a DC 10 Constitution save or lose concentration'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
BrainCircuit,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Dices,
|
||||
@@ -81,11 +82,14 @@ export function EncounterTracker({ encounter, campaign }: { encounter: Encounter
|
||||
*/
|
||||
const noteConcentrationCheck = (combatant: Combatant, damage: number) => {
|
||||
if (damage <= 0) return;
|
||||
// The GM-set combatant flag works for any creature (monster/NPC/PC); fall back to
|
||||
// the linked Character's concentration (set when a seated player casts a spell).
|
||||
const ch = pcCharacter(combatant);
|
||||
if (!ch?.concentration) return;
|
||||
const spellName = combatant.concentrating || ch?.concentration?.spellName;
|
||||
if (!spellName) return;
|
||||
const dc = concentrationDC(damage);
|
||||
void encountersRepo.mutate(encounter.id, (e) =>
|
||||
logEvent(e, `${ch.name}: roll a DC ${dc} Constitution save or lose concentration on ${ch.concentration!.spellName}.`));
|
||||
logEvent(e, `${combatant.name}: roll a DC ${dc} Constitution save or lose concentration on ${spellName}.`));
|
||||
};
|
||||
|
||||
/** Advance the turn; remind (don't roll) when a downed PC's turn begins. */
|
||||
@@ -481,6 +485,16 @@ function CombatantRow({
|
||||
</Badge>
|
||||
)}
|
||||
<span className="smallcaps" style={{ fontSize: 9 }}>{c.kind}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange({ concentrating: c.concentrating ? null : 'a spell' })}
|
||||
className={cn('rounded p-0.5', c.concentrating ? 'text-accent' : 'text-faint hover:text-muted')}
|
||||
title={c.concentrating ? `Concentrating on ${c.concentrating} — click to clear` : 'Mark as concentrating (prompts a Con save when damaged)'}
|
||||
aria-label={c.concentrating ? `${c.name} is concentrating; clear` : `Mark ${c.name} as concentrating`}
|
||||
aria-pressed={!!c.concentrating}
|
||||
>
|
||||
<BrainCircuit size={13} aria-hidden />
|
||||
</button>
|
||||
{dead && (
|
||||
<Badge tone="ember">
|
||||
<Skull size={11} aria-hidden /> DOWN
|
||||
|
||||
@@ -34,6 +34,8 @@ export const combatantSchema = z.object({
|
||||
ac: int,
|
||||
hp: hpSchema,
|
||||
conditions: z.array(conditionSchema).default([]),
|
||||
/** Spell this combatant is concentrating on (GM-set). Drives the post-damage save prompt. */
|
||||
concentrating: z.string().max(120).nullable().optional(),
|
||||
notes: z.string().max(2000).default(''),
|
||||
/** damage resistances/immunities snapshotted from the bestiary, if known */
|
||||
damageDefenses: damageDefensesSchema.optional(),
|
||||
|
||||
Reference in New Issue
Block a user