D&D 5e death-save state machine, undo/redo fixes, dev bulk delete #4

Merged
robert merged 11 commits from single-source into main 2026-07-06 22:14:05 -04:00
2 changed files with 16 additions and 8 deletions
Showing only changes of commit 47056788e8 - Show all commits
-7
View File
@@ -6,13 +6,6 @@ Backlog of bugs + long-term items. Milestones live in REWORK_PLAN.md.
### FEAT: critical damage button for dying/stable participants
- Shared logic already supports `applyHpChange(..., { isCriticalHit:true })`.
- UI only has normal Damage, which adds 1 failure at 0 HP.
- Add button near Revive/Stabilize: **Crit Damage** / **Critical Hit**.
- Use only for dying/stable character/NPC at 0 HP; adds 2 death-save failures.
- Dead target remains dead; no action.
### dm list - keep active particpant in view (scroll) ### dm list - keep active particpant in view (scroll)
### combat.js doesnt seem to actually exercise the add characters. there are no characters in the campaign. only in the encounter.. ### combat.js doesnt seem to actually exercise the add characters. there are no characters in the campaign. only in the encounter..
+16 -1
View File
@@ -7,7 +7,7 @@ import {
Play as PlayIcon, Pause as PauseIcon, SkipForward as SkipForwardIcon, Play as PlayIcon, Pause as PauseIcon, SkipForward as SkipForwardIcon,
StopCircle as StopCircleIcon, Users2, Dices, ChevronUp, ChevronDown, ScrollText, StopCircle as StopCircleIcon, Users2, Dices, ChevronUp, ChevronDown, ScrollText,
Maximize2, Minimize2, Moon, Coffee, Clock, ChevronRight, X, Maximize2, Minimize2, Moon, Coffee, Clock, ChevronRight, X,
Undo2, Redo2 Undo2, Redo2, Crosshair
} from 'lucide-react'; } from 'lucide-react';
// ----- UI feedback: toast (transient) + info modal (persistent) ----- // ----- UI feedback: toast (transient) + info modal (persistent) -----
@@ -1153,6 +1153,16 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters, camp
} }
}; };
const handleCritDamage = async (participantId) => {
if (!db) return;
try {
// Any damage at 0 HP + isCriticalHit = 2 death-save failures (5e crit-at-0).
await combatApplyHpChange(encounter, participantId, 'damage', 1, { isCriticalHit: true }, buildCtx(encounterPath));
} catch (err) {
showToast(err.message);
}
};
const toggleCondition = async (participantId, conditionId) => { const toggleCondition = async (participantId, conditionId) => {
if (!db) return; if (!db) return;
try { try {
@@ -1464,6 +1474,11 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters, camp
/> />
</span> </span>
<span>HP: {p.currentHp}/{p.maxHp}</span> <span>HP: {p.currentHp}/{p.maxHp}</span>
{hasDeathSaves && (participantStatus === 'dying' || participantStatus === 'stable') && encounter.isStarted && (
<button onClick={() => handleCritDamage(p.id)} className="ml-auto px-2 py-0.5 text-xs rounded bg-red-900 hover:bg-red-800 text-red-100 border border-red-700" title="Critical hit at 0 HP: +2 death-save failures">
<Crosshair size={12} className="inline mr-1" />Crit
</button>
)}
</div> </div>
{participantStatus === 'dead' && ( {participantStatus === 'dead' && (