diff --git a/server/src/socket.ts b/server/src/socket.ts index 8616c6e..efef1b8 100644 --- a/server/src/socket.ts +++ b/server/src/socket.ts @@ -154,6 +154,96 @@ export function setupSocket(io: Server) { io.to(`campaign:${campaignId}`).emit("atmosphere:update", atmosphere); }); + socket.on("death:recovery-roll", async (data: { + campaignId: number; + characterId: number; + }) => { + const userId = socket.data.user?.userId; + + // Verify caller is DM + const [memberRows] = await db.execute( + "SELECT role FROM campaign_members WHERE campaign_id = ? AND user_id = ?", + [data.campaignId, userId] + ); + if (memberRows.length === 0 || memberRows[0].role !== "dm") return; + + // Verify character has a Dying condition + const [dyingRows] = await db.execute( + "SELECT id FROM character_conditions WHERE character_id = ? AND name = 'Dying'", + [data.characterId] + ); + if (dyingRows.length === 0) return; + + // Get character info for roll log + const [charRows] = await db.execute( + "SELECT name, color, campaign_id FROM characters WHERE id = ?", + [data.characterId] + ); + if (charRows.length === 0) return; + const char = charRows[0]; + + // Roll d20 server-side + const result = rollDice("1d20"); + const roll = result.total; + const nat20 = roll === 20; + const success = roll >= 18; + + // Log to roll_log with "Death Save" label (success gets suffix) + const label = success ? "Death Save \u2014 stands at 1 HP!" : "Death Save"; + + const [insertResult] = await db.execute( + `INSERT INTO roll_log + (campaign_id, character_id, character_name, character_color, type, label, + dice_expression, rolls, modifier, total, advantage, disadvantage, nat20) + VALUES (?, ?, ?, ?, 'custom', ?, '1d20', ?, 0, ?, 0, 0, ?)`, + [ + data.campaignId, + data.characterId, + char.name, + char.color, + label, + JSON.stringify(result.rolls), + roll, + nat20 ? 1 : 0, + ] + ); + + const [savedRows] = await db.execute( + "SELECT * FROM roll_log WHERE id = ?", + [insertResult.insertId] + ); + + io.to(`campaign:${data.campaignId}`).emit("roll:result", { + ...savedRows[0], + rolls: result.rolls, + advantage: false, + disadvantage: false, + nat20, + }); + + // On 18+: heal to 1 HP and clear Dying condition + if (success) { + await db.execute("UPDATE characters SET hp_current = 1 WHERE id = ?", [data.characterId]); + await db.execute( + "DELETE FROM character_conditions WHERE character_id = ? AND name = 'Dying'", + [data.characterId] + ); + + const [updatedChar] = await db.execute( + "SELECT * FROM characters WHERE id = ?", + [data.characterId] + ); + const [updatedConditions] = await db.execute( + "SELECT * FROM character_conditions WHERE character_id = ?", + [data.characterId] + ); + io.to(`campaign:${data.campaignId}`).emit("character:updated", { + ...updatedChar[0], + conditions: updatedConditions, + }); + } + }); + socket.on("disconnect", () => { // Rooms are cleaned up automatically by Socket.IO });