fix: death:recovery-roll — add is_dead guard, try/catch, ownership check, subtype
This commit is contained in:
parent
43a5b85dcf
commit
f853fdbce3
1 changed files with 86 additions and 73 deletions
|
|
@ -158,89 +158,102 @@ export function setupSocket(io: Server) {
|
||||||
campaignId: number;
|
campaignId: number;
|
||||||
characterId: number;
|
characterId: number;
|
||||||
}) => {
|
}) => {
|
||||||
const userId = socket.data.user?.userId;
|
try {
|
||||||
|
const userId = socket.data.user?.userId;
|
||||||
|
|
||||||
// Verify caller is DM
|
// Verify caller is DM
|
||||||
const [memberRows] = await db.execute<RowDataPacket[]>(
|
const [memberRows] = await db.execute<RowDataPacket[]>(
|
||||||
"SELECT role FROM campaign_members WHERE campaign_id = ? AND user_id = ?",
|
"SELECT role FROM campaign_members WHERE campaign_id = ? AND user_id = ?",
|
||||||
[data.campaignId, userId]
|
[data.campaignId, userId]
|
||||||
);
|
);
|
||||||
if (memberRows.length === 0 || memberRows[0].role !== "dm") return;
|
if (memberRows.length === 0 || memberRows[0].role !== "dm") return;
|
||||||
|
|
||||||
// Verify character has a Dying condition
|
// Verify character has a Dying condition
|
||||||
const [dyingRows] = await db.execute<RowDataPacket[]>(
|
const [dyingRows] = await db.execute<RowDataPacket[]>(
|
||||||
"SELECT id FROM character_conditions WHERE character_id = ? AND name = 'Dying'",
|
"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<RowDataPacket[]>(
|
|
||||||
"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<import("mysql2").ResultSetHeader>(
|
|
||||||
`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<RowDataPacket[]>(
|
|
||||||
"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]
|
[data.characterId]
|
||||||
);
|
);
|
||||||
|
if (dyingRows.length === 0) return;
|
||||||
|
|
||||||
const [updatedChar] = await db.execute<RowDataPacket[]>(
|
// Verify character is not permanently dead
|
||||||
"SELECT * FROM characters WHERE id = ?",
|
const [deadCheck] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT is_dead FROM characters WHERE id = ?",
|
||||||
[data.characterId]
|
[data.characterId]
|
||||||
);
|
);
|
||||||
const [updatedConditions] = await db.execute<RowDataPacket[]>(
|
if (deadCheck.length === 0 || deadCheck[0].is_dead) return;
|
||||||
"SELECT * FROM character_conditions WHERE character_id = ?",
|
|
||||||
|
// Get character info for roll log
|
||||||
|
const [charRows] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT name, color, campaign_id FROM characters WHERE id = ?",
|
||||||
[data.characterId]
|
[data.characterId]
|
||||||
);
|
);
|
||||||
io.to(`campaign:${data.campaignId}`).emit("character:updated", {
|
if (charRows.length === 0) return;
|
||||||
...updatedChar[0],
|
const char = charRows[0];
|
||||||
conditions: updatedConditions,
|
|
||||||
|
if (char.campaign_id !== data.campaignId) return;
|
||||||
|
|
||||||
|
// 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<import("mysql2").ResultSetHeader>(
|
||||||
|
`INSERT INTO roll_log
|
||||||
|
(campaign_id, character_id, character_name, character_color, type, subtype, label,
|
||||||
|
dice_expression, rolls, modifier, total, advantage, disadvantage, nat20)
|
||||||
|
VALUES (?, ?, ?, ?, 'custom', 'death-save', ?, '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<RowDataPacket[]>(
|
||||||
|
"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<RowDataPacket[]>(
|
||||||
|
"SELECT * FROM characters WHERE id = ?",
|
||||||
|
[data.characterId]
|
||||||
|
);
|
||||||
|
const [updatedConditions] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT * FROM character_conditions WHERE character_id = ?",
|
||||||
|
[data.characterId]
|
||||||
|
);
|
||||||
|
io.to(`campaign:${data.campaignId}`).emit("character:updated", {
|
||||||
|
...updatedChar[0],
|
||||||
|
conditions: updatedConditions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[death:recovery-roll]", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue