feat: luck bonus, inventory slots bonus, multi-enhancements, magic skill modifier
Release Creation / build (release) Successful in 1m25s

- Add luck.bonus field to character DataModel; included in prepareDerivedData
   (luck.max = fate.rank + bonus); shown as editable +bonus field next to
   luck max in edit mode (same UX as grit bonus)

 - Add inventory.slotsBonus field to character DataModel; Equipment tab now
   shows an editable "Bonus Slots" input that adds to the calculated max slots
   (10 + Might×2 + bonus)

 - Replace single Enhancement <select> in spell cast dialog with a checkbox
   list; multiple enhancements can now be selected simultaneously — stress
   costs, pool penalties, and boolean flags (redDice, noStress) are aggregated
   across all active enhancements

 - Include skills.magic.modifier in basePool for both spell and miracle dialogs
   and in baseDice in rollSpellCast / rollMiracleCast; modifier is shown in
   the dialog pool-info line and in the chat card when non-zero

 - Fix: pool-reduction indicator in rollSpellCast now compares against
   intRank + magicRank + magicMod (was missing magicMod)
This commit is contained in:
2026-05-12 08:16:57 +02:00
parent f67d9079dd
commit 0381e8e024
21 changed files with 192 additions and 39 deletions
+5 -1
View File
@@ -8,8 +8,10 @@ export default class OathHammerMiracleDialog {
const wpRank = actorSys.attributes.willpower.rank
const magicRank = actorSys.skills.magic.rank
const magicMod = actorSys.skills.magic.modifier ?? 0
const magicColor = actorSys.skills.magic.colorDiceType ?? "white"
const basePool = wpRank + magicRank
const basePool = wpRank + magicRank + magicMod
const magicModDisplay = magicMod > 0 ? `+${magicMod}` : magicMod < 0 ? `${magicMod}` : ""
const isRitual = sys.isRitual
const dv = isRitual ? (sys.difficultyValue || 1) : null
@@ -60,6 +62,8 @@ export default class OathHammerMiracleDialog {
spellSave: sys.spellSave,
wpRank,
magicRank,
magicMod,
magicModDisplay,
basePool,
miracleCountOptions,
colorOptions,
@@ -39,6 +39,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
adjustCurrency: OathHammerCharacterSheet.#onAdjustCurrency,
adjustLuck: OathHammerCharacterSheet.#onAdjustLuck,
adjustGrit: OathHammerCharacterSheet.#onAdjustGrit,
adjustStress: OathHammerCharacterSheet.#onAdjustStress,
clearStress: OathHammerCharacterSheet.#onClearStress,
},
}
@@ -190,8 +191,8 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
}
})
context.ammunition = doc.itemTypes.ammunition
// Slot tracking: max = 10 + (Might rank × 2); used = sum of all items' slots × quantity
context.slotsMax = 10 + (doc.system.attributes.might.rank * 2)
// Slot tracking: max = 10 + (Might rank × 2) + bonus; used = sum of all items' slots × quantity
context.slotsMax = 10 + (doc.system.attributes.might.rank * 2) + (doc.system.inventory?.slotsBonus ?? 0)
context.slotsUsed = doc.items.reduce((sum, item) => {
const qty = item.system.quantity ?? 1
return sum + (item.system.slots ?? 0) * Math.max(qty, 1)
@@ -225,7 +226,7 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
id: m.id, uuid: m.uuid, img: m.img, name: m.name, system: m.system,
_descTooltip: _stripHtml(m.system.description)
}))
context.slotsMax = 10 + (doc.system.attributes.might.rank * 2)
context.slotsMax = 10 + (doc.system.attributes.might.rank * 2) + (doc.system.inventory?.slotsBonus ?? 0)
context.slotsUsed = doc.items.reduce((sum, item) => {
const qty = item.system.quantity ?? 1
return sum + (item.system.slots ?? 0) * Math.max(qty, 1)
+27 -13
View File
@@ -27,8 +27,10 @@ export default class OathHammerSpellDialog {
const intRank = actorSys.attributes.intelligence.rank
const magicRank = actorSys.skills.magic.rank
const magicMod = actorSys.skills.magic.modifier ?? 0
const magicColor = actorSys.skills.magic.colorDiceType ?? "white"
const basePool = intRank + magicRank
const basePool = intRank + magicRank + magicMod
const magicModDisplay = magicMod > 0 ? `+${magicMod}` : magicMod < 0 ? `${magicMod}` : ""
const currentStress = actorSys.arcaneStress.value
const stressThreshold = actorSys.arcaneStress.threshold
@@ -48,11 +50,12 @@ export default class OathHammerSpellDialog {
{ value: "black", label: game.i18n.localize("OATHHAMMER.ColorDice.Black"), selected: magicColor === "black" },
]
const enhancementOptions = Object.entries(SPELL_ENHANCEMENTS).map(([key, def]) => ({
value: key,
label: game.i18n.localize(def.label),
selected: key === "none",
}))
const enhancementOptions = Object.entries(SPELL_ENHANCEMENTS)
.filter(([key]) => key !== "none")
.map(([key, def]) => ({
value: key,
label: game.i18n.localize(def.label),
}))
const bonusOptions = Array.from({ length: 13 }, (_, i) => {
const v = i - 6
@@ -92,6 +95,8 @@ export default class OathHammerSpellDialog {
element: sys.element,
intRank,
magicRank,
magicMod,
magicModDisplay,
basePool,
poolSizeOptions,
colorOptions,
@@ -132,16 +137,25 @@ export default class OathHammerSpellDialog {
if (!result) return null
const enhKey = result.enhancement ?? "none"
const enh = SPELL_ENHANCEMENTS[enhKey] ?? SPELL_ENHANCEMENTS.none
// Collect all checked enhancements and aggregate their effects
const selectedEnhs = Object.keys(SPELL_ENHANCEMENTS)
.filter(k => k !== "none" && result[`enh_${k}`] === "true")
const aggregated = selectedEnhs.reduce((acc, key) => {
const def = SPELL_ENHANCEMENTS[key]
acc.stress += def.stress
acc.penalty += def.penalty
if (def.redDice) acc.redDice = true
if (def.noStress) acc.noStress = true
return acc
}, { stress: 0, penalty: 0, redDice: false, noStress: false })
return {
dv,
enhancement: enhKey,
stressCost: enh.stress,
poolPenalty: enh.penalty,
redDice: enh.redDice,
noStress: enh.noStress,
enhancements: selectedEnhs.length ? selectedEnhs : ["none"],
stressCost: aggregated.stress,
poolPenalty: aggregated.penalty,
redDice: aggregated.redDice,
noStress: aggregated.noStress,
colorOverride: result.colorOverride ?? magicColor,
elementalBonus: parseInt(result.elementalBonus) || 0,
bonus: parseInt(result.bonus) || 0,