Files
fvtt-oath-hammer/module/applications/defense-dialog.mjs
LeRatierBretonnier b3fd7e1aa1 feat: add Settlement actor type with Overview/Buildings/Inventory tabs
- New TypeDataModel: archetype, territory, renown, currency (gp/sp/cp),
  garrison, underSiege, isCapital, founded, taxNotes, description, notes
- 3-tab ApplicationV2 sheet with drag & drop for building/weapon/armor/equipment
- Currency steppers (+/−), building constructed toggle, qty controls
- LESS-based CSS (settlement-sheet.less) + base.less updated for shared styles
- Full i18n keys in lang/en.json (8 settlement archetypes)
- system.json: registered settlement actor type

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-20 17:01:38 +01:00

114 lines
4.4 KiB
JavaScript

/**
* Defense roll dialog.
*
* Pool = governing attribute (Agility default; Might option for melee) + Defense skill
* + armorPenalty (auto from equipped armor, always ≤ 0)
* + parryBonus / blockBonus (from equipped weapon traits)
* + manual bonus
*
* Parry trait on equipped weapon → red dice (3+) vs melee; +1 if two Parry weapons
* Block trait on equipped weapon → red dice (3+) vs ranged; +1 to ranged defense
*/
export default class OathHammerDefenseDialog {
static async prompt(actor) {
const actorSys = actor.system
// ── Attributes & skill ──────────────────────────────────────────────
const agiRank = actorSys.attributes.agility.rank
const mightRank = actorSys.attributes.might.rank
const defRank = actorSys.skills.defense.rank
// ── Equipped weapons ────────────────────────────────────────────────
const equipped = actor.items.filter(i => i.type === "weapon" && i.system.equipped)
const parryCount = equipped.filter(w => w.system.traits?.has?.("parry") || [...(w.system.traits ?? [])].includes("parry")).length
const blockCount = equipped.filter(w => w.system.traits?.has?.("block") || [...(w.system.traits ?? [])].includes("block")).length
// ── Equipped armor penalty (sum) ────────────────────────────────────
const armorPenalty = actor.items
.filter(i => i.type === "armor" && i.system.equipped)
.reduce((sum, a) => sum + (a.system.penalty ?? 0), 0)
// ── Build option lists ───────────────────────────────────────────────
const attackTypeOptions = [
{ value: "melee", label: game.i18n.localize("OATHHAMMER.Dialog.DefenseMelee"), selected: true },
{ value: "ranged", label: game.i18n.localize("OATHHAMMER.Dialog.DefenseRanged"), selected: false },
]
const attrOptions = [
{ value: "agility", label: `${game.i18n.localize("OATHHAMMER.Attribute.Agility")} (${agiRank})`, selected: true },
{ value: "might", label: `${game.i18n.localize("OATHHAMMER.Attribute.Might")} (${mightRank})`, selected: false },
]
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
const v = i - 6
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,
agiRank,
mightRank,
defRank,
parryCount,
blockCount,
armorPenalty,
attackTypeOptions,
attrOptions,
bonusOptions,
rollModes,
visibility: game.settings.get("core", "rollMode"),
}
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-oath-hammer/templates/defense-roll-dialog.hbs",
context
)
const result = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.format("OATHHAMMER.Dialog.DefenseTitle", { actor: actor.name }), resizable: true },
classes: ["fvtt-oath-hammer"],
position: { width: 420 },
content,
rejectClose: false,
buttons: [{
label: game.i18n.localize("OATHHAMMER.Dialog.RollDefense"),
callback: (_ev, btn) => Object.fromEntries(
[...btn.form.elements].filter(e => e.name).map(e => [e.name, e.value])
),
}],
})
if (!result) return null
const attackType = result.attackType ?? "melee"
const attrChoice = result.attribute ?? "agility"
const attrRank = attrChoice === "might" ? mightRank : agiRank
const bonus = parseInt(result.bonus) || 0
// Determine red dice and trait bonus from equipped weapons
let redDice = false
let traitBonus = 0
if (attackType === "melee" && parryCount > 0) {
redDice = true
if (parryCount >= 2) traitBonus = 1
} else if (attackType === "ranged" && blockCount > 0) {
redDice = true
traitBonus = 1
}
return {
attackType,
attrRank,
attrChoice,
redDice,
traitBonus,
armorPenalty,
bonus,
visibility: result.visibility ?? game.settings.get("core", "rollMode"),
}
}
}