Files
fvtt-oath-hammer/module/models/character.mjs
T
uberwald 0381e8e024
Release Creation / build (release) Successful in 1m25s
feat: luck bonus, inventory slots bonus, multi-enhancements, magic skill modifier
- 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)
2026-05-12 08:16:57 +02:00

144 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export default class OathHammerCharacter extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.background = new fields.HTMLField({ required: true, textSearch: true })
schema.description = new fields.HTMLField({ required: true, textSearch: true })
schema.notes = new fields.HTMLField({ required: true, textSearch: true })
// Lineage (simple fields on the actor — not an Item)
schema.lineage = new fields.SchemaField({
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
traits: new fields.HTMLField({ required: true, textSearch: true }),
})
const attributeField = () => new fields.SchemaField({
rank: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 })
})
schema.attributes = new fields.SchemaField({
might: attributeField(),
toughness: attributeField(),
agility: attributeField(),
willpower: attributeField(),
intelligence: attributeField(),
fate: attributeField()
})
// Skills: 26 skills each with a rank (04), a bonus/penalty modifier, and color dice.
// Total dice = attr rank + skill rank. Modifier = bonus (+) or penalty (-) dice.
// Color dice: type (white 4+, red 3+, black 2+) + count of colored dice in the pool.
const skillField = () => new fields.SchemaField({
rank: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0, max: 6 }),
modifier: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
colorDiceType: new fields.StringField({ required: true, nullable: false, initial: "white",
choices: { white: "OATHHAMMER.ColorDice.White", red: "OATHHAMMER.ColorDice.Red", black: "OATHHAMMER.ColorDice.Black" } }),
colorDice: new fields.NumberField({ ...requiredInteger, initial: 4, min: 0 }),
})
schema.skills = new fields.SchemaField({
academics: skillField(),
acrobatics: skillField(),
animalHandling:skillField(),
athletics: skillField(),
brewing: skillField(),
carpentry: skillField(),
defense: skillField(),
dexterity: skillField(),
diplomacy: skillField(),
discipline: skillField(),
fighting: skillField(),
folklore: skillField(),
fortune: skillField(),
heal: skillField(),
leadership: skillField(),
magic: skillField(),
masonry: skillField(),
orientation: skillField(),
perception: skillField(),
resilience: skillField(),
ride: skillField(),
shooting: skillField(),
smithing: skillField(),
stealth: skillField(),
survival: skillField(),
tracking: skillField(),
})
schema.grit = new fields.SchemaField({
value: 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.
schema.luck = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1, min: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
schema.arcaneStress = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
threshold: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
thresholdBonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.miracleBlocked = new fields.BooleanField({ required: true, initial: false })
schema.movement = new fields.SchemaField({
base: new fields.NumberField({ ...requiredInteger, initial: 30, min: 0 }),
adjusted: new fields.NumberField({ ...requiredInteger, initial: 30, min: 0 })
})
schema.defense = new fields.SchemaField({
value: new fields.NumberField({ ...requiredInteger, initial: 10, min: 0 }),
armorRating: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.experience = new fields.SchemaField({
current: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
total: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
level: new fields.NumberField({ ...requiredInteger, initial: 1, min: 1 })
})
schema.biodata = new fields.SchemaField({
age: new fields.StringField({ required: true, nullable: false, initial: "" }),
gender: new fields.StringField({ required: true, nullable: false, initial: "" }),
height: new fields.StringField({ required: true, nullable: false, initial: "" }),
weight: new fields.StringField({ required: true, nullable: false, initial: "" }),
eyes: new fields.StringField({ required: true, nullable: false, initial: "" }),
hair: new fields.StringField({ required: true, nullable: false, initial: "" }),
alignment: new fields.StringField({ required: true, nullable: false, initial: "" })
})
schema.currency = new fields.SchemaField({
gold: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
silver: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 }),
copper: new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
})
schema.inventory = new fields.SchemaField({
slotsBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
})
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Character"]
prepareDerivedData() {
super.prepareDerivedData()
// 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 + bonus; restores at session start
this.luck.max = this.attributes.fate.rank + (this.luck.bonus ?? 0)
// Defense score = 10 + Agility + Armor Rating + bonus
this.defense.value = 10 + this.attributes.agility.rank + this.defense.armorRating + this.defense.bonus
// Stress Threshold = Willpower rank + Magic rank + bonus (rulebook p.101)
this.arcaneStress.threshold = this.attributes.willpower.rank + this.skills.magic.rank + this.arcaneStress.thresholdBonus
}
}