Enhancements as per issue tracking sheet
This commit is contained in:
123
module/applications/free-roll.mjs
Normal file
123
module/applications/free-roll.mjs
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
Reference in New Issue
Block a user