import { Server } from "socket.io"; import db from "./db.js"; import { rollDice } from "./dice.js"; export function setupSocket(io: Server) { io.on("connection", (socket) => { socket.on("join-campaign", (campaignId: string) => { socket.join(`campaign:${campaignId}`); }); socket.on("leave-campaign", (campaignId: string) => { socket.leave(`campaign:${campaignId}`); }); socket.on( "roll:request", (data: { campaignId: number; characterId?: number; characterName?: string; characterColor?: string; type: string; dice: string; label: string; modifier?: number; advantage?: boolean; disadvantage?: boolean; }) => { const result = rollDice(data.dice, { advantage: data.advantage, disadvantage: data.disadvantage, }); if (result.error) { socket.emit("roll:error", { error: result.error }); return; } // Detect nat 20 const isD20Roll = data.dice.match(/d20/i); let nat20 = false; if (isD20Roll && result.rolls.length > 0) { if (data.advantage) { // With advantage, nat20 if the chosen (higher) die is 20 nat20 = Math.max(...result.rolls) === 20; } else if (data.disadvantage) { // With disadvantage, nat20 if the chosen (lower) die is 20 nat20 = Math.min(...result.rolls) === 20; } else { nat20 = result.rolls[0] === 20; } } const row = db .prepare( ` INSERT INTO roll_log (campaign_id, character_id, character_name, character_color, type, label, dice_expression, rolls, modifier, total, advantage, disadvantage, nat20) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, ) .run( data.campaignId, data.characterId ?? null, data.characterName || "Roll", data.characterColor || "", data.type || "custom", data.label, data.dice, JSON.stringify(result.rolls), result.modifier, result.total, data.advantage ? 1 : 0, data.disadvantage ? 1 : 0, nat20 ? 1 : 0, ); const saved = db .prepare("SELECT * FROM roll_log WHERE id = ?") .get(row.lastInsertRowid) as Record; const broadcast = { ...saved, rolls: result.rolls, advantage: data.advantage || false, disadvantage: data.disadvantage || false, nat20, }; io.to(`campaign:${data.campaignId}`).emit("roll:result", broadcast); }, ); socket.on( "atmosphere:update", (data: { campaignId: number; fog: boolean }) => { io.to(`campaign:${data.campaignId}`).emit("atmosphere:update", { fog: data.fog, }); }, ); socket.on("disconnect", () => { // Rooms are cleaned up automatically by Socket.IO }); }); } export function broadcastToCampaign( io: Server, campaignId: number, event: string, data: unknown, ) { io.to(`campaign:${campaignId}`).emit(event, data); }