Add roll windows from actor sheet

This commit is contained in:
2026-03-15 23:20:32 +01:00
parent 82fddb0cb3
commit 49347370c7
57 changed files with 6372 additions and 184 deletions

View File

@@ -0,0 +1,154 @@
import { SYSTEM } from "../config/system.mjs"
/**
* Roll configuration dialog for Oath Hammer skill checks.
* Uses DialogV2.wait() — pattern from fvtt-hellborn / fvtt-lethal-fantasy.
*
* Dice rules (from rulebook):
* Pool = Attribute rank + Skill rank + per-skill modifier + bonus + (luckSpend × 2) + supporters
* White dice: succeed on 4+ | Red: 3+ | Black: 2+
* All dice explode on 6 (roll extra die).
* Luck Points: spending 1 LP adds +2 dice; LP restored each session.
* Supporters: each ally with ranks in the skill adds +1 die.
*/
export default class OathHammerRollDialog {
/**
* Dual-attribute skills: show an alternate attribute option in the dialog.
* key → { primary, alt, altLabel i18n key }
*/
static DUAL_ATTRIBUTE_SKILLS = {
defense: { alt: "might", altLabelKey: "OATHHAMMER.Roll.DualAttr.DefenseMelee" },
fighting: { alt: "agility", altLabelKey: "OATHHAMMER.Roll.DualAttr.FightingNimble" },
magic: { alt: "intelligence", altLabelKey: "OATHHAMMER.Roll.DualAttr.MagicSpells" },
}
/**
* Show a skill check dialog and return the user's choices.
*
* @param {Actor} actor Actor performing the check
* @param {string} skillKey SYSTEM.SKILLS key (e.g. "fortune", "fighting")
* @returns {Promise<{dv, bonus, luckSpend, supporters, attrOverride, visibility}|null>}
* Resolved options, or null if the dialog was cancelled.
*/
static async prompt(actor, skillKey) {
const sys = actor.system
const skillDef = SYSTEM.SKILLS[skillKey]
if (!skillDef) throw new Error(`Unknown skill: ${skillKey}`)
const defaultAttrKey = skillDef.attribute
const attrRank = sys.attributes[defaultAttrKey].rank
const skill = sys.skills[skillKey]
const skillRank = skill.rank
const skillMod = skill.modifier ?? 0
const baseTotal = attrRank + skillRank + skillMod
const colorType = skill.colorDiceType ?? "white"
const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4
const colorLabel = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜"
const dualDef = this.DUAL_ATTRIBUTE_SKILLS[skillKey]
// Build attribute options for dual-attribute skills
let attrOptions = null
if (dualDef) {
const altRank = sys.attributes[dualDef.alt].rank
attrOptions = [
{
value: defaultAttrKey,
label: `${game.i18n.localize(`OATHHAMMER.Attribute.${defaultAttrKey.charAt(0).toUpperCase()}${defaultAttrKey.slice(1)}`)} (${attrRank}) — default`,
selected: true,
},
{
value: dualDef.alt,
label: `${game.i18n.localize(`OATHHAMMER.Attribute.${dualDef.alt.charAt(0).toUpperCase()}${dualDef.alt.slice(1)}`)} (${altRank}) — ${game.i18n.localize(dualDef.altLabelKey)}`,
selected: false,
},
]
}
const availableLuck = sys.luck?.value ?? 0
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
// Build select option arrays
const dvOptions = Array.from({ length: 10 }, (_, i) => {
const v = i + 1
return { value: v, label: String(v), selected: v === 2 }
})
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
const v = i - 6
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
})
const supportersOptions = Array.from({ length: 7 }, (_, i) => ({
value: i, label: String(i), selected: i === 0,
}))
const luckOptions = Array.from({ length: availableLuck + 1 }, (_, i) => ({
value: i,
label: i === 0 ? `0` : `${i} (+${i * 2}d)`,
selected: i === 0,
}))
const context = {
actorName: actor.name,
skillKey,
skillLabel: game.i18n.localize(skillDef.label),
attrKey: defaultAttrKey,
attrLabel: game.i18n.localize(`OATHHAMMER.Attribute.${defaultAttrKey.charAt(0).toUpperCase()}${defaultAttrKey.slice(1)}`),
attrRank,
skillRank,
skillMod,
baseTotal,
colorType,
colorLabel,
threshold,
availableLuck,
attrOptions,
isDualAttr: !!dualDef,
rollModes,
visibility: game.settings.get("core", "rollMode"),
dvOptions,
bonusOptions,
supportersOptions,
luckOptions,
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-oath-hammer/templates/roll-dialog.hbs",
context
)
const title = game.i18n.format("OATHHAMMER.Dialog.SkillCheckTitle", { skill: context.skillLabel })
const result = await foundry.applications.api.DialogV2.wait({
window: { title },
classes: ["fvtt-oath-hammer"],
content,
rejectClose: false,
buttons: [
{
label: game.i18n.localize("OATHHAMMER.Dialog.Roll"),
callback: (_event, button) => {
const out = {}
for (const el of button.form.elements) {
if (el.name) out[el.name] = el.value
}
return out
},
},
],
})
if (!result) return null
const attrOverride = result.attrOverride || defaultAttrKey
return {
dv: Math.max(1, parseInt(result.dv) || 2),
bonus: parseInt(result.bonus) || 0,
luckSpend: Math.min(Math.max(0, parseInt(result.luckSpend) || 0), availableLuck),
supporters: Math.max(0, parseInt(result.supporters) || 0),
attrOverride,
visibility: result.visibility ?? game.settings.get("core", "rollMode"),
}
}
}