From 7bc5ea4910a12e4c633e3f2770638e4e69bee1d7 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 6 Feb 2026 09:59:06 +0100 Subject: [PATCH] Initial --- daggerheart.mjs | 12 ++++ lang/en.json | 4 +- .../applications/sheets-configs/_module.mjs | 1 + .../armorActiveEffectConfig.mjs | 59 +++++++++++++++++ module/data/activeEffect/_module.mjs | 6 +- module/data/activeEffect/armorEffect.mjs | 64 +++++++++++++++++++ module/documents/activeEffect.mjs | 5 +- .../sheets/activeEffects/armorEffects.less | 16 +++++ styles/less/sheets/index.less | 1 + system.json | 3 +- .../sheets/activeEffect/armor/details.hbs | 20 ++++++ .../sheets/activeEffect/armor/settings.hbs | 12 ++++ 12 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 module/applications/sheets-configs/armorActiveEffectConfig.mjs create mode 100644 module/data/activeEffect/armorEffect.mjs create mode 100644 styles/less/sheets/activeEffects/armorEffects.less create mode 100644 templates/sheets/activeEffect/armor/details.hbs create mode 100644 templates/sheets/activeEffect/armor/settings.hbs diff --git a/daggerheart.mjs b/daggerheart.mjs index 8c817327..92908045 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -209,10 +209,22 @@ Hooks.once('init', () => { SYSTEM.id, applications.sheetConfigs.ActiveEffectConfig, { + types: ['base', 'beastform', 'horde'], makeDefault: true, label: sheetLabel('DOCUMENT.ActiveEffect') } ); + DocumentSheetConfig.registerSheet( + CONFIG.ActiveEffect.documentClass, + SYSTEM.id, + applications.sheetConfigs.ArmorActiveEffectConfig, + { + types: ['armor'], + makeDefault: true, + label: () => + `${game.i18n.localize('TYPES.ActiveEffect.armor')} ${game.i18n.localize('DAGGERHEART.GENERAL.effect')}` + } + ); game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent); diff --git a/lang/en.json b/lang/en.json index c4b611d0..0d62bd8b 100755 --- a/lang/en.json +++ b/lang/en.json @@ -16,7 +16,8 @@ "ActiveEffect": { "base": "Standard", "beastform": "Beastform", - "horde": "Horde" + "horde": "Horde", + "armor": "Armor" }, "Actor": { "character": "Character", @@ -2167,6 +2168,7 @@ "duality": "Duality", "dualityDice": "Duality Dice", "dualityRoll": "Duality Roll", + "effect": "Effect", "enabled": "Enabled", "evasion": "Evasion", "equipment": "Equipment", diff --git a/module/applications/sheets-configs/_module.mjs b/module/applications/sheets-configs/_module.mjs index d3fb3c39..8b09dc29 100644 --- a/module/applications/sheets-configs/_module.mjs +++ b/module/applications/sheets-configs/_module.mjs @@ -6,6 +6,7 @@ export { default as CompanionSettings } from './companion-settings.mjs'; export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs'; export { default as SettingFeatureConfig } from './setting-feature-config.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs'; +export { default as ArmorActiveEffectConfig } from './armorActiveEffectConfig.mjs'; export { default as ActiveEffectConfig } from './activeEffectConfig.mjs'; export { default as DhTokenConfig } from './token-config.mjs'; export { default as DhPrototypeTokenConfig } from './prototype-token-config.mjs'; diff --git a/module/applications/sheets-configs/armorActiveEffectConfig.mjs b/module/applications/sheets-configs/armorActiveEffectConfig.mjs new file mode 100644 index 00000000..ef407782 --- /dev/null +++ b/module/applications/sheets-configs/armorActiveEffectConfig.mjs @@ -0,0 +1,59 @@ +const { HandlebarsApplicationMixin, DocumentSheetV2 } = foundry.applications.api; + +export default class ArmorActiveEffectConfig extends HandlebarsApplicationMixin(DocumentSheetV2) { + static DEFAULT_OPTIONS = { + classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'armor-effect-config'], + form: { + handler: this.updateForm, + submitOnChange: true, + closeOnSubmit: false + }, + actions: { + addEffect: ArmorActiveEffectConfig.#addEffect + } + }; + + static PARTS = { + header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' }, + tabs: { template: 'templates/generic/tab-navigation.hbs' }, + details: { template: 'systems/daggerheart/templates/sheets/activeEffect/armor/details.hbs' }, + settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/armor/settings.hbs' }, + footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' } + }; + + static TABS = { + sheet: { + tabs: [ + { id: 'details', icon: 'fa-solid fa-book' }, + { id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' } + ], + initial: 'details', + labelPrefix: 'EFFECT.TABS' + } + }; + + async _prepareContext(options) { + const context = await super._prepareContext(options); + context.systemFields = context.document.system.schema.fields; + + return context; + } + + /** @inheritDoc */ + async _preparePartContext(partId, context) { + const partContext = await super._preparePartContext(partId, context); + if (partId in partContext.tabs) partContext.tab = partContext.tabs[partId]; + + return partContext; + } + + async updateForm(_event, _form, formData) { + await this.document.update(formData.object); + this.render(); + } + + static #addEffect() { + this.document.update({ 'system.changes': [...this.document.system.changes, {}] }); + this.render(); + } +} diff --git a/module/data/activeEffect/_module.mjs b/module/data/activeEffect/_module.mjs index 1a50088a..b4b89c78 100644 --- a/module/data/activeEffect/_module.mjs +++ b/module/data/activeEffect/_module.mjs @@ -1,11 +1,13 @@ import BaseEffect from './baseEffect.mjs'; import BeastformEffect from './beastformEffect.mjs'; import HordeEffect from './hordeEffect.mjs'; +import ArmorEffect from './armorEffect.mjs'; -export { BaseEffect, BeastformEffect, HordeEffect }; +export { BaseEffect, BeastformEffect, HordeEffect, ArmorEffect }; export const config = { base: BaseEffect, beastform: BeastformEffect, - horde: HordeEffect + horde: HordeEffect, + armor: ArmorEffect }; diff --git a/module/data/activeEffect/armorEffect.mjs b/module/data/activeEffect/armorEffect.mjs new file mode 100644 index 00000000..31ce4dca --- /dev/null +++ b/module/data/activeEffect/armorEffect.mjs @@ -0,0 +1,64 @@ +export default class ArmorEffect extends foundry.data.ActiveEffectTypeDataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + ...super.defineSchema(), + changes: new fields.ArrayField( + new fields.SchemaField({ + type: new fields.StringField({ + required: true, + blank: false, + choices: CONFIG.DH.GENERAL.activeEffectModes, + initial: CONFIG.DH.GENERAL.activeEffectModes.add.id, + validate: ArmorEffect.#validateType + }), + phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), + priority: new fields.NumberField(), + marked: new fields.NumberField({ + required: true, + integer: true, + initial: 0, + min: 0, + label: 'DAGGERHEART.GENERAL.value' + }), + max: new fields.NumberField({ + required: true, + integer: true, + initial: 1, + min: 1, + label: 'DAGGERHEART.GENERAL.max' + }) + }) + ) + }; + } + + static applyChangeField(model, change, field) { + return [model, change, field]; + } + + prepareBaseData() { + for (const change of this.changes) { + change.key = 'system.armorTest'; + change.value = Math.max(change.max - change.marked, change.max); + } + } + + /** + * Validate that an {@link EffectChangeData#type} string is well-formed. + * @param {string} type The string to be validated + * @returns {true} + * @throws {Error} An error if the type string is malformed + */ + static #validateType(type) { + if (type.length < 3) throw new Error('must be at least three characters long'); + if (!/^custom\.-?\d+$/.test(type) && !type.split('.').every(s => /^[a-z0-9]+$/i.test(s))) { + throw new Error( + 'A change type must either be a sequence of dot-delimited, alpha-numeric substrings or of the form' + + ' "custom.{number}"' + ); + } + return true; + } +} diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 9a326282..82ac17f1 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -62,7 +62,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { throw new Error('The array of sub-types to restrict to must not be empty.'); } - const creatableEffects = ['base']; + const creatableEffects = ['base', 'armor']; const documentTypes = this.TYPES.filter(type => creatableEffects.includes(type)).map(type => { const labelKey = `TYPES.ActiveEffect.${type}`; const label = game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type; @@ -140,6 +140,9 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /**@inheritdoc*/ static applyChangeField(model, change, field) { + if (this.system?.applyChangeField) + super.applyChangeField(...this.system.applyChangeField(model, change, field)); + change.value = Number.isNumeric(change.value) ? change.value : DhActiveEffect.getChangeValue(model, change, change.effect); diff --git a/styles/less/sheets/activeEffects/armorEffects.less b/styles/less/sheets/activeEffects/armorEffects.less new file mode 100644 index 00000000..9756edce --- /dev/null +++ b/styles/less/sheets/activeEffects/armorEffects.less @@ -0,0 +1,16 @@ +.application.sheet.daggerheart.dh-style.armor-effect-config { + .armor-effects-container { + display: flex; + flex-direction: column; + gap: 8px; + + .armor-effect-container { + display: flex; + gap: 4px; + + * { + flex: 1; + } + } + } +} diff --git a/styles/less/sheets/index.less b/styles/less/sheets/index.less index e5ffbf3e..25ec6fc3 100644 --- a/styles/less/sheets/index.less +++ b/styles/less/sheets/index.less @@ -44,3 +44,4 @@ @import './actions/actions.less'; @import './activeEffects/activeEffects.less'; +@import './activeEffects/armorEffects.less'; diff --git a/system.json b/system.json index 48a2319a..d62142a0 100644 --- a/system.json +++ b/system.json @@ -278,7 +278,8 @@ }, "ActiveEffect": { "beastform": {}, - "horde": {} + "horde": {}, + "armor": {} }, "Combat": { "combat": {} diff --git a/templates/sheets/activeEffect/armor/details.hbs b/templates/sheets/activeEffect/armor/details.hbs new file mode 100644 index 00000000..2b16560e --- /dev/null +++ b/templates/sheets/activeEffect/armor/details.hbs @@ -0,0 +1,20 @@ +
+ {{formGroup fields.tint value=source.tint rootId=rootId placeholder="#ffffff"}} + {{formGroup fields.description value=source.description rootId=rootId}} + {{formGroup fields.disabled value=source.disabled rootId=rootId}} + + {{#if isActorEffect}} +
+ +
+ +
+
+ {{/if}} + + {{#if isItemEffect}} + {{formGroup fields.transfer value=source.transfer rootId=rootId label=legacyTransfer.label hint=(localize "DAGGERHEART.EFFECTS.Attachments.transferHint")}} + {{/if}} + + {{formGroup fields.showIcon value=source.showIcon options=showIconOptions rootId=rootId}} +
\ No newline at end of file diff --git a/templates/sheets/activeEffect/armor/settings.hbs b/templates/sheets/activeEffect/armor/settings.hbs new file mode 100644 index 00000000..58a22d63 --- /dev/null +++ b/templates/sheets/activeEffect/armor/settings.hbs @@ -0,0 +1,12 @@ +
+ + +
+ {{#each source.system.changes as |change index|}} +
+ {{formGroup @root.systemFields.changes.element.fields.marked value=change.marked localize=true}} + {{formGroup @root.systemFields.changes.element.fields.max value=change.max localize=true}} +
+ {{/each}} +
+
\ No newline at end of file