Files
fvtt-oath-hammer/module/applications/roll-dialog.mjs

158 lines
5.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.
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: 11 }, (_, i) => {
const v = i // 0..10
const label = v === 0 ? "0 (opposed)" : String(v)
return { value: v, label, 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) continue
out[el.name] = el.type === "checkbox" ? String(el.checked) : el.value
}
return out
},
},
],
})
if (!result) return null
const attrOverride = result.attrOverride || defaultAttrKey
return {
dv: Math.max(0, 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),
explodeOn5: result.explodeOn5 === "true",
attrOverride,
visibility: result.visibility ?? game.settings.get("core", "rollMode"),
}
}
}