Add roll windows from actor sheet
This commit is contained in:
154
module/applications/roll-dialog.mjs
Normal file
154
module/applications/roll-dialog.mjs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user