Files
fvtt-lethal-fantasy/module/applications/free-roll.mjs

140 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Free Dice Tray — injected into the Foundry chat sidebar.
*
* Provides a compact bar for GM and players to roll any standard die (d4d30)
* or its exploding variant (dXx) without needing an actor sheet.
* Supports selecting how many dice to roll (19).
*/
/** 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 =>
`<button type="button" class="lf-dt-die-btn" data-die="${d}" title="${d.toUpperCase()}">${d.toUpperCase()}</button>`
).join("")
const countOptions = Array.from({ length: 9 }, (_, i) =>
`<option value="${i + 1}">${i + 1}</option>`
).join("")
bar.innerHTML = `
<div class="lf-dt-row">
<span class="lf-dt-label"><i class="fa-solid fa-dice"></i></span>
<select class="lf-dt-count" title="${game.i18n.localize("LETHALFANTASY.DiceTray.CountTitle")}">
${countOptions}
</select>
<div class="lf-dt-dice">${diceButtons}</div>
<label class="lf-dt-explode-label" title="${game.i18n.localize("LETHALFANTASY.DiceTray.ExplodeTitle")}">
<input type="checkbox" class="lf-dt-explode" />
<i class="fa-solid fa-explosion"></i>
</label>
</div>
`
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 (19)
* @param {boolean} explode Whether to use the exploding modifier (x = max-value explode)
* @returns {Promise<void>}
*/
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 ? `<i class="fa-solid fa-burst lf-dt-explode-icon"></i>` : ""
const classes = ["lf-frc-die-chip", isMax ? "lf-frc-max" : "", isMin ? "lf-frc-min" : ""].filter(Boolean).join(" ")
return `<div class="${classes}">
<span class="lf-frc-die-type">${dieLabel}</span>
<span class="lf-frc-die-sep">→</span>
<span class="lf-frc-die-val">${val}${explodeIcon}</span>
</div>`
}).join("")
const totalLabel = game.i18n.localize("LETHALFANTASY.Label.total").toUpperCase()
const content = `
<div class="lf-free-roll-card">
<div class="lf-frc-header">
<i class="fa-solid fa-dice"></i>
<span class="lf-frc-title-text">${game.i18n.localize("LETHALFANTASY.DiceTray.ChatTitle")}</span>
<span class="lf-frc-badge">${label}</span>
</div>
<div class="lf-frc-dice">${resultHtml}</div>
<div class="lf-frc-total-bar">
<span class="lf-frc-total-label">${totalLabel}</span>
<span class="lf-frc-total-value">${total}</span>
</div>
</div>
`
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)
}