diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index a134bb36..66301f13 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -181,7 +181,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { const feature = await cls.create({ type: 'feature', name: cls.defaultName({ type: 'feature' }), - [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.featureSubTypes[type] + [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.itemLinkFeatureTypes[type] }); await this.document.update({ 'system.features': [...this.document.system.features, feature].map(f => f.uuid) @@ -275,7 +275,9 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { 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.featureSubTypes[type] }); + await item.update({ + [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.itemLinkFeatureTypes[type] + }); if (!previouslyLinked) { const current = this.document.system.features.map(x => x.uuid); diff --git a/module/applications/sheets/items/class.mjs b/module/applications/sheets/items/class.mjs index 5edceafe..f3a7a0d3 100644 --- a/module/applications/sheets/items/class.mjs +++ b/module/applications/sheets/items/class.mjs @@ -8,8 +8,7 @@ export default class ClassSheet extends DHBaseItemSheet { classes: ['class'], position: { width: 700 }, actions: { - removeItemFromCollection: ClassSheet.#removeItemFromCollection, - removeSuggestedItem: ClassSheet.#removeSuggestedItem + removeLinkedItem: ClassSheet.#removeLinkedItem }, tagifyConfigs: [ { @@ -76,6 +75,34 @@ export default class ClassSheet extends DHBaseItemSheet { } /* -------------------------------------------- */ + /* Application Drag/Drop */ + /* -------------------------------------------- */ + async linkedItemUpdate(item, property, replace) { + const removedLinkedItems = []; + const existingLink = item.system.itemLinks[this.document.uuid]; + if (replace) { + const toRemove = this.document.system.linkedItems.find( + x => x.uuid !== item.uuid && x.system.itemLinks[this.document.uuid] === property + ); + if (toRemove) { + removedLinkedItems.push(toRemove.uuid); + await toRemove.update({ [`system.itemLinks.-=${this.document.uuid}`]: null }); + } + } + + await item.update({ [`system.itemLinks.${this.document.uuid}`]: CONFIG.DH.ITEM.itemLinkTypes[property] }); + + if (!existingLink) { + await this.document.update({ + 'system.linkedItems': [ + ...this.document.system.linkedItems.map(x => x.uuid).filter(x => !removedLinkedItems.includes(x)), + item.uuid + ] + }); + } else { + this.render(); + } + } async _onDrop(event) { event.stopPropagation(); @@ -83,6 +110,10 @@ 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; + + 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] }); @@ -90,47 +121,30 @@ export default class ClassSheet extends DHBaseItemSheet { super._onDrop(event); } else if (item.type === 'weapon') { if (target.classList.contains('primary-weapon-section')) { - if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary) - await this.document.update({ - 'system.characterGuide.suggestedPrimaryWeapon': item.uuid - }); + if (!item.system.secondary) { + await this.linkedItemUpdate(item, CONFIG.DH.ITEM.itemLinkTypes.primaryWeapon, true); + } } else if (target.classList.contains('secondary-weapon-section')) { - if (!this.document.system.characterGuide.suggestedSecondaryWeapon && item.system.secondary) - await this.document.update({ - 'system.characterGuide.suggestedSecondaryWeapon': item.uuid - }); + if (item.system.secondary) { + await this.linkedItemUpdate(item, CONFIG.DH.ITEM.itemLinkTypes.secondaryWeapon, true); + } } } else if (item.type === 'armor') { if (target.classList.contains('armor-section')) { - if (!this.document.system.characterGuide.suggestedArmor) - await this.document.update({ - 'system.characterGuide.suggestedArmor': item.uuid - }); + await this.linkedItemUpdate(item, CONFIG.DH.ITEM.itemLinkTypes.armor, true); } } else if (target.classList.contains('choice-a-section')) { if (item.type === 'miscellaneous' || item.type === 'consumable') { - if (this.document.system.inventory.choiceA.length < 2) - await this.document.update({ - 'system.inventory.choiceA': [ - ...this.document.system.inventory.choiceA.map(x => x.uuid), - item.uuid - ] - }); + if (this.document.system.choiceA.length < 2) + await this.linkedItemUpdate(item, CONFIG.DH.ITEM.itemLinkTypes.choiceA); } } else if (item.type === 'miscellaneous') { if (target.classList.contains('take-section')) { - if (this.document.system.inventory.take.length < 3) - await this.document.update({ - 'system.inventory.take': [...this.document.system.inventory.take.map(x => x.uuid), item.uuid] - }); + if (this.document.system.take.length < 3) + await this.linkedItemUpdate(item, CONFIG.DH.ITEM.itemLinkTypes.take); } else if (target.classList.contains('choice-b-section')) { - if (this.document.system.inventory.choiceB.length < 2) - await this.document.update({ - 'system.inventory.choiceB': [ - ...this.document.system.inventory.choiceB.map(x => x.uuid), - item.uuid - ] - }); + if (this.document.system.choiceB.length < 2) + await this.linkedItemUpdate(item, CONFIG.DH.ITEM.itemLinkTypes.choiceB); } } } @@ -140,23 +154,18 @@ export default class ClassSheet extends DHBaseItemSheet { /* -------------------------------------------- */ /** - * Removes an item from an class collection by UUID. + * Removes an item from class LinkedItems by uuid. * @param {PointerEvent} event - The originating click event - * @param {HTMLElement} element - The capturing HTML element which defines the [data-action="removeItemFromCollection"] + * @param {HTMLElement} element - The capturing HTML element which defines the [data-action="removeLinkedItem"] */ - static async #removeItemFromCollection(_event, element) { - const { uuid, target } = element.dataset; - const prop = foundry.utils.getProperty(this.document.system, target); - await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid) }); - } + static async #removeLinkedItem(_event, element) { + const { uuid } = element.dataset; + const item = this.document.system.linkedItems.find(x => x.uuid === uuid); + if (!item) return; - /** - * Removes an suggested item from the class. - * @param {PointerEvent} _event - The originating click event - * @param {HTMLElement} element - The capturing HTML element which defines the [data-action="removeSuggestedItem"] - */ - static async #removeSuggestedItem(_event, element) { - const { target } = element.dataset; - await this.document.update({ [`system.characterGuide.${target}`]: null }); + 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) + }); } } diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index 9deed7f1..fadcf253 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -1311,7 +1311,7 @@ export const featureTypes = { } }; -export const featureSubTypes = { +export const itemLinkFeatureTypes = { primary: 'primary', secondary: 'secondary', hope: 'hope', @@ -1321,6 +1321,20 @@ export const featureSubTypes = { mastery: 'mastery' }; +export const itemLinkItemTypes = { + primaryWeapon: 'primaryWeapon', + secondaryWeapon: 'secondaryWeapon', + armor: 'armor', + choiceA: 'choiceA', + choiceB: 'choiceB', + take: 'take' +}; + +export const itemLinkTypes = { + ...itemLinkFeatureTypes, + ...itemLinkItemTypes +}; + export const actionTypes = { passive: { id: 'passive', diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 34a1f525..2b5ee3c5 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -361,9 +361,9 @@ export default class DhCharacter extends BaseDataActor { const subclassState = this.class.subclass.system.featureState; const subType = item.system.subType; if ( - subType === CONFIG.DH.ITEM.featureSubTypes.foundation || - (subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) || - (subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3) + subType === CONFIG.DH.ITEM.itemLinkFeatureTypes.foundation || + (subType === CONFIG.DH.ITEM.itemLinkFeatureTypes.specialization && subclassState >= 2) || + (subType === CONFIG.DH.ITEM.itemLinkFeatureTypes.mastery && subclassState >= 3) ) { subclassFeatures.push(item); } diff --git a/module/data/fields/itemLinksField.mjs b/module/data/fields/itemLinksField.mjs index af61c95d..e9ebd943 100644 --- a/module/data/fields/itemLinksField.mjs +++ b/module/data/fields/itemLinksField.mjs @@ -6,7 +6,7 @@ export default class ItemLinksField extends foundry.data.fields.TypedObjectField constructor(options, context) { super( new foundry.data.fields.StringField({ - choices: CONFIG.DH.ITEM.featureSubTypes, + choices: CONFIG.DH.ITEM.itemLinkTypes, nullable: true, initial: null }), diff --git a/module/data/item/ancestry.mjs b/module/data/item/ancestry.mjs index ec0581e0..d1093590 100644 --- a/module/data/item/ancestry.mjs +++ b/module/data/item/ancestry.mjs @@ -20,12 +20,14 @@ export default class DHAncestry extends BaseDataItem { } get primaryFeature() { - return this.features.find(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.primary); + return this.features.find( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.primary + ); } get secondaryFeature() { return this.features.find( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.secondary + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.secondary ); } } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 8522c8fc..7719de44 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -10,7 +10,8 @@ export default class DHArmor extends AttachableItem { label: 'TYPES.Item.armor', type: 'armor', hasDescription: true, - isInventoryItem: true + isInventoryItem: true, + isItemLinkable: true }); } diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs index e924f292..73a7eb00 100644 --- a/module/data/item/base.mjs +++ b/module/data/item/base.mjs @@ -24,7 +24,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { hasResource: false, isQuantifiable: false, isInventoryItem: false, - isItemLinkable: true + isItemLinkable: false }; } @@ -143,13 +143,27 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel { } async _preDelete() { - if (!this.actor || this.actor.type !== 'character') return; + if (this.actor && this.actor.type === 'character') { + const items = this.actor.items.filter(item => item.system.originId === this.parent.id); + if (items.length > 0) + await this.actor.deleteEmbeddedDocuments( + 'Item', + items.map(x => x.id) + ); + } - const items = this.actor.items.filter(item => item.system.originId === this.parent.id); - if (items.length > 0) - await this.actor.deleteEmbeddedDocuments( - 'Item', - items.map(x => x.id) - ); + 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 3b2bf119..0f5f92de 100644 --- a/module/data/item/class.mjs +++ b/module/data/item/class.mjs @@ -28,12 +28,8 @@ 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 }), - inventory: new fields.SchemaField({ - take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), - choiceA: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }), - choiceB: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }) - }), characterGuide: new fields.SchemaField({ suggestedTraits: new fields.SchemaField({ agility: new fields.NumberField({ initial: 0, integer: true }), @@ -42,21 +38,54 @@ export default class DHClass extends BaseDataItem { instinct: new fields.NumberField({ initial: 0, integer: true }), presence: new fields.NumberField({ initial: 0, integer: true }), knowledge: new fields.NumberField({ initial: 0, integer: true }) - }), - suggestedPrimaryWeapon: new ForeignDocumentUUIDField({ type: 'Item' }), - suggestedSecondaryWeapon: new ForeignDocumentUUIDField({ type: 'Item' }), - suggestedArmor: new ForeignDocumentUUIDField({ type: 'Item' }) + }) }), isMulticlass: new fields.BooleanField({ initial: false }) }; } get hopeFeatures() { - return this.features.filter(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.hope); + return this.features.filter( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.hope + ); } get classFeatures() { - return this.features.filter(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.class); + return this.features.filter( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.class + ); + } + + get suggestedPrimaryWeapon() { + return this.linkedItems.find( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.primaryWeapon + ); + } + + get suggestedSecondaryWeapon() { + return this.linkedItems.find( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.secondaryWeapon + ); + } + + get suggestedArmor() { + return this.linkedItems.find(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.armor); + } + + get take() { + return this.linkedItems.filter(x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.take); + } + + get choiceA() { + return this.linkedItems.filter( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.choiceA + ); + } + + get choiceB() { + return this.linkedItems.filter( + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkTypes.choiceB + ); } async _preCreate(data, options, user) { diff --git a/module/data/item/consumable.mjs b/module/data/item/consumable.mjs index 3e70f97a..0414b360 100644 --- a/module/data/item/consumable.mjs +++ b/module/data/item/consumable.mjs @@ -9,7 +9,8 @@ export default class DHConsumable extends BaseDataItem { type: 'consumable', hasDescription: true, isQuantifiable: true, - isInventoryItem: true + isInventoryItem: true, + isItemLinkable: true }); } diff --git a/module/data/item/feature.mjs b/module/data/item/feature.mjs index c01d100c..d2627014 100644 --- a/module/data/item/feature.mjs +++ b/module/data/item/feature.mjs @@ -23,7 +23,11 @@ export default class DHFeature extends BaseDataItem { nullable: true, initial: null }), - subType: new fields.StringField({ choices: CONFIG.DH.ITEM.featureSubTypes, nullable: true, initial: null }), + subType: new fields.StringField({ + choices: CONFIG.DH.ITEM.itemLinkFeatureTypes, + nullable: true, + initial: null + }), originId: new fields.StringField({ nullable: true, initial: null }), identifier: new fields.StringField(), actions: new fields.ArrayField(new ActionField()) diff --git a/module/data/item/miscellaneous.mjs b/module/data/item/miscellaneous.mjs index cad07f48..29e3a8c2 100644 --- a/module/data/item/miscellaneous.mjs +++ b/module/data/item/miscellaneous.mjs @@ -9,7 +9,8 @@ export default class DHMiscellaneous extends BaseDataItem { type: 'miscellaneous', hasDescription: true, isQuantifiable: true, - isInventoryItem: true + isInventoryItem: true, + isItemLinkable: true }); } diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index c11a4bb2..4e0010f6 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -7,7 +7,8 @@ export default class DHSubclass extends BaseDataItem { return foundry.utils.mergeObject(super.metadata, { label: 'TYPES.Item.subclass', type: 'subclass', - hasDescription: true + hasDescription: true, + isItemLinkable: true }); } @@ -30,19 +31,19 @@ export default class DHSubclass extends BaseDataItem { get foundationFeatures() { return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.foundation + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.foundation ); } get specializationFeatures() { return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.specialization + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.specialization ); } get masteryFeatures() { return this.features.filter( - x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.featureSubTypes.mastery + x => x.system.itemLinks[this.parent.uuid] === CONFIG.DH.ITEM.itemLinkFeatureTypes.mastery ); } diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index 0d0c7f76..a01ec0a0 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -9,8 +9,8 @@ export default class DHWeapon extends AttachableItem { label: 'TYPES.Item.weapon', type: 'weapon', hasDescription: true, - isInventoryItem: true - // hasInitialAction: true + isInventoryItem: true, + isItemLinkable: true }); } diff --git a/templates/sheets/items/class/features.hbs b/templates/sheets/items/class/features.hbs index 4eecdaee..84e19a63 100644 --- a/templates/sheets/items/class/features.hbs +++ b/templates/sheets/items/class/features.hbs @@ -23,7 +23,7 @@ -
+
{{localize "TYPES.Item.subclass"}}
{{#each source.system.subclasses as |subclass index|}} @@ -36,8 +36,8 @@
diff --git a/templates/sheets/items/class/settings.hbs b/templates/sheets/items/class/settings.hbs index 756687b4..d58ab50b 100644 --- a/templates/sheets/items/class/settings.hbs +++ b/templates/sheets/items/class/settings.hbs @@ -37,45 +37,51 @@
{{localize "DAGGERHEART.ITEMS.Class.guide.suggestedPrimaryWeaponTitle"}}
{{localize "DAGGERHEART.ITEMS.Class.guide.suggestedSecondaryWeaponTitle"}}
- {{#if document.system.characterGuide.suggestedSecondaryWeapon}} -
- - {{document.system.characterGuide.suggestedSecondaryWeapon.name}} -
- + {{#with (ifThen document.system.suggestedSecondaryWeapon document.system.suggestedSecondaryWeapon null)}} + {{#if this}} +
+ + {{this.name}} +
+ +
-
- {{/if}} + {{/if}} + {{/with}}
{{localize "DAGGERHEART.ITEMS.Class.guide.suggestedArmorTitle"}}
- {{#if document.system.characterGuide.suggestedArmor}} -
- - {{document.system.characterGuide.suggestedArmor.name}} -
- + {{#with (ifThen document.system.suggestedArmor document.system.suggestedArmor null)}} + {{#if this}} +
+ + {{this.name}} +
+ +
-
- {{/if}} + {{/if}} + {{/with}}
@@ -85,12 +91,12 @@
{{localize "DAGGERHEART.GENERAL.take"}}
- {{#each source.system.inventory.take}} + {{#each source.system.take}}
{{this.name}}
- +
{{/each}} @@ -100,12 +106,12 @@
{{localize "DAGGERHEART.ITEMS.Class.guide.inventory.thenChoose"}}
- {{#each source.system.inventory.choiceA}} + {{#each source.system.choiceA}}
{{this.name}}
- +
{{/each}} @@ -115,12 +121,12 @@
{{localize "DAGGERHEART.ITEMS.Class.guide.inventory.andEither"}}
- {{#each source.system.inventory.choiceB}} + {{#each source.system.choiceB}}
{{this.name}}
- +
{{/each}}