124 lines
4.7 KiB
JavaScript
124 lines
4.7 KiB
JavaScript
/**
|
||
* Free Dice Roll widget injected into the Foundry chat sidebar.
|
||
*
|
||
* Provides a compact bar for GM and players to roll any dice pool without
|
||
* needing an actor — useful for quick checks, table rolls, narration, etc.
|
||
*
|
||
* Features:
|
||
* - Pool size (1–20 dice)
|
||
* - Color: White (4+), Red (3+), Black (2+)
|
||
* - Explode on 5+ checkbox
|
||
*/
|
||
|
||
import { _rollPool, _diceHtml } from "../rolls.mjs"
|
||
|
||
/**
|
||
* Inject the Free Roll bar into the ChatLog HTML.
|
||
* Called from `Hooks.on("renderChatLog", ...)`.
|
||
*
|
||
* @param {Application} _chatLog
|
||
* @param {HTMLElement} html
|
||
*/
|
||
export function injectFreeRollBar(_chatLog, html) {
|
||
// Avoid double-injection on re-renders
|
||
if (html.querySelector(".oh-free-roll-bar")) return
|
||
|
||
const bar = document.createElement("div")
|
||
bar.className = "oh-free-roll-bar"
|
||
bar.innerHTML = `
|
||
<span class="oh-frb-label">
|
||
<i class="fa-solid fa-dice-d6"></i>
|
||
${game.i18n.localize("OATHHAMMER.FreeRoll.Label")}
|
||
</span>
|
||
<div class="oh-frb-controls">
|
||
<select class="oh-frb-pool" title="${game.i18n.localize("OATHHAMMER.FreeRoll.PoolTitle")}">
|
||
${Array.from({length: 16}, (_, i) => `<option value="${i+1}"${i+1===2?" selected":""}>${i+1}d</option>`).join("")}
|
||
</select>
|
||
<select class="oh-frb-color" title="${game.i18n.localize("OATHHAMMER.FreeRoll.ColorTitle")}">
|
||
<option value="white">⬜ ${game.i18n.localize("OATHHAMMER.FreeRoll.ColorWhite")}</option>
|
||
<option value="red">🔴 ${game.i18n.localize("OATHHAMMER.FreeRoll.ColorRed")}</option>
|
||
<option value="black">⬛ ${game.i18n.localize("OATHHAMMER.FreeRoll.ColorBlack")}</option>
|
||
</select>
|
||
<label class="oh-frb-explode-label" title="${game.i18n.localize("OATHHAMMER.FreeRoll.ExplodeTitle")}">
|
||
<input type="checkbox" class="oh-frb-explode">
|
||
💥 5+
|
||
</label>
|
||
<button type="button" class="oh-frb-roll-btn">
|
||
${game.i18n.localize("OATHHAMMER.FreeRoll.Roll")}
|
||
</button>
|
||
</div>
|
||
`
|
||
|
||
bar.querySelector(".oh-frb-roll-btn").addEventListener("click", () => {
|
||
const pool = parseInt(bar.querySelector(".oh-frb-pool").value) || 2
|
||
const color = bar.querySelector(".oh-frb-color").value
|
||
const explode5 = bar.querySelector(".oh-frb-explode").checked
|
||
rollFree(pool, color, explode5)
|
||
})
|
||
|
||
// Insert between .chat-scroll and .chat-form
|
||
const chatForm = html.querySelector(".chat-form")
|
||
if (chatForm) {
|
||
html.insertBefore(bar, chatForm)
|
||
} else {
|
||
html.appendChild(bar)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Execute a free dice roll and post the result to chat.
|
||
*
|
||
* @param {number} pool Number of d6 to roll
|
||
* @param {string} colorType "white" | "red" | "black"
|
||
* @param {boolean} explode5 True to explode on 5+
|
||
*/
|
||
export async function rollFree(pool, colorType, explode5 = false) {
|
||
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
|
||
const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
|
||
const colorLabel = colorType === "black"
|
||
? game.i18n.localize("OATHHAMMER.FreeRoll.ColorBlack")
|
||
: colorType === "red"
|
||
? game.i18n.localize("OATHHAMMER.FreeRoll.ColorRed")
|
||
: game.i18n.localize("OATHHAMMER.FreeRoll.ColorWhite")
|
||
|
||
const { rolls, successes, diceResults } = await _rollPool(pool, threshold, explode5)
|
||
|
||
const explodedCount = diceResults.filter(d => d.exploded).length
|
||
const diceHtml = _diceHtml(diceResults, threshold)
|
||
|
||
const modParts = []
|
||
if (explode5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`)
|
||
if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`)
|
||
const modLine = modParts.length ? `<div class="oh-roll-mods">${modParts.join(" · ")}</div>` : ""
|
||
|
||
const resultClass = successes > 0 ? "roll-success" : "roll-failure"
|
||
const resultLabel = successes > 0
|
||
? game.i18n.localize("OATHHAMMER.Roll.Success")
|
||
: game.i18n.localize("OATHHAMMER.Roll.Failure")
|
||
|
||
const content = `
|
||
<div class="oh-roll-card">
|
||
<div class="oh-roll-header">${game.i18n.localize("OATHHAMMER.FreeRoll.CardTitle")}</div>
|
||
<div class="oh-roll-info">
|
||
<span>${colorEmoji} ${pool}d6 (${threshold}+) — ${colorLabel}</span>
|
||
</div>
|
||
${modLine}
|
||
<div class="oh-roll-dice">${diceHtml}</div>
|
||
<div class="oh-roll-result ${resultClass}">
|
||
<span class="oh-roll-successes">${successes}</span>
|
||
<span class="oh-roll-verdict">${resultLabel}</span>
|
||
</div>
|
||
</div>
|
||
`
|
||
|
||
const rollMode = game.settings.get("core", "rollMode")
|
||
const msgData = {
|
||
speaker: ChatMessage.getSpeaker(),
|
||
content,
|
||
rolls,
|
||
sound: CONFIG.sounds.dice,
|
||
}
|
||
ChatMessage.applyRollMode(msgData, rollMode)
|
||
await ChatMessage.create(msgData)
|
||
}
|