Fixes and enhancements, from issue list

This commit is contained in:
2026-03-27 22:28:47 +01:00
parent c22c3d713b
commit 2bf737a3ef
6 changed files with 126 additions and 105 deletions

View File

@@ -20,8 +20,12 @@ import { _rollPool, _diceHtml } from "../rolls.mjs"
* @param {HTMLElement} html
*/
export function injectFreeRollBar(_chatLog, html) {
// Normalise: renderChatLog may pass jQuery (AppV1) or HTMLElement (AppV2/v13)
const el = (html instanceof HTMLElement) ? html : (html[0] ?? html)
if (!el?.querySelector) return
// Avoid double-injection on re-renders
if (html.querySelector(".oh-free-roll-bar")) return
if (el.querySelector(".oh-free-roll-bar")) return
const bar = document.createElement("div")
bar.className = "oh-free-roll-bar"
@@ -49,20 +53,30 @@ export function injectFreeRollBar(_chatLog, html) {
</div>
`
bar.querySelector(".oh-frb-roll-btn").addEventListener("click", () => {
// Use event delegation on the bar container — direct child listeners can be
// swallowed by Foundry's own delegated click handlers in the sidebar.
bar.addEventListener("click", async (ev) => {
if (!ev.target.closest(".oh-frb-roll-btn")) return
ev.stopPropagation()
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)
try {
await rollFree(pool, color, explode5)
} catch (err) {
console.error("Oath Hammer | Free Roll error:", err)
ui.notifications?.error("Free Roll failed — see console")
}
})
// Insert before the chat form — use chatForm.parentElement for AppV2 compatibility
// (in v13 parts are nested inside the app element, not direct children)
const chatForm = html.querySelector(".chat-form")
if (chatForm) {
chatForm.parentElement.insertBefore(bar, chatForm)
// Insert before the chat form — try multiple selectors for v12/v13 compatibility
const anchor = el.querySelector(".chat-form")
?? el.querySelector(".chat-message-form")
?? el.querySelector("form")
if (anchor) {
anchor.parentElement.insertBefore(bar, anchor)
} else {
html.appendChild(bar)
el.appendChild(bar)
}
}

View File

@@ -181,22 +181,20 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
}
)
const result = await foundry.applications.api.DialogV2.prompt({
window: { title: attack.name, resizable: true },
classes: ["fvtt-oath-hammer"],
position: { width: 420 },
content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-burst" },
const result = await foundry.applications.api.DialogV2.wait({
window: { title: attack.name, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-burst",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
await rollNPCAttackDamage(this.document, attack, {
bonus: parseInt(getValue("bonus")) || 0,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
bonus: parseInt(result.bonus) || 0,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}
@@ -236,24 +234,22 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
}
)
const result = await foundry.applications.api.DialogV2.prompt({
window: { title: spell.name, resizable: true },
classes: ["fvtt-oath-hammer"],
position: { width: 420 },
content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-wand-sparkles" },
const result = await foundry.applications.api.DialogV2.wait({
window: { title: spell.name, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-wand-sparkles",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
await rollNPCSpell(this.document, spell, {
dicePool: parseInt(getValue("dicePool")) || 3,
bonus: parseInt(getValue("bonus")) || 0,
colorOverride: getValue("colorOverride") || null,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
dicePool: parseInt(result.dicePool) || 3,
bonus: parseInt(result.bonus) || 0,
colorOverride: result.colorOverride || null,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}
@@ -282,23 +278,21 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
}
)
const result = await foundry.applications.api.DialogV2.prompt({
window: { title: miracle.name, resizable: true },
classes: ["fvtt-oath-hammer"],
position: { width: 420 },
content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-hands-praying" },
const result = await foundry.applications.api.DialogV2.wait({
window: { title: miracle.name, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-hands-praying",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
await rollNPCMiracle(this.document, miracle, {
dicePool: parseInt(getValue("dicePool")) || 3,
bonus: parseInt(getValue("bonus")) || 0,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
dicePool: parseInt(result.dicePool) || 3,
bonus: parseInt(result.bonus) || 0,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}
@@ -335,23 +329,21 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
}
)
const result = await foundry.applications.api.DialogV2.prompt({
window: { title: game.i18n.localize("OATHHAMMER.Label.ArmorDice"), resizable: true },
classes: ["fvtt-oath-hammer"],
position: { width: 420 },
content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
const result = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.localize("OATHHAMMER.Label.ArmorDice"), resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
await rollNPCArmor(actor, {
bonus: parseInt(getValue("bonus")) || 0,
colorOverride: getValue("colorOverride") || null,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
bonus: parseInt(result.bonus) || 0,
colorOverride: result.colorOverride || null,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}
@@ -395,23 +387,21 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet {
}
)
const result = await foundry.applications.api.DialogV2.prompt({
window: { title: item.name, resizable: true },
classes: ["fvtt-oath-hammer"],
position: { width: 420 },
content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
const result = await foundry.applications.api.DialogV2.wait({
window: { title: item.name, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = name => form.querySelector(`[name="${name}"]`)?.value
await rollNPCSkill(this.document, item, {
bonus: parseInt(getValue("bonus")) || 0,
colorOverride: getValue("colorOverride") || null,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
bonus: parseInt(result.bonus) || 0,
colorOverride: result.colorOverride || null,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}

View File

@@ -163,19 +163,20 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
visibility: game.settings.get("core", "rollMode")
}
)
const result = await foundry.applications.api.DialogV2.prompt({
const result = await foundry.applications.api.DialogV2.wait({
window: { title: `${doc.name}${game.i18n.localize("OATHHAMMER.Roll.ArmorRoll")}`, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = n => form.querySelector(`[name="${n}"]`)?.value
await rollNPCArmor(doc, {
bonus: parseInt(getValue("bonus")) || 0,
colorOverride: getValue("colorOverride") || null,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
bonus: parseInt(result.bonus) || 0,
colorOverride: result.colorOverride || null,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}
@@ -200,19 +201,20 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
visibility: game.settings.get("core", "rollMode")
}
)
const result = await foundry.applications.api.DialogV2.prompt({
const result = await foundry.applications.api.DialogV2.wait({
window: { title: `${skill.name}${game.i18n.localize("OATHHAMMER.Tab.Skills")}`, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = n => form.querySelector(`[name="${n}"]`)?.value
await rollNPCSkill(this.document, skill, {
bonus: parseInt(getValue("bonus")) || 0,
colorOverride: getValue("colorOverride") || null,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
bonus: parseInt(result.bonus) || 0,
colorOverride: result.colorOverride || null,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}
@@ -244,19 +246,20 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet {
visibility: game.settings.get("core", "rollMode")
}
)
const result = await foundry.applications.api.DialogV2.prompt({
const result = await foundry.applications.api.DialogV2.wait({
window: { title: `${attack.name}${game.i18n.localize("OATHHAMMER.Dialog.Damage")}`, resizable: true },
classes: ["fvtt-oath-hammer"], position: { width: 420 }, content,
ok: { label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6" },
rejectClose: false,
buttons: [{ label: game.i18n.localize("OATHHAMMER.Dialog.Roll"), icon: "fa-solid fa-dice-d6",
callback: (_ev, btn) => { const o = {}; for (const el of btn.form.elements) { if (!el.name) continue; o[el.name] = el.type === "checkbox" ? String(el.checked) : el.value } return o }
}],
})
if (!result) return
const form = new DOMParser().parseFromString(result, "text/html")
const getValue = n => form.querySelector(`[name="${n}"]`)?.value
await rollNPCAttackDamage(this.document, attack, {
bonus: parseInt(getValue("bonus")) || 0,
colorOverride: getValue("colorOverride") || null,
explodeOn5: getValue("explodeOn5") === "true",
visibility: getValue("visibility"),
bonus: parseInt(result.bonus) || 0,
colorOverride: result.colorOverride || null,
explodeOn5: result.explodeOn5 === "true",
visibility: result.visibility,
})
}