From 77c9359601ba99ff0c468455b7d4c6e78744fbd7 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 29 Dec 2025 11:40:12 -0600 Subject: [PATCH] Add Dodge and Block defense rolls to character sheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Dodge roll under Reflex save (auto-hindered by heavy armor) - Add Block roll under Endure save (hindered vs ranged attacks toggle) - Create DodgeRollDialog and BlockRollDialog with templates - Display defense rolls as indented sub-rows on Main tab - Block row visually dimmed when no shield equipped, shows notification on click 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- lang/en.json | 3 + module/applications/_module.mjs | 2 + module/applications/block-roll-dialog.mjs | 208 ++++++++++++++++++++++ module/applications/dodge-roll-dialog.mjs | 169 ++++++++++++++++++ module/sheets/base-actor-sheet.mjs | 35 ++++ module/sheets/character-sheet.mjs | 14 ++ module/vagabond.mjs | 4 + styles/scss/sheets/_actor-sheet.scss | 81 +++++++++ templates/actor/character-main.hbs | 55 +++++- templates/dialog/block-roll.hbs | 92 ++++++++++ templates/dialog/dodge-roll.hbs | 86 +++++++++ 11 files changed, 742 insertions(+), 7 deletions(-) create mode 100644 module/applications/block-roll-dialog.mjs create mode 100644 module/applications/dodge-roll-dialog.mjs create mode 100644 templates/dialog/block-roll.hbs create mode 100644 templates/dialog/dodge-roll.hbs diff --git a/lang/en.json b/lang/en.json index 4297a71..6cbbbed 100644 --- a/lang/en.json +++ b/lang/en.json @@ -262,6 +262,9 @@ "VAGABOND.DodgeInfo": "Dodge allows you to avoid the attack entirely", "VAGABOND.BlockedWith": "Blocked with shield", "VAGABOND.DodgedAttack": "Dodged the attack", + "VAGABOND.VsRangedAttack": "vs Ranged Attack?", + "VAGABOND.HinderedByHeavyArmor": "Heavy Armor", + "VAGABOND.HinderedByRanged": "Ranged Attack", "VAGABOND.CriticalSuccess": "Critical Success!", "VAGABOND.CastSpell": "Cast Spell", diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs index 2f91ad9..165cdb3 100644 --- a/module/applications/_module.mjs +++ b/module/applications/_module.mjs @@ -7,6 +7,8 @@ export { default as VagabondRollDialog } from "./base-roll-dialog.mjs"; export { default as SkillCheckDialog } from "./skill-check-dialog.mjs"; export { default as AttackRollDialog } from "./attack-roll-dialog.mjs"; export { default as SaveRollDialog } from "./save-roll-dialog.mjs"; +export { default as DodgeRollDialog } from "./dodge-roll-dialog.mjs"; +export { default as BlockRollDialog } from "./block-roll-dialog.mjs"; export { default as SpellCastDialog } from "./spell-cast-dialog.mjs"; export { default as FavorHinderDebug } from "./favor-hinder-debug.mjs"; export { default as LevelUpDialog } from "./level-up-dialog.mjs"; diff --git a/module/applications/block-roll-dialog.mjs b/module/applications/block-roll-dialog.mjs new file mode 100644 index 0000000..51d0062 --- /dev/null +++ b/module/applications/block-roll-dialog.mjs @@ -0,0 +1,208 @@ +/** + * Block Roll Dialog for Vagabond RPG + * + * Dialog for block defense rolls: + * - Uses Endure save difficulty + * - Has "vs Ranged Attack?" toggle that applies hinder when checked + * - Requires shield to be equipped + * + * @extends VagabondRollDialog + */ + +import VagabondRollDialog from "./base-roll-dialog.mjs"; +import { saveRoll } from "../dice/rolls.mjs"; + +export default class BlockRollDialog extends VagabondRollDialog { + /** + * @param {VagabondActor} actor - The actor making the roll + * @param {Object} options - Dialog options + */ + constructor(actor, options = {}) { + super(actor, options); + + // Block always uses Endure + this.saveType = "endure"; + this.defenseType = "block"; + this.vsRanged = false; // Toggle state for ranged attack hinder + + // Load base favor/hinder for endure saves + this.rollConfig.autoFavorHinder = actor.getNetFavorHinder({ saveType: "endure" }); + } + + /* -------------------------------------------- */ + /* Static Properties */ + /* -------------------------------------------- */ + + /** @override */ + static DEFAULT_OPTIONS = foundry.utils.mergeObject( + super.DEFAULT_OPTIONS, + { + id: "vagabond-block-roll-dialog", + window: { + title: "VAGABOND.Block", + icon: "fa-solid fa-shield", + }, + position: { + width: 340, + }, + }, + { inplace: false } + ); + + /** @override */ + static PARTS = { + form: { + template: "systems/vagabond/templates/dialog/block-roll.hbs", + }, + }; + + /* -------------------------------------------- */ + /* Getters */ + /* -------------------------------------------- */ + + /** @override */ + get title() { + return game.i18n.localize("VAGABOND.Block"); + } + + /** + * Get the difficulty for this block (Endure save difficulty). + * @returns {number} + */ + get difficulty() { + return this.actor.system.saves.endure.difficulty; + } + + /** + * Get the net favor/hinder value including ranged toggle. + * @override + * @returns {number} -1, 0, or +1 + */ + get netFavorHinder() { + const manual = this.rollConfig.favorHinder; + let auto = this.rollConfig.autoFavorHinder.net; + + // Apply ranged hinder if toggled + if (this.vsRanged) { + auto = Math.max(-1, auto - 1); + } + + return Math.clamp(manual + auto, -1, 1); + } + + /* -------------------------------------------- */ + /* Data Preparation */ + /* -------------------------------------------- */ + + /** @override */ + async _prepareRollContext(_options) { + // Build hinder sources including ranged if toggled + const hinderSources = [...this.rollConfig.autoFavorHinder.hinderSources]; + if (this.vsRanged) { + hinderSources.push(game.i18n.localize("VAGABOND.HinderedByRanged")); + } + + return { + difficulty: this.difficulty, + vsRanged: this.vsRanged, + saveStats: "MIT + MIT", + hinderSources, + }; + } + + /* -------------------------------------------- */ + /* Event Handlers */ + /* -------------------------------------------- */ + + /** @override */ + _onRender(context, options) { + super._onRender(context, options); + + // vs Ranged toggle + const rangedToggle = this.element.querySelector('[name="vsRanged"]'); + rangedToggle?.addEventListener("change", (event) => { + this.vsRanged = event.target.checked; + this.render(); + }); + } + + /* -------------------------------------------- */ + /* Roll Execution */ + /* -------------------------------------------- */ + + /** @override */ + async _executeRoll() { + const result = await saveRoll(this.actor, "endure", this.difficulty, { + favorHinder: this.netFavorHinder, + modifier: this.rollConfig.modifier, + isBlock: true, + }); + + await this._sendToChat(result); + } + + /** + * Send the roll result to chat. + * + * @param {VagabondRollResult} result - The roll result + * @returns {Promise} + * @private + */ + async _sendToChat(result) { + // Build hinder sources including ranged + const hinderSources = [...this.rollConfig.autoFavorHinder.hinderSources]; + if (this.vsRanged) { + hinderSources.push(game.i18n.localize("VAGABOND.HinderedByRanged")); + } + + const templateData = { + actor: this.actor, + saveType: "endure", + saveLabel: game.i18n.localize("VAGABOND.SaveEndure"), + stats: ["MIT", "MIT"], + difficulty: result.difficulty, + total: result.total, + d20Result: result.d20Result, + favorDie: result.favorDie, + modifier: this.rollConfig.modifier, + success: result.success, + isCrit: result.isCrit, + isFumble: result.isFumble, + formula: result.roll.formula, + netFavorHinder: this.netFavorHinder, + favorSources: this.rollConfig.autoFavorHinder.favorSources, + hinderSources, + isDefense: true, + defenseType: "block", + defenseLabel: game.i18n.localize("VAGABOND.Block"), + }; + + const content = await renderTemplate( + "systems/vagabond/templates/chat/save-roll.hbs", + templateData + ); + + return ChatMessage.create({ + user: game.user.id, + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + content, + rolls: [result.roll], + sound: CONFIG.sounds.dice, + }); + } + + /* -------------------------------------------- */ + /* Static Methods */ + /* -------------------------------------------- */ + + /** + * Create and render a block roll dialog. + * + * @param {VagabondActor} actor - The actor making the roll + * @param {Object} [options] - Additional options + * @returns {Promise} + */ + static async prompt(actor, options = {}) { + return this.create(actor, options); + } +} diff --git a/module/applications/dodge-roll-dialog.mjs b/module/applications/dodge-roll-dialog.mjs new file mode 100644 index 0000000..50f39e2 --- /dev/null +++ b/module/applications/dodge-roll-dialog.mjs @@ -0,0 +1,169 @@ +/** + * Dodge Roll Dialog for Vagabond RPG + * + * Simplified dialog for dodge defense rolls: + * - Uses Reflex save difficulty + * - Automatically applies hinder if wearing heavy armor + * - Shows favor/hinder toggles and modifiers + * + * @extends VagabondRollDialog + */ + +import VagabondRollDialog from "./base-roll-dialog.mjs"; +import { saveRoll } from "../dice/rolls.mjs"; + +export default class DodgeRollDialog extends VagabondRollDialog { + /** + * @param {VagabondActor} actor - The actor making the roll + * @param {Object} options - Dialog options + */ + constructor(actor, options = {}) { + super(actor, options); + + // Dodge always uses Reflex + this.saveType = "reflex"; + this.defenseType = "dodge"; + + // Check for heavy armor hinder + this.hasHeavyArmor = actor.getEquippedArmor().some((a) => a.system.hindersDodge); + + // Build auto favor/hinder including heavy armor + const baseFavorHinder = actor.getNetFavorHinder({ saveType: "reflex" }); + if (this.hasHeavyArmor) { + baseFavorHinder.hinderSources.push(game.i18n.localize("VAGABOND.HinderedByHeavyArmor")); + baseFavorHinder.net = Math.max(-1, baseFavorHinder.net - 1); + } + this.rollConfig.autoFavorHinder = baseFavorHinder; + } + + /* -------------------------------------------- */ + /* Static Properties */ + /* -------------------------------------------- */ + + /** @override */ + static DEFAULT_OPTIONS = foundry.utils.mergeObject( + super.DEFAULT_OPTIONS, + { + id: "vagabond-dodge-roll-dialog", + window: { + title: "VAGABOND.Dodge", + icon: "fa-solid fa-person-running", + }, + position: { + width: 340, + }, + }, + { inplace: false } + ); + + /** @override */ + static PARTS = { + form: { + template: "systems/vagabond/templates/dialog/dodge-roll.hbs", + }, + }; + + /* -------------------------------------------- */ + /* Getters */ + /* -------------------------------------------- */ + + /** @override */ + get title() { + return game.i18n.localize("VAGABOND.Dodge"); + } + + /** + * Get the difficulty for this dodge (Reflex save difficulty). + * @returns {number} + */ + get difficulty() { + return this.actor.system.saves.reflex.difficulty; + } + + /* -------------------------------------------- */ + /* Data Preparation */ + /* -------------------------------------------- */ + + /** @override */ + async _prepareRollContext(_options) { + return { + difficulty: this.difficulty, + hasHeavyArmor: this.hasHeavyArmor, + saveStats: "DEX + AWR", + }; + } + + /* -------------------------------------------- */ + /* Roll Execution */ + /* -------------------------------------------- */ + + /** @override */ + async _executeRoll() { + const result = await saveRoll(this.actor, "reflex", this.difficulty, { + favorHinder: this.netFavorHinder, + modifier: this.rollConfig.modifier, + isDodge: true, + }); + + await this._sendToChat(result); + } + + /** + * Send the roll result to chat. + * + * @param {VagabondRollResult} result - The roll result + * @returns {Promise} + * @private + */ + async _sendToChat(result) { + const templateData = { + actor: this.actor, + saveType: "reflex", + saveLabel: game.i18n.localize("VAGABOND.SaveReflex"), + stats: ["DEX", "AWR"], + difficulty: result.difficulty, + total: result.total, + d20Result: result.d20Result, + favorDie: result.favorDie, + modifier: this.rollConfig.modifier, + success: result.success, + isCrit: result.isCrit, + isFumble: result.isFumble, + formula: result.roll.formula, + netFavorHinder: this.netFavorHinder, + favorSources: this.rollConfig.autoFavorHinder.favorSources, + hinderSources: this.rollConfig.autoFavorHinder.hinderSources, + isDefense: true, + defenseType: "dodge", + defenseLabel: game.i18n.localize("VAGABOND.Dodge"), + }; + + const content = await renderTemplate( + "systems/vagabond/templates/chat/save-roll.hbs", + templateData + ); + + return ChatMessage.create({ + user: game.user.id, + speaker: ChatMessage.getSpeaker({ actor: this.actor }), + content, + rolls: [result.roll], + sound: CONFIG.sounds.dice, + }); + } + + /* -------------------------------------------- */ + /* Static Methods */ + /* -------------------------------------------- */ + + /** + * Create and render a dodge roll dialog. + * + * @param {VagabondActor} actor - The actor making the roll + * @param {Object} [options] - Additional options + * @returns {Promise} + */ + static async prompt(actor, options = {}) { + return this.create(actor, options); + } +} diff --git a/module/sheets/base-actor-sheet.mjs b/module/sheets/base-actor-sheet.mjs index 9ecd440..8d7686a 100644 --- a/module/sheets/base-actor-sheet.mjs +++ b/module/sheets/base-actor-sheet.mjs @@ -58,6 +58,8 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor editImage: VagabondActorSheet.#onEditImage, rollSkill: VagabondActorSheet.#onRollSkill, rollSave: VagabondActorSheet.#onRollSave, + rollDodge: VagabondActorSheet.#onRollDodge, + rollBlock: VagabondActorSheet.#onRollBlock, rollAttack: VagabondActorSheet.#onRollAttack, castSpell: VagabondActorSheet.#onCastSpell, itemEdit: VagabondActorSheet.#onItemEdit, @@ -685,6 +687,39 @@ export default class VagabondActorSheet extends HandlebarsApplicationMixin(Actor await SaveRollDialog.prompt(this.actor, saveType); } + /** + * Handle dodge roll action. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onRollDodge(event, target) { + event.preventDefault(); + const { DodgeRollDialog } = game.vagabond.applications; + await DodgeRollDialog.prompt(this.actor); + } + + /** + * Handle block roll action. + * @param {PointerEvent} event + * @param {HTMLElement} target + */ + static async #onRollBlock(event, target) { + event.preventDefault(); + + // Check if shield is equipped + const hasShield = this.actor + .getEquippedArmor() + .some((armor) => armor.system.armorType === "shield"); + + if (!hasShield) { + ui.notifications.warn(game.i18n.localize("VAGABOND.RequiresShield")); + return; + } + + const { BlockRollDialog } = game.vagabond.applications; + await BlockRollDialog.prompt(this.actor); + } + /** * Handle attack roll action. * @param {PointerEvent} event diff --git a/module/sheets/character-sheet.mjs b/module/sheets/character-sheet.mjs index a864667..be407ef 100644 --- a/module/sheets/character-sheet.mjs +++ b/module/sheets/character-sheet.mjs @@ -226,6 +226,11 @@ export default class VagabondCharacterSheet extends VagabondActorSheet { _prepareSaves() { const system = this.actor.system; + // Check armor conditions for defense rolls + const equippedArmor = this.actor.getEquippedArmor(); + const hasHeavyArmor = equippedArmor.some((a) => a.system.hindersDodge); + const hasShield = equippedArmor.some((a) => a.system.armorType === "shield"); + return { reflex: { id: "reflex", @@ -233,6 +238,11 @@ export default class VagabondCharacterSheet extends VagabondActorSheet { stats: "DEX + AWR", difficulty: system.saves.reflex.difficulty, bonus: system.saves.reflex.bonus, + dodge: { + difficulty: system.saves.reflex.difficulty, + hindered: hasHeavyArmor, + hinderSource: hasHeavyArmor ? "VAGABOND.HinderedByHeavyArmor" : null, + }, }, endure: { id: "endure", @@ -240,6 +250,10 @@ export default class VagabondCharacterSheet extends VagabondActorSheet { stats: "MIT + MIT", difficulty: system.saves.endure.difficulty, bonus: system.saves.endure.bonus, + block: { + difficulty: system.saves.endure.difficulty, + hasShield, + }, }, will: { id: "will", diff --git a/module/vagabond.mjs b/module/vagabond.mjs index 1ed66e8..497a5ba 100644 --- a/module/vagabond.mjs +++ b/module/vagabond.mjs @@ -31,6 +31,8 @@ import { SkillCheckDialog, AttackRollDialog, SaveRollDialog, + DodgeRollDialog, + BlockRollDialog, SpellCastDialog, FavorHinderDebug, } from "./applications/_module.mjs"; @@ -109,6 +111,8 @@ Hooks.once("init", async () => { SkillCheckDialog, AttackRollDialog, SaveRollDialog, + DodgeRollDialog, + BlockRollDialog, SpellCastDialog, FavorHinderDebug, }, diff --git a/styles/scss/sheets/_actor-sheet.scss b/styles/scss/sheets/_actor-sheet.scss index bc74816..ff4e5ba 100644 --- a/styles/scss/sheets/_actor-sheet.scss +++ b/styles/scss/sheets/_actor-sheet.scss @@ -603,6 +603,87 @@ transition: color $transition-fast; } } + + // Defense sub-rows (Dodge, Block) - indented under parent saves + .defense-row { + display: flex; + align-items: center; + gap: $spacing-2; + padding: $spacing-1 $spacing-3; + padding-left: $spacing-6; + margin-left: $spacing-4; + background-color: var(--color-bg-input); + border: 1px solid var(--color-border-light); + border-radius: $radius-md; + cursor: pointer; + transition: all $transition-fast; + font-size: $font-size-sm; + + &:hover:not(.disabled) { + background-color: var(--color-bg-secondary); + border-color: var(--color-accent-primary); + + .roll-icon { + color: var(--color-accent-primary); + } + } + + &.no-shield { + opacity: 0.5; + + &:hover { + background-color: var(--color-bg-input); + border-color: var(--color-border-light); + } + } + + .defense-icon { + font-size: $font-size-sm; + color: var(--color-text-secondary); + width: 16px; + text-align: center; + } + + .defense-label { + font-weight: $font-weight-medium; + color: var(--color-text-primary); + margin-right: auto; + } + + .hinder-indicator { + color: var(--color-warning); + font-size: $font-size-xs; + + i { + font-size: 10px; + } + } + + .defense-difficulty { + font-family: $font-family-header; + font-size: $font-size-base; + font-weight: $font-weight-bold; + color: var(--color-text-primary); + min-width: 24px; + text-align: center; + } + + .roll-icon { + color: var(--color-text-muted); + font-size: $font-size-sm; + transition: color $transition-fast; + } + + // Dodge row icon color + &.dodge-row .defense-icon { + color: var(--color-info); + } + + // Block row icon color + &.block-row .defense-icon { + color: var(--color-success); + } + } } // Skills Section diff --git a/templates/actor/character-main.hbs b/templates/actor/character-main.hbs index 467ff27..98f3903 100644 --- a/templates/actor/character-main.hbs +++ b/templates/actor/character-main.hbs @@ -22,16 +22,57 @@

{{localize "VAGABOND.Saves"}}

- {{#each saves}} + {{!-- Reflex Save --}}
- {{localize this.label}} - ({{this.stats}}) - {{this.difficulty}} + data-action="rollSave" data-save="reflex" + aria-label="{{localize saves.reflex.label}} {{localize 'VAGABOND.Save'}}: {{localize 'VAGABOND.Difficulty'}} {{saves.reflex.difficulty}}"> + {{localize saves.reflex.label}} + ({{saves.reflex.stats}}) + {{saves.reflex.difficulty}} + +
+ {{!-- Dodge sub-row (indented under Reflex) --}} +
+ {{localize "VAGABOND.Dodge"}} + {{#if saves.reflex.dodge.hindered}} + + + + {{/if}} + {{saves.reflex.dodge.difficulty}} + +
+ + {{!-- Endure Save --}} +
+ {{localize saves.endure.label}} + ({{saves.endure.stats}}) + {{saves.endure.difficulty}} + +
+ {{!-- Block sub-row (indented under Endure) --}} +
+ {{localize "VAGABOND.Block"}} + {{saves.endure.block.difficulty}} + +
+ + {{!-- Will Save --}} +
+ {{localize saves.will.label}} + ({{saves.will.stats}}) + {{saves.will.difficulty}}
- {{/each}}
diff --git a/templates/dialog/block-roll.hbs b/templates/dialog/block-roll.hbs new file mode 100644 index 0000000..d6e5847 --- /dev/null +++ b/templates/dialog/block-roll.hbs @@ -0,0 +1,92 @@ +{{!-- Block Roll Dialog Template --}} +{{!-- Defense roll using Endure save with vs Ranged toggle --}} + +
+ {{!-- Automatic Favor/Hinder from Active Effects --}} + {{#if hasAutoFavor}} +
+ + {{localize "VAGABOND.AutoFavor"}}: {{#each autoFavorHinder.favorSources}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} +
+ {{/if}} + {{#if rollSpecific.hinderSources.length}} +
+ + {{localize "VAGABOND.AutoHinder"}}: {{#each rollSpecific.hinderSources}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} +
+ {{/if}} + + {{!-- Block Info Header --}} +
+

{{localize "VAGABOND.Block"}}

+
+ + {{!-- Save Info --}} +
+
+ {{localize "VAGABOND.Stats"}}: + {{rollSpecific.saveStats}} +
+
+ {{localize "VAGABOND.Difficulty"}}: + {{rollSpecific.difficulty}} +
+
+ + {{!-- vs Ranged Attack Toggle --}} +
+ + {{#if rollSpecific.vsRanged}} +
+ + {{localize "VAGABOND.Hinder"}} +
+ {{/if}} +
+ + {{!-- Favor/Hinder Toggles --}} +
+ +
+ + +
+ {{#if (gt netFavorHinder 0)}} +
+d6 {{localize "VAGABOND.Favor"}}
+ {{else if (lt netFavorHinder 0)}} +
-d6 {{localize "VAGABOND.Hinder"}}
+ {{/if}} +
+ + {{!-- Situational Modifier --}} +
+ +
+ {{#each modifierPresets}} + + {{/each}} +
+
+ +
+
+ + {{!-- Roll Button --}} +
+ +
+
diff --git a/templates/dialog/dodge-roll.hbs b/templates/dialog/dodge-roll.hbs new file mode 100644 index 0000000..3020f91 --- /dev/null +++ b/templates/dialog/dodge-roll.hbs @@ -0,0 +1,86 @@ +{{!-- Dodge Roll Dialog Template --}} +{{!-- Simplified defense roll using Reflex save --}} + +
+ {{!-- Automatic Favor/Hinder from Active Effects --}} + {{#if hasAutoFavor}} +
+ + {{localize "VAGABOND.AutoFavor"}}: {{#each autoFavorHinder.favorSources}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} +
+ {{/if}} + {{#if hasAutoHinder}} +
+ + {{localize "VAGABOND.AutoHinder"}}: {{#each autoFavorHinder.hinderSources}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} +
+ {{/if}} + + {{!-- Dodge Info Header --}} +
+

{{localize "VAGABOND.Dodge"}}

+
+ + {{!-- Save Info --}} +
+
+ {{localize "VAGABOND.Stats"}}: + {{rollSpecific.saveStats}} +
+
+ {{localize "VAGABOND.Difficulty"}}: + {{rollSpecific.difficulty}} +
+
+ + {{!-- Heavy Armor Warning --}} + {{#if rollSpecific.hasHeavyArmor}} +
+ + {{localize "VAGABOND.HinderedByHeavyArmor"}} +
+ {{/if}} + + {{!-- Favor/Hinder Toggles --}} +
+ +
+ + +
+ {{#if (gt netFavorHinder 0)}} +
+d6 {{localize "VAGABOND.Favor"}}
+ {{else if (lt netFavorHinder 0)}} +
-d6 {{localize "VAGABOND.Hinder"}}
+ {{/if}} +
+ + {{!-- Situational Modifier --}} +
+ +
+ {{#each modifierPresets}} + + {{/each}} +
+
+ +
+
+ + {{!-- Roll Button --}} +
+ +
+