diff --git a/scripts/claude-gm.js b/scripts/claude-gm.js index fc60ea4..827c1ac 100644 --- a/scripts/claude-gm.js +++ b/scripts/claude-gm.js @@ -21,8 +21,96 @@ Hooks.on("createChatMessage", async (chatData, options, userId) => { if (chatData.speaker?.alias === 'AI DM') return if(chatData.isRoll){ console.log(chatData); + const parts = []; + const speaker = chatData.speaker?.alias || "Someone"; + const flavor = chatData.flavor || ""; + const pf2eFlags = chatData.flags?.pf2e || {}; + const pf2eContext = pf2eFlags.context || {}; + + // Who rolled + parts.push(`${speaker}`); + + // What type of roll (PF2E-specific context) + // pf2eContext.type can be: "skill-check", "attack-roll", "damage-roll", + // "saving-throw", "perception-check", "flat-check", "initiative", etc. + if (pf2eContext.type) { + parts.push(`made a ${pf2eContext.type.replace(/-/g, " ")}`); + } else if (flavor) { + parts.push(`rolled ${flavor.replace(/<[^>]*>/g, "")}`); // strip HTML tags + } else { + parts.push("rolled"); + } + + // What action/item triggered the roll (e.g. "Longsword", "Perception", "Fireball") + const origin = pf2eFlags.origin; + if (origin?.type && origin?.sourceId) { + parts.push(`using ${origin.type}`); + } + + // The strike info if it's an attack + if (pf2eFlags.strike) { + const strike = pf2eFlags.strike; + if (strike.name) parts.push(`(${strike.name})`); + } + + // Outcome for checks (PF2E stores degree of success) + // pf2eContext.outcome can be: "criticalSuccess", "success", "failure", "criticalFailure" + if (pf2eContext.outcome) { + const outcomeMap = { + criticalSuccess: "Critical Success", + success: "Success", + failure: "Failure", + criticalFailure: "Critical Failure" + }; + parts.push(`— ${outcomeMap[pf2eContext.outcome] || pf2eContext.outcome}`); + } + + // The actual roll numbers + const rollDetails = chatData.rolls.map(r => { + let detail = `${r.formula} = ${r.total}`; + // For damage rolls, try to extract damage types from the terms + if (pf2eContext.type === "damage-roll" && r.options?.damage?.categories) { + // Damage categories might be available + } + return detail; + }).join("; "); + + parts.push(`[${rollDetails}]`); + + // DC if present (PF2E often includes the target DC) + if (pf2eContext.dc !== undefined && pf2eContext.dc !== null) { + const dc = typeof pf2eContext.dc === "object" ? pf2eContext.dc.value : pf2eContext.dc; + if (dc) parts.push(`against DC ${dc}`); + } + + // Target info + if (pf2eContext.target) { + const targetName = pf2eContext.target?.token?.name + || pf2eContext.target?.actor?.name + || null; + if (targetName) parts.push(`targeting ${targetName}`); + } + + // Traits (e.g. "fire", "mental", "incapacitation") + if (pf2eContext.traits?.length > 0) { + parts.push(`(traits: ${pf2eContext.traits.join(", ")})`); + } + + // Modifiers/notes from the flavor text (often contains the breakdown) + // The flavor field in PF2E often has rich HTML with modifier breakdowns + // We'll grab a clean text version as supplemental info + if (flavor) { + const cleanFlavor = flavor.replace(/<[^>]*>/g, " ").replace(/\s{2,}/g, '. ').trim(); + if (cleanFlavor && !parts.some(p => p.includes(cleanFlavor))) { + parts.push(`| ${cleanFlavor}`); + } + } + + const rollString = `[ROLL] ${parts.join(" ")}`; + console.log(rollString); } }) + Hooks.on("chatMessage", async (chatLog, message, chatData) => { const formattedContent = chatData.speaker.actor ? `${chatData.speaker.alias} says, "${message}"` : message