From d4790edc1ab0a17259c4641de9e6ecdb7b40b0b6 Mon Sep 17 00:00:00 2001 From: Aaron Wood Date: Sun, 12 Apr 2026 01:23:21 -0400 Subject: [PATCH] feat: auto-start Dying condition when HP hits 0, clear when HP recovers Co-Authored-By: Claude Sonnet 4.6 --- server/src/routes/characters.ts | 45 ++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/server/src/routes/characters.ts b/server/src/routes/characters.ts index 0220017..58807b1 100644 --- a/server/src/routes/characters.ts +++ b/server/src/routes/characters.ts @@ -6,6 +6,7 @@ import db from "../db.js"; import { broadcastToCampaign } from "../socket.js"; import { parseJson } from "../utils/parseJson.js"; import { requireAuth } from "../auth/middleware.js"; +import { rollDice } from "../dice.js"; type CampaignParams = ParamsDictionary & { campaignId: string }; @@ -192,7 +193,7 @@ router.patch("/:id", requireAuth, async (req, res) => { "name", "class", "ancestry", "level", "xp", "hp_current", "hp_max", "ac", "alignment", "title", "notes", "background", "deity", "languages", "gp", "sp", "cp", "gear_slots_max", "overrides", "color", "luck_token", - "torch_lit_at", + "torch_lit_at", "is_dead", ]; const updates: string[] = []; @@ -218,6 +219,48 @@ router.patch("/:id", requireAuth, async (req, res) => { return; } + // Auto-start or clear Dying condition based on HP change + if (req.body.hp_current !== undefined) { + const newHp = Number(req.body.hp_current); + + if (newHp <= 0) { + // Check if already dying or permanently dead + const [dyingRows] = await db.execute( + "SELECT id FROM character_conditions WHERE character_id = ? AND name = 'Dying'", + [id] + ); + const [deadRows] = await db.execute( + "SELECT is_dead FROM characters WHERE id = ?", + [id] + ); + const isAlreadyDying = dyingRows.length > 0; + const isAlreadyDead = Boolean(deadRows[0]?.is_dead); + + if (!isAlreadyDying && !isAlreadyDead) { + // Roll 1d4, add CON modifier, clamp to minimum 1 + const d4 = rollDice("1d4"); + const [statRows] = await db.execute( + "SELECT value FROM character_stats WHERE character_id = ? AND stat_name = 'CON'", + [id] + ); + const conValue = (statRows[0]?.value as number) ?? 10; + const conMod = Math.floor((conValue - 10) / 2); + const roundsRemaining = Math.max(1, d4.total + conMod); + + await db.execute( + "INSERT INTO character_conditions (character_id, name, description, rounds_remaining) VALUES (?, 'Dying', '', ?)", + [id, roundsRemaining] + ); + } + } else { + // HP above 0: remove any Dying condition (character was healed) + await db.execute( + "DELETE FROM character_conditions WHERE character_id = ? AND name = 'Dying'", + [id] + ); + } + } + const [rows] = await db.execute( "SELECT * FROM characters WHERE id = ?", [id]