diff --git a/changelog.md b/changelog.md index 1e9d6a7b..d8dc3cf3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,15 @@ # Item Piles Changelog +## Version 3.0.9 + +- Added support for secretly giving items to others via the right-click context menu on items on the D&D5e system +- Fixed some systems not allowing players to preview items by clicking on the names in item piles when the player had no permissions configured on the item pile +- Fixed item pile on interact macros not working when utilizing module or system macro compendiums +- Fixed updating item piles tokens would sometimes reset it to a default non-item pile token +- Fixed item piles not being deleted after being emptied even though they were configured to be deleted +- Fixed being unable to switch inspecting character in item piles +- Fixed some interfaces being broken when other modules were active due to them declaring global CSS classes + ## Version 3.0.8 - Fixed not being able to drop items onto scenes (thanks diwako on github!) diff --git a/languages/en.json b/languages/en.json index 318a239c..2173ecd0 100644 --- a/languages/en.json +++ b/languages/en.json @@ -267,6 +267,7 @@ "Header": "Giving Item: {item_name}", "SelectPlaceholder": "Pick character to send item to", "ContentMultipleQuantity": "You have {quantity} of this item, how many do you want to give away?", + "Secret": "Give without letting others know (except GMs)", "Submit": "Send" }, "ReceiveItem": { diff --git a/src/API/chat-api.js b/src/API/chat-api.js index 0b95f2b2..00cad161 100644 --- a/src/API/chat-api.js +++ b/src/API/chat-api.js @@ -8,7 +8,7 @@ import TradeAPI from "./trade-api.js"; export default class ChatAPI { - static CHAT_MESSAGE_STYLES = CONSTANTS.IS_V12 ? CONST.CHAT_MESSAGE_STYLES : CONST.CHAT_MESSAGE_TYPES; + static CHAT_MESSAGE_STYLES = CONSTANTS.IS_V12 ? CONST.CHAT_MESSAGE_TYPES : CONST.CHAT_MESSAGE_STYLES; static initialize() { @@ -152,12 +152,14 @@ export default class ChatAPI { * @param target * @param item * @param userId + * @param targetUserId + * @param secret * @returns {Promise} */ - static async _outputGiveItem(source, target, item, userId) { + static async _outputGiveItem(source, target, item, userId, targetUserId, secret) { if (game.user.id !== userId || !Helpers.getSetting(SETTINGS.OUTPUT_TO_CHAT)) return; const [itemData, itemCurrencies] = await this._formatItemData(source, [item]); - return this._giveChatMessage(source, target, itemData.concat(itemCurrencies), userId); + return this._giveChatMessage(source, target, itemData.concat(itemCurrencies), userId, targetUserId, secret); } /** @@ -434,7 +436,7 @@ export default class ChatAPI { return this._createNewChatMessage(game.user.id, { user: game.user.id, - type: isPrivate ? CONST.CHAT_MESSAGE_STYLES.WHISPER : ChatAPI.CHAT_MESSAGE_STYLES.OTHER, + type: isPrivate ? ChatAPI.CHAT_MESSAGE_STYLES.WHISPER : ChatAPI.CHAT_MESSAGE_STYLES.OTHER, content: chatCardHtml, flavor: "Item Piles" + (isPrivate ? ": " + game.i18n.localize("ITEM-PILES.Chat.PrivateTrade") : ""), speaker: ChatMessage.getSpeaker({ alias: game.user.name }), @@ -495,7 +497,7 @@ export default class ChatAPI { } - static async _giveChatMessage(source, target, items) { + static async _giveChatMessage(source, target, items, userId, targetUserId, secret) { const now = (+new Date()); @@ -510,7 +512,7 @@ export default class ChatAPI { for (const message of messages) { const flags = foundry.utils.getProperty(message, CONSTANTS.FLAGS.PILE); - if (flags && flags.version && !foundry.utils.isNewerVersion(Helpers.getModuleVersion(), flags.version) && flags.source === sourceActor.uuid && flags.target === targetActor.uuid && message.isAuthor) { + if (flags && flags.version && !foundry.utils.isNewerVersion(Helpers.getModuleVersion(), flags.version) && flags.source === sourceActor.uuid && flags.target === targetActor.uuid && message.isAuthor && (flags.secret === undefined || flags.secret === secret)) { return this._updateExistingGiveMessage(message, sourceActor, targetActor, items) } } @@ -522,19 +524,33 @@ export default class ChatAPI { items: items }); - return this._createNewChatMessage(game.user.id, { - user: game.user.id, + const user = game.users.get(userId); + + const chatData = { + user: user.id, type: ChatAPI.CHAT_MESSAGE_STYLES.OTHER, content: chatCardHtml, flavor: "Item Piles", - speaker: ChatMessage.getSpeaker({ alias: game.user.name }), + speaker: ChatMessage.getSpeaker({ alias: user.name }), [CONSTANTS.FLAGS.PILE]: { version: Helpers.getModuleVersion(), source: sourceActor.uuid, target: targetActor.uuid, - items: items + items: items, + secret } - }) + } + + if (secret) { + chatData.whisper = Array.from(game.users) + .filter(user => user.isGM) + .map(user => user.id); + chatData.whisper.push(userId); + chatData.whisper.push(targetUserId); + chatData.type = ChatAPI.CHAT_MESSAGE_STYLES.WHISPER; + } + + return this._createNewChatMessage(user.id, chatData) } @@ -631,9 +647,11 @@ export default class ChatAPI { if (mode === 2) { chatData.whisper.push(userId); } - chatData.type = CONST.CHAT_MESSAGE_STYLES.WHISPER; + chatData.type = ChatAPI.CHAT_MESSAGE_STYLES.WHISPER; } + } else if (chatData.whisper.length) { + chatData.whisper = Array.from(new Set(chatData.whisper)); } return ChatMessage.create(chatData); diff --git a/src/API/private-api.js b/src/API/private-api.js index 6dc9c0ba..89df11d5 100644 --- a/src/API/private-api.js +++ b/src/API/private-api.js @@ -151,11 +151,12 @@ export default class PrivateAPI { } static _onPreUpdateToken(doc, changes) { - if (!foundry.utils.hasProperty(changes, "actorLink")) return; + const diff = foundry.utils.diffObject(doc, changes); + if (!foundry.utils.hasProperty(diff, "actorLink")) return; if (!PileUtilities.isValidItemPile(doc)) return; const flagData = PileUtilities.getActorFlagData(doc); const cleanFlagData = PileUtilities.cleanFlagData(flagData); - changes[CONSTANTS.FLAGS.PILE] = doc.actorLink ? cleanFlagData : null; + changes[CONSTANTS.FLAGS.PILE] = diff.actorLink ? cleanFlagData : null; } /** @@ -1477,7 +1478,8 @@ export default class PrivateAPI { * @private */ static async _evaluateItemPileChange(doc, changes = {}, force = false) { - const duplicatedChanges = foundry.utils.deepClone(changes); + const diff = foundry.utils.diffObject(doc, changes); + const duplicatedChanges = foundry.utils.deepClone(diff); const target = doc?.token ?? doc; if (!Helpers.isResponsibleGM()) return; if (!force && !PileUtilities.shouldEvaluateChange(target, duplicatedChanges)) return; @@ -1875,13 +1877,13 @@ export default class PrivateAPI { Helpers.custom_notify(game.i18n.format("ITEM-PILES.Notifications.ItemTransferred", { source_actor_name: sourceActor.name, target_actor_name: targetActor.name, item_name: item.name })); - Hooks.callAll(CONSTANTS.HOOKS.ITEM.GIVE, sourceActor, targetActor, dropData.itemData, game.user.id); + Hooks.callAll(CONSTANTS.HOOKS.ITEM.GIVE, sourceActor, targetActor, dropData.itemData, game.user.id, game.user.id, dropData?.secret); return this._transferItems(sourceUuid, targetUuid, [dropData.itemData], game.user.id) } } return ItemPileSocket.executeForUsers(ItemPileSocket.HANDLERS.GIVE_ITEMS, [user ? user.id : gms[0]], { - userId: game.user.id, sourceUuid, targetUuid, itemData: dropData.itemData + userId: game.user.id, sourceUuid, targetUuid, itemData: dropData.itemData, secret: dropData?.secret }); } } @@ -1911,10 +1913,10 @@ export default class PrivateAPI { } - static async _giveItemsResponse({ userId, accepted, sourceUuid, targetUuid, itemData } = {}) { + static async _giveItemsResponse({ userId, accepted, sourceUuid, targetUuid, itemData, secret } = {}) { const user = game.users.get(userId); if (accepted) { - await ItemPileSocket.callHook(CONSTANTS.HOOKS.ITEM.GIVE, sourceUuid, targetUuid, itemData, game.user.id, userId) + await ItemPileSocket.callHook(CONSTANTS.HOOKS.ITEM.GIVE, sourceUuid, targetUuid, itemData, game.user.id, userId, secret) await PrivateAPI._removeItems(sourceUuid, [itemData], game.user.id); return Helpers.custom_notify(game.i18n.format("ITEM-PILES.Notifications.GiveItemAccepted", { user_name: user.name })); } diff --git a/src/applications/components/ActorPicker.svelte b/src/applications/components/ActorPicker.svelte index a0aae8c2..ee68af1d 100644 --- a/src/applications/components/ActorPicker.svelte +++ b/src/applications/components/ActorPicker.svelte @@ -10,19 +10,22 @@ let editQuantities = store.editQuantities; let changingActor = false; - let playerActors = game.actors.filter(actor => actor.isOwner && actor !== store.pileActor && actor.prototypeToken.actorLink); + let playerActors = game.actors.filter(actor => actor.isOwner && actor !== store.actor && actor.prototypeToken.actorLink); let recipientUuid = Utilities.getUuid(store.recipient); const recipientDoc = store.recipientDocument; $: { $recipientDoc; - recipientUuid = store.recipient ? Utilities.getUuid(store.recipient) : false; + if (!changingActor) { + recipientUuid = store.recipient ? Utilities.getUuid(store.recipient) : false; + } } function changeRecipientActor() { - store.recipient = playerActors.find(actor => Utilities.getUuid(actor) === recipientUuid); - store.update(); + const newRecipient = playerActors.find(actor => Utilities.getUuid(actor) === recipientUuid); changingActor = false; + if (recipientUuid === store.recipient.uuid) return; + store.updateRecipient(newRecipient) } diff --git a/src/applications/dialogs/give-items-dialog/give-items-shell.svelte b/src/applications/dialogs/give-items-dialog/give-items-shell.svelte index 0f379cc7..aea87960 100644 --- a/src/applications/dialogs/give-items-dialog/give-items-shell.svelte +++ b/src/applications/dialogs/give-items-dialog/give-items-shell.svelte @@ -17,23 +17,26 @@ const canItemStack = PileUtilities.canItemStack(item); let quantity = 1; + let secret = false; let selectedActor = localStorage.getItem("item-piles-give-item") ?? false; let items = Array.from(game.actors) .filter(actor => { - return (actor.type === "character" || actor.type === "npc") - && actor !== item.parent - && !PileUtilities.isValidItemPile(actor) - && (game.user.isGM || (actor.ownership["default"] >= 1 || actor.ownership[game.user.id] >= 1)) + return game.user.isGM || (actor.ownership["default"] >= 1 || actor.ownership[game.user.id] >= 1); + }) + .concat(game.users.map(user => user.character).filter(Boolean)) + .filter(actor => actor !== item.parent) + .filter((actor, index, self) => { + return index === self.findIndex(a => a.uuid === actor.uuid) }) .map(actor => ({ value: actor.uuid, label: actor.name, actor, - group: actor.hasPlayerOwner ? "Player Characters" : "Other Characters" + group: actor.hasPlayerOwner ? "Player Characters" : "Unassigned Characters" })) .sort((a, b) => { - return a.name > b.name ? ((b.actor.hasPlayerOwner - a.actor.hasPlayerOwner) - 1) : ((b.actor.hasPlayerOwner - a.actor.hasPlayerOwner) + 1); + return (a.group >= b.group ? 100000 : -100000) + (a.label >= b.label ? 1 : -1); }); if (selectedActor && !items.some(data => data.value === selectedActor)) { @@ -48,7 +51,7 @@ function submit() { localStorage.setItem("item-piles-give-item", selectedActor.value) - application.options.resolve({ quantity, target: selectedActor.value }); + application.options.resolve({ quantity, secret, target: selectedActor.value }); application.close(); } @@ -73,7 +76,6 @@ {/if} -
+
+ + +
+ - - diff --git a/src/applications/item-editor/item-editor-shell.svelte b/src/applications/item-editor/item-editor-shell.svelte index 393465e2..45316f7b 100644 --- a/src/applications/item-editor/item-editor-shell.svelte +++ b/src/applications/item-editor/item-editor-shell.svelte @@ -71,9 +71,9 @@ { value: "vault", label: localize("ITEM-PILES.Applications.ItemEditor.Vault") }, ]}/> -
+
-
+
{#if activeTab === 'general'} diff --git a/src/applications/item-pile-inventory-app/item-pile-inventory-shell.svelte b/src/applications/item-pile-inventory-app/item-pile-inventory-shell.svelte index 640c857f..79815333 100644 --- a/src/applications/item-pile-inventory-app/item-pile-inventory-shell.svelte +++ b/src/applications/item-pile-inventory-app/item-pile-inventory-shell.svelte @@ -176,13 +176,13 @@ {/if}