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

216 lines
8.0 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 dialogs for weapon attacks and damage.
*
* Attack flow:
* 1. promptAttack(actor, weapon) → options
* 2. rollWeaponAttack posts a chat card with a "Roll Damage" button
* 3. Clicking the button calls promptDamage with attackSuccesses pre-filled
* 4. rollWeaponDamage posts the damage chat card
*/
export default class OathHammerWeaponDialog {
// ------------------------------------------------------------------ //
// ATTACK DIALOG
// ------------------------------------------------------------------ //
static async promptAttack(actor, weapon) {
const sys = weapon.system
const actorSys = actor.system
const isRanged = !sys.usesMight && (sys.shortRange > 0 || sys.longRange > 0)
const skillKey = isRanged ? "shooting" : "fighting"
const skillDef = SYSTEM.SKILLS[skillKey]
const defaultAttr = skillDef.attribute
const attrRank = actorSys.attributes[defaultAttr].rank
const skillRank = actorSys.skills[skillKey].rank
const skillColor = actorSys.skills[skillKey].colorDiceType ?? "white"
const threshold = skillColor === "black" ? 2 : skillColor === "red" ? 3 : 4
const hasNimble = sys.traits.has("nimble")
// Auto-bonuses from special properties
let autoAttackBonus = 0
if (sys.specialProperties.has("master-crafted")) autoAttackBonus += 1
if (sys.specialProperties.has("accurate")) autoAttackBonus += 1 // bows
if (sys.specialProperties.has("balanced")) autoAttackBonus += 1 // grants Fast
// Damage info for reference
const hasBrutal = sys.traits.has("brutal")
const hasDeadly = sys.traits.has("deadly")
const damageColorType = hasDeadly ? "black" : hasBrutal ? "red" : "white"
const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4
const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
const mightRank = actorSys.attributes.might.rank
const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
const traitLabels = [...sys.traits].map(t => {
const key = SYSTEM.WEAPON_TRAITS[t]
return key ? game.i18n.localize(key) : t
})
// Option arrays
const attackBonusOptions = Array.from({ length: 13 }, (_, i) => {
const v = i - 6
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
})
const rangeOptions = [
{ value: 0, label: game.i18n.localize("OATHHAMMER.Dialog.RangeNormal") },
{ value: -1, label: game.i18n.localize("OATHHAMMER.Dialog.RangeLong") + " (1)" },
{ value: -2, label: game.i18n.localize("OATHHAMMER.Dialog.RangeMoving") + " (2)" },
{ value: -2, label: game.i18n.localize("OATHHAMMER.Dialog.RangeConcealment") + " (2)" },
{ value: -3, label: game.i18n.localize("OATHHAMMER.Dialog.RangeCover") + " (3)" },
]
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
const context = {
actorName: actor.name,
weaponName: weapon.name,
weaponImg: weapon.img,
skillKey,
skillLabel: game.i18n.localize(skillDef.label),
attrKey: defaultAttr,
attrLabel: game.i18n.localize(`OATHHAMMER.Attribute.${_cap(defaultAttr)}`),
attrRank,
skillRank,
colorType: skillColor,
threshold,
baseAttackPool: attrRank + skillRank,
autoAttackBonus,
hasNimble,
mightLabel: game.i18n.localize("OATHHAMMER.Attribute.Might"),
mightRank,
agilityLabel: game.i18n.localize("OATHHAMMER.Attribute.Agility"),
agilityRank: actorSys.attributes.agility.rank,
isRanged,
shortRange: sys.shortRange,
longRange: sys.longRange,
damageLabel: sys.damageLabel,
damageColorType,
damageThreshold,
damageColorLabel,
baseDamageDice,
apValue: sys.ap,
traits: traitLabels,
attackBonusOptions,
rangeOptions,
rollModes,
visibility: game.settings.get("core", "rollMode"),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-oath-hammer/templates/weapon-attack-dialog.hbs",
context
)
const result = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.format("OATHHAMMER.Dialog.AttackTitle", { weapon: weapon.name }) },
classes: ["fvtt-oath-hammer"],
content,
rejectClose: false,
buttons: [{
label: game.i18n.localize("OATHHAMMER.Dialog.RollAttack"),
callback: (_ev, btn) => Object.fromEntries(
[...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value])
),
}],
})
if (!result) return null
return {
attackBonus: parseInt(result.attackBonus) || 0,
rangeCondition: parseInt(result.rangeCondition) || 0,
attrOverride: result.attrOverride || defaultAttr,
visibility: result.visibility ?? game.settings.get("core", "rollMode"),
autoAttackBonus,
}
}
// ------------------------------------------------------------------ //
// DAMAGE DIALOG
// ------------------------------------------------------------------ //
static async promptDamage(actor, weapon, defaultSV = 0) {
const sys = weapon.system
const actorSys = actor.system
const hasBrutal = sys.traits.has("brutal")
const hasDeadly = sys.traits.has("deadly")
const damageColorType = hasDeadly ? "black" : hasBrutal ? "red" : "white"
const damageThreshold = hasDeadly ? 2 : hasBrutal ? 3 : 4
const damageColorLabel = hasDeadly ? "⬛" : hasBrutal ? "🔴" : "⬜"
const mightRank = actorSys.attributes.might.rank
const baseDamageDice = sys.usesMight ? Math.max(mightRank + sys.damageMod, 1) : Math.max(sys.damageMod, 1)
// Auto-bonuses from special properties
let autoDamageBonus = 0
if (sys.specialProperties.has("master-crafted")) autoDamageBonus += 1
if (sys.specialProperties.has("tempered")) autoDamageBonus += 1
if (sys.specialProperties.has("heavy-draw")) autoDamageBonus += 1
const svOptions = Array.from({ length: 11 }, (_, i) => ({
value: i,
label: i === 0 ? "0" : `+${i}d`,
selected: i === defaultSV,
}))
const damageBonusOptions = Array.from({ length: 9 }, (_, i) => {
const v = i - 4
return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 }
})
const rollModes = foundry.utils.duplicate(CONFIG.Dice.rollModes)
const context = {
actorName: actor.name,
weaponName: weapon.name,
weaponImg: weapon.img,
damageLabel: sys.damageLabel,
damageColorType,
damageThreshold,
damageColorLabel,
baseDamageDice,
autoDamageBonus,
apValue: sys.ap,
defaultSV,
svOptions,
damageBonusOptions,
rollModes,
visibility: game.settings.get("core", "rollMode"),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-oath-hammer/templates/weapon-damage-dialog.hbs",
context
)
const result = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.format("OATHHAMMER.Dialog.DamageTitle", { weapon: weapon.name }) },
classes: ["fvtt-oath-hammer"],
content,
rejectClose: false,
buttons: [{
label: game.i18n.localize("OATHHAMMER.Dialog.RollDamage"),
callback: (_ev, btn) => Object.fromEntries(
[...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value])
),
}],
})
if (!result) return null
return {
sv: parseInt(result.sv) || 0,
damageBonus: parseInt(result.damageBonus) || 0,
visibility: result.visibility ?? game.settings.get("core", "rollMode"),
autoDamageBonus,
}
}
}
function _cap(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}