155 lines
5.5 KiB
JavaScript
155 lines
5.5 KiB
JavaScript
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"),
|
||
}
|
||
}
|
||
}
|