Latest fixes for Oath Hammer
This commit is contained in:
@@ -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"
|
||||
|
||||
28
module/applications/sheets/building-sheet.mjs
Normal file
28
module/applications/sheets/building-sheet.mjs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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":
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
44
module/models/building.mjs
Normal file
44
module/models/building.mjs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
43
module/models/trait.mjs
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user