diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index 66301f13..fff6aece 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -176,12 +176,13 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { * @type {ApplicationClickAction} */ static async #addFeature(_, target) { - const { type } = target.dataset; const cls = foundry.documents.Item.implementation; + + const featurePath = `system.itemLinks.${CONFIG.DH.ITEM.itemLinkFeatureTypes[target.dataset.type]}`; const feature = await cls.create({ type: 'feature', name: cls.defaultName({ type: 'feature' }), - [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.itemLinkFeatureTypes[type] + [featurePath]: [this.document.uuid] }); await this.document.update({ 'system.features': [...this.document.system.features, feature].map(f => f.uuid) @@ -192,10 +193,15 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { * Remove a feature from the item. * @type {ApplicationClickAction} */ - static async #deleteFeature(event, target) { + static async #deleteFeature(event, element) { + const target = element.closest('[data-item-uuid]'); const feature = getDocFromElement(target); if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing')); - await feature.update({ [`system.itemLinks.-=${this.document.uuid}`]: null }); + + const featurePath = `system.itemLinks.${CONFIG.DH.ITEM.itemLinkFeatureTypes[target.dataset.type]}`; + await feature.update({ + [featurePath]: foundry.utils.getProperty(feature, featurePath).filter(x => x !== this.document.uuid) + }); await this.document.update({ 'system.features': this.document.system.features.map(x => x.uuid).filter(uuid => uuid !== feature.uuid) }); @@ -267,23 +273,20 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { * @param {DragEvent} event - The drag event */ async _onDrop(event) { + event.stopPropagation(); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); if (data.fromInternal) return; const target = event.target.closest('fieldset.drop-section'); const item = await fromUuid(data.uuid); if (item?.type === 'feature') { - const { type } = target.dataset; - const previouslyLinked = item.system.itemLinks[this.document.uuid] !== undefined; - await item.update({ - [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.itemLinkFeatureTypes[type] - }); + const existing = await item.addItemLink(this.document.uuid, target.dataset.type, true); - if (!previouslyLinked) { + if (existing) { + this.render(); + } else { const current = this.document.system.features.map(x => x.uuid); await this.document.update({ 'system.features': [...current, item.uuid] }); - } else { - this.render(); } } } diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index f3a7a0d3..b9fc6da8 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -79,23 +79,29 @@ export default class ClassSheet extends DHBaseItemSheet { /* -------------------------------------------- */ async linkedItemUpdate(item, property, replace) { const removedLinkedItems = []; - const existingLink = item.system.itemLinks[this.document.uuid]; + const existing = Object.values(item.system.itemLinks).some(x => x.some(uuid => uuid === this.document.uuid)); if (replace) { const toRemove = this.document.system.linkedItems.find( - x => x.uuid !== item.uuid && x.system.itemLinks[this.document.uuid] === property + x => x.uuid !== item.uuid && x.system.itemLinks[property]?.has(this.document.uuid) ); if (toRemove) { removedLinkedItems.push(toRemove.uuid); - await toRemove.update({ [`system.itemLinks.-=${this.document.uuid}`]: null }); + await toRemove.update({ + [`system.itemLinks.${property}`]: toRemove.system.itemLinks[property].filter( + x => x !== this.document.uuid + ) + }); } } - await item.update({ [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.itemLinkTypes[property] }); + await item.addItemLink(this.document.uuid, property, true); - if (!existingLink) { + if (!existing) { await this.document.update({ 'system.linkedItems': [ - ...this.document.system.linkedItems.map(x => x.uuid).filter(x => !removedLinkedItems.includes(x)), + ...this.document.system.linkedItems + .filter(x => !removedLinkedItems.includes(x.uuid)) + .map(x => x.uuid), item.uuid ] }); @@ -110,13 +116,15 @@ export default class ClassSheet extends DHBaseItemSheet { const item = await fromUuid(data.uuid); const target = event.target.closest('fieldset.drop-section'); if (item.type === 'subclass') { - const previouslyLinked = item.system.itemLinks[this.document.uuid] !== undefined; - if (previouslyLinked) return; + const existing = await item.addItemLink(this.document.uuid, CONFIG.DH.ITEM.itemLinkTypes.subclass, true); - await item.update({ [`system.itemLinks.${this.document.uuid}`]: null }); - await this.document.update({ - 'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid] - }); + if (existing) { + this.render(); + } else { + await this.document.update({ + 'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid] + }); + } } else if (item.type === 'feature') { super._onDrop(event); } else if (item.type === 'weapon') { @@ -159,13 +167,16 @@ export default class ClassSheet extends DHBaseItemSheet { * @param {HTMLElement} element - The capturing HTML element which defines the [data-action="removeLinkedItem"] */ static async #removeLinkedItem(_event, element) { - const { uuid } = element.dataset; - const item = this.document.system.linkedItems.find(x => x.uuid === uuid); + const { uuid, target } = element.dataset; + const prop = target === 'subclass' ? 'subclasses' : 'linkedItems'; + const item = this.document.system[prop].find(x => x.uuid === uuid); if (!item) return; - await item.update({ [`system.itemLinks-=${uuid}`]: null }); await this.document.update({ - 'system.linkedItems': this.document.system.linkedItems.filter(x => x.uuid !== uuid).map(x => x.uuid) + [`system.${prop}`]: this.document.system[prop].filter(x => x.uuid !== uuid).map(x => x.uuid) + }); + await item.update({ + [`system.itemLinks.${target}`]: item.system.itemLinks[target].filter(x => x !== this.document.uuid) }); } } diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index fadcf253..d8c39b26 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1318,7 +1318,8 @@ export const itemLinkFeatureTypes = { class: 'class', foundation: 'foundation', specialization: 'specialization', - mastery: 'mastery' + mastery: 'mastery', + subclass: 'subclass' }; export const itemLinkItemTypes = { diff --git a/module/data/fields/itemLinksField.mjs b/module/data/fields/itemLinksField.mjs index e9ebd943..73d51dbb 100644 --- a/module/data/fields/itemLinksField.mjs +++ b/module/data/fields/itemLinksField.mjs @@ -4,15 +4,7 @@ export default class ItemLinksField extends foundry.data.fields.TypedObjectField * @param {DataFieldContext} [context] Additional context which describes the field */ constructor(options, context) { - super( - new foundry.data.fields.StringField({ - choices: CONFIG.DH.ITEM.itemLinkTypes, - nullable: true, - initial: null - }), - options, - context - ); + super(new foundry.data.fields.SetField(new foundry.data.fields.DocumentUUIDField()), options, context); } /** @inheritDoc */ @@ -24,15 +16,6 @@ export default class ItemLinksField extends foundry.data.fields.TypedObjectField * @param {Object} [value] The candidate object to be added. */ static validateKey(value) { - const parsed = foundry.utils.parseUuid(value); - if (!parsed || parsed.type !== foundry.documents.Item.documentName) return false; - if (!foundry.data.validators.isValidId(parsed.documentId)) return false; - return true; - } - - /**@inheritdoc */ - _cast(value) { - value = super._cast(value); - return foundry.utils.flattenObject(value); + return Boolean(CONFIG.DH.ITEM.itemLinkTypes[value]); } } diff --git a/module/data/item/ancestry.mjs b/module/data/item/ancestry.mjs index d1093590..d14d760d 100644 --- a/module/data/item/ancestry.mjs +++ b/module/data/item/ancestry.mjs @@ -20,14 +20,14 @@ export default class DHAncestry extends BaseDataItem { } get primaryFeature() { - return this.features.find( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.primary + return this.features.find(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.primary]?.has(this.parent.uuid) ); } get secondaryFeature() { - return this.features.find( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.secondary + return this.features.find(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.secondary]?.has(this.parent.uuid) ); } } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index 31e2a631..e24eb28d 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -171,18 +171,18 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { ); } - if (this.metadata.isItemLinkable) { - const linkEntries = Object.entries(this.itemLinks); - for (let [uuid, type] of linkEntries) { - const item = await foundry.utils.fromUuid(uuid); - const path = CONFIG.DH.ITEM.itemLinkFeatureTypes[type] ? 'system.features' : 'system.linkedItems'; - await item.update({ - [path]: foundry.utils - .getProperty(item, path) - .filter(x => x.uuid !== this.parent.uuid) - .map(x => x.uuid) - }); - } - } + // if (this.metadata.isItemLinkable) { + // const linkEntries = Object.entries(this.itemLinks); + // for (let [uuid, type] of linkEntries) { + // const item = await foundry.utils.fromUuid(uuid); + // const path = CONFIG.DH.ITEM.itemLinkFeatureTypes[type] ? 'system.features' : 'system.linkedItems'; + // await item.update({ + // [path]: foundry.utils + // .getProperty(item, path) + // .filter(x => x.uuid !== this.parent.uuid) + // .map(x => x.uuid) + // }); + // } + // } } } diff --git a/module/data/item/class.mjs b/module/data/item/class.mjs index 0f5f92de..ddb45d05 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -1,5 +1,4 @@ import BaseDataItem from './base.mjs'; -import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs'; export default class DHClass extends BaseDataItem { @@ -29,7 +28,7 @@ export default class DHClass extends BaseDataItem { evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), linkedItems: new ForeignDocumentUUIDArrayField({ type: 'Item' }), - subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), + subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item' }), characterGuide: new fields.SchemaField({ suggestedTraits: new fields.SchemaField({ agility: new fields.NumberField({ initial: 0, integer: true }), @@ -45,46 +44,50 @@ export default class DHClass extends BaseDataItem { } get hopeFeatures() { - return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.hope + return this.features.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.hope]?.has(this.parent.uuid) ); } get classFeatures() { - return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.class + return this.features.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.class]?.has(this.parent.uuid) ); } get suggestedPrimaryWeapon() { - return this.linkedItems.find( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.primaryWeapon + return this.linkedItems.find(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkItemTypes.primaryWeapon]?.has(this.parent.uuid) ); } get suggestedSecondaryWeapon() { - return this.linkedItems.find( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.secondaryWeapon + return this.linkedItems.find(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkItemTypes.secondaryWeapon]?.has(this.parent.uuid) ); } get suggestedArmor() { - return this.linkedItems.find(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.armor); + return this.linkedItems.find(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkItemTypes.armor]?.has(this.parent.uuid) + ); } get take() { - return this.linkedItems.filter(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.take); + return this.linkedItems.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkItemTypes.take]?.has(this.parent.uuid) + ); } get choiceA() { - return this.linkedItems.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.choiceA + return this.linkedItems.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkItemTypes.choiceA]?.has(this.parent.uuid) ); } get choiceB() { - return this.linkedItems.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.choiceB + return this.linkedItems.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkItemTypes.choiceB]?.has(this.parent.uuid) ); } diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index 4e0010f6..70cb1482 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -30,20 +30,20 @@ export default class DHSubclass extends BaseDataItem { } get foundationFeatures() { - return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.foundation + return this.features.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.foundation]?.has(this.parent.uuid) ); } get specializationFeatures() { - return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.specialization + return this.features.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.specialization]?.has(this.parent.uuid) ); } get masteryFeatures() { - return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.mastery + return this.features.filter(x => + x.system.itemLinks[CONFIG.DH.ITEM.itemLinkFeatureTypes.mastery]?.has(this.parent.uuid) ); } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index d8a02335..0702cd95 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -20,6 +20,27 @@ export default class DHItem extends foundry.documents.Item { for (const action of this.system.actions ?? []) action.prepareData(); } + async addItemLink(documentUuid, type, replace) { + if (!this.system.metadata.isItemLinkable) return; + + let existing = false; + if (replace) { + await this.update({ + 'system.itemLinks': Object.keys(CONFIG.DH.ITEM.itemLinkTypes).reduce((acc, key) => { + const filtered = (this.system.itemLinks[key] ?? []).filter(uuid => uuid !== documentUuid); + acc[key] = key === type ? [...filtered, documentUuid] : filtered; + + existing = existing ? existing : (this.system.itemLinks[key] ?? []).size > filtered.size; + return acc; + }, {}) + }); + } else { + await this.update({ [`system.itemLinks.${type}`]: [...(this.system.itemLinks[type] ?? []), documentUuid] }); + } + + return existing; + } + /** * @inheritdoc * @param {object} options - Options which modify the getRollData method. diff --git a/templates/sheets/global/partials/feature-section-item.hbs b/templates/sheets/global/partials/feature-section-item.hbs index 238031c5..a6cc3d5d 100644 --- a/templates/sheets/global/partials/feature-section-item.hbs +++ b/templates/sheets/global/partials/feature-section-item.hbs @@ -1,4 +1,4 @@ -