diff --git a/.gitignore b/.gitignore index fc5ec3a..2c0bfb7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ _docs_private/ node_modules/ package-lock.json .history/ +.github/ +# LevelDB runtime files (lock/log are regenerated on open) +packs/*/LOCK +packs/*/LOG +packs/*/LOG.old diff --git a/css/fvtt-oath-hammer.css b/css/fvtt-oath-hammer.css index 72867fc..725bbcb 100644 --- a/css/fvtt-oath-hammer.css +++ b/css/fvtt-oath-hammer.css @@ -565,6 +565,21 @@ opacity: 0.7; font-size: calc(0.86rem * 0.9); } +.oathhammer .character-main .character-stats-band .character-resources .character-resource .grit-max-group { + display: flex; + align-items: center; + gap: 2px; +} +.oathhammer .character-main .character-stats-band .character-resources .character-resource .grit-max-group .res-bonus-label { + opacity: 0.6; + font-size: calc(0.86rem * 0.9); +} +.oathhammer .character-main .character-stats-band .character-resources .character-resource .grit-max-group .res-bonus-input { + width: 2.2rem; + opacity: 0.85; + border-left: 1px dashed #535128; + padding-left: 2px; +} .oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-stepper { display: flex; align-items: center; @@ -1775,6 +1790,45 @@ .oathhammer .rarity-roll-btn i { font-size: 0.85em; } +.oh-luck-btn-row { + margin-top: 6px; + text-align: center; +} +.oh-post-luck-btn { + background: linear-gradient(135deg, #2d6a2d 0%, #4a8c4a 100%); + color: #e8d97a; + border: 1px solid #3a7a3a; + border-radius: 4px; + padding: 4px 12px; + font-size: 0.85em; + cursor: pointer; + letter-spacing: 0.03em; + transition: filter 0.15s; +} +.oh-post-luck-btn:hover { + filter: brightness(1.15); +} +.oh-luck-result { + display: flex; + align-items: center; + gap: 6px; + margin-top: 6px; + padding: 4px 8px; + background: rgba(45, 106, 45, 0.15); + border-left: 3px solid #4a8c4a; + border-radius: 0 4px 4px 0; + font-size: 0.85em; + color: #2d6a2d; +} +.oh-luck-result .oh-luck-result-icon { + font-size: 1.1em; +} +.oh-luck-result .oh-luck-dice { + display: flex; + gap: 3px; + flex-wrap: wrap; + margin-left: auto; +} .fvtt-oath-hammer .window-content { background: #f5ead0; padding: 6px 8px; @@ -2196,6 +2250,16 @@ .item-list--weapon .item-entry .item-actions a[data-action="edit"] { margin-left: 4px; } +.oh-spell-dialog .spell-header .weapon-img-sm, +.oh-miracle-dialog .spell-header .weapon-img-sm { + width: 40px; + height: 40px; + -o-object-fit: contain; + object-fit: contain; + border: 1px solid rgba(83, 81, 40, 0.2); + border-radius: 4px; + flex-shrink: 0; +} .oh-spell-dialog .dv-badge, .oh-miracle-dialog .dv-badge { background: #3a0e6b; diff --git a/lang/en.json b/lang/en.json index d378af4..afd1785 100644 --- a/lang/en.json +++ b/lang/en.json @@ -230,6 +230,7 @@ "NPC": "NPC", "Grit": "Grit", "Luck": "Luck", + "LuckAvailable": "Available Luck", "Defense": "Defense", "DefenseValue": "Defense Value", "ArmorRating": "Armor Rating", @@ -433,6 +434,10 @@ "LuckHint": "+2 dice each", "LuckHuman": "Human (+3d)", "Available": "available", + "LuckPoints": "Luck Points to Spend", + "LuckPostRollTitle": "Luck Roll — {name}", + "LuckPostRollConfirm": "Roll Luck Dice", + "LuckPostRollHint": "Spend Luck Points to roll extra dice and add successes", "Visibility": "Visibility", "Attribute": "Attribute", "RollSkill": "Click to roll skill check", @@ -544,7 +549,11 @@ "DefenseMelee": "melee defense", "FightingNimble": "nimble weapon", "MagicSpells": "spells" - } + }, + "NoLuckLeft": "No Luck Points remaining!", + "LuckRollPost": "Spend Luck", + "LuckResult": "Luck bonus:", + "NoBonus": "no bonus successes" }, "Rune": { "Attached": "Rune \"{name}\" attached.", diff --git a/less/actor-sheet.less b/less/actor-sheet.less index 016c3dd..c269f22 100644 --- a/less/actor-sheet.less +++ b/less/actor-sheet.less @@ -169,6 +169,24 @@ .res-sep { opacity: 0.7; font-size: @font-size-xs; } + .grit-max-group { + display: flex; + align-items: center; + gap: 2px; + + .res-bonus-label { + opacity: 0.6; + font-size: @font-size-xs; + } + + .res-bonus-input { + width: 2.2rem; + opacity: 0.85; + border-left: 1px dashed @color-olive; + padding-left: 2px; + } + } + &.character-resource--luck { .luck-stepper { display: flex; diff --git a/less/roll-dialog.less b/less/roll-dialog.less index 6e5529f..b9d3463 100644 --- a/less/roll-dialog.less +++ b/less/roll-dialog.less @@ -450,6 +450,17 @@ .oh-miracle-dialog { // Spell/miracle header (reuses .spell-header, .weapon-img-sm, .weapon-name-lg, .weapon-badges) + .spell-header { + .weapon-img-sm { + width: 40px; + height: 40px; + object-fit: contain; + border: 1px solid @color-olive-faint; + border-radius: 4px; + flex-shrink: 0; + } + } + .dv-badge { background: #3a0e6b; color: #e8d9ff; diff --git a/less/rolls.less b/less/rolls.less index 104c90b..d42f402 100644 --- a/less/rolls.less +++ b/less/rolls.less @@ -115,3 +115,47 @@ i { font-size: 0.85em; } } } + +// Post-roll luck button and result +.oh-luck-btn-row { + margin-top: 6px; + text-align: center; +} + +.oh-post-luck-btn { + background: linear-gradient(135deg, #2d6a2d 0%, #4a8c4a 100%); + color: #e8d97a; + border: 1px solid #3a7a3a; + border-radius: 4px; + padding: 4px 12px; + font-size: 0.85em; + cursor: pointer; + letter-spacing: 0.03em; + transition: filter 0.15s; + + &:hover { + filter: brightness(1.15); + } +} + +.oh-luck-result { + display: flex; + align-items: center; + gap: 6px; + margin-top: 6px; + padding: 4px 8px; + background: fade(#2d6a2d, 15%); + border-left: 3px solid #4a8c4a; + border-radius: 0 4px 4px 0; + font-size: 0.85em; + color: #2d6a2d; + + .oh-luck-result-icon { font-size: 1.1em; } + + .oh-luck-dice { + display: flex; + gap: 3px; + flex-wrap: wrap; + margin-left: auto; + } +} diff --git a/module/applications/luck-roll-dialog.mjs b/module/applications/luck-roll-dialog.mjs new file mode 100644 index 0000000..67fba11 --- /dev/null +++ b/module/applications/luck-roll-dialog.mjs @@ -0,0 +1,52 @@ +/** + * Dialog for spending Luck Points after a roll result is known. + * Called from the "🍀 Luck" button on chat cards. + */ +export default class OathHammerLuckRollDialog { + + /** + * Prompt the actor's owner to spend luck points after seeing a roll result. + * @param {Actor} actor + * @returns {Promise<{luckSpend: number, luckIsHuman: boolean}|null>} + */ + static async prompt(actor) { + const actorSys = actor.system + const availableLuck = actorSys.luck?.value ?? 0 + if (availableLuck <= 0) { + ui.notifications.warn(game.i18n.localize("OATHHAMMER.Roll.NoLuckLeft")) + return null + } + + const isHuman = (actorSys.lineage?.name ?? "").toLowerCase() === "human" + const luckDicePerPoint = isHuman ? 3 : 2 + + const luckOptions = Array.from({ length: availableLuck + 1 }, (_, i) => ({ + value: i, + label: i === 0 ? "0" : `${i} LP (+${i * luckDicePerPoint}d)`, + selected: i === 1, + })) + + const content = await foundry.applications.handlebars.renderTemplate( + "systems/fvtt-oath-hammer/templates/luck-roll-dialog.hbs", + { availableLuck, isHuman, luckOptions } + ) + + const result = await foundry.applications.api.DialogV2.wait({ + window: { title: game.i18n.format("OATHHAMMER.Dialog.LuckPostRollTitle", { name: actor.name }) }, + classes: ["fvtt-oath-hammer"], + content, + rejectClose: false, + buttons: [{ + label: game.i18n.localize("OATHHAMMER.Dialog.LuckPostRollConfirm"), + callback: (_ev, btn) => { + const spend = parseInt(btn.form.elements.luckSpend?.value) || 0 + const isH = btn.form.elements.luckIsHuman?.checked ?? false + return { luckSpend: spend, luckIsHuman: isH } + }, + }], + }) + + if (!result || result.luckSpend <= 0) return null + return result + } +} diff --git a/module/applications/sheets/base-item-sheet.mjs b/module/applications/sheets/base-item-sheet.mjs index 2e1e8f2..2c5898f 100644 --- a/module/applications/sheets/base-item-sheet.mjs +++ b/module/applications/sheets/base-item-sheet.mjs @@ -79,7 +79,7 @@ export default class OathHammerItemSheet extends HandlebarsApplicationMixin(foun ) // Weapon-specific numeric selects context.damageModChoices = Object.fromEntries( - Array.from({ length: 10 }, (_, i) => [i - 4, i - 4 >= 0 ? `+${i - 4}` : String(i - 4)]) + Array.from({ length: 20 }, (_, i) => [i - 4, i - 4 >= 0 ? `+${i - 4}` : String(i - 4)]) ) context.apChoices = Object.fromEntries( Array.from({ length: 7 }, (_, i) => [i, String(i)]) diff --git a/module/applications/weapon-dialog.mjs b/module/applications/weapon-dialog.mjs index c9c1fe2..842e020 100644 --- a/module/applications/weapon-dialog.mjs +++ b/module/applications/weapon-dialog.mjs @@ -348,7 +348,7 @@ export default class OathHammerWeaponDialog { selected: i === defaultSV, })) - const damageBonusOptions = Array.from({ length: 9 }, (_, i) => { + const damageBonusOptions = Array.from({ length: 20 }, (_, i) => { const v = i - 4 return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } }) diff --git a/module/models/character.mjs b/module/models/character.mjs index 57841b3..8438d75 100644 --- a/module/models/character.mjs +++ b/module/models/character.mjs @@ -16,7 +16,7 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel }) const attributeField = () => new fields.SchemaField({ - rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1, max: 4 }) + rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 }) }) schema.attributes = new fields.SchemaField({ might: attributeField(), @@ -68,7 +68,8 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel schema.grit = new fields.SchemaField({ value: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 }), - max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 }) + max: new fields.NumberField({ ...requiredInteger, initial: 2, min: 0 }), + bonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }), }) // Luck.max is derived from fate.rank; resets at session start. @@ -125,8 +126,8 @@ export default class OathHammerCharacter extends foundry.abstract.TypeDataModel prepareDerivedData() { super.prepareDerivedData() - // Grit max = Resilience skill rank + Toughness attribute rank (rulebook p.5) - this.grit.max = this.skills.resilience.rank + this.attributes.toughness.rank + // Grit max = Resilience skill rank + Toughness attribute rank + bonus (rulebook p.5) + this.grit.max = this.skills.resilience.rank + this.attributes.toughness.rank + (this.grit.bonus ?? 0) // Luck max = Fate rank; restores at session start this.luck.max = this.attributes.fate.rank // Defense score = 10 + Agility + Armor Rating + bonus diff --git a/module/models/weapon.mjs b/module/models/weapon.mjs index ec920d7..7f8a225 100644 --- a/module/models/weapon.mjs +++ b/module/models/weapon.mjs @@ -15,7 +15,7 @@ export default class OathHammerWeapon extends foundry.abstract.TypeDataModel { // Damage: melee/throwing = Might rank + damageMod dice; bows = baseDice (fixed, no Might) // usesMight is now derived from proficiencyGroup (see getter below) - schema.damageMod = new fields.NumberField({ ...requiredInteger, initial: 0, min: -4, max: 16 }) + schema.damageMod = new fields.NumberField({ ...requiredInteger, initial: 0, min: -4, max: 15 }) // AP (Armor Penetration): penalty imposed on armor/defense rolls schema.ap = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 16 }) diff --git a/module/rolls.mjs b/module/rolls.mjs index c83092e..5f2e903 100644 --- a/module/rolls.mjs +++ b/module/rolls.mjs @@ -139,6 +139,7 @@ export async function rollSkillCheck(actor, skillKey, dv, options = {}) { content, rolls: allRolls, sound: CONFIG.sounds.dice, + flags: { "fvtt-oath-hammer": { luckRoll: _luckFlagData(actor, threshold, colorType, dv, isOpposed, explodeOn5) } }, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) @@ -224,6 +225,59 @@ export function _diceHtml(diceResults, threshold) { }).join(" ") } +/** + * Build the luck flag data to store on a chat message, enabling post-roll luck spending. + * @param {Actor} actor + * @param {number} threshold Dice success threshold (2/3/4) + * @param {string} colorType "white"|"red"|"black" + * @param {number} dv Difficulty value (0 = opposed) + * @param {boolean} isOpposed + * @param {boolean} explodeOn5 + */ +export function _luckFlagData(actor, threshold, colorType, dv, isOpposed, explodeOn5) { + return { actorUuid: actor.uuid, threshold, colorType, dv, isOpposed, explodeOn5 } +} + +/** + * Perform a post-roll luck spend: roll extra dice and update the chat message. + * @param {ChatMessage} message + * @param {number} luckSpend + * @param {boolean} luckIsHuman + */ +export async function rollPostRollLuck(message, luckSpend, luckIsHuman) { + const flag = message.getFlag("fvtt-oath-hammer", "luckRoll") + if (!flag || flag.used || luckSpend <= 0) return + + const actor = await fromUuid(flag.actorUuid) + if (!actor) return + + const currentLuck = actor.system.luck?.value ?? 0 + const safeSpend = Math.min(luckSpend, currentLuck) + if (safeSpend <= 0) { + ui.notifications.warn(game.i18n.localize("OATHHAMMER.Roll.NoLuckLeft")) + return + } + + const luckDicePerPoint = luckIsHuman ? 3 : 2 + const extraDice = safeSpend * luckDicePerPoint + + const { successes, diceResults } = await _rollPool(extraDice, flag.threshold, flag.explodeOn5) + + await actor.update({ "system.luck.value": Math.max(0, currentLuck - safeSpend) }) + + const luckDiceHtml = _diceHtml(diceResults, flag.threshold) + + await message.setFlag("fvtt-oath-hammer", "luckRoll", { + ...flag, + used: true, + luckSpend: safeSpend, + luckIsHuman, + bonusSuccesses: successes, + extraDiceResults: diceResults, + luckDiceHtml, + }) +} + // ============================================================ // WEAPON ATTACK ROLL // ============================================================ @@ -309,7 +363,7 @@ export async function rollWeaponAttack(actor, weapon, options = {}) { content, rolls: rolls, sound: CONFIG.sounds.dice, - flags: { "fvtt-oath-hammer": { weaponAttack: flagData } }, + flags: { "fvtt-oath-hammer": { weaponAttack: flagData, luckRoll: _luckFlagData(actor, threshold, colorType, 0, true, explodeOn5) } }, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) @@ -506,11 +560,13 @@ export async function rollSpellCast(actor, spell, options = {}) { ` const rollMode = visibility ?? game.settings.get("core", "rollMode") + const spellColorType = redDice ? "red" : "white" const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice, + flags: { "fvtt-oath-hammer": { luckRoll: _luckFlagData(actor, threshold, spellColorType, dv, false, explodeOn5) } }, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) @@ -608,6 +664,7 @@ export async function rollMiracleCast(actor, miracle, options = {}) { content, rolls: rolls, sound: CONFIG.sounds.dice, + flags: { "fvtt-oath-hammer": { luckRoll: _luckFlagData(actor, threshold, "white", dv, false, explodeOn5) } }, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) @@ -683,11 +740,13 @@ export async function rollDefense(actor, options = {}) { ` const rollMode = visibility ?? game.settings.get("core", "rollMode") + const defColorType = redDice ? "red" : "white" const msgData = { speaker: ChatMessage.getSpeaker({ actor }), content, rolls: rolls, sound: CONFIG.sounds.dice, + flags: { "fvtt-oath-hammer": { luckRoll: _luckFlagData(actor, threshold, defColorType, 0, true, false) } }, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) @@ -786,6 +845,7 @@ export async function rollWeaponDefense(actor, weapon, options = {}) { content, rolls: rolls, sound: CONFIG.sounds.dice, + flags: { "fvtt-oath-hammer": { luckRoll: _luckFlagData(actor, threshold, colorOverride, 0, true, explodeOn5) } }, } ChatMessage.applyRollMode(msgData, rollMode) await ChatMessage.create(msgData) diff --git a/oath-hammer.mjs b/oath-hammer.mjs index 4f10480..3777907 100644 --- a/oath-hammer.mjs +++ b/oath-hammer.mjs @@ -8,7 +8,9 @@ import OathHammerUtils from "./module/utils.mjs" import OathHammerWeaponDialog from "./module/applications/weapon-dialog.mjs" import OathHammerCombat from "./module/combat.mjs" import { rollWeaponDamage } from "./module/rolls.mjs" +import { rollPostRollLuck } from "./module/rolls.mjs" import { injectFreeRollBar } from "./module/applications/free-roll.mjs" +import OathHammerLuckRollDialog from "./module/applications/luck-roll-dialog.mjs" Hooks.once("init", function () { console.info(SYSTEM.ASCII) @@ -152,19 +154,64 @@ Hooks.on("preCreateActor", (actor, _data, _options, _userId) => { }) // Handle "Roll Damage" button in weapon attack chat cards -Hooks.on("renderChatMessageHTML", (message, html) => { +Hooks.on("renderChatMessageHTML", async (message, html) => { + // Weapon damage button const btn = html.querySelector("[data-action=\"rollWeaponDamage\"]") - if (!btn) return - btn.addEventListener("click", async () => { - const flagData = message.getFlag("fvtt-oath-hammer", "weaponAttack") - if (!flagData) return - const { actorUuid, weaponUuid, attackSuccesses } = flagData - const actor = await fromUuid(actorUuid) - const weapon = await fromUuid(weaponUuid) - if (!actor || !weapon) return ui.notifications.warn(game.i18n.localize("OATHHAMMER.Roll.NoActor")) - const opts = await OathHammerWeaponDialog.promptDamage(actor, weapon, attackSuccesses ?? 0) - if (opts) await rollWeaponDamage(actor, weapon, opts) - }) + if (btn) { + btn.addEventListener("click", async () => { + const flagData = message.getFlag("fvtt-oath-hammer", "weaponAttack") + if (!flagData) return + const { actorUuid, weaponUuid, attackSuccesses } = flagData + const actor = await fromUuid(actorUuid) + const weapon = await fromUuid(weaponUuid) + if (!actor || !weapon) return ui.notifications.warn(game.i18n.localize("OATHHAMMER.Roll.NoActor")) + const opts = await OathHammerWeaponDialog.promptDamage(actor, weapon, attackSuccesses ?? 0) + if (opts) await rollWeaponDamage(actor, weapon, opts) + }) + } + + // Luck post-roll button + const luckFlag = message.getFlag("fvtt-oath-hammer", "luckRoll") + if (!luckFlag) return + + const resultDiv = html.querySelector(".oh-roll-result") + if (!resultDiv) return + + if (luckFlag.used) { + // Show luck result section + const bonusLabel = luckFlag.bonusSuccesses > 0 + ? `+${luckFlag.bonusSuccesses} ${game.i18n.localize("OATHHAMMER.Roll.Successes")}` + : game.i18n.localize("OATHHAMMER.Roll.NoBonus") + const resultHtml = ` +
+ 🍀 + ${game.i18n.localize("OATHHAMMER.Roll.LuckResult")} ${bonusLabel} + ${luckFlag.luckDiceHtml ?? ""} +
` + resultDiv.insertAdjacentHTML("afterend", resultHtml) + } else { + // Show "Spend Luck" button if actor owns the message and has luck left + const actor = await fromUuid(luckFlag.actorUuid).catch(() => null) + if (!actor?.isOwner) return + const availableLuck = actor.system.luck?.value ?? 0 + if (availableLuck <= 0) return + + const btnHtml = ` +
+ +
` + resultDiv.insertAdjacentHTML("afterend", btnHtml) + + html.querySelector("[data-action=\"postRollLuck\"]")?.addEventListener("click", async () => { + const actor = await fromUuid(luckFlag.actorUuid).catch(() => null) + if (!actor) return + const opts = await OathHammerLuckRollDialog.prompt(actor) + if (!opts) return + await rollPostRollLuck(message, opts.luckSpend, opts.luckIsHuman) + }) + } }) // Inject Free Roll bar into the chat sidebar diff --git a/packs/scenes/000019.log b/packs/scenes/000023.log similarity index 100% rename from packs/scenes/000019.log rename to packs/scenes/000023.log diff --git a/packs/scenes/CURRENT b/packs/scenes/CURRENT index 056df57..e60e154 100644 --- a/packs/scenes/CURRENT +++ b/packs/scenes/CURRENT @@ -1 +1 @@ -MANIFEST-000017 +MANIFEST-000021 diff --git a/packs/scenes/MANIFEST-000017 b/packs/scenes/MANIFEST-000021 similarity index 68% rename from packs/scenes/MANIFEST-000017 rename to packs/scenes/MANIFEST-000021 index c2180d4..9405db7 100644 Binary files a/packs/scenes/MANIFEST-000017 and b/packs/scenes/MANIFEST-000021 differ diff --git a/system.json b/system.json index b07d7c2..0f48960 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "fvtt-oath-hammer", "title": "Oath Hammer RPG", "description": "Oath Hammer RPG System for FoundryVTT", - "version": "14.0.0", + "version": "14.0.1", "authors": [ { "name": "Uberwald", @@ -156,8 +156,8 @@ } }, "url": "https://www.uberwald.me/gitea/uberwald/fvtt-oath-hammer", - "manifest": "https://www.uberwald.me/gitea/public/fvtt-oath-hammer/releases/download/latest/system.json", - "download": "https://www.uberwald.me/gitea/public/fvtt-oath-hammer/releases/download/latest/fvtt-oath-hammer.zip", + "manifest": "https://www.uberwald.me/gitea/uberwald/fvtt-oath-hammer/releases/download/latest/system.json", + "download": "https://www.uberwald.me/gitea/uberwald/fvtt-oath-hammer/releases/download/14.0.1/fvtt-oath-hammer.zip", "packs": [ { "label": "Scenes", diff --git a/templates/actor/character-sheet.hbs b/templates/actor/character-sheet.hbs index 131a3e7..666e402 100644 --- a/templates/actor/character-sheet.hbs +++ b/templates/actor/character-sheet.hbs @@ -56,7 +56,13 @@ + / - {{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}} + + {{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=true}} + {{#unless isPlayMode}} + + + {{formInput systemFields.grit.fields.bonus value=system.grit.bonus name="system.grit.bonus" class="res-bonus-input"}} + {{/unless}} +
{{localize "OATHHAMMER.Label.Luck"}} diff --git a/templates/luck-roll-dialog.hbs b/templates/luck-roll-dialog.hbs new file mode 100644 index 0000000..c81cbd7 --- /dev/null +++ b/templates/luck-roll-dialog.hbs @@ -0,0 +1,30 @@ +
+ +
+ + {{localize "OATHHAMMER.Dialog.LuckPostRollHint"}} +
+ +
+ {{localize "OATHHAMMER.Dialog.LuckSpend"}} + +
+ {{localize "OATHHAMMER.Label.LuckAvailable"}}: {{availableLuck}} +
+ +
+ + +
+ +
+ + + {{localize "OATHHAMMER.Dialog.LuckHint"}} +
+ +
+ +