[V14] Armor System ArmorScore (#1744)

* Readded so that armor items have their system defined armor instead of using an ActiveEffect

* Consolidate armor source retrieval

* Fix regression with updating armor when sources are disabled

* Simplify armor pip update

* Use helper in damage reduction dialog

* .

* Corrected SRD Armor Items

---------

Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
This commit is contained in:
WBHarry 2026-03-22 01:16:32 +01:00 committed by GitHub
parent 50dcbf4396
commit 33fb7bcc69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 365 additions and 1535 deletions

View file

@ -151,7 +151,7 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
const newArmorTotal = (changed.system?.changes ?? []).reduce((acc, change) => {
if (change.type === 'armor') acc += change.value.current;
return acc;
}, 0);
}, this.parent.actor.system.armor?.system?.armor?.current ?? 0);
const armorData = getScrollTextData(this.parent.actor, { value: newArmorTotal }, 'armor');
options.scrollingTextData = [armorData];

View file

@ -6,6 +6,7 @@ import DhCreature from './creature.mjs';
import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
import { ActionField } from '../fields/actionField.mjs';
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
import { getArmorSources } from '../../helpers/utils.mjs';
export default class DhCharacter extends DhCreature {
/**@override */
@ -469,43 +470,57 @@ export default class DhCharacter extends DhCreature {
const increasing = armorChange >= 0;
let remainingChange = Math.abs(armorChange);
const armorEffects = Array.from(this.parent.allApplicableEffects()).filter(x => x.system.armorData);
const orderedEffects = game.system.api.data.activeEffects.changeTypes.armor.orderEffectsForAutoChange(
armorEffects,
increasing
);
const orderedSources = getArmorSources(this.parent).filter(s => !s.disabled);
const embeddedUpdates = [];
for (const armorEffect of orderedEffects) {
const handleArmorData = (embeddedUpdates, doc, armorData) => {
let usedArmorChange = 0;
if (clear) {
usedArmorChange -= armorEffect.system.armorChange.value.current;
usedArmorChange -= armorData.current;
} else {
if (increasing) {
const remainingArmor = armorEffect.system.armorData.max - armorEffect.system.armorData.current;
const remainingArmor = armorData.max - armorData.current;
usedArmorChange = Math.min(remainingChange, remainingArmor);
remainingChange -= usedArmorChange;
} else {
const changeChange = Math.min(armorEffect.system.armorData.current, remainingChange);
const changeChange = Math.min(armorData.current, remainingChange);
usedArmorChange -= changeChange;
remainingChange -= changeChange;
}
}
if (!usedArmorChange) continue;
if (!usedArmorChange) return usedArmorChange;
else {
if (!embeddedUpdates[armorEffect.parent.id])
embeddedUpdates[armorEffect.parent.id] = { doc: armorEffect.parent, updates: [] };
if (!embeddedUpdates[doc.id]) embeddedUpdates[doc.id] = { doc: doc, updates: [] };
embeddedUpdates[armorEffect.parent.id].updates.push({
'_id': armorEffect.id,
'system.changes': armorEffect.system.changes.map(change => ({
return usedArmorChange;
}
};
const armorUpdates = [];
const effectUpdates = [];
for (const { document: armorSource } of orderedSources) {
const usedArmorChange = handleArmorData(
armorSource.type === 'armor' ? armorUpdates : effectUpdates,
armorSource.parent,
armorSource.type === 'armor' ? armorSource.system.armor : armorSource.system.armorData
);
if (!usedArmorChange) continue;
if (armorSource.type === 'armor') {
armorUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
'system.armor.current': armorSource.system.armor.current + usedArmorChange
});
} else {
effectUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
'system.changes': armorSource.system.changes.map(change => ({
...change,
value:
change.type === 'armor'
? {
...change.value,
current: armorEffect.system.armorChange.value.current + usedArmorChange
current: armorSource.system.armorChange.value.current + usedArmorChange
}
: change.value
}))
@ -515,22 +530,34 @@ export default class DhCharacter extends DhCreature {
if (remainingChange === 0 && !clear) break;
}
const updateValues = Object.values(embeddedUpdates);
for (const [index, { doc, updates }] of updateValues.entries())
await doc.updateEmbeddedDocuments('ActiveEffect', updates, { render: index === updateValues.length - 1 });
const armorUpdateValues = Object.values(armorUpdates);
for (const [index, { doc, updates }] of armorUpdateValues.entries())
await doc.updateEmbeddedDocuments('Item', updates, { render: index === armorUpdateValues.length - 1 });
const effectUpdateValues = Object.values(effectUpdates);
for (const [index, { doc, updates }] of effectUpdateValues.entries())
await doc.updateEmbeddedDocuments('ActiveEffect', updates, {
render: index === effectUpdateValues.length - 1
});
}
async updateArmorEffectValue({ uuid, value }) {
const effect = await foundry.utils.fromUuid(uuid);
const effectValue = effect.system.armorChange.value;
await effect.update({
'system.changes': [
{
...effect.system.armorChange,
value: { ...effectValue, current: effectValue.current + value }
}
]
});
const source = await foundry.utils.fromUuid(uuid);
if (source.type === 'armor') {
await source.update({
'system.armor.current': source.system.armor.current + value
});
} else {
const effectValue = source.system.armorChange.value;
await source.update({
'system.changes': [
{
...source.system.armorChange,
value: { ...effectValue, current: effectValue.current + value }
}
]
});
}
}
get sheetLists() {
@ -657,8 +684,8 @@ export default class DhCharacter extends DhCreature {
prepareBaseData() {
super.prepareBaseData();
this.armorScore = {
max: 0,
value: 0
max: this.armor?.system.armor.max ?? 0,
value: this.armor?.system.armor.current ?? 0
};
this.evasion += this.class.value?.system?.evasion ?? 0;

View file

@ -19,6 +19,14 @@ export default class DHArmor extends AttachableItem {
...super.defineSchema(),
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
equipped: new fields.BooleanField({ initial: false }),
armor: new fields.SchemaField({
current: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
max: new fields.NumberField({ required: true, integer: true, initial: 0 })
}),
baseThresholds: new fields.SchemaField({
major: new fields.NumberField({ integer: true, initial: 0 }),
severe: new fields.NumberField({ integer: true, initial: 0 })
}),
armorFeatures: new fields.ArrayField(
new fields.SchemaField({
value: new fields.StringField({
@ -27,11 +35,7 @@ export default class DHArmor extends AttachableItem {
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
})
),
baseThresholds: new fields.SchemaField({
major: new fields.NumberField({ integer: true, initial: 0 }),
severe: new fields.NumberField({ integer: true, initial: 0 })
})
)
};
}
@ -48,17 +52,6 @@ export default class DHArmor extends AttachableItem {
);
}
get armorEffect() {
return this.parent.effects.find(x => x.system.armorData);
}
get armorData() {
const armorEffect = this.armorEffect;
if (!armorEffect) return { value: 0, max: 0 };
return armorEffect.system.armorData;
}
/**@inheritdoc */
async getDescriptionData() {
const baseDescription = this.description;
@ -73,17 +66,6 @@ export default class DHArmor extends AttachableItem {
return { prefix, value: baseDescription, suffix: null };
}
/**@inheritdoc */
async _onCreate(_data, _options, userId) {
if (userId !== game.user.id) return;
if (!this.parent.effects.some(x => x.system.armorData)) {
this.parent.createEmbeddedDocuments('ActiveEffect', [
game.system.api.data.activeEffects.changeTypes.armor.getDefaultArmorEffect()
]);
}
}
/**@inheritdoc */
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
@ -184,7 +166,7 @@ export default class DHArmor extends AttachableItem {
*/
_getTags() {
const tags = [
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorData.max}`,
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`,
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseThresholds.base')}: ${this.baseThresholds.major} / ${this.baseThresholds.severe}`
];
@ -196,7 +178,7 @@ export default class DHArmor extends AttachableItem {
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/
_getLabels() {
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armorData.max}`];
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
return labels;
}

View file

@ -220,6 +220,19 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
addLinkedItemsDiff(changed.system?.features, this.features, options);
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const armorChanged =
changed.system?.armor?.current !== undefined && changed.system.armor.current !== this.armor.current;
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
const armorChangeValue = changed.system.armor.current - this.armor.current;
const armorData = getScrollTextData(
this.parent.parent,
{ value: armorChangeValue + this.parent.parent.system.armorScore.value },
'armor'
);
options.scrollingTextData = [armorData];
}
if (changed.system?.actions) {
const triggersToRemove = Object.keys(changed.system.actions).reduce((acc, key) => {
const action = changed.system.actions[key];