Compare commits

...

5 commits

Author SHA1 Message Date
Carlos Fernandez
6d09c5504d
[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>
2026-04-26 00:06:41 +02:00
WBHarry
c82bcbeb01 Linted 2026-04-25 22:50:59 +02:00
WBHarry
d0afee59d8 Corrected secondaryWeapon translation 2026-04-25 18:51:49 +02:00
WBHarry
4d17a7d9bf Fixed that new weapons couldn't be created on the Character Sheet 2026-04-25 17:45:50 +02:00
WBHarry
b8e00b2807 Translation fixes 2026-04-25 17:39:20 +02:00
11 changed files with 82 additions and 45 deletions

View file

@ -725,7 +725,7 @@ export default function DHApplicationMixin(Base) {
: null : null
: this.document; : this.document;
let systemData = {}; let systemData = null;
if (featureOnCharacter) { if (featureOnCharacter) {
systemData = { systemData = {
originItemType: this.document.type, originItemType: this.document.type,
@ -738,10 +738,11 @@ export default function DHApplicationMixin(Base) {
const data = { const data = {
name: cls.defaultName({ type, parent }), name: cls.defaultName({ type, parent }),
type, type
system: systemData
}; };
if (systemData) data.system = systemData;
if (inVault) data['system.inVault'] = true; if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true; if (disabled) data.disabled = true;
if (type === 'domainCard' && parent?.system.domains?.length) { if (type === 'domainCard' && parent?.system.domains?.length) {

View file

@ -177,8 +177,8 @@ export const typeConfig = {
key: 'system.secondary', key: 'system.secondary',
label: 'DAGGERHEART.UI.ItemBrowser.subtype', label: 'DAGGERHEART.UI.ItemBrowser.subtype',
choices: [ choices: [
{ value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon' }, { value: false, label: 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.full' },
{ value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' } { value: true, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full' }
] ]
}, },
{ {

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

@ -28,7 +28,10 @@ export default class DHWeapon extends AttachableItem {
equipped: new fields.BooleanField({ initial: false }), equipped: new fields.BooleanField({ initial: false }),
//SETTINGS //SETTINGS
secondary: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon' }), secondary: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full'
}),
burden: new fields.StringField({ burden: new fields.StringField({
required: true, required: true,
choices: CONFIG.DH.GENERAL.burden, choices: CONFIG.DH.GENERAL.burden,

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;
}
});
}

View file

@ -32,7 +32,7 @@
<footer class="form-footer"> <footer class="form-footer">
<button data-action="reset"> <button data-action="reset">
<i class="fa-solid fa-arrow-rotate-left"></i> <i class="fa-solid fa-arrow-rotate-left"></i>
<span>{{localize "SETTINGS.UI.ACTIONS.ResetReset"}}</span> <span>{{localize "SETTINGS.UI.ACTIONS.Reset"}}</span>
</button> </button>
<button data-action="save" > <button data-action="save" >
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk"></i>

View file

@ -66,7 +66,7 @@
{{#if (and combats.length user.isGM)}} {{#if (and combats.length user.isGM)}}
<div class="encounter-battlepoints" data-tooltip="#battlepoints#" data-combat-id="{{combat.id}}"> <div class="encounter-battlepoints" data-tooltip="#battlepoints#" data-combat-id="{{combat.id}}">
{{battlepoints.current}}/{{battlepoints.max}} BP{{#if battlepoints.hasModifierBP}}*{{/if}} {{battlepoints.current}}/{{battlepoints.max}} {{localize "DAGGERHEART.GENERAL.Battlepoints.short"}}{{#if battlepoints.hasModifierBP}}*{{/if}}
</div> </div>
{{/if}} {{/if}}