diff --git a/module/applications/sheets/api/item-attachment-sheet.mjs b/module/applications/sheets/api/item-attachment-sheet.mjs index 9cfc22d8..c4677ac4 100644 --- a/module/applications/sheets/api/item-attachment-sheet.mjs +++ b/module/applications/sheets/api/item-attachment-sheet.mjs @@ -1,8 +1,3 @@ -import { - removeAttachmentFromItem, - prepareAttachmentContext, - addAttachmentToItem -} from '../../../helpers/attachmentHelper.mjs'; export default function ItemAttachmentSheet(Base) { return class extends Base { @@ -62,5 +57,149 @@ export default function ItemAttachmentSheet(Base) { parentType: this.document.type }); } + + async removeAttachmentEffectsFromActor({ parentItem, attachedUuid, parentType }) { + const actor = parentItem.parent; + if (!actor) return; + + const parentUuidProperty = `${parentType}Uuid`; + const effectsToRemove = actor.effects.filter(effect => { + const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource); + return attachmentSource && + attachmentSource[parentUuidProperty] === parentItem.uuid && + attachmentSource.itemUuid === attachedUuid; + }); + + if (effectsToRemove.length > 0) { + await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id)); + } + } + + async removeAttachmentFromItem({ parentItem, attachedUuid, parentType }) { + const currentAttached = parentItem.system.attached; + + // Remove the attachment from the parent item's attached array + await parentItem.update({ + 'system.attached': currentAttached.filter(uuid => uuid !== attachedUuid) + }); + + // Remove any effects that came from this attachment + await removeAttachmentEffectsFromActor({ + parentItem, + attachedUuid, + parentType + }); + } + async prepareAttachmentContext(parentItem) { + const attachedUUIDs = parentItem.system.attached; + return await Promise.all( + attachedUUIDs.map(async uuid => { + const item = await fromUuid(uuid); + return { + uuid: uuid, + name: item?.name || 'Unknown Item', + img: item?.img || 'icons/svg/item-bag.svg' + }; + }) + ); + } + + async addAttachmentToItem({ parentItem, droppedItem, parentType }) { + const currentAttached = parentItem.system.attached; + const newUUID = droppedItem.uuid; + + if (currentAttached.includes(newUUID)) { + ui.notifications.warn(`${droppedItem.name} is already attached to this ${parentType}.`); + return; + } + + const updatedAttached = [...currentAttached, newUUID]; + + await parentItem.update({ + 'system.attached': updatedAttached + }); + + // Copy effects from attached item to actor (only if parent item is equipped) + const actor = parentItem.parent; + if (actor && droppedItem.effects.size > 0 && parentItem.system.equipped) { + await copyAttachmentEffectsToActor({ + parentItem, + attachedItem: droppedItem, + attachedUuid: newUUID, + parentType + }); + } + } + + async copyAttachmentEffectsToActor({ parentItem, attachedItem, attachedUuid, parentType }) { + const actor = parentItem.parent; + if (!actor || !attachedItem.effects.size > 0 || !parentItem.system.equipped) { + return []; + } + + const effectsToCreate = []; + for (const effect of attachedItem.effects) { + // Copy ALL effects when item is attached - attachment-only flag only matters for non-attached items + const effectData = effect.toObject(); + effectData.origin = `${parentItem.uuid}:${attachedUuid}`; + + // Set up attachment source metadata with the appropriate property name + const attachmentSource = { + itemUuid: attachedUuid, + originalEffectId: effect.id + }; + attachmentSource[`${parentType}Uuid`] = parentItem.uuid; + + effectData.flags = { + ...effectData.flags, + [CONFIG.DH.id]: { + ...effectData.flags?.[CONFIG.DH.id], + [CONFIG.DH.FLAGS.itemAttachmentSource]: attachmentSource + } + }; + effectsToCreate.push(effectData); + } + + if (effectsToCreate.length > 0) { + return await actor.createEmbeddedDocuments('ActiveEffect', effectsToCreate); + } + + return []; + } + + async handleAttachmentEffectsOnEquipChange({ parentItem, newEquippedStatus, parentType }) { + // Try to get the actor - it might be parentItem.parent instead of parentItem.parent.parent + const actor = parentItem.parent?.type === 'character' ? parentItem.parent : parentItem.parent?.parent; + + if (!actor || !parentItem.system.attached?.length) { + return; + } + + if (newEquippedStatus) { + // Item is being equipped - add attachment effects + for (const attachedUuid of parentItem.system.attached) { + const attachedItem = await fromUuid(attachedUuid); + if (attachedItem && attachedItem.effects.size > 0) { + this.copyAttachmentEffectsToActor({ + parentItem, + attachedItem, + attachedUuid, + parentType + }); + } + } + } else { + // Item is being unequipped - remove attachment effects + const parentUuidProperty = `${parentType}Uuid`; + const effectsToRemove = actor.effects.filter(effect => { + const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource); + return attachmentSource && attachmentSource[parentUuidProperty] === parentItem.uuid; + }); + + if (effectsToRemove.length > 0) { + await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id)); + } + } + } }; } \ No newline at end of file diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 413b63ab..7323bcab 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -2,7 +2,6 @@ import AttachableItem from './attachableItem.mjs'; import ActionField from '../fields/actionField.mjs'; import { armorFeatures } from '../../config/itemConfig.mjs'; import { actionsTypes } from '../action/_module.mjs'; -import { handleAttachmentEffectsOnEquipChange } from '../../helpers/attachmentHelper.mjs'; export default class DHArmor extends AttachableItem { /** @inheritDoc */ diff --git a/module/data/item/attachableItem.mjs b/module/data/item/attachableItem.mjs index 945e750a..90fd50fd 100644 --- a/module/data/item/attachableItem.mjs +++ b/module/data/item/attachableItem.mjs @@ -1,5 +1,4 @@ import BaseDataItem from './base.mjs'; -import { handleAttachmentEffectsOnEquipChange } from '../../helpers/attachmentHelper.mjs'; export default class AttachableItem extends BaseDataItem { static defineSchema() { @@ -9,6 +8,77 @@ export default class AttachableItem extends BaseDataItem { attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: "Item", nullable: true })) }; } + + async copyAttachmentEffectsToActor({ parentItem, attachedItem, attachedUuid, parentType }) { + const actor = parentItem.parent; + if (!actor || !attachedItem.effects.size > 0 || !parentItem.system.equipped) { + return []; + } + + const effectsToCreate = []; + for (const effect of attachedItem.effects) { + // Copy ALL effects when item is attached - attachment-only flag only matters for non-attached items + const effectData = effect.toObject(); + effectData.origin = `${parentItem.uuid}:${attachedUuid}`; + + // Set up attachment source metadata with the appropriate property name + const attachmentSource = { + itemUuid: attachedUuid, + originalEffectId: effect.id + }; + attachmentSource[`${parentType}Uuid`] = parentItem.uuid; + + effectData.flags = { + ...effectData.flags, + [CONFIG.DH.id]: { + ...effectData.flags?.[CONFIG.DH.id], + [CONFIG.DH.FLAGS.itemAttachmentSource]: attachmentSource + } + }; + effectsToCreate.push(effectData); + } + + if (effectsToCreate.length > 0) { + return await actor.createEmbeddedDocuments('ActiveEffect', effectsToCreate); + } + + return []; + } + + async handleAttachmentEffectsOnEquipChange({ parentItem, newEquippedStatus, parentType }) { + // Try to get the actor - it might be parentItem.parent instead of parentItem.parent.parent + const actor = parentItem.parent?.type === 'character' ? parentItem.parent : parentItem.parent?.parent; + + if (!actor || !parentItem.system.attached?.length) { + return; + } + + if (newEquippedStatus) { + // Item is being equipped - add attachment effects + for (const attachedUuid of parentItem.system.attached) { + const attachedItem = await fromUuid(attachedUuid); + if (attachedItem && attachedItem.effects.size > 0) { + this.copyAttachmentEffectsToActor({ + parentItem, + attachedItem, + attachedUuid, + parentType + }); + } + } + } else { + // Item is being unequipped - remove attachment effects + const parentUuidProperty = `${parentType}Uuid`; + const effectsToRemove = actor.effects.filter(effect => { + const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource); + return attachmentSource && attachmentSource[parentUuidProperty] === parentItem.uuid; + }); + + if (effectsToRemove.length > 0) { + await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id)); + } + } + } async _preUpdate(changes, options, user) { const allowed = await super._preUpdate(changes, options, user); @@ -16,7 +86,7 @@ export default class AttachableItem extends BaseDataItem { // Handle equipped status changes for attachment effects if (changes.system?.equipped !== undefined && changes.system.equipped !== this.equipped) { - await handleAttachmentEffectsOnEquipChange({ + await this.handleAttachmentEffectsOnEquipChange({ parentItem: this.parent, newEquippedStatus: changes.system.equipped, parentType: this.parent.type