[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 DHItem from '../../documents/item.mjs';
import { getScrollTextData } from '../../helpers/utils.mjs';
import { createShallowProxy, getScrollTextData } from '../../helpers/utils.mjs';
const fields = foundry.data.fields;
@ -180,8 +180,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
* @returns {object}
*/
getRollData() {
const data = { ...this };
return data;
return createShallowProxy(this);
}
/**

View file

@ -7,7 +7,12 @@
* @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 FormulaField from '../fields/formulaField.mjs';
@ -159,8 +164,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
* @returns {object}
*/
getRollData(options = {}) {
const actorRollData = this.actor?.getRollData() ?? {};
const data = { ...actorRollData, item: { ...this } };
const data = this.actor?.getRollData() ?? {};
data.item = createShallowProxy(this);
return data;
}

View file

@ -169,27 +169,36 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
super._applyChangeUnguided(actor, change, changes, options);
}
static getChangeValue(model, change, effect) {
let key = change.value.toString();
const isOriginTarget = key.toLowerCase().includes('origin.@');
let parseModel = model;
if (isOriginTarget && effect.origin) {
key = change.key.replaceAll(/origin\.@/gi, '@');
try {
const originEffect = foundry.utils.fromUuidSync(effect.origin);
const doc =
originEffect.parent?.parent instanceof game.system.api.documents.DhpActor
? originEffect.parent
: originEffect.parent.parent;
if (doc) parseModel = doc;
} catch (_) {}
/** 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) {
let value = change.value.toString();
const useOrigin = value.toLowerCase().includes('origin.@') && effect.origin;
let origin = null;
if (effect.origin) {
if (useOrigin) value = value.replaceAll(/origin\.@/gi, '@');
const originEffect = foundry.utils.fromUuidSync(effect.origin);
origin = this.#resolveParentDocument(originEffect, Item);
}
// 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
? Roll.replaceFormulaData(key, { stacks: effect.system.stacking.value })
: key;
const evalValue = itemAbleRollParse(stackingParsedValue, parseModel, effect.parent);
return evalValue ?? key;
? Roll.replaceFormulaData(value, { stacks: effect.system.stacking.value })
: value;
const evalValue = this.effectSafeEval(itemAbleRollParse(stackingParsedValue, actor, item));
return evalValue ?? value;
}
/**

View file

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

View file

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

View file

@ -372,10 +372,11 @@ export const itemAbleRollParse = (value, actor, item) => {
const isItemTarget = value.toLowerCase().includes('item.@');
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 {
return Roll.replaceFormulaData(slicedValue, isItemTarget || !model?.getRollData ? model : model.getRollData());
return Roll.replaceFormulaData(slicedValue, rollData);
} catch (_) {
return '';
}
@ -809,3 +810,31 @@ export function sortBy(arr, fn) {
};
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;
}
});
}