Latest fixes for Oath Hammer

This commit is contained in:
2026-03-12 17:29:43 +01:00
parent e8d84615c5
commit 82fddb0cb3
20 changed files with 376 additions and 97 deletions

View File

@@ -7,7 +7,8 @@ export { default as OathHammerEquipmentSheet } from "./sheets/equipment-sheet.mj
export { default as OathHammerSpellSheet } from "./sheets/spell-sheet.mjs"
export { default as OathHammerMiracleSheet } from "./sheets/miracle-sheet.mjs"
export { default as OathHammerMagicItemSheet } from "./sheets/magic-item-sheet.mjs"
export { default as OathHammerAbilitySheet } from "./sheets/ability-sheet.mjs"
export { default as OathHammerTraitSheet } from "./sheets/trait-sheet.mjs"
export { default as OathHammerOathSheet } from "./sheets/oath-sheet.mjs"
export { default as OathHammerLineageSheet } from "./sheets/lineage-sheet.mjs"
export { default as OathHammerClassSheet } from "./sheets/class-sheet.mjs"
export { default as OathHammerBuildingSheet } from "./sheets/building-sheet.mjs"

View File

@@ -0,0 +1,28 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerBuildingSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["building"],
position: {
width: 640,
},
window: {
contentClasses: ["building-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/building-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes ?? "", { async: true })
return context
}
}

View File

@@ -72,8 +72,26 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
break
case "identity":
context.tab = context.tabs.identity
context.abilities = doc.itemTypes.ability
context.oaths = doc.itemTypes.oath
context.traits = doc.itemTypes.trait.map(a => {
const typeKey = SYSTEM.TRAIT_TYPE_CHOICES[a.system.traitType]
const periodKey = SYSTEM.TRAIT_USAGE_PERIOD[a.system.usagePeriod]
const isPassive = a.system.usagePeriod === "none"
return {
id: a.id, uuid: a.uuid, img: a.img, name: a.name, system: a.system,
_typeLabel: typeKey ? game.i18n.localize(typeKey) : a.system.traitType,
_usageLabel: isPassive
? game.i18n.localize("OATHHAMMER.UsagePeriod.None")
: `${a.system.maxUses > 0 ? a.system.maxUses + " / " : ""}${game.i18n.localize(periodKey)}`
}
})
context.oaths = doc.itemTypes.oath.map(o => {
const typeEntry = SYSTEM.OATH_TYPES[o.system.oathType]
return {
id: o.id, uuid: o.uuid, img: o.img, name: o.name, system: o.system,
_typeLabel: typeEntry ? game.i18n.localize(typeEntry.label) : o.system.oathType,
_violated: o.system.violated
}
})
break
case "skills": {
context.tab = context.tabs.skills
@@ -116,8 +134,32 @@ export default class OathHammerCharacterSheet extends OathHammerActorSheet {
}
case "combat":
context.tab = context.tabs.combat
context.weapons = doc.itemTypes.weapon
context.armors = doc.itemTypes.armor
context.weapons = doc.itemTypes.weapon.map(w => {
const groupKey = SYSTEM.WEAPON_PROFICIENCY_GROUPS[w.system.proficiencyGroup]
const traitsLabel = [...w.system.traits].map(t => {
const tk = SYSTEM.WEAPON_TRAITS[t]
return tk ? game.i18n.localize(tk) : t
}).join(", ")
return {
id: w.id, uuid: w.uuid, img: w.img, name: w.name, system: w.system,
_groupLabel: groupKey ? game.i18n.localize(groupKey) : w.system.proficiencyGroup,
_traitsTooltip: traitsLabel || null,
_isMagic: w.system.isMagic
}
})
context.armors = doc.itemTypes.armor.map(a => {
const typeKey = SYSTEM.ARMOR_TYPE_CHOICES[a.system.armorType]
const traitsLabel = [...a.system.traits].map(t => {
const tk = SYSTEM.ARMOR_TRAITS[t]
return tk ? game.i18n.localize(tk) : t
}).join(", ")
return {
id: a.id, uuid: a.uuid, img: a.img, name: a.name, system: a.system,
_typeLabel: typeKey ? game.i18n.localize(typeKey) : a.system.armorType,
_traitsTooltip: traitsLabel || null,
_isMagic: a.system.isMagic
}
})
context.ammunition = doc.itemTypes.ammunition
break
case "magic":

View File

@@ -1,21 +1,21 @@
import OathHammerItemSheet from "./base-item-sheet.mjs"
export default class OathHammerAbilitySheet extends OathHammerItemSheet {
export default class OathHammerTraitSheet extends OathHammerItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["ability"],
classes: ["trait"],
position: {
width: 620,
},
window: {
contentClasses: ["ability-content"],
contentClasses: ["trait-content"],
},
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-oath-hammer/templates/item/ability-sheet.hbs",
template: "systems/fvtt-oath-hammer/templates/item/trait-sheet.hbs",
},
}

