diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 0172bff7..702c7734 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import FilterMenu from '../../ux/filter-menu.mjs'; -import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; +import { getArmorSources, getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ @@ -943,29 +943,14 @@ export default class CharacterSheet extends DHBaseActorSheet { return; } - const armorSources = []; - for (var effect of Array.from(this.document.allApplicableEffects())) { - const origin = effect.origin ? await foundry.utils.fromUuid(effect.origin) : effect.parent; - if (!effect.system.armorData || effect.disabled || effect.isSuppressed) continue; - - const useEffectName = origin.type === 'armor' || origin instanceof Actor; - const name = useEffectName ? effect.name : origin.name; - armorSources.push({ - uuid: effect.uuid, - name, - ...effect.system.armorData - }); - } - - if (this.document.system.armor) { - armorSources.push({ - ...this.document.system.armor.system.armor, - uuid: this.document.system.armor.uuid, - name: this.document.system.armor.name, - isArmorItem: true - }); - } - + const armorSources = getArmorSources(this.document) + .filter(s => !s.disabled) + .toReversed() + .map(({ name, document, data }) => ({ + ...data, + uuid: document.uuid, + name + })); if (!armorSources.length) return; const useResourcePips = game.settings.get( @@ -1005,34 +990,31 @@ export default class CharacterSheet extends DHBaseActorSheet { /** Update specific armor source */ static async armorSourcePipUpdate(event) { const target = event.target.closest('.armor-slot'); - const { uuid, value, isArmorItem: isArmorItemString } = target.dataset; - const isArmorItem = Boolean(isArmorItemString); + const { uuid, value } = target.dataset; + const document = await foundry.utils.fromUuid(uuid); let inputValue = Number.parseInt(value); let decreasing = false; let newCurrent = 0; - if (isArmorItem) { - const armor = await foundry.utils.fromUuid(uuid); - decreasing = armor.system.armor.current >= inputValue; + if (document.type === 'armor') { + decreasing = document.system.armor.current >= inputValue; newCurrent = decreasing ? inputValue - 1 : inputValue; - - await armor.update({ 'system.armor.current': newCurrent }); + await document.update({ 'system.armor.current': newCurrent }); } else { - const effect = await foundry.utils.fromUuid(uuid); - const armorChange = effect.system.armorChange; + const armorChange = document.system.armorChange; if (!armorChange) return; - const { current } = effect.system.armorData; + const { current } = document.system.armorData; decreasing = current >= inputValue; newCurrent = decreasing ? inputValue - 1 : inputValue; - const newChanges = effect.system.changes.map(change => ({ + const newChanges = document.system.changes.map(change => ({ ...change, value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value })); - await effect.update({ 'system.changes': newChanges }); + await document.update({ 'system.changes': newChanges }); } const container = target.closest('.slot-bar'); diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 216a0d72..5a9956a5 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -6,7 +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 { orderSourcesForArmorAutoChange } from '../../helpers/utils.mjs'; +import { getArmorSources } from '../../helpers/utils.mjs'; export default class DhCharacter extends DhCreature { /**@override */ @@ -470,10 +470,7 @@ export default class DhCharacter extends DhCreature { const increasing = armorChange >= 0; let remainingChange = Math.abs(armorChange); - const armorSources = Array.from(this.parent.allApplicableEffects()).filter(x => x.system.armorData); - if (this.armor) armorSources.push(this.armor); - - const orderedSources = orderSourcesForArmorAutoChange(armorSources, increasing); + const orderedSources = getArmorSources(this.parent); const handleArmorData = (embeddedUpdates, doc, armorData) => { let usedArmorChange = 0; @@ -501,7 +498,7 @@ export default class DhCharacter extends DhCreature { const armorUpdates = []; const effectUpdates = []; - for (const armorSource of orderedSources) { + for (const { document: armorSource } of orderedSources) { const usedArmorChange = handleArmorData( armorSource.type === 'armor' ? armorUpdates : effectUpdates, armorSource.parent, diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index e9bd09a9..9038bb08 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -744,15 +744,27 @@ export function getUnusedDamageTypes(parts) { }, []); } -/** - * - * @param {{ type: string, parent: Object, disabled: boolean, isSuppressed: boolean }} sources - * @param { boolean } increasing - * @returns - */ -export function orderSourcesForArmorAutoChange(sources, increasing) { - const getSourceWeight = source => { - switch (source.type) { +/** Returns resolved armor sources ordered by application order */ +export function getArmorSources(actor) { + const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData); + if (actor.system.armor) rawArmorSources.push(actor.system.armor); + + const data = rawArmorSources.map(doc => { + // Get the origin item. Since the actor is already loaded, it should already be cached + // Consider the relative function versions if this causes an issue + const isItem = doc instanceof Item; + const origin = isItem ? doc : doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc.parent; + return { + origin, + name: origin.name, + document: doc, + data: doc.system.armor ?? doc.system.armorData, + disabled: !!doc.disabled || !!doc.isSuppressed + }; + }); + + return sortBy(data, ({ origin }) => { + switch (origin?.type) { case 'class': case 'subclass': case 'ancestry': @@ -760,23 +772,38 @@ export function orderSourcesForArmorAutoChange(sources, increasing) { case 'feature': case 'domainCard': return 2; - case 'armor': - return 3; case 'loot': case 'consumable': + return 3; + case 'character': return 4; case 'weapon': return 5; - case 'character': + case 'armor': return 6; default: return 1; } - }; - - return sources - .filter(x => !x.disabled && !x.isSuppressed) - .sort((a, b) => - increasing ? getSourceWeight(b) - getSourceWeight(a) : getSourceWeight(a) - getSourceWeight(b) - ); + }); +} + +/** + * Returns an array sorted by a function that returns a thing to compare, or an array to compare in order + * Similar to lodash's sortBy function. + */ +export function sortBy(arr, fn) { + const directCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0); + const cmp = (a, b) => { + const resultA = fn(a); + const resultB = fn(b); + if (Array.isArray(resultA) && Array.isArray(resultB)) { + for (let idx = 0; idx < Math.min(resultA.length, resultB.length); idx++) { + const result = directCompare(resultA[idx], resultB[idx]); + if (result !== 0) return result; + } + return 0; + } + return directCompare(resultA, resultB); + }; + return arr.sort(cmp); } diff --git a/templates/ui/tooltip/armorManagement.hbs b/templates/ui/tooltip/armorManagement.hbs index 251840c9..963959c5 100644 --- a/templates/ui/tooltip/armorManagement.hbs +++ b/templates/ui/tooltip/armorManagement.hbs @@ -5,7 +5,7 @@

{{source.name}}

{{#times source.max}} - + {{#if (gte ../current (add this 1))}} {{else}}