diff --git a/css/fvtt-oath-hammer.css b/css/fvtt-oath-hammer.css index e48de6a..4a95fc4 100644 --- a/css/fvtt-oath-hammer.css +++ b/css/fvtt-oath-hammer.css @@ -550,6 +550,26 @@ opacity: 0.7; font-size: calc(0.86rem * 0.9); } +.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-stepper { + display: flex; + align-items: center; + gap: 1px; +} +.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.2rem; + height: 1.4rem; + font-size: calc(0.86rem * 0.85); + font-weight: bold; + color: #535128; + cursor: pointer; + line-height: 1; +} +.oathhammer .character-main .character-stats-band .character-resources .character-resource.character-resource--luck .luck-btn:hover { + color: #2a1a0a; +} .oathhammer .character-main .character-stats-band .character-resources .resource-label { min-width: 4.2rem; font-family: "BlueDragon", "Palatino Linotype", serif; @@ -919,7 +939,7 @@ grid-template-columns: 1fr 1fr 1fr; } .oathhammer .npc-main .regiment-vitals-grid.regiment-row2 { - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr 1fr 1fr; border-top: none; margin-top: 4px; padding-top: 4px; @@ -1160,7 +1180,7 @@ } .oathhammer .item-list--oath .item-list-header, .oathhammer .item-list--oath .item-entry { - grid-template-columns: 24px 1fr 7rem 3.5rem 3.5rem; + grid-template-columns: 24px 12rem 1fr 3.5rem 3.5rem; } .oathhammer .item-list--npc-skill .item-list-header, .oathhammer .item-list--npc-skill .item-entry { @@ -2574,9 +2594,39 @@ .oathhammer .party-main .party-treasury .party-currency-cp .currency-label { color: #aa6633; } +.oathhammer .party-main .party-slots { + display: flex; + align-items: center; + gap: 0.4rem; + margin-top: 0.3rem; + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.86rem * 0.85); +} +.oathhammer .party-main .party-slots .party-slots-label { + font-weight: bold; + color: #535128; + margin-right: 0.3rem; + text-transform: uppercase; + font-size: calc(0.86rem * 0.9); + letter-spacing: 0.04em; +} +.oathhammer .party-main .party-slots .party-slots-current { + font-weight: bold; + min-width: 1.8rem; + text-align: right; +} +.oathhammer .party-main .party-slots .party-slots-sep { + color: #535128; +} +.oathhammer .party-main .party-slots .party-slots-max { + width: 3.5rem; + text-align: center; + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.86rem * 0.85); +} .oathhammer .item-list--party-member .item-list-header, .oathhammer .item-list--party-member .item-entry { - grid-template-columns: 1.8rem 24px 1fr 7rem 3rem 5rem 5.5rem; + grid-template-columns: 1.8rem 24px 1fr 7rem 5rem 3rem 5.5rem; } .oathhammer .item-list--party-member .party-member-order { font-family: "BlueDragon", "Palatino Linotype", serif; @@ -2586,6 +2636,15 @@ text-align: center; align-self: center; } +.oathhammer .item-list--party-member .item-detail--center { + text-align: center; +} +.oathhammer .item-list--party-member .item-detail--center a { + color: #2a1a0a; +} +.oathhammer .item-list--party-member .item-detail--center .fa-faded { + opacity: 0.2; +} .oathhammer .item-list--party-loot .item-list-header, .oathhammer .item-list--party-loot .item-entry { grid-template-columns: 24px 1fr 6rem 5.5rem 5rem; @@ -2752,3 +2811,79 @@ color: #987d2e; font-family: "BlueDragon", "Palatino Linotype", serif; } +.oh-free-roll-bar { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-top: 1px solid rgba(83, 81, 40, 0.4); + background: rgba(83, 81, 40, 0.08); + flex-shrink: 0; + flex-wrap: wrap; + z-index: 1; + position: relative; +} +.oh-free-roll-bar .oh-frb-label { + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.86rem * 0.9); + font-weight: bold; + color: #2a1a0a; + white-space: nowrap; + display: flex; + align-items: center; + gap: 4px; +} +.oh-free-roll-bar .oh-frb-controls { + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + flex-wrap: wrap; + flex: 1; +} +.oh-free-roll-bar .oh-frb-controls select { + font-size: calc(0.86rem * 0.9); + padding: 1px 2px; + height: 1.6rem; + border: 1px solid #535128; + border-radius: 3px; + background: #fff; + cursor: pointer; +} +.oh-free-roll-bar .oh-frb-controls .oh-frb-pool { + width: 3.8rem; +} +.oh-free-roll-bar .oh-frb-controls .oh-frb-color { + flex: 1; + min-width: 6rem; +} +.oh-free-roll-bar .oh-frb-controls .oh-frb-explode-label { + display: flex; + align-items: center; + gap: 3px; + font-size: calc(0.86rem * 0.9); + cursor: pointer; + white-space: nowrap; + color: #2a1a0a; +} +.oh-free-roll-bar .oh-frb-controls .oh-frb-explode-label input[type="checkbox"] { + cursor: pointer; +} +.oh-free-roll-bar .oh-frb-controls .oh-frb-roll-btn { + font-family: "BlueDragon", "Palatino Linotype", serif; + font-size: calc(0.86rem * 0.9); + font-weight: bold; + padding: 2px 8px; + height: 1.6rem; + border: 1px solid #535128; + border-radius: 3px; + background: rgba(83, 81, 40, 0.2); + color: #2a1a0a; + cursor: pointer; + white-space: nowrap; +} +.oh-free-roll-bar .oh-frb-controls .oh-frb-roll-btn:hover { + background: #c8a84b; + border-color: #ac8d34; +} diff --git a/lang/en.json b/lang/en.json index 2966300..49af425 100644 --- a/lang/en.json +++ b/lang/en.json @@ -190,7 +190,8 @@ "ClassTrait": "Class Trait", "LineageTrait": "Lineage Trait", "NpcTrait": "NPC Trait", - "CreatureTrait": "Creature Trait" + "CreatureTrait": "Creature Trait", + "RegimentTrait": "Regiment Trait" }, "Condition": { "Blinded": "Blinded", @@ -355,7 +356,7 @@ "DropLeaderHint": "Drop a linked actor here", "MarchingOrder": "Marching Order", "NoMembers": "No members yet — drag characters here.", - "DropMemberHint": "Drag a character actor here to add them to the party.", + "DropMemberHint": "Drag a character or NPC actor here to add them to the party.", "Loot": "Loot", "NoLoot": "No loot yet — drag items here.", "DropLootHint": "Drag weapons, armor or equipment here to add party loot.", @@ -368,7 +369,11 @@ "Location": "Location", "Regiments": "Regiments", "DropRegimentHint": "Drag a regiment actor (must be token-linked) to add it to this army.", - "TotalSupply": "Total Supply" + "TotalSupply": "Total Supply", + "Mercenary": "Mercenary", + "CurrentXP": "XP", + "CarriesLight": "Carries Light", + "Slots": "Slots" }, "ColorDice": { "White": "White (4+)", diff --git a/less/actor-sheet.less b/less/actor-sheet.less index 5b19130..5765a0b 100644 --- a/less/actor-sheet.less +++ b/less/actor-sheet.less @@ -168,6 +168,27 @@ } .res-sep { opacity: 0.7; font-size: @font-size-xs; } + + &.character-resource--luck { + .luck-stepper { + display: flex; + align-items: center; + gap: 1px; + } + .luck-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.2rem; + height: 1.4rem; + font-size: @font-size-sm; + font-weight: bold; + color: @color-olive; + cursor: pointer; + line-height: 1; + &:hover { color: @color-dark; } + } + } } .resource-label { diff --git a/less/free-roll.less b/less/free-roll.less new file mode 100644 index 0000000..389eb6b --- /dev/null +++ b/less/free-roll.less @@ -0,0 +1,77 @@ +// ============================================================ +// FREE ROLL BAR — injected below the chat log +// ============================================================ + +.oh-free-roll-bar { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-top: 1px solid fade(@color-olive, 40%); + background: fade(@color-olive, 8%); + flex-shrink: 0; + flex-wrap: wrap; + z-index: 1; + position: relative; + + .oh-frb-label { + font-family: @font-secondary; + font-size: @font-size-xs; + font-weight: bold; + color: @color-dark; + white-space: nowrap; + display: flex; + align-items: center; + gap: 4px; + } + + .oh-frb-controls { + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + flex-wrap: wrap; + flex: 1; + + select { + font-size: @font-size-xs; + padding: 1px 2px; + height: 1.6rem; + border: 1px solid @color-olive; + border-radius: 3px; + background: #fff; + cursor: pointer; + } + + .oh-frb-pool { width: 3.8rem; } + .oh-frb-color { flex: 1; min-width: 6rem; } + + .oh-frb-explode-label { + display: flex; + align-items: center; + gap: 3px; + font-size: @font-size-xs; + cursor: pointer; + white-space: nowrap; + color: @color-dark; + + input[type="checkbox"] { cursor: pointer; } + } + + .oh-frb-roll-btn { + font-family: @font-secondary; + font-size: @font-size-xs; + font-weight: bold; + padding: 2px 8px; + height: 1.6rem; + border: 1px solid @color-olive; + border-radius: 3px; + background: @color-olive-faint; + color: @color-dark; + cursor: pointer; + white-space: nowrap; + &:hover { background: @color-gold; border-color: darken(@color-gold, 10%); } + } + } +} diff --git a/less/fvtt-oath-hammer.less b/less/fvtt-oath-hammer.less index 8aa4ffd..38e483c 100644 --- a/less/fvtt-oath-hammer.less +++ b/less/fvtt-oath-hammer.less @@ -14,3 +14,4 @@ @import "settlement-sheet"; @import "party-sheet"; @import "army-sheet"; +@import "free-roll"; diff --git a/less/item-list.less b/less/item-list.less index 74ea3fe..d71a634 100644 --- a/less/item-list.less +++ b/less/item-list.less @@ -191,7 +191,7 @@ .item-list--oath { .item-list-header, .item-entry { - grid-template-columns: @item-img-size 1fr 7rem 3.5rem 3.5rem; + grid-template-columns: @item-img-size 12rem 1fr 3.5rem 3.5rem; } } diff --git a/less/npc-sheet.less b/less/npc-sheet.less index c14618a..33b59c5 100644 --- a/less/npc-sheet.less +++ b/less/npc-sheet.less @@ -216,7 +216,7 @@ .oathhammer .npc-main .regiment-vitals-grid { &.regiment-row1 { grid-template-columns: 1fr 1fr 1fr; } &.regiment-row2 { - grid-template-columns: 1fr 1fr; + grid-template-columns: 1fr 1fr 1fr; border-top: none; margin-top: 4px; padding-top: 4px; diff --git a/less/party-sheet.less b/less/party-sheet.less index 87599bd..e6b98f2 100644 --- a/less/party-sheet.less +++ b/less/party-sheet.less @@ -132,13 +132,43 @@ .party-currency-sp .currency-label { color: #888; } .party-currency-cp .currency-label { color: #aa6633; } } + + .party-slots { + display: flex; + align-items: center; + gap: 0.4rem; + margin-top: 0.3rem; + font-family: @font-secondary; + font-size: @font-size-sm; + + .party-slots-label { + font-weight: bold; + color: @color-olive; + margin-right: 0.3rem; + text-transform: uppercase; + font-size: @font-size-xs; + letter-spacing: 0.04em; + } + .party-slots-current { + font-weight: bold; + min-width: 1.8rem; + text-align: right; + } + .party-slots-sep { color: @color-olive; } + .party-slots-max { + width: 3.5rem; + text-align: center; + font-family: @font-secondary; + font-size: @font-size-sm; + } + } } // ── Member list ──────────────────────────────────────────────── .oathhammer .item-list--party-member { .item-list-header, .item-entry { - // order# | img | name | class | level | grit | actions - grid-template-columns: 1.8rem @item-img-size 1fr 7rem 3rem 5rem 5.5rem; + // order# | img | name | lineage | grit | light | actions + grid-template-columns: 1.8rem @item-img-size 1fr 7rem 5rem 3rem 5.5rem; } .party-member-order { @@ -149,6 +179,12 @@ text-align: center; align-self: center; } + + .item-detail--center { + text-align: center; + a { color: @color-dark; } + .fa-faded { opacity: 0.2; } + } } // ── Loot list ────────────────────────────────────────────────── diff --git a/module/applications/free-roll.mjs b/module/applications/free-roll.mjs index cd55794..b18913e 100644 --- a/module/applications/free-roll.mjs +++ b/module/applications/free-roll.mjs @@ -56,10 +56,11 @@ export function injectFreeRollBar(_chatLog, html) { rollFree(pool, color, explode5) }) - // Insert between .chat-scroll and .chat-form + // Insert before the chat form — use chatForm.parentElement for AppV2 compatibility + // (in v13 parts are nested inside the app element, not direct children) const chatForm = html.querySelector(".chat-form") if (chatForm) { - html.insertBefore(bar, chatForm) + chatForm.parentElement.insertBefore(bar, chatForm) } else { html.appendChild(bar) } diff --git a/module/applications/sheets/character-sheet.mjs b/module/applications/sheets/character-sheet.mjs index c80f7af..1ad6327 100644 --- a/module/applications/sheets/character-sheet.mjs +++ b/module/applications/sheets/character-sheet.mjs @@ -37,7 +37,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { rollInitiative: OathHammerCharacterSheet.#onRollInitiative, adjustQty: OathHammerCharacterSheet.#onAdjustQty, adjustCurrency: OathHammerCharacterSheet.#onAdjustCurrency, - adjustStress: OathHammerCharacterSheet.#onAdjustStress, + adjustLuck: OathHammerCharacterSheet.#onAdjustLuck, clearStress: OathHammerCharacterSheet.#onClearStress, }, } @@ -410,6 +410,13 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet { await this.document.update({ [field]: Math.max(0, current + delta) }) } + static async #onAdjustLuck(event, target) { + const delta = parseInt(target.dataset.delta, 10) + const current = this.document.system.luck.value ?? 0 + // No upper cap — luck can exceed max (e.g. from blessings/bonuses) + await this.document.update({ "system.luck.value": Math.max(0, current + delta) }) + } + static async #onAdjustStress(event, target) { const delta = parseInt(target.dataset.delta, 10) const current = this.document.system.arcaneStress.value ?? 0 diff --git a/module/applications/sheets/npc-sheet.mjs b/module/applications/sheets/npc-sheet.mjs index 42113b7..a991b2e 100644 --- a/module/applications/sheets/npc-sheet.mjs +++ b/module/applications/sheets/npc-sheet.mjs @@ -172,6 +172,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { colorType: attack.system.colorDiceType, threshold: attack.system.threshold, bonusOptions, + showExplodeOn5: true, colorChoices: Object.fromEntries( Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)]) ), @@ -194,6 +195,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { await rollNPCAttackDamage(this.document, attack, { bonus: parseInt(getValue("bonus")) || 0, + explodeOn5: getValue("explodeOn5") === "true", visibility: getValue("visibility"), }) } @@ -228,6 +230,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { itemName: spell.name, itemImg: spell.img, dv: spell.system.difficultyValue, poolOptions, bonusOptions, colorChoices, showColor: true, + showExplodeOn5: true, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode"), } @@ -249,6 +252,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { dicePool: parseInt(getValue("dicePool")) || 3, bonus: parseInt(getValue("bonus")) || 0, colorOverride: getValue("colorOverride") || null, + explodeOn5: getValue("explodeOn5") === "true", visibility: getValue("visibility"), }) } @@ -272,6 +276,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { itemName: miracle.name, itemImg: miracle.img, dv: null, showColor: false, poolOptions, bonusOptions, + showExplodeOn5: true, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode"), } @@ -292,6 +297,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { await rollNPCMiracle(this.document, miracle, { dicePool: parseInt(getValue("dicePool")) || 3, bonus: parseInt(getValue("bonus")) || 0, + explodeOn5: getValue("explodeOn5") === "true", visibility: getValue("visibility"), }) } @@ -323,6 +329,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { threshold, bonusOptions, colorChoices, + showExplodeOn5: true, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode"), } @@ -343,6 +350,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { await rollNPCArmor(actor, { bonus: parseInt(getValue("bonus")) || 0, colorOverride: getValue("colorOverride") || null, + explodeOn5: getValue("explodeOn5") === "true", visibility: getValue("visibility"), }) } @@ -381,6 +389,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { threshold: item.system.threshold, bonusOptions, colorChoices, + showExplodeOn5: true, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode"), } @@ -401,6 +410,7 @@ export default class OathHammerNPCSheet extends OathHammerActorSheet { await rollNPCSkill(this.document, item, { bonus: parseInt(getValue("bonus")) || 0, colorOverride: getValue("colorOverride") || null, + explodeOn5: getValue("explodeOn5") === "true", visibility: getValue("visibility"), }) } diff --git a/module/applications/sheets/party-sheet.mjs b/module/applications/sheets/party-sheet.mjs index 32fea02..53fb6e9 100644 --- a/module/applications/sheets/party-sheet.mjs +++ b/module/applications/sheets/party-sheet.mjs @@ -10,9 +10,10 @@ export default class OathHammerPartySheet extends OathHammerActorSheet { window: { contentClasses: ["party-content"] }, actions: { openMember: OathHammerPartySheet.#onOpenMember, - removeMember: OathHammerPartySheet.#onRemoveMember, - moveMemberUp: OathHammerPartySheet.#onMoveMemberUp, - moveMemberDown: OathHammerPartySheet.#onMoveMemberDown, + removeMember: OathHammerPartySheet.#onRemoveMember, + moveMemberUp: OathHammerPartySheet.#onMoveMemberUp, + moveMemberDown: OathHammerPartySheet.#onMoveMemberDown, + toggleCarriesLight: OathHammerPartySheet.#onToggleCarriesLight, adjustCurrency: OathHammerPartySheet.#onAdjustCurrency, adjustQty: OathHammerPartySheet.#onAdjustQty, }, @@ -53,28 +54,41 @@ export default class OathHammerPartySheet extends OathHammerActorSheet { async _preparePartContext(partId, context) { const doc = this.document switch (partId) { - case "main": + case "main": { + const lootItems = doc.items.contents.filter(i => ALLOWED_LOOT_TYPES.has(i.type)) + context.currentSlots = lootItems.reduce((sum, i) => { + const slots = i.system.slots ?? 0 + const qty = i.system.quantity ?? 1 + return sum + slots * qty + }, 0) break + } case "members": { context.tab = context.tabs.members const refs = doc.system.memberRefs ?? [] - context.members = refs.map((id, idx) => { - const actor = game.actors?.get(id) + context.members = refs.map((ref, idx) => { + const actor = game.actors?.get(ref.id) if (!actor) return null const sys = actor.system - const classItem = actor.items?.find(i => i.type === "class") + const isNpc = actor.type === "npc" + const classItem = !isNpc ? actor.items?.find(i => i.type === "class") : null return { - id: actor.id, - name: actor.name, - img: actor.img, + id: actor.id, + name: actor.name, + img: actor.img, + type: actor.type, idx, - position: idx + 1, - isFirst: idx === 0, - isLast: idx === refs.length - 1, - classLabel: classItem?.name ?? "—", - level: sys.level ?? "—", - grit: sys.grit ? `${sys.grit.value}/${sys.grit.max}` : "—", + position: idx + 1, + isFirst: idx === 0, + isLast: idx === refs.length - 1, + carriesLight: ref.carriesLight ?? false, + classLabel: isNpc + ? game.i18n.localize(`OATHHAMMER.NpcSubtype.${sys.subtype === "creature" ? "Creature" : "Npc"}`) + : (classItem?.name ?? "—"), + lineage: !isNpc ? (sys.lineage?.name || "—") : "—", + current: !isNpc ? (sys.experience?.current ?? "—") : "—", + grit: sys.grit ? `${sys.grit.value}/${sys.grit.max}` : "—", } }).filter(Boolean) break @@ -109,10 +123,10 @@ export default class OathHammerPartySheet extends OathHammerActorSheet { if (data.type === "Actor") { const actor = await fromUuid(data.uuid) - if (!actor || actor.type !== "character") return + if (!actor || !["character", "npc"].includes(actor.type)) return const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? []) - if (refs.includes(actor.id)) return - refs.push(actor.id) + if (refs.some(r => r.id === actor.id)) return + refs.push({ id: actor.id, carriesLight: false }) return this.document.update({ "system.memberRefs": refs }) } @@ -132,7 +146,7 @@ export default class OathHammerPartySheet extends OathHammerActorSheet { static async #onRemoveMember(event, target) { const id = target.dataset.actorId - const refs = (this.document.system.memberRefs ?? []).filter(r => r !== id) + const refs = (this.document.system.memberRefs ?? []).filter(r => r.id !== id) await this.document.update({ "system.memberRefs": refs }) } @@ -152,6 +166,14 @@ export default class OathHammerPartySheet extends OathHammerActorSheet { await this.document.update({ "system.memberRefs": refs }) } + static async #onToggleCarriesLight(event, target) { + const idx = parseInt(target.dataset.idx, 10) + const refs = foundry.utils.deepClone(this.document.system.memberRefs ?? []) + if (!refs[idx]) return + refs[idx].carriesLight = !refs[idx].carriesLight + await this.document.update({ "system.memberRefs": refs }) + } + static async #onAdjustCurrency(event, target) { const field = target.dataset.field const delta = parseInt(target.dataset.delta, 10) diff --git a/module/applications/sheets/regiment-sheet.mjs b/module/applications/sheets/regiment-sheet.mjs index d0a0d0b..b10a4fd 100644 --- a/module/applications/sheets/regiment-sheet.mjs +++ b/module/applications/sheets/regiment-sheet.mjs @@ -56,6 +56,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { context.colorChoices = Object.fromEntries( Object.entries(SYSTEM.DICE_COLOR_TYPES).map(([k, v]) => [k, game.i18n.localize(v)]) ) + context.traitTypeLabels = Object.fromEntries( + Object.entries(SYSTEM.TRAIT_TYPE_CHOICES).map(([k, v]) => [k, v]) + ) // Resolve leader actor const leaderUuid = this.document.system.leaderUuid if (leaderUuid) { @@ -75,7 +78,7 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { break case "skills": context.tab = context.tabs.skills - context.skills = doc.itemTypes.skillnpc ?? [] + context.skills = (doc.itemTypes.skillnpc ?? []).slice().sort((a, b) => a.name.localeCompare(b.name)) break case "combat": context.tab = context.tabs.combat @@ -141,6 +144,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { const doc = this.document const armorDice = doc.system.armorDice if (!armorDice?.value) return ui.notifications.info("No armor dice to roll.") + const colorType = armorDice.colorDiceType || "white" + const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 + const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" const bonusOptions = Array.from({ length: 13 }, (_, i) => { const v = i - 6 return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } @@ -149,9 +155,10 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { "systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs", { skillName: game.i18n.localize("OATHHAMMER.Label.ArmorDice"), - skillImg: doc.img, basePool: armorDice.value, bonusOptions, + skillImg: doc.img, dicePool: armorDice.value, + colorType, colorEmoji, threshold, bonusOptions, colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" }, - selectedColor: armorDice.colorDiceType, + showExplodeOn5: true, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode") } @@ -165,15 +172,19 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { const form = new DOMParser().parseFromString(result, "text/html") const getValue = n => form.querySelector(`[name="${n}"]`)?.value await rollNPCArmor(doc, { - bonus: parseInt(getValue("bonus")) || 0, + bonus: parseInt(getValue("bonus")) || 0, colorOverride: getValue("colorOverride") || null, - visibility: getValue("visibility"), + explodeOn5: getValue("explodeOn5") === "true", + visibility: getValue("visibility"), }) } static async #onRollSkillNPC(event, target) { const skill = this.document.items.get(target.dataset.itemId) if (!skill) return + const colorType = skill.system.colorDiceType || "white" + const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 + const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" const bonusOptions = Array.from({ length: 13 }, (_, i) => { const v = i - 6 return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } @@ -181,9 +192,10 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs", { - skillName: skill.name, skillImg: skill.img, basePool: skill.system.dicePool, bonusOptions, + skillName: skill.name, skillImg: skill.img, dicePool: skill.system.dicePool, + colorType, colorEmoji, threshold, bonusOptions, colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" }, - selectedColor: skill.system.colorDiceType, + showExplodeOn5: true, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode") } @@ -197,9 +209,10 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { const form = new DOMParser().parseFromString(result, "text/html") const getValue = n => form.querySelector(`[name="${n}"]`)?.value await rollNPCSkill(this.document, skill, { - bonus: parseInt(getValue("bonus")) || 0, + bonus: parseInt(getValue("bonus")) || 0, colorOverride: getValue("colorOverride") || null, - visibility: getValue("visibility"), + explodeOn5: getValue("explodeOn5") === "true", + visibility: getValue("visibility"), }) } @@ -212,6 +225,9 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { static async #onRollNpcAttack(event, target) { const attack = this.document.items.get(target.dataset.itemId) if (!attack) return + const colorType = attack.system.colorDiceType || "white" + const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 + const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" const bonusOptions = Array.from({ length: 13 }, (_, i) => { const v = i - 6 return { value: v, label: v > 0 ? `+${v}` : String(v), selected: v === 0 } @@ -219,9 +235,11 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { const content = await foundry.applications.handlebars.renderTemplate( "systems/fvtt-oath-hammer/templates/npc-skill-dialog.hbs", { - skillName: attack.name, skillImg: attack.img, basePool: attack.system.damageDice, bonusOptions, + skillName: attack.name, skillImg: attack.img, + dicePool: attack.system.damageDice, + colorType, colorEmoji, threshold, bonusOptions, + showExplodeOn5: true, colorChoices: { white: "⬜ White (4+)", red: "🔴 Red (3+)", black: "⬛ Black (2+)" }, - selectedColor: attack.system.colorDiceType, rollModes: foundry.utils.duplicate(CONFIG.Dice.rollModes), visibility: game.settings.get("core", "rollMode") } @@ -235,9 +253,10 @@ export default class OathHammerRegimentSheet extends OathHammerActorSheet { const form = new DOMParser().parseFromString(result, "text/html") const getValue = n => form.querySelector(`[name="${n}"]`)?.value await rollNPCAttackDamage(this.document, attack, { - bonus: parseInt(getValue("bonus")) || 0, + bonus: parseInt(getValue("bonus")) || 0, colorOverride: getValue("colorOverride") || null, - visibility: getValue("visibility"), + explodeOn5: getValue("explodeOn5") === "true", + visibility: getValue("visibility"), }) } diff --git a/module/config/system.mjs b/module/config/system.mjs index a59e404..be4c51c 100644 --- a/module/config/system.mjs +++ b/module/config/system.mjs @@ -215,7 +215,8 @@ export const TRAIT_TYPE_CHOICES = { "class-trait": "OATHHAMMER.TraitType.ClassTrait", "lineage-trait": "OATHHAMMER.TraitType.LineageTrait", "npc-trait": "OATHHAMMER.TraitType.NpcTrait", - "creature-trait": "OATHHAMMER.TraitType.CreatureTrait" + "creature-trait": "OATHHAMMER.TraitType.CreatureTrait", + "regiment-trait": "OATHHAMMER.TraitType.RegimentTrait" } export const NPC_SUBTYPES = { diff --git a/module/models/party.mjs b/module/models/party.mjs index 0a686fd..8a99759 100644 --- a/module/models/party.mjs +++ b/module/models/party.mjs @@ -6,9 +6,12 @@ export default class OathHammerParty extends foundry.abstract.TypeDataModel { schema.notes = new fields.HTMLField({ required: false, nullable: true, initial: "" }) - // Ordered list of character actor IDs — position = marching order + // Ordered list of member entries — position = marching order schema.memberRefs = new fields.ArrayField( - new fields.StringField({ required: true, nullable: false, blank: false }) + new fields.SchemaField({ + id: new fields.StringField({ required: true, nullable: false, blank: false }), + carriesLight: new fields.BooleanField({ initial: false }), + }) ) schema.treasury = new fields.SchemaField({ @@ -17,8 +20,19 @@ export default class OathHammerParty extends foundry.abstract.TypeDataModel { cp: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }), }) + schema.maxSlots = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + return schema } + static migrateData(source) { + if (Array.isArray(source.memberRefs)) { + source.memberRefs = source.memberRefs.map(r => + typeof r === "string" ? { id: r, carriesLight: false } : r + ) + } + return super.migrateData(source) + } + static LOCALIZATION_PREFIXES = ["OATHHAMMER.Party"] } diff --git a/module/models/regiment.mjs b/module/models/regiment.mjs index af707f5..c93d11a 100644 --- a/module/models/regiment.mjs +++ b/module/models/regiment.mjs @@ -22,6 +22,7 @@ export default class OathHammerRegiment extends foundry.abstract.TypeDataModel { schema.movement = new fields.NumberField({ ...requiredInteger, initial: 60, min: 0, max: 500 }) schema.supplyCost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) schema.recruitmentCost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }) + schema.mercenary = new fields.BooleanField({ required: true, initial: false }) schema.leaderUuid = new fields.StringField({ required: false, nullable: true, initial: null }) diff --git a/module/rolls.mjs b/module/rolls.mjs index dfe8ead..f6b2195 100644 --- a/module/rolls.mjs +++ b/module/rolls.mjs @@ -915,7 +915,15 @@ export async function rollInitiativeCheck(actor, options = {}) { }) } - // NPC: Fate rank + initiativeBonus + // NPC: find Leadership skillnpc item, fall back to Fate rank + initiativeBonus + const leadershipSkill = actor.items.find( + i => i.type === "skillnpc" && i.name.toLowerCase() === "leadership" + ) + if (leadershipSkill) { + return rollNPCSkill(actor, leadershipSkill, { bonus, visibility }) + } + + // Fallback: Fate rank + initiativeBonus const sys = actor.system const fateRank = sys.attributes?.fate?.rank ?? 1 const initBonus = sys.initiativeBonus ?? 0 @@ -967,7 +975,7 @@ export async function rollInitiativeCheck(actor, options = {}) { * @param {object} options */ export async function rollNPCSkill(actor, skillItem, options = {}) { - const { bonus = 0, colorOverride, visibility } = options + const { bonus = 0, colorOverride, visibility, explodeOn5 = false } = options const sys = skillItem.system const colorType = colorOverride || sys.colorDiceType @@ -976,12 +984,14 @@ export async function rollNPCSkill(actor, skillItem, options = {}) { const totalDice = Math.max(sys.dicePool + bonus, 1) - const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false) + const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5) const diceHtml = _diceHtml(diceResults, threshold) - const modLine = bonus !== 0 - ? `
${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}
` - : "" + const explodedCount = diceResults.filter(d => d.exploded).length + const modParts = [] + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const content = `
@@ -1025,6 +1035,7 @@ export async function rollNPCWeaponAttack(actor, weapon, options = {}) { const modParts = [] if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (diceResults.filter(d => d.exploded).length > 0) modParts.push(`💥 ${diceResults.filter(d => d.exploded).length} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const content = ` @@ -1067,6 +1078,7 @@ export async function rollNPCWeaponDamage(actor, weapon, options = {}) { const modParts = [] if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (diceResults.filter(d => d.exploded).length > 0) modParts.push(`💥 ${diceResults.filter(d => d.exploded).length} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const content = ` @@ -1098,7 +1110,7 @@ export async function rollNPCWeaponDamage(actor, weapon, options = {}) { * NPC armor dice roll — rolls actor's armorDice.value dice with armorDice.colorDiceType color. */ export async function rollNPCArmor(actor, options = {}) { - const { bonus = 0, colorOverride, visibility } = options + const { bonus = 0, colorOverride, visibility, explodeOn5 = false } = options const sys = actor.system const basePool = sys.armorDice?.value ?? 0 @@ -1107,12 +1119,15 @@ export async function rollNPCArmor(actor, options = {}) { const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" const totalDice = Math.max(basePool + bonus, 1) - const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false) + const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5) const diceHtml = _diceHtml(diceResults, threshold) - const modLine = bonus !== 0 - ? `
${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}
` - : "" + const explodedCount = diceResults.filter(d => d.exploded).length + const modParts = [] + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`) + if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const label = game.i18n.localize("OATHHAMMER.Label.ArmorDice") const content = ` @@ -1144,20 +1159,23 @@ export async function rollNPCArmor(actor, options = {}) { * NPC spell cast — flat dice pool, no arcane stress, posts DV success/failure to chat. */ export async function rollNPCSpell(actor, spell, options = {}) { - const { dicePool = 3, bonus = 0, colorOverride, visibility } = options + const { dicePool = 3, bonus = 0, colorOverride, visibility, explodeOn5 = false } = options const dv = spell.system.difficultyValue ?? 1 const colorType = colorOverride || "white" const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 const colorEmoji = colorType === "black" ? "⬛" : colorType === "red" ? "🔴" : "⬜" const totalDice = Math.max(dicePool + bonus, 1) - const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false) + const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5) const diceHtml = _diceHtml(diceResults, threshold) const isSuccess = successes >= dv - const modLine = bonus !== 0 - ? `
${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}
` - : "" + const explodedCount = diceResults.filter(d => d.exploded).length + const modParts = [] + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`) + if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const resultClass = isSuccess ? "roll-success" : "roll-failure" const resultLabel = isSuccess @@ -1193,19 +1211,22 @@ export async function rollNPCSpell(actor, spell, options = {}) { * NPC miracle invocation — flat dice pool, no blocked tracking, posts DV success/failure to chat. */ export async function rollNPCMiracle(actor, miracle, options = {}) { - const { dicePool = 3, bonus = 0, visibility } = options + const { dicePool = 3, bonus = 0, visibility, explodeOn5 = false } = options const dv = 1 const threshold = 4 const colorEmoji = "⬜" const totalDice = Math.max(dicePool + bonus, 1) - const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false) + const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5) const diceHtml = _diceHtml(diceResults, threshold) const isSuccess = successes >= dv - const modLine = bonus !== 0 - ? `
${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}
` - : "" + const explodedCount = diceResults.filter(d => d.exploded).length + const modParts = [] + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`) + if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) + const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const resultClass = isSuccess ? "roll-success" : "roll-failure" const resultLabel = isSuccess @@ -1241,7 +1262,7 @@ export async function rollNPCMiracle(actor, miracle, options = {}) { * NPC attack damage roll — flat dice pool from the npcattack item, no Might. */ export async function rollNPCAttackDamage(actor, attack, options = {}) { - const { bonus = 0, visibility } = options + const { bonus = 0, visibility, explodeOn5 = false } = options const sys = attack.system const colorType = sys.colorDiceType || "white" const threshold = colorType === "black" ? 2 : colorType === "red" ? 3 : 4 @@ -1249,12 +1270,15 @@ export async function rollNPCAttackDamage(actor, attack, options = {}) { const totalDice = Math.max((sys.damageDice ?? 1) + bonus, 1) const ap = sys.ap ?? 0 - const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, false) + const { roll, rolls, successes, diceResults } = await _rollPool(totalDice, threshold, explodeOn5) const diceHtml = _diceHtml(diceResults, threshold) + const explodedCount = diceResults.filter(d => d.exploded).length const modParts = [] - if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) - if (ap > 0) modParts.push(`AP ${ap}`) + if (bonus !== 0) modParts.push(`${bonus > 0 ? "+" : ""}${bonus} ${game.i18n.localize("OATHHAMMER.Dialog.Modifier")}`) + if (ap > 0) modParts.push(`AP ${ap}`) + if (explodeOn5) modParts.push(`💥 ${game.i18n.localize("OATHHAMMER.Dialog.ExplodeOn5")}`) + if (explodedCount > 0) modParts.push(`💥 ${explodedCount} ${game.i18n.localize("OATHHAMMER.Roll.Exploded")}`) const modLine = modParts.length ? `
${modParts.join(" · ")}
` : "" const content = ` diff --git a/oath-hammer.mjs b/oath-hammer.mjs index 6a3e948..ddcce8a 100644 --- a/oath-hammer.mjs +++ b/oath-hammer.mjs @@ -104,24 +104,45 @@ Hooks.once("init", function () { Hooks.once("ready", async function () { console.info("Oath Hammer | System Ready") - // Migration: remove orphaned items with removed types (lineage → actor field, ability → trait) - const removedTypes = new Set(["lineage", "ability"]) + // Migration: remove orphaned items with removed types (lineage → actor field, ability → trait, regiment → actor type) + const removedTypes = new Set(["lineage", "ability", "regiment"]) for (const actor of game.actors) { - const invalidItems = actor._source.items?.filter(i => removedTypes.has(i.type)) ?? [] - if (invalidItems.length) { - console.info(`Oath Hammer | Migrating ${actor.name}: removing ${invalidItems.length} obsolete item(s)`) - await actor.deleteEmbeddedDocuments("Item", invalidItems.map(i => i._id)) + const toDelete = [] + + // Catch items that failed validation and landed in invalidDocumentIds + for (const id of (actor.items.invalidDocumentIds ?? [])) { + const raw = actor.items.getInvalid(id) + if (raw && removedTypes.has(raw._source?.type)) toDelete.push(id) + } + + // Also catch any that slipped through in _source (belt-and-suspenders) + for (const src of (actor._source.items ?? [])) { + if (removedTypes.has(src.type) && !toDelete.includes(src._id)) toDelete.push(src._id) + } + + if (toDelete.length) { + console.info(`Oath Hammer | Migrating ${actor.name}: removing ${toDelete.length} obsolete item(s)`) + await actor.deleteEmbeddedDocuments("Item", toDelete) } } + + // Purge invalid world items of removed types for (const id of game.items.invalidDocumentIds) { const item = game.items.getInvalid(id) - if (item && removedTypes.has(item._source.type)) { + if (item && removedTypes.has(item._source?.type)) { console.info(`Oath Hammer | Deleting world item: ${item._source.name} (${item._source.type})`) await item.delete() } } }) +// Auto-link regiment (and army) actor tokens so they can be added to garrisons/armies +Hooks.on("preCreateActor", (actor, _data, _options, _userId) => { + if (actor.type === "regiment" || actor.type === "army") { + actor.updateSource({ "prototypeToken.actorLink": true }) + } +}) + // Handle "Roll Damage" button in weapon attack chat cards Hooks.on("renderChatMessageHTML", (message, html) => { const btn = html.querySelector("[data-action=\"rollWeaponDamage\"]") diff --git a/templates/actor/character-sheet.hbs b/templates/actor/character-sheet.hbs index 3d9e2f3..ed676d2 100644 --- a/templates/actor/character-sheet.hbs +++ b/templates/actor/character-sheet.hbs @@ -54,9 +54,13 @@ / {{formInput systemFields.grit.fields.max value=system.grit.max name="system.grit.max" disabled=isPlayMode}}
-
+
{{localize "OATHHAMMER.Label.Luck"}} - {{formInput systemFields.luck.fields.value value=system.luck.value name="system.luck.value" disabled=isPlayMode}} +
+ + {{formInput systemFields.luck.fields.value value=system.luck.value name="system.luck.value"}} + + +
/ {{formInput systemFields.luck.fields.max value=system.luck.max name="system.luck.max" disabled=isPlayMode}}
diff --git a/templates/actor/party-members.hbs b/templates/actor/party-members.hbs index 9aaa495..da83107 100644 --- a/templates/actor/party-members.hbs +++ b/templates/actor/party-members.hbs @@ -9,9 +9,9 @@ # {{localize "OATHHAMMER.Label.Name"}} - {{localize "OATHHAMMER.Label.Class"}} - {{localize "OATHHAMMER.Label.Level"}} + {{localize "OATHHAMMER.Label.Lineage"}} {{localize "OATHHAMMER.Label.Grit"}} + {{#each members as |member|}} @@ -21,9 +21,13 @@ {{member.name}} - {{member.classLabel}} - {{member.level}} + {{member.lineage}} {{member.grit}} + + + + +
{{#unless member.isFirst}} diff --git a/templates/actor/party-sheet.hbs b/templates/actor/party-sheet.hbs index 88c7906..8d7d3fd 100644 --- a/templates/actor/party-sheet.hbs +++ b/templates/actor/party-sheet.hbs @@ -48,6 +48,14 @@
+ + +
+ {{localize "OATHHAMMER.Label.Slots"}} + {{currentSlots}} + / + +
diff --git a/templates/actor/regiment-sheet.hbs b/templates/actor/regiment-sheet.hbs index f62980f..2e511ca 100644 --- a/templates/actor/regiment-sheet.hbs +++ b/templates/actor/regiment-sheet.hbs @@ -70,7 +70,7 @@ {{localize "OATHHAMMER.Label.SupplyCost"}} - gp / month + gp / day @@ -83,6 +83,14 @@ + +
+ {{localize "OATHHAMMER.Label.Mercenary"}} + + {{formInput systemFields.mercenary value=system.mercenary name="system.mercenary" disabled=isPlayMode}} + +
+ diff --git a/templates/actor/settlement-overview.hbs b/templates/actor/settlement-overview.hbs index 0e65860..0a295dd 100644 --- a/templates/actor/settlement-overview.hbs +++ b/templates/actor/settlement-overview.hbs @@ -1,23 +1,18 @@
-
- {{localize "OATHHAMMER.Label.Treasury"}} -
-
- -
- - {{formInput systemFields.currency.fields.gold value=system.currency.gold name="system.currency.gold"}} - + +
+
+ {{localize "OATHHAMMER.Label.Treasury"}} +
+
+ +
+ + {{formInput systemFields.currency.fields.gold value=system.currency.gold name="system.currency.gold"}} + + +
-
-
- -
-
- {{localize "OATHHAMMER.Label.Garrison"}} - {{formInput systemFields.garrison value=system.garrison name="system.garrison" disabled=isPlayMode}}
@@ -33,13 +28,7 @@
{{localize "OATHHAMMER.Label.Description"}} - {{#if isEditMode}} - - {{{system.description}}} - - {{else}} -
{{{enrichedDescription}}}
- {{/if}} + {{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
diff --git a/templates/npc-magic-dialog.hbs b/templates/npc-magic-dialog.hbs index bba6e96..cc0dd87 100644 --- a/templates/npc-magic-dialog.hbs +++ b/templates/npc-magic-dialog.hbs @@ -37,6 +37,11 @@ {{/if}} +
+ + +
+
diff --git a/templates/npc-skill-dialog.hbs b/templates/npc-skill-dialog.hbs index 2c74299..254d4b3 100644 --- a/templates/npc-skill-dialog.hbs +++ b/templates/npc-skill-dialog.hbs @@ -28,6 +28,13 @@ + {{#if showExplodeOn5}} +
+ + +
+ {{/if}} +