View File

@@ -172,14 +172,14 @@ export const RARITY_CHOICES = {
legendary: "OATHHAMMER.Rarity.Legendary"
}
// Two types of ability: class traits and lineage traits. No feats in Oath Hammer.
export const ABILITY_TYPE_CHOICES = {
"class-ability": "OATHHAMMER.AbilityType.ClassAbility",
"lineage-trait": "OATHHAMMER.AbilityType.LineageTrait"
// Two types of trait per the rulebook terminology
export const TRAIT_TYPE_CHOICES = {
"special-trait": "OATHHAMMER.TraitType.SpecialTrait",
"class-trait": "OATHHAMMER.TraitType.ClassTrait"
}
// When an ability's uses reset (none = passive/always on)
export const ABILITY_USAGE_PERIOD = {
// When a trait's uses reset (none = passive/always on)
export const TRAIT_USAGE_PERIOD = {
none: "OATHHAMMER.UsagePeriod.None",
encounter: "OATHHAMMER.UsagePeriod.Encounter",
day: "OATHHAMMER.UsagePeriod.Day"
@@ -331,6 +331,11 @@ export const ASCII = `
·················································
`
export const BUILDING_SKILL_CHOICES = {
carpentry: "OATHHAMMER.BuildingSkill.Carpentry",
masonry: "OATHHAMMER.BuildingSkill.Masonry"
}
export const SYSTEM = {
id: SYSTEM_ID,
ATTRIBUTES,
@@ -347,14 +352,15 @@ export const SYSTEM = {
WEAPON_TRAITS,
ARMOR_TYPE_CHOICES,
ARMOR_TRAITS,
CURRENCY_CHOICES,
CURRENCY_CHOICES,
AMMO_TYPE_CHOICES,
EQUIPMENT_TYPE_CHOICES,
MAGIC_ITEM_TYPE_CHOICES,
MAGIC_QUALITY_CHOICES,
RARITY_CHOICES,
ABILITY_TYPE_CHOICES,
ABILITY_USAGE_PERIOD,
TRAIT_TYPE_CHOICES,
TRAIT_USAGE_PERIOD,
BUILDING_SKILL_CHOICES,
STATUS_EFFECTS,
ATTRIBUTE_RANK_CHOICES,
ASCII

View File

@@ -6,7 +6,7 @@ const defaultItemImg = {
spell: "systems/fvtt-oath-hammer/assets/icons/icon_spell.webp",
miracle: "systems/fvtt-oath-hammer/assets/icons/icon_miracle.webp",
"magic-item": "systems/fvtt-oath-hammer/assets/icons/icon_magic_item.webp",
ability: "systems/fvtt-oath-hammer/assets/icons/icon_ability.webp",
trait: "systems/fvtt-oath-hammer/assets/icons/icon_ability.webp",
oath: "systems/fvtt-oath-hammer/assets/icons/icon_oath.webp"
}

View File

@@ -7,7 +7,8 @@ export { default as OathHammerEquipment } from "./equipment.mjs"
export { default as OathHammerSpell } from "./spell.mjs"
export { default as OathHammerMiracle } from "./miracle.mjs"
export { default as OathHammerMagicItem } from "./magic-item.mjs"
export { default as OathHammerAbility } from "./ability.mjs"
export { default as OathHammerTrait } from "./trait.mjs"
export { default as OathHammerOath } from "./oath.mjs"
export { default as OathHammerLineage } from "./lineage.mjs"
export { default as OathHammerClass } from "./class.mjs"
export { default as OathHammerBuilding } from "./building.mjs"

View File

@@ -1,33 +0,0 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerAbility extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
// lineage-trait (racial) or class-ability (starting or advancement trait)
schema.abilityType = new fields.StringField({
required: true, initial: "class-ability", choices: SYSTEM.ABILITY_TYPE_CHOICES
})
// Which class or lineage this trait belongs to (e.g. "Berserker", "Wood Elf")
schema.source = new fields.StringField({ required: true, nullable: false, initial: "" })
// When uses reset: none = passive (always on), encounter, day
schema.usagePeriod = new fields.StringField({
required: true, initial: "none", choices: SYSTEM.ABILITY_USAGE_PERIOD
})
// Maximum uses per period. 0 = passive / unlimited.
// Use a descriptive string when the limit is formula-based
// (e.g. "equal to Fate ranks") — store the note in the description.
schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Ability"]
}

View File

@@ -0,0 +1,44 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerBuilding extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
// Narrative description and special effects
schema.description = new fields.HTMLField({ required: true, textSearch: true })
// Skill required to build (Carpentry or Masonry)
schema.skillCheck = new fields.StringField({
required: true, initial: "carpentry", choices: SYSTEM.BUILDING_SKILL_CHOICES
})
// Cost in gold pieces (after skill check SV negotiation)
schema.cost = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
// Construction duration (free text: "3 weeks", "6 months", etc.)
schema.buildTime = new fields.StringField({ required: true, nullable: false, initial: "" })
// Monthly tax revenue formula ("3d6", "2d6", "1d3", "" = none)
schema.taxRevenue = new fields.StringField({ required: true, nullable: false, initial: "" })
// Is this building currently constructed in the settlement?
schema.constructed = new fields.BooleanField({ required: true, initial: false })
// Which settlement this building belongs to (free text or settlement name)
schema.settlement = new fields.StringField({ required: true, nullable: false, initial: "" })
// Additional GM notes (special conditions, upgrades, damage, etc.)
schema.notes = new fields.HTMLField({ required: false, textSearch: true })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Building"]
/** Returns true if this building generates tax income */
get hasTaxRevenue() {
return this.taxRevenue.trim().length > 0
}
}

View File

@@ -31,7 +31,7 @@ export default class OathHammerMagicItem extends foundry.abstract.TypeDataModel
// Limited-use items (e.g. "once per day"); none = always active
schema.usagePeriod = new fields.StringField({
required: true, initial: "none", choices: SYSTEM.ABILITY_USAGE_PERIOD
required: true, initial: "none", choices: SYSTEM.TRAIT_USAGE_PERIOD
})
schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })

43
module/models/trait.mjs Normal file
View File

@@ -0,0 +1,43 @@
import { SYSTEM } from "../config/system.mjs"
export default class OathHammerTrait extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields
const requiredInteger = { required: true, nullable: false, integer: true }
const schema = {}
schema.description = new fields.HTMLField({ required: true, textSearch: true })
// class-trait (class-exclusive, acquirable) or special-trait (starting class trait)
schema.traitType = new fields.StringField({
required: true, initial: "special-trait", choices: SYSTEM.TRAIT_TYPE_CHOICES
})
// Which class or lineage this trait belongs to (e.g. "Berserker", "Wood Elf")
schema.source = new fields.StringField({ required: true, nullable: false, initial: "" })
// When uses reset: none = passive (always on), encounter, day
schema.usagePeriod = new fields.StringField({
required: true, initial: "none", choices: SYSTEM.TRAIT_USAGE_PERIOD
})
// Maximum uses per period. 0 = passive / unlimited.
schema.maxUses = new fields.NumberField({ ...requiredInteger, initial: 0, min: 0 })
return schema
}
static LOCALIZATION_PREFIXES = ["OATHHAMMER.Trait"]
static migrateData(source) {
// Migrate old abilityType field → traitType
if (source.abilityType !== undefined && source.traitType === undefined) {
const map = { "class-ability": "class-trait", "lineage-trait": "special-trait" }
source.traitType = map[source.abilityType] ?? "special-trait"
}
// Migrate old traitType values
if (source.traitType === "lineage-trait") source.traitType = "special-trait"
if (source.traitType === "class-ability") source.traitType = "class-trait"
return super.migrateData(source)
}
}