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 {
|
import {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
ArrowUp,
|
ArrowUp,
|
||||||
|
BrainCircuit,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Dices,
|
Dices,
|
||||||
@@ -81,11 +82,14 @@ export function EncounterTracker({ encounter, campaign }: { encounter: Encounter
|
|||||||
*/
|
*/
|
||||||
const noteConcentrationCheck = (combatant: Combatant, damage: number) => {
|
const noteConcentrationCheck = (combatant: Combatant, damage: number) => {
|
||||||
if (damage <= 0) return;
|
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);
|
const ch = pcCharacter(combatant);
|
||||||
if (!ch?.concentration) return;
|
const spellName = combatant.concentrating || ch?.concentration?.spellName;
|
||||||
|
if (!spellName) return;
|
||||||
const dc = concentrationDC(damage);
|
const dc = concentrationDC(damage);
|
||||||
void encountersRepo.mutate(encounter.id, (e) =>
|
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. */
|
/** Advance the turn; remind (don't roll) when a downed PC's turn begins. */
|
||||||
@@ -481,6 +485,16 @@ function CombatantRow({
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
<span className="smallcaps" style={{ fontSize: 9 }}>{c.kind}</span>
|
<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 && (
|
{dead && (
|
||||||
<Badge tone="ember">
|
<Badge tone="ember">
|
||||||
<Skull size={11} aria-hidden /> DOWN
|
<Skull size={11} aria-hidden /> DOWN
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ export const combatantSchema = z.object({
|
|||||||
ac: int,
|
ac: int,
|
||||||
hp: hpSchema,
|
hp: hpSchema,
|
||||||
conditions: z.array(conditionSchema).default([]),
|
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(''),
|
notes: z.string().max(2000).default(''),
|
||||||
/** damage resistances/immunities snapshotted from the bestiary, if known */
|
/** damage resistances/immunities snapshotted from the bestiary, if known */
|
||||||
damageDefenses: damageDefensesSchema.optional(),
|
damageDefenses: damageDefensesSchema.optional(),
|
||||||
|
|||||||
Reference in New Issue
Block a user