[Fix] origin change values and prioritize item options (#1835)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run

* Fix origin key replacement and prioritize item options

* Rename key to value

* Some fixes

* Attempt to always retrieve the source item for same actor origin

* Fix getters in item roll data

* Performance improvement

* Allow apply to item system data

* Replace implementation with shallow proxy

* Add delete to the shallow proxy

* Add proxy trap for the in operator

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
This commit is contained in:
Carlos Fernandez 2026-04-25 18:06:41 -04:00 committed by GitHub
parent c82bcbeb01
commit 6d09c5504d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 37 deletions

View file

@ -1,6 +1,6 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs'; import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
import DHItem from '../../documents/item.mjs'; import DHItem from '../../documents/item.mjs';
import { getScrollTextData } from '../../helpers/utils.mjs'; import { createShallowProxy, getScrollTextData } from '../../helpers/utils.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -180,8 +180,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
* @returns {object} * @returns {object}
*/ */
getRollData() { getRollData() {
const data = { ...this }; return createShallowProxy(this);
return data;
} }
/** /**

View file

@ -7,7 +7,12 @@
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item * @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
*/ */
import { addLinkedItemsDiff, getScrollTextData, updateLinkedItemApps } from '../../helpers/utils.mjs'; import {
addLinkedItemsDiff,
getScrollTextData,
createShallowProxy,
updateLinkedItemApps
} from '../../helpers/utils.mjs';
import { ActionsField } from '../fields/actionField.mjs'; import { ActionsField } from '../fields/actionField.mjs';
import FormulaField from '../fields/formulaField.mjs'; import FormulaField from '../fields/formulaField.mjs';
@ -159,8 +164,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
* @returns {object} * @returns {object}
*/ */
getRollData(options = {}) { getRollData(options = {}) {
const actorRollData = this.actor?.getRollData() ?? {}; const data = this.actor?.getRollData() ?? {};
const data = { ...actorRollData, item: { ...this } }; data.item = createShallowProxy(this);
return data; return data;
} }

View file

@ -169,27 +169,36 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
super._applyChangeUnguided(actor, change, changes, options); super._applyChangeUnguided(actor, change, changes, options);
} }
/** Recursively finds the first parent document of the given object */
static #resolveParentDocument(model, documentClass) {
return model instanceof documentClass
? model
: model.parent
? this.#resolveParentDocument(model.parent, documentClass)
: null;
}
static getChangeValue(model, change, effect) { static getChangeValue(model, change, effect) {
let key = change.value.toString(); let value = change.value.toString();
const isOriginTarget = key.toLowerCase().includes('origin.@'); const useOrigin = value.toLowerCase().includes('origin.@') && effect.origin;
let parseModel = model; let origin = null;
if (isOriginTarget && effect.origin) { if (effect.origin) {
key = change.key.replaceAll(/origin\.@/gi, '@'); if (useOrigin) value = value.replaceAll(/origin\.@/gi, '@');
try { const originEffect = foundry.utils.fromUuidSync(effect.origin);
const originEffect = foundry.utils.fromUuidSync(effect.origin); origin = this.#resolveParentDocument(originEffect, Item);
const doc =
originEffect.parent?.parent instanceof game.system.api.documents.DhpActor
? originEffect.parent
: originEffect.parent.parent;
if (doc) parseModel = doc;
} catch (_) {}
} }
// Get the actor and item documents. Note that actor roll data is inclusive of system roll data
const actor = this.#resolveParentDocument(model, Actor);
const item =
(useOrigin ? origin : null) ??
this.#resolveParentDocument(effect.parent, Item) ??
(origin?.actor === actor ? origin : null);
const stackingParsedValue = effect.system.stacking const stackingParsedValue = effect.system.stacking
? Roll.replaceFormulaData(key, { stacks: effect.system.stacking.value }) ? Roll.replaceFormulaData(value, { stacks: effect.system.stacking.value })
: key; : value;
const evalValue = itemAbleRollParse(stackingParsedValue, parseModel, effect.parent); const evalValue = this.effectSafeEval(itemAbleRollParse(stackingParsedValue, actor, item));
return evalValue ?? key; return evalValue ?? value;
} }
/** /**

View file

@ -1,7 +1,7 @@
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs'; import { createScrollText, damageKeyToNumber, getDamageKey, createShallowProxy } from '../helpers/utils.mjs';
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs'; import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
import { abilities } from '../config/actorConfig.mjs'; import { abilities } from '../config/actorConfig.mjs';
@ -595,10 +595,7 @@ export default class DhpActor extends Actor {
/**@inheritdoc */ /**@inheritdoc */
getRollData() { getRollData() {
const rollData = foundry.utils.deepClone(super.getRollData()); const rollData = createShallowProxy(super.getRollData());
/* system gets repeated infinately which causes issues when trying to use the data for document creation */
delete rollData.system;
rollData.id = this.id; rollData.id = this.id;
rollData.name = this.name; rollData.name = this.name;
rollData.system = this.system.getRollData(); rollData.system = this.system.getRollData();

View file

@ -54,13 +54,7 @@ export default class DHItem extends foundry.documents.Item {
* @returns * @returns
*/ */
getRollData(options = {}) { getRollData(options = {}) {
let data; let data = this.system.getRollData(options);
if (this.system.getRollData) data = this.system.getRollData(options);
else {
const actorRollData = this.actor?.getRollData(options) ?? {};
data = { ...actorRollData, item: { ...this.system } };
}
if (data?.item) { if (data?.item) {
data.item.flags = { ...this.flags }; data.item.flags = { ...this.flags };
data.item.name = this.name; data.item.name = this.name;

View file

@ -372,10 +372,11 @@ export const itemAbleRollParse = (value, actor, item) => {
const isItemTarget = value.toLowerCase().includes('item.@'); const isItemTarget = value.toLowerCase().includes('item.@');
const slicedValue = isItemTarget ? value.replaceAll(/item\.@/gi, '@') : value; const slicedValue = isItemTarget ? value.replaceAll(/item\.@/gi, '@') : value;
const model = isItemTarget ? item : actor; const model = isItemTarget || item instanceof Item ? item : actor;
const rollData = isItemTarget || !model?.getRollData ? model : model.getRollData();
try { try {
return Roll.replaceFormulaData(slicedValue, isItemTarget || !model?.getRollData ? model : model.getRollData()); return Roll.replaceFormulaData(slicedValue, rollData);
} catch (_) { } catch (_) {
return ''; return '';
} }
@ -809,3 +810,31 @@ export function sortBy(arr, fn) {
}; };
return arr.sort(cmp); return arr.sort(cmp);
} }
/**
* Creates a proxy that allows retrieval of top data but diverts updates to a different object.
* Generally used for roll data
*/
export function createShallowProxy(obj) {
if (obj._isShallowProxy) return obj;
const overrides = {};
return new Proxy(obj, {
get(target, prop, receiver) {
if (prop === '_isShallowProxy') return true;
if (prop in overrides) return overrides[prop];
return Reflect.get(target, prop, receiver);
},
set(_target, prop, newValue) {
overrides[prop] = newValue;
return true;
},
deleteProperty(_target, prop) {
delete overrides[prop];
return true;
},
has(target, key) {
return key in overrides || key in target;
}
});
}