From b0309c245b1fef8001ac810dec7f83136435219c Mon Sep 17 00:00:00 2001 From: Nils Briggen Date: Tue, 9 Jun 2026 19:40:16 +0200 Subject: [PATCH] Combat: GM concentration flag on combatants so the save prompt actually fires MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/features/combat/EncounterTracker.tsx | 18 ++++++++++++++++-- src/lib/schemas/encounter.ts | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/features/combat/EncounterTracker.tsx b/src/features/combat/EncounterTracker.tsx index c2401d0..ecff130 100644 --- a/src/features/combat/EncounterTracker.tsx +++ b/src/features/combat/EncounterTracker.tsx @@ -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({ )} {c.kind} + {dead && ( DOWN diff --git a/src/lib/schemas/encounter.ts b/src/lib/schemas/encounter.ts index 393800d..9808d43 100644 --- a/src/lib/schemas/encounter.ts +++ b/src/lib/schemas/encounter.ts @@ -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(),