/** * Free Dice Tray — injected into the Foundry chat sidebar. * * Provides a compact bar for GM and players to roll any standard die (d4–d30) * or its exploding variant (dXx) without needing an actor sheet. * Supports selecting how many dice to roll (1–9). */ /** Standard dice available in Lethal Fantasy */ const DICE_TYPES = ["d4", "d6", "d8", "d10", "d12", "d20", "d30"] /** * Inject the dice tray bar into the ChatLog HTML. * Called from `Hooks.on("renderChatLog", ...)`. * * @param {Application} _chatLog * @param {HTMLElement|jQuery} html */ export function injectDiceTray(_chatLog, html) { const el = (html instanceof HTMLElement) ? html : (html[0] ?? html) if (!el?.querySelector) return if (el.querySelector(".lf-dice-tray")) return const bar = document.createElement("div") bar.className = "lf-dice-tray" const diceButtons = DICE_TYPES.map(d => `` ).join("") const countOptions = Array.from({ length: 9 }, (_, i) => `` ).join("") bar.innerHTML = `
${diceButtons}
` bar.addEventListener("click", async ev => { const btn = ev.target.closest(".lf-dt-die-btn") if (!btn) return ev.stopPropagation() const dieType = btn.dataset.die const count = parseInt(bar.querySelector(".lf-dt-count").value) || 1 const explode = bar.querySelector(".lf-dt-explode").checked try { await rollFreeDie(dieType, count, explode) } catch (err) { console.error("Lethal Fantasy | Dice Tray error:", err) ui.notifications?.error("Dice Tray roll failed — see console") } }) const anchor = el.querySelector(".chat-form") ?? el.querySelector(".chat-message-form") ?? el.querySelector("form") if (anchor) { anchor.parentElement.insertBefore(bar, anchor) } else { el.appendChild(bar) } } /** * Roll one or more dice of the given type and post the result to chat. * * @param {string} dieType Die face, e.g. "d20" * @param {number} count Number of dice to roll (1–9) * @param {boolean} explode Whether to use the exploding modifier (x = max-value explode) * @returns {Promise} */ export async function rollFreeDie(dieType, count = 1, explode = false) { const sides = parseInt(dieType.replace("d", "")) || 20 const formula = explode ? `${count}d${sides}x` : `${count}d${sides}` const label = explode ? `${count}${dieType.toUpperCase()}E` : `${count}${dieType.toUpperCase()}` const roll = new Roll(formula) await roll.evaluate() const results = roll.terms .filter(t => t.results) .flatMap(t => t.results) const total = roll.total const dieLabel = dieType.toUpperCase() const resultHtml = results.map(r => { const val = r.result const isMax = val === sides const isMin = val === 1 const explodeIcon = r.exploded ? `` : "" const classes = ["lf-frc-die-chip", isMax ? "lf-frc-max" : "", isMin ? "lf-frc-min" : ""].filter(Boolean).join(" ") return `
${dieLabel} ${val}${explodeIcon}
` }).join("") const totalLabel = game.i18n.localize("LETHALFANTASY.Label.total").toUpperCase() const content = `
${game.i18n.localize("LETHALFANTASY.DiceTray.ChatTitle")} ${label}
${resultHtml}
${totalLabel} ${total}
` const rollMode = game.settings.get("core", "rollMode") const msgData = { speaker: ChatMessage.getSpeaker(), content, rolls: [roll], sound: CONFIG.sounds.dice, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) }