diff --git a/daggerheart.mjs b/daggerheart.mjs index 67d1c24f..ba209856 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -43,7 +43,7 @@ CONFIG.Item.dataModels = models.items.config; CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect; CONFIG.ActiveEffect.dataModels = models.activeEffects.config; -CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeTypes }; +CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeEffects }; CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.Combat.dataModels = { base: models.DhCombat }; diff --git a/lang/en.json b/lang/en.json index fa3099df..dcfba5ce 100755 --- a/lang/en.json +++ b/lang/en.json @@ -778,8 +778,8 @@ }, "ArmorInteraction": { "none": { "label": "Ignores Armor" }, - "active": { "label": "Only Active With Armor" }, - "inactive": { "label": "Only Active Without Armor" } + "active": { "label": "Active w/ Armor" }, + "inactive": { "label": "Inactive w/ Armor" } }, "ArmorFeature": { "burning": { diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 2bd7d5b9..4434931c 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -8,7 +8,10 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac } static DEFAULT_OPTIONS = { - classes: ['daggerheart', 'sheet', 'dh-style'] + classes: ['daggerheart', 'sheet', 'dh-style'], + actions: { + addTypedChange: DhActiveEffectConfig.#addTypedChange + } }; static PARTS = { @@ -187,38 +190,51 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac break; case 'changes': const fields = this.document.system.schema.fields.changes.element.fields; + const { base, ...typedChanges } = context.source.changes.reduce((acc, change, index) => { + const type = CONFIG.DH.GENERAL.baseActiveEffectModes[change.type] ? 'base' : change.type; + if (!acc[type]) acc[type] = []; + acc[type].push({ ...change, index }); + return acc; + }, {}); partContext.changes = await Promise.all( - foundry.utils - .deepClone(context.source.changes) - .map((c, i) => this._prepareChangeContext(c, i, fields)) + foundry.utils.deepClone(base ?? []).map(c => this._prepareChangeContext(c, fields)) ); + partContext.typedChanges = typedChanges; break; } return partContext; } - _prepareChangeContext(change, index, fields) { + /* Could be generalised if needed later */ + static #addTypedChange() { + const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form)); + const changes = Object.values(submitData.system?.changes ?? {}); + changes.push(game.system.api.data.activeEffects.changeTypes.armor.getInitialValue()); + return this.submit({ updateData: { system: { changes } } }); + } + + _prepareChangeContext(change, fields) { if (typeof change.value !== 'string') change.value = JSON.stringify(change.value); const defaultPriority = game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type]?.defaultPriority; Object.assign( change, ['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => { - paths[`${fieldName}Path`] = `system.changes.${index}.${fieldName}`; + paths[`${fieldName}Path`] = `system.changes.${change.index}.${fieldName}`; return paths; }, {}) ); return ( game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type].render?.( change, - index, + change.index, defaultPriority ) ?? foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/sheets/activeEffect/change.hbs', { change, - index, + index: change.index, defaultPriority, fields } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index 4ac46618..391f0699 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -959,12 +959,7 @@ export const sceneRangeMeasurementSetting = { } }; -export const activeEffectModes = { - armor: { - id: 'armor', - priority: 20, - label: 'TYPES.ActiveEffect.armor' - }, +export const baseActiveEffectModes = { custom: { id: 'custom', priority: 0, @@ -1002,6 +997,15 @@ export const activeEffectModes = { } }; +export const activeEffectModes = { + armor: { + id: 'armor', + priority: 20, + label: 'TYPES.ActiveEffect.armor' + }, + ...baseActiveEffectModes +}; + export const activeEffectArmorInteraction = { none: { id: 'none', label: 'DAGGERHEART.CONFIG.ArmorInteraction.none.label' }, active: { id: 'active', label: 'DAGGERHEART.CONFIG.ArmorInteraction.active.label' }, diff --git a/module/data/activeEffect/_module.mjs b/module/data/activeEffect/_module.mjs index 62f10e3e..3c933a9c 100644 --- a/module/data/activeEffect/_module.mjs +++ b/module/data/activeEffect/_module.mjs @@ -1,17 +1,12 @@ import BaseEffect from './baseEffect.mjs'; import BeastformEffect from './beastformEffect.mjs'; import HordeEffect from './hordeEffect.mjs'; -import ArmorEffect from './armorEffect.mjs'; +export { changeTypes, changeEffects } from './changeTypes/_module.mjs'; -export { BaseEffect, BeastformEffect, HordeEffect, ArmorEffect }; +export { BaseEffect, BeastformEffect, HordeEffect }; export const config = { base: BaseEffect, beastform: BeastformEffect, - horde: HordeEffect, - armor: ArmorEffect -}; - -export const changeTypes = { - armor: ArmorEffect.armorChangeEffect + horde: HordeEffect }; diff --git a/module/data/activeEffect/baseEffect.mjs b/module/data/activeEffect/baseEffect.mjs index 98a961d7..111cf4c2 100644 --- a/module/data/activeEffect/baseEffect.mjs +++ b/module/data/activeEffect/baseEffect.mjs @@ -12,6 +12,8 @@ * "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility) */ +import { changeTypes } from './_module.mjs'; + export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { static defineSchema() { const fields = foundry.data.fields; @@ -30,7 +32,8 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel { }), value: new fields.AnyField({ required: true, nullable: true, serializable: true, initial: '' }), phase: new fields.StringField({ required: true, blank: false, initial: 'initial' }), - priority: new fields.NumberField() + priority: new fields.NumberField(), + typeData: new fields.TypedSchemaField(changeTypes, { nullable: true, initial: null }) }) ), duration: new fields.SchemaField({ diff --git a/module/data/activeEffect/changeTypes/_module.mjs b/module/data/activeEffect/changeTypes/_module.mjs new file mode 100644 index 00000000..cf872304 --- /dev/null +++ b/module/data/activeEffect/changeTypes/_module.mjs @@ -0,0 +1,9 @@ +import Armor from './armor.mjs'; + +export const changeEffects = { + armor: Armor.changeEffect +}; + +export const changeTypes = { + armor: Armor +}; diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs new file mode 100644 index 00000000..a27b3030 --- /dev/null +++ b/module/data/activeEffect/changeTypes/armor.mjs @@ -0,0 +1,141 @@ +const fields = foundry.data.fields; + +export default class Armor extends foundry.abstract.DataModel { + static defineSchema() { + return { + type: new fields.StringField({ required: true, initial: 'armor', blank: false }), + max: new fields.StringField({ + required: true, + nullable: false, + initial: '1', + label: 'DAGGERHEART.GENERAL.max' + }), + armorInteraction: new fields.StringField({ + required: true, + choices: CONFIG.DH.GENERAL.activeEffectArmorInteraction, + initial: CONFIG.DH.GENERAL.activeEffectArmorInteraction.none.id, + label: 'DAGGERHEART.EFFECTS.Armor.FIELDS.armorInteraction.label', + hint: 'DAGGERHEART.EFFECTS.Armor.FIELDS.armorInteraction.hint' + }) + }; + } + + static changeEffect = { + label: 'Armor', + defaultPriortiy: 20, + handler: (actor, change, _options, _field, replacementData) => { + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.armorScore.value', + type: CONFIG.DH.GENERAL.activeEffectModes.add.id, + value: change.value + }, + replacementData + ); + game.system.api.documents.DhActiveEffect.applyChange( + actor, + { + ...change, + key: 'system.armorScore.max', + type: CONFIG.DH.GENERAL.activeEffectModes.add.id, + value: change.max + }, + replacementData + ); + return {}; + }, + render: null + }; + + get isSuppressed() { + switch (this.armorInteraction) { + case CONFIG.DH.GENERAL.activeEffectArmorInteraction.active.id: + return !this.parent.parent?.actor.system.armor; + case CONFIG.DH.GENERAL.activeEffectArmorInteraction.inactive.id: + return Boolean(this.parent.parent?.actor.system.armor); + default: + return false; + } + } + + static getInitialValue() { + return { + key: 'Armor', + type: CONFIG.DH.GENERAL.activeEffectModes.armor.id, + value: 0, + typeData: { + type: 'armor', + max: 0 + }, + phase: 'initial', + priority: 20 + }; + } + + /* Helpers */ + + get armorChange() { + if (this.changes.length !== 1) + throw new Error('Unexpected error. An armor effect should have a changes field of length 1.'); + + const actor = this.parent.actor?.type === 'character' ? this.parent.actor : null; + const changeData = this.changes[0]; + const maxParse = actor ? itemAbleRollParse(changeData.max, actor, this.parent.parent) : null; + const maxRoll = maxParse ? new Roll(maxParse).evaluateSync() : null; + const maxEvaluated = maxRoll ? (maxRoll.isDeterministic ? maxRoll.total : null) : null; + + return { + ...changeData, + max: maxEvaluated ?? changeData.max + }; + } + + get armorData() { + return { value: this.armorChange.value, max: this.armorChange.max }; + } + + async updateArmorMax(newMax) { + const { effect, ...baseChange } = this.armorChange; + const newChanges = [ + { + ...baseChange, + max: newMax, + value: Math.min(this.armorChange.value, newMax) + } + ]; + await this.parent.update({ 'system.changes': newChanges }); + } + + static orderEffectsForAutoChange(armorEffects, increasing) { + const getEffectWeight = effect => { + switch (effect.parent.type) { + case 'class': + case 'subclass': + case 'ancestry': + case 'community': + case 'feature': + case 'domainCard': + return 2; + case 'armor': + return 3; + case 'loot': + case 'consumable': + return 4; + case 'weapon': + return 5; + case 'character': + return 6; + default: + return 1; + } + }; + + return armorEffects + .filter(x => !x.disabled && !x.isSuppressed) + .sort((a, b) => + increasing ? getEffectWeight(b) - getEffectWeight(a) : getEffectWeight(a) - getEffectWeight(b) + ); + } +} diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index f51e1035..058b1b56 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -47,6 +47,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', 'systems/daggerheart/templates/scene/dh-config.hbs', - 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs' + 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs', + 'systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs' ]); }; diff --git a/styles/less/sheets/activeEffects/activeEffects.less b/styles/less/sheets/activeEffects/activeEffects.less index ba3ff43f..f710d1f2 100644 --- a/styles/less/sheets/activeEffects/activeEffects.less +++ b/styles/less/sheets/activeEffects/activeEffects.less @@ -31,5 +31,12 @@ text-align: center; } } + + .armor-change-container { + header, + ol { + grid-template-columns: 6rem 6rem 12rem 4rem 1rem; + } + } } } diff --git a/templates/sheets/activeEffect/changes.hbs b/templates/sheets/activeEffect/changes.hbs index 026ffd90..9ae66477 100644 --- a/templates/sheets/activeEffect/changes.hbs +++ b/templates/sheets/activeEffect/changes.hbs @@ -13,4 +13,21 @@ {{{change}}} {{/each}} + +
diff --git a/templates/sheets/activeEffect/typeChanges/armorChange.hbs b/templates/sheets/activeEffect/typeChanges/armorChange.hbs new file mode 100644 index 00000000..006f936d --- /dev/null +++ b/templates/sheets/activeEffect/typeChanges/armorChange.hbs @@ -0,0 +1,11 @@ +