diff --git a/changelog.md b/changelog.md index c4b55c19..512d2812 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Item Piles Changelog +## Version 1.4.4 + +- Improved splitting API functions to improve performance when playing on Forge +- Improved documentation to better describe what each API method requires +- Tweaked `Split n ways` button to disable itself instead of becoming hidden +- Tweaked system recognition to allow systems to set the required settings through the API, which suppresses the system incompatibility warning +- Fixed various bugs surrounding splitting item piles +- Fixed issue with the `Split n ways` button not working sometimes + ## Version 1.4.3 - Fixed minor issue with creating item piles diff --git a/scripts/api.js b/scripts/api.js index 3f28eb50..29d19d44 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -6,12 +6,14 @@ import DropItemDialog from "./formapplications/drop-item-dialog.js"; import HOOKS from "./hooks.js"; import { hotkeyState } from "./hotkeys.js"; -const API = { +const preloadedFiles = new Set(); + +export default { /** * The actor class type used for the original item pile actor in this system * - * @returns {string} + * @returns {String} */ get ACTOR_CLASS_TYPE() { return game.settings.get(CONSTANTS.MODULE_NAME, "actorClassType"); @@ -20,7 +22,7 @@ const API = { /** * The currencies used in this system * - * @returns {array} + * @returns {Array<{name: String, currency: String, img: String}>} */ get CURRENCIES() { return game.settings.get(CONSTANTS.MODULE_NAME, "currencies"); @@ -29,7 +31,7 @@ const API = { /** * The attribute used to track the quantity of items in this system * - * @returns {string} + * @returns {String} */ get ITEM_QUANTITY_ATTRIBUTE() { return game.settings.get(CONSTANTS.MODULE_NAME, "itemQuantityAttribute"); @@ -38,7 +40,7 @@ const API = { /** * The filters for item types eligible for interaction within this system * - * @returns {Array} + * @returns {Array<{name: String, filters: String}>} */ get ITEM_FILTERS() { return lib.cleanItemFilters(game.settings.get(CONSTANTS.MODULE_NAME, "itemFilters")); @@ -47,7 +49,7 @@ const API = { /** * The attributes for detecting item similarities * - * @returns {Array} + * @returns {Array} */ get ITEM_SIMILARITIES() { return game.settings.get(CONSTANTS.MODULE_NAME, "itemSimilarities"); @@ -56,20 +58,21 @@ const API = { /** * Sets the actor class type used for the original item pile actor in this system * - * @param {string} inClassType + * @param {String} inClassType * @returns {Promise} */ async setActorClassType(inClassType) { if (typeof inClassType !== "string") { throw lib.custom_error("setActorTypeClass | inClassType must be of type string"); } + await game.settings.set(CONSTANTS.MODULE_NAME, "preconfiguredSystem", true); return game.settings.set(CONSTANTS.MODULE_NAME, "actorClassType", inClassType); }, /** * Sets the currencies used in this system * - * @param {array} inCurrencies + * @param {Array<{name: String, currency: String, img: String}>} inCurrencies * @returns {Promise} */ async setCurrencies(inCurrencies) { @@ -90,26 +93,28 @@ const API = { throw lib.custom_error("setCurrencies | currency.img must be of type string"); } }) + await game.settings.set(CONSTANTS.MODULE_NAME, "preconfiguredSystem", true); return game.settings.set(CONSTANTS.MODULE_NAME, "currencies", inCurrencies); }, /** * Sets the inAttribute used to track the quantity of items in this system * - * @param {string} inAttribute + * @param {String} inAttribute * @returns {Promise} */ async setItemQuantityAttribute(inAttribute) { if (typeof inAttribute !== "string") { throw lib.custom_error("setItemQuantityAttribute | inAttribute must be of type string"); } + await game.settings.set(CONSTANTS.MODULE_NAME, "preconfiguredSystem", true); return game.settings.set(CONSTANTS.MODULE_NAME, "itemQuantityAttribute", inAttribute); }, /** * Sets the items filters for interaction within this system * - * @param {array} inFilters + * @param {Array<{path: String, filters: String}>} inFilters * @returns {Promise} */ async setItemFilters(inFilters) { @@ -124,13 +129,14 @@ const API = { throw lib.custom_error("setItemFilters | each entry in inFilters must have a \"filters\" property with a value that is of type string"); } }); + await game.settings.set(CONSTANTS.MODULE_NAME, "preconfiguredSystem", true); return game.settings.set(CONSTANTS.MODULE_NAME, "itemFilters", inFilters); }, /** * Sets the attributes for detecting item similarities * - * @param {array} inPaths + * @param {Array} inPaths * @returns {Promise} */ async setItemSimilarities(inPaths) { @@ -142,16 +148,17 @@ const API = { throw lib.custom_error("setItemSimilarities | each entry in inPaths must be of type string"); } }); + await game.settings.set(CONSTANTS.MODULE_NAME, "preconfiguredSystem", true); return game.settings.set(CONSTANTS.MODULE_NAME, "itemSimilarities", inPaths); }, /** * Creates the default item pile token at a location. * - * @param {object} position The position to create the item pile at - * @param {string/boolean} [sceneId=false] Which scene to create the item pile on - * @param {array/boolean} [items=false] Any items to create on the item pile - * @param {string/boolean} [pileActorName=false] Whether to use an existing item pile actor as the basis of this new token + * @param {Object} position The position to create the item pile at + * @param {String/Boolean} [sceneId=false] Which scene to create the item pile on + * @param {Array/Boolean} [items=false] Any items to create on the item pile + * @param {String/Boolean} [pileActorName=false] Whether to use an existing item pile actor as the basis of this new token * * @returns {Promise} */ @@ -184,8 +191,8 @@ const API = { * Turns tokens and its actors into item piles * * @param {Token/TokenDocument/Array} targets The targets to be turned into item piles - * @param {object} pileSettings Overriding settings to be put on the item piles' settings - * @param {object} tokenSettings Overriding settings that will update the tokens' settings + * @param {Object} pileSettings Overriding settings to be put on the item piles' settings + * @param {Object} tokenSettings Overriding settings that will update the tokens' settings * * @return {Promise} The uuids of the targets after they were turned into item piles */ @@ -278,7 +285,7 @@ const API = { * Reverts tokens from an item pile into a normal token and actor * * @param {Token/TokenDocument/Array} targets The targets to be reverted from item piles - * @param {object} tokenSettings Overriding settings that will update the tokens + * @param {Object} tokenSettings Overriding settings that will update the tokens * * @return {Promise} The uuids of the targets after they were reverted from being item piles */ @@ -359,7 +366,7 @@ const API = { * Opens a pile if it is enabled and a container * * @param {Token/TokenDocument} target - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -389,7 +396,7 @@ const API = { * Closes a pile if it is enabled and a container * * @param {Token/TokenDocument} target Target pile to close - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -413,7 +420,7 @@ const API = { * Toggles a pile's closed state if it is enabled and a container * * @param {Token/TokenDocument} target Target pile to open or close - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -435,7 +442,7 @@ const API = { * Locks a pile if it is enabled and a container * * @param {Token/TokenDocument} target Target pile to lock - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -464,7 +471,7 @@ const API = { * Unlocks a pile if it is enabled and a container * * @param {Token/TokenDocument} target Target pile to unlock - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -483,7 +490,7 @@ const API = { * Toggles a pile's locked state if it is enabled and a container * * @param {Token/TokenDocument} target Target pile to lock or unlock - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -503,7 +510,7 @@ const API = { * Causes the item pile to play a sound as it was attempted to be opened, but was locked * * @param {Token/TokenDocument} target - * @param {Token/TokenDocument/boolean} [interactingToken=false] + * @param {Token/TokenDocument/Boolean} [interactingToken=false] * * @return {Promise} */ @@ -526,7 +533,7 @@ const API = { * * @param {Token/TokenDocument} target * - * @return {boolean} + * @return {Boolean} */ isItemPileLocked(target) { const targetDocument = lib.getDocument(target); @@ -540,7 +547,7 @@ const API = { * * @param {Token/TokenDocument} target * - * @return {boolean} + * @return {Boolean} */ isItemPileClosed(target) { const targetDocument = lib.getDocument(target); @@ -554,7 +561,7 @@ const API = { * * @param {Token/TokenDocument} target * - * @return {boolean} + * @return {Boolean} */ isItemPileContainer(target) { const targetDocument = lib.getDocument(target); @@ -566,9 +573,9 @@ const API = { * Updates a pile with new data. * * @param {Token/TokenDocument} target - * @param {object} newData - * @param {Token/TokenDocument/boolean} [interactingToken=false] - * @param {object/boolean} [tokenSettings=false] + * @param {Object} newData + * @param {Token/TokenDocument/Boolean} [interactingToken=false] + * @param {Object/Boolean} [tokenSettings=false] * * @return {Promise} */ @@ -712,9 +719,9 @@ const API = { * Remotely opens an item pile's inventory, if you have permission to edit the item pile. Passing a user ID, or a list of user IDs, will cause those users to open the item pile. * * @param {Token/TokenDocument/Actor} target The item pile actor or token whose inventory to open - * @param {array} userIds The IDs of the users that should open this item pile inventory + * @param {Array} userIds The IDs of the users that should open this item pile inventory * @param {boolean/Token/TokenDocument/Actor} [inspectingTarget=false] This will force the users to inspect this item pile as a specific character - * @param {boolean} [useDefaultCharacter=true] Causes the users to inspect the item pile inventory as their default character + * @param {Boolean} [useDefaultCharacter=true] Causes the users to inspect the item pile inventory as their default character * @returns {Promise} */ async renderItemPileInterface(target, userIds = [game.user.id], { @@ -777,7 +784,7 @@ const API = { * Whether a given document is a valid pile or not * * @param {Token/TokenDocument/Actor} document - * @return {boolean} + * @return {Boolean} */ isValidItemPile(document) { return lib.isValidItemPile(document); @@ -787,7 +794,7 @@ const API = { * Whether the item pile is empty * * @param {Token/TokenDocument/Actor} target - * @returns {boolean} + * @returns {Boolean} */ isItemPileEmpty(target) { return lib.isItemPileEmpty(target); @@ -797,7 +804,7 @@ const API = { * Returns the items this item pile can transfer * * @param {Token/TokenDocument/Actor} target - * @param {array/boolean} [itemFilters=false] Array of item types disallowed - will default to pile settings or module settings if none provided + * @param {Array/Boolean} [itemFilters=false] Array of item types disallowed - will default to pile settings or module settings if none provided * @returns {Array} */ getItemPileItems(target, itemFilters = false) { @@ -808,7 +815,7 @@ const API = { * Returns the currencies this item pile can transfer * * @param {Token/TokenDocument/Actor} target - * @returns {array} + * @returns {Array} */ getItemPileCurrencies(target) { return lib.getActorCurrencies(target); @@ -858,8 +865,8 @@ const API = { /** * Causes all connected users to re-render a specific pile's inventory UI * - * @param {string} inPileUuid The uuid of the pile to be re-rendered - * @param {boolean} [deleted=false] Whether the pile was deleted as a part of this re-render + * @param {String} inPileUuid The uuid of the pile to be re-rendered + * @param {Boolean} [deleted=false] Whether the pile was deleted as a part of this re-render * @return {Promise} */ async rerenderItemPileInventoryApplication(inPileUuid, deleted = false) { @@ -915,45 +922,70 @@ const API = { }, + /** + * @private + */ async _splitItemPileContents(itemPileUuid, actorUuids, userId, instigator) { const itemPile = await fromUuid(itemPileUuid); const itemPileActor = itemPile?.actor ?? itemPile; - const transferData = {}; + const actors = await Promise.all(actorUuids.map((uuid) => fromUuid(uuid))); + + const itemsToRemove = {}; + const currenciesToRemove = {} - for (let actorUuid of actorUuids) { + const transferData = { + items: {}, + currencies: {}, + num_players: actors.length + } - const actor = await fromUuid(actorUuid); + for(const actor of actors){ const itemsToTransfer = lib.getItemPileItemsForActor(itemPileActor, actor, true).filter(item => item.toShare).map(item => { + itemsToRemove[item.id] = (itemsToRemove[item.id] ?? 0) + item.shareLeft; + transferData.items[item.id] = { + id: item.id, + name: item.name, + img: item.img, + quantity: (transferData.items[item.id]?.quantity ?? 0) + (item.shareLeft + item.previouslyTaken) + } return { _id: item.id, quantity: item.shareLeft }; }).filter(item => item.quantity); - const currenciesToTransfer = Object.fromEntries(lib.getItemPileCurrenciesForActor(itemPileActor, actor, true).filter(item => item.toShare).map(entry => { - return [entry.path, entry.shareLeft] - }).filter(currency => currency[1])) + const currenciesToTransfer = Object.fromEntries(lib.getItemPileCurrenciesForActor(itemPileActor, actor, true).filter(item => item.toShare).map(currency => { + currenciesToRemove[currency.path] = (currenciesToRemove[currency.path] ?? 0) + currency.shareLeft; + transferData.currencies[currency.path] = { + path: currency.path, + name: currency.name, + img: currency.img, + quantity: (transferData.currencies[currency.path]?.quantity ?? 0) + (currency.shareLeft + currency.previouslyTaken), + index: currency.index + } + return [currency.path, currency.shareLeft] + }).filter(currency => currency[1])); + + await API._addItems(actor.uuid, itemsToTransfer, userId, { runHooks: false }); + await API._addAttributes(actor.uuid, currenciesToTransfer, userId, { runHooks: false }); - transferData[actorUuid] = { - itemsTransferred: [], - currenciesTransferred: [] - } + } - if (itemsToTransfer.length) { - transferData[actorUuid].itemsTransferred = await API._transferItems(itemPileUuid, actorUuid, itemsToTransfer, userId, { isEverything: true }); - } + transferData.items = Object.values(transferData.items).map(item => { + item.quantity = item.quantity / actors.length; + return item; + }); - if (!foundry.utils.isObjectEmpty(currenciesToTransfer)) { - transferData[actorUuid].currenciesTransferred = await API._transferAttributes(itemPileUuid, actorUuid, currenciesToTransfer, userId, { isEverything: true }); - } + transferData.currencies = Object.values(transferData.currencies).map(currency => { + currency.quantity = currency.quantity / actors.length; + return currency; + }); - await lib.setItemPileSharingData(itemPileUuid, actorUuid, { - items: transferData[actorUuid].itemsTransferred, - currencies: transferData[actorUuid].currenciesTransferred - }); + await lib.clearItemPileSharingData(itemPileActor); - } + await API._removeItems(itemPileUuid, Object.entries(itemsToRemove).map(entry => ({ _id: entry[0], quantity: entry[1] })), userId, { runHooks: false }); + await API._removeAttributes(itemPileUuid, currenciesToRemove, userId, { runHooks: false }); await itemPileSocket.executeForEveryone( SOCKET_HANDLERS.CALL_HOOK, @@ -980,8 +1012,6 @@ const API = { if (shouldBeDeleted) { await API._deleteItemPile(itemPileUuid); - } else if (lib.isItemPileEmpty(itemPileActor)) { - await lib.clearItemPileSharingData(itemPileActor); } return transferData; @@ -994,9 +1024,9 @@ const API = { * Adds item to an actor, increasing item quantities if matches were found * * @param {Actor/TokenDocument/Token} target The target to add an item to - * @param {array} items An array of objects, with the key "item" being an item object or an Item class (the foundry class), with an optional key of "quantity" being the amount of the item to add - * @param {boolean} [mergeSimilarItems=true] Whether to merge similar items based on their name and type - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Array} items An array of objects, with the key "item" being an item object or an Item class (the foundry class), with an optional key of "quantity" being the amount of the item to add + * @param {Boolean} [mergeSimilarItems=true] Whether to merge similar items based on their name and type + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An array of objects, each containing the item that was added or updated, and the quantity that was added */ @@ -1121,8 +1151,8 @@ const API = { * Subtracts the quantity of items on an actor. If the quantity of an item reaches 0, the item is removed from the actor. * * @param {Actor/Token/TokenDocument} target The target to remove a items from - * @param {array} items An array of objects each containing the item id (key "_id") and the quantity to remove (key "quantity"), or Items (the foundry class) or strings of IDs to remove all quantities of - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Array} items An array of objects each containing the item id (key "_id") and the quantity to remove (key "quantity"), or Items (the foundry class) or strings of IDs to remove all quantities of + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An array of objects, each containing the item that was removed or updated, the quantity that was removed, and whether the item was deleted */ @@ -1254,8 +1284,8 @@ const API = { * * @param {Actor/Token/TokenDocument} source The source to transfer the items from * @param {Actor/Token/TokenDocument} target The target to transfer the items to - * @param {array} items An array of objects each containing the item id (key "_id") and the quantity to transfer (key "quantity"), or Items (the foundry class) or strings of IDs to transfer all quantities of - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Array} items An array of objects each containing the item id (key "_id") and the quantity to transfer (key "quantity"), or Items (the foundry class) or strings of IDs to transfer all quantities of + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An array of objects, each containing the item that was added or updated, and the quantity that was transferred */ @@ -1356,8 +1386,8 @@ const API = { * * @param {Actor/Token/TokenDocument} source The actor to transfer all items from * @param {Actor/Token/TokenDocument} target The actor to receive all the items - * @param {array/boolean} [itemFilters=false] Array of item types disallowed - will default to module settings if none provided - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Array/Boolean} [itemFilters=false] Array of item types disallowed - will default to module settings if none provided + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An array containing all of the items that were transferred to the target */ @@ -1441,8 +1471,8 @@ const API = { * Adds to attributes on an actor * * @param {Actor/Token/TokenDocument} target The target whose attribute will have a set quantity added to it - * @param {object} attributes An object with each key being an attribute path, and its value being the quantity to add - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Object} attributes An object with each key being an attribute path, and its value being the quantity to add + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An array containing a key value pair of the attribute path and the quantity of that attribute that was removed * @@ -1525,8 +1555,8 @@ const API = { * Subtracts attributes on the target * * @param {Token/TokenDocument} target The target whose attributes will be subtracted from - * @param {array/object} attributes This can be either an array of attributes to subtract (to zero out a given attribute), or an object with each key being an attribute path, and its value being the quantity to subtract - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Array/object} attributes This can be either an array of attributes to subtract (to zero out a given attribute), or an object with each key being an attribute path, and its value being the quantity to subtract + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An array containing a key value pair of the attribute path and the quantity of that attribute that was removed */ @@ -1633,8 +1663,8 @@ const API = { * * @param {Actor/Token/TokenDocument} source The source to transfer the attribute from * @param {Actor/Token/TokenDocument} target The target to transfer the attribute to - * @param {array/object} attributes This can be either an array of attributes to transfer (to transfer all of a given attribute), or an object with each key being an attribute path, and its value being the quantity to transfer - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {Array/object} attributes This can be either an array of attributes to transfer (to transfer all of a given attribute), or an object with each key being an attribute path, and its value being the quantity to transfer + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An object containing a key value pair of each attribute transferred, the key being the attribute path and its value being the quantity that was transferred */ @@ -1744,7 +1774,7 @@ const API = { * * @param {Actor/Token/TokenDocument} source The source to transfer the attributes from * @param {Actor/Token/TokenDocument} target The target to transfer the attributes to - * @param {string/boolean} [interactionId=false] The interaction ID of this action + * @param {String/Boolean} [interactionId=false] The interaction ID of this action * * @returns {Promise} An object containing a key value pair of each attribute transferred, the key being the attribute path and its value being the quantity that was transferred */ @@ -1826,8 +1856,8 @@ const API = { * * @param {Actor/Token/TokenDocument} source The actor to transfer all items and attributes from * @param {Actor/Token/TokenDocument} target The actor to receive all the items and attributes - * @param {array/boolean} [itemFilters=false] Array of item types disallowed - will default to module settings if none provided - * @param {string/boolean} [interactionId=false] The ID of this interaction + * @param {Array/Boolean} [itemFilters=false] Array of item types disallowed - will default to module settings if none provided + * @param {String/Boolean} [interactionId=false] The ID of this interaction * * @returns {Promise} An object containing all items and attributes transferred to the target */ @@ -2016,8 +2046,8 @@ const API = { * This handles any dropped data onto the canvas or a set item pile * * @param {canvas} canvas - * @param {object} data - * @param {Actor/Token/TokenDocument/boolean}[target=false] + * @param {Object} data + * @param {Actor/Token/TokenDocument/Boolean}[target=false] * @return {Promise} * @private */ @@ -2185,12 +2215,12 @@ const API = { * * If an actor was provided, it will transfer the item from the actor to the target actor. * - * @param {string} userId - * @param {string} sceneId - * @param {string/boolean} [sourceUuid=false] - * @param {string/boolean} [targetUuid=false] - * @param {object/boolean} [position=false] - * @param {object} [itemData=false] + * @param {String} userId + * @param {String} sceneId + * @param {String/Boolean} [sourceUuid=false] + * @param {String/Boolean} [targetUuid=false] + * @param {Object/Boolean} [position=false] + * @param {Object} [itemData=false] * * @returns {Promise<{sourceUuid: string/boolean, targetUuid: string/boolean, position: object/boolean, itemsDropped: array }>} * @private @@ -2238,10 +2268,10 @@ const API = { }, /** - * @param {string} sceneId - * @param {object} position - * @param {string/boolean} [pileActorName=false] - * @param {array/boolean} [items=false] + * @param {String} sceneId + * @param {Object} position + * @param {String/Boolean} [pileActorName=false] + * @param {Array/Boolean} [items=false] * @returns {Promise} * @private */ @@ -2422,8 +2452,4 @@ const API = { } -} - -const preloadedFiles = new Set(); - -export default API; \ No newline at end of file +}; \ No newline at end of file diff --git a/scripts/chathandler.js b/scripts/chathandler.js index d728ed25..b059ffe5 100644 --- a/scripts/chathandler.js +++ b/scripts/chathandler.js @@ -125,40 +125,7 @@ const chatHandler = { _outputSplitItemPileInventory(source, transferData, userId) { if (!API.isValidItemPile(source)) return; if (game.user.id !== userId || game.settings.get(CONSTANTS.MODULE_NAME, "outputToChat") === "off") return; - - const sharingData = lib.getItemPileSharingData(source); - - let itemData = []; - if (sharingData.items) { - itemData = sharingData.items.map(item => { - const totalQuantity = item.actors.reduce((acc, item) => acc + item.quantity, 0) - return { - name: item.name, - img: item.img, - quantity: Math.floor(totalQuantity / item.actors.length) - } - }) - } - - let currencyData = []; - if (sharingData.currencies) { - const currencyList = lib.getActorCurrencyList(source); - currencyData = sharingData.currencies.map(currencyData => { - const currency = currencyList.find(currency => currency.path === currencyData.path); - const totalQuantity = currencyData.actors.reduce((acc, storedCurrency) => acc + storedCurrency.quantity, 0); - return { - name: game.i18n.has(currency.name) ? game.i18n.localize(currency.name) : currency.name, - img: currency.img ?? "", - quantity: Math.floor(totalQuantity / currencyData.actors.length), - currency: true, - index: currencyList.indexOf(currency) - } - }) - } - - const num_players = Object.keys(transferData).length; - - return itemPileSocket.executeAsGM(SOCKET_HANDLERS.SPLIT_CHAT_MESSAGE, source.uuid, num_players, itemData, currencyData, userId); + return itemPileSocket.executeAsGM(SOCKET_HANDLERS.SPLIT_CHAT_MESSAGE, source.uuid, transferData, userId); }, async _outputTradeStarted(party_1, party_2, publicTradeId, isPrivate) { @@ -167,11 +134,8 @@ const chatHandler = { }, async _outputTradeComplete(party_1, party_2, publicTradeId, isPrivate) { - if (game.settings.get(CONSTANTS.MODULE_NAME, "outputToChat") === "off") return; - return this._outputTradeCompleteToChat(party_1, party_2, publicTradeId, isPrivate); - }, /** @@ -207,7 +171,6 @@ const chatHandler = { name: game.i18n.has(currency.name) ? game.i18n.localize(currency.name) : currency.name, img: currency.img ?? "", quantity: entry[1], - currency: true, index: currencyList.indexOf(currency) } }); @@ -316,18 +279,17 @@ const chatHandler = { }, - - async _outputSplitToChat(sourceUuid, num_players, items, currencies, userId) { + async _outputSplitToChat(sourceUuid, transferData, userId) { const source = await fromUuid(sourceUuid); const sourceActor = source?.actor ?? source; const chatCardHtml = await renderTemplate(CONSTANTS.PATH + "templates/loot-chat-message.html", { - message: game.i18n.format("ITEM-PILES.Chat.Split", { num_players: num_players }), + message: game.i18n.format("ITEM-PILES.Chat.Split", { num_players: transferData.num_players }), itemPile: sourceActor, - items: items, - currencies: currencies + items: transferData.items, + currencies: transferData.currencies }); return this._createNewChatMessage(userId, { diff --git a/scripts/formapplications/item-pile-config.js b/scripts/formapplications/item-pile-config.js index 00e11daf..0ccfec7c 100644 --- a/scripts/formapplications/item-pile-config.js +++ b/scripts/formapplications/item-pile-config.js @@ -1,7 +1,7 @@ import CONSTANTS from "../constants.js"; import API from "../api.js"; -import { ItemPileCurrenciesEditor } from "./item-pile-currencies-editor.js"; import * as lib from "../lib/lib.js"; +import { ItemPileCurrenciesEditor } from "./item-pile-currencies-editor.js"; import { ItemPileFiltersEditor } from "./item-pile-filters-editor.js"; export class ItemPileConfig extends FormApplication { @@ -205,7 +205,7 @@ export class ItemPileConfig extends FormApplication { icon: '', label: game.i18n.localize("ITEM-PILES.Dialogs.ResetSharingData.Confirm"), callback: () => { - lib.updateItemPileSharingData(this.document, {}); + lib.clearItemPileSharingData(this.document); } }, cancel: { diff --git a/scripts/formapplications/item-pile-inventory.js b/scripts/formapplications/item-pile-inventory.js index 7f2e4b94..84ef4854 100644 --- a/scripts/formapplications/item-pile-inventory.js +++ b/scripts/formapplications/item-pile-inventory.js @@ -30,6 +30,7 @@ export class ItemPileInventory extends FormApplication { /** @inheritdoc */ static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { + closeOnSubmit: false, classes: ["sheet"], template: `${CONSTANTS.PATH}templates/item-pile-inventory.html`, width: 550, @@ -257,13 +258,33 @@ export class ItemPileInventory extends FormApplication { data.isEmpty = !data?.hasItems && !data?.hasCurrencies; - const num_players = lib.getPlayersForItemPile(this.pile).length; - data.shareItemsEnabled = pileData.shareItemsEnabled; data.shareCurrenciesEnabled = pileData.shareCurrenciesEnabled; - const hasSplittableQuantities = (pileData.shareItemsEnabled && data.items.find((item) => (item.quantity / num_players) > 1)) - || (pileData.shareCurrenciesEnabled && data.currencies.find((attribute) => (attribute.quantity / num_players) > 1)) + const sharingData = lib.getItemPileSharingData(this.pile); + const num_players = lib.getPlayersForItemPile(this.pile).length; + + const hasSplittableItems = pileData.shareItemsEnabled && data.items && !!data.items?.find(item => { + let quantity = item.quantity; + if(sharingData.currencies){ + const itemSharingData = sharingData.currencies.find(sharingCurrency => sharingCurrency.path === item.path); + if(itemSharingData){ + quantity += itemSharingData.actors.reduce((acc, data) => acc + data.quantity, 0); + } + } + return (quantity / num_players) >= 1; + }); + + const hasSplittableCurrencies = pileData.shareCurrenciesEnabled && data.currencies && !!data.currencies?.find(currency => { + let quantity = currency.quantity; + if(sharingData.currencies) { + const currencySharingData = sharingData.currencies.find(sharingCurrency => sharingCurrency.path === currency.path); + if (currencySharingData) { + quantity += currencySharingData.actors.reduce((acc, data) => acc + data.quantity, 0); + } + } + return (quantity / num_players) >= 1; + }); data.buttons = []; @@ -275,7 +296,7 @@ export class ItemPileInventory extends FormApplication { }); } - if ((data.hasRecipient || game.user.isGM) && pileData.splitAllEnabled && hasSplittableQuantities && (pileData.shareItemsEnabled || pileData.shareCurrenciesEnabled)) { + if (pileData.splitAllEnabled && (data.hasRecipient || game.user.isGM)) { let buttonText; if (pileData.shareItemsEnabled && pileData.shareCurrenciesEnabled) { @@ -290,7 +311,7 @@ export class ItemPileInventory extends FormApplication { value: "splitAll", icon: "far fa-handshake", text: buttonText, - disabled: !num_players, + disabled: !num_players || !(hasSplittableItems || hasSplittableCurrencies), type: "button" }); @@ -487,12 +508,12 @@ export class ItemPileInventory extends FormApplication { async _updateObject(event, formData) { if (event.submitter.value === "update") { - return this.updatePile(formData); + await this.updatePile(formData); + return this.render(true); } if (event.submitter.value === "takeAll") { API.transferEverything(this.pile, this.recipient, { interactionId: this.interactionId }); - return; } if (event.submitter.value === "close") { @@ -501,6 +522,8 @@ export class ItemPileInventory extends FormApplication { }); } + return this.close(); + } updatePile(data) { diff --git a/scripts/lib/lib.js b/scripts/lib/lib.js index aa03ec6e..e155ac33 100644 --- a/scripts/lib/lib.js +++ b/scripts/lib/lib.js @@ -676,6 +676,7 @@ export function getItemPileItemsForActor(pile, recipient, floor = false) { currentQuantity: 1, quantity: quantity, shareLeft: quantity, + previouslyTaken: 0, toShare: pileData.shareItemsEnabled && recipientUuid }; @@ -685,7 +686,9 @@ export function getItemPileItemsForActor(pile, recipient, floor = false) { let totalShares = quantity; if (foundItem) { - totalShares += foundItem.actors.reduce((acc, actor) => acc + actor.quantity, 0); + totalShares += foundItem.actors.reduce((acc, actor) => { + return acc + actor.quantity; + }, 0); } let totalActorShare = totalShares / players.length; @@ -693,9 +696,10 @@ export function getItemPileItemsForActor(pile, recipient, floor = false) { totalActorShare += 1; } - let actorQuantity = foundItem?.actors ? foundItem?.actors?.find(actor => actor.uuid === recipientUuid)?.quantity ?? 0 : 0; + const takenBefore = foundItem?.actors?.find(actor => actor.uuid === recipientUuid); + data.previouslyTaken = takenBefore ? takenBefore.quantity : 0; - data.shareLeft = Math.max(0, Math.min(quantity, Math.floor(totalActorShare - actorQuantity))); + data.shareLeft = Math.max(0, Math.min(quantity, Math.floor(totalActorShare - data.previouslyTaken))); } @@ -718,11 +722,13 @@ export function getItemPileCurrenciesForActor(pile, recipient, floor) { return pileCurrencies.filter(currency => { return !recipient || hasProperty(recipient?.data ?? {}, currency.path); - }).map(currency => { + }).map((currency, index) => { currency.currentQuantity = 1; currency.shareLeft = currency.quantity; - currency.toShare = pileData.shareCurrenciesEnabled && recipientUuid; + currency.toShare = pileData.shareCurrenciesEnabled && !!recipientUuid; + currency.previouslyTaken = 0; + currency.index = index; if (currency.toShare) { @@ -738,9 +744,10 @@ export function getItemPileCurrenciesForActor(pile, recipient, floor) { totalActorShare += 1; } - let actorQuantity = foundCurrency?.actors ? foundCurrency?.actors?.find(actor => actor.uuid === recipientUuid)?.quantity ?? 0 : 0; + const takenBefore = foundCurrency?.actors?.find(actor => actor.uuid === recipientUuid); + currency.previouslyTaken = takenBefore ? takenBefore.quantity : 0; - currency.shareLeft = Math.max(0, Math.min(currency.quantity, Math.floor(totalActorShare - actorQuantity))); + currency.shareLeft = Math.max(0, Math.min(currency.quantity, Math.floor(totalActorShare - currency.previouslyTaken))); } @@ -748,4 +755,4 @@ export function getItemPileCurrenciesForActor(pile, recipient, floor) { }); -} +} \ No newline at end of file diff --git a/scripts/settings.js b/scripts/settings.js index 5b73476e..c1171863 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -160,6 +160,13 @@ const otherSettings = { config: false, default: false, type: Boolean + }, + + "preconfiguredSystem": { + scope: "world", + config: false, + default: false, + type: Boolean } } @@ -291,10 +298,21 @@ async function applyDefaultSettings() { export async function checkSystem() { + await lib.wait(1000); + + if(game.settings.get(CONSTANTS.MODULE_NAME, "preconfiguredSystem")) return; + if (!SYSTEMS.DATA) { if (game.settings.get(CONSTANTS.MODULE_NAME, "systemNotFoundWarningShown")) return; + let settingsValid = true; + for (const [name, data] of Object.entries(defaultSettings())) { + settingsValid = settingsValid && game.settings.get(CONSTANTS.MODULE_NAME, name).length !== (new data.type).length + } + + if(settingsValid) return; + await game.settings.set(CONSTANTS.MODULE_NAME, "systemNotFoundWarningShown", true); return Dialog.prompt({ diff --git a/styles/module.css.map b/styles/module.css.map index 160816cc..fb38b929 100644 --- a/styles/module.css.map +++ b/styles/module.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AASJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;;AAKN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIA;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AASJ;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;;AAKN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file