diff --git a/changelog.md b/changelog.md index af8df4f4..015cc473 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,11 @@ # Item Piles Changelog ## Version 1.1.3 +- Adjusted display one-type item piles to also take into account dynamic attributes (gold piles!) - Fixed prototype tokens not being updated when editing an item pile through its sheet +- Fixed item piles with both "Is Container" and "Override single item token scale" enabled acting strange - item piles will now prioritize the container images over "Display Single Item Image" when "Is Container" is enabled +- Added warning to point out the above +- Adjusted Item Pile UI to be editable even when not enabled ## Version 1.1.2 - Fixed dropping items onto piles not working when it had an interaction distance of infinite diff --git a/languages/de.json b/languages/de.json index 4f09bb02..0f6bb122 100644 --- a/languages/de.json +++ b/languages/de.json @@ -87,6 +87,7 @@ "DisplayOneExplanation": "Besteht der Stapel aus einem einzigen Gegenstand, wird das Bild des Stapels auf das Bild des Gegenstandes gesetzt.", "OverrideSingleItemScale": "Einzelne Gegenstands-Token-Skalierung außer Kraft setzen", "SingleItemScale": "Gegenstands-Token-Skalierung", + "DisplayOneContainerWarning": "Achtung! Du hast sowohl die Einstellung \"Einzelnes Artikelbild anzeigen\" als auch \"Ist Container\" aktiviert. In diesem Fall werden Bilder für den Container bevorzugt verwendet.", "IsContainer": "Ist Container", "Locked": "Ist verschlossen", "Closed": "Ist geschlossen", diff --git a/languages/en.json b/languages/en.json index e15b1fb8..8f7bc658 100644 --- a/languages/en.json +++ b/languages/en.json @@ -87,6 +87,7 @@ "DisplayOneExplanation": "If the pile is made up of a single item, this sets the pile token's image to be the image of the item.", "OverrideSingleItemScale": "Override single item token scale", "SingleItemScale": "Single item token scale", + "DisplayOneContainerWarning": "Warning! You have both \"Display Single Item Image\" and \"Is Container\" enabled. In this case, the container images takes priority.", "IsContainer": "Is Container", "Locked": "Is Locked", "Closed": "Is Closed", diff --git a/languages/fr.json b/languages/fr.json index 074b6b84..b06c0df6 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -82,6 +82,7 @@ "DisplayOneExplanation": "Si la pile est composée d'un seul objet, l'image du token de la pile sera l'image de l'objet.", "OverrideSingleItemScale": "Remplacer l'échelle du Token objet unique", "SingleItemScale": "Échelle du Token", + "DisplayOneContainerWarning": "Attention ! Vous avez activé à la fois les options \"Afficher l'image d'un seul élément\" et \"Est un conteneur\". Dans ce cas, l'images du conteneur est prioritaires.", "IsContainer": "Est un conteneur", "Locked": "est verrouillé", "Closed": "Est fermé", diff --git a/languages/ja.json b/languages/ja.json index e28fdbf8..752d2d77 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -87,6 +87,7 @@ "DisplayOneExplanation": "お宝の中身が単体のアイテムの場合、そのアイテムの画像がお宝の画像そのものになります。", "OverrideSingleItemScale": "単体アイテムのコマサイズ調整", "SingleItemScale": "単体アイテムコマサイズ", + "DisplayOneContainerWarning": "警告:\"単体時、アイテム画像を表示\"と\"コンテナである\"の両方の設定が有効化されています。この場合、コンテナ画像のほうが優先されますのでご注意ください。", "IsContainer": "コンテナである", "Locked": "ロックされている", "Closed": "閉まっている", diff --git a/scripts/api.js b/scripts/api.js index 59575381..a3435ce2 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -4,7 +4,7 @@ import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; import DropDialog from "./formapplications/dropDialog.js"; import { HOOKS } from "./hooks.js"; -import { getItemPileTokenImage, getItemPileTokenScale, tokens_close_enough } from "./lib/lib.js"; +import { getItemPileAttributes, getItemPileTokenImage, getItemPileTokenScale, tokens_close_enough } from "./lib/lib.js"; export default class API { @@ -207,7 +207,7 @@ export default class API { await pileActor.update({ "token": { - name: "ItemPile", + name: "Item Pile", actorLink: false, bar1: { attribute: "" }, vision: false, @@ -233,20 +233,31 @@ export default class API { overrideData['actorData'] = { "items": items || {} }; const pileConfig = lib.getItemPileData(pileActor); + const attributes = getItemPileAttributes(pileActor); - if (items.length === 1 && pileConfig.displayOne) { - overrideData["img"] = items[0].img + const numItems = items.length + attributes.length; + + if (pileConfig.displayOne && numItems === 1) { + overrideData["img"] = items.length > 0 + ? items[0].img + : attributes[0].img; if (pileConfig.overrideSingleItemScale) { overrideData["scale"] = pileConfig.singleItemScale; } - }else{ + } + + if (pileConfig.isContainer) { + overrideData["img"] = lib.getItemPileTokenImage(pileActor); overrideData["scale"] = lib.getItemPileTokenScale(pileActor); + } } else { + overrideData["img"] = lib.getItemPileTokenImage(pileActor); overrideData["scale"] = lib.getItemPileTokenScale(pileActor); + } const tokenData = await pileActor.getTokenData(overrideData); diff --git a/scripts/formapplications/itemPileConfig.js b/scripts/formapplications/itemPileConfig.js index a8cea6a7..28ebf0a7 100644 --- a/scripts/formapplications/itemPileConfig.js +++ b/scripts/formapplications/itemPileConfig.js @@ -63,34 +63,25 @@ export class ItemPileConfig extends FormApplication { const slider = html.find("#scaleRange"); const input = html.find("#scaleInput"); - let firstTime = true; enabledCheckbox.change(async function () { let isEnabled = $(this).is(":checked"); - if(!firstTime){ - const existingData = lib.getItemPileData(self.document); - if(isEnabled && !existingData?.enabled) { - const isLinked = self.document instanceof Actor - ? self.document.data.token.actorLink - : self.document.isLinked; - if(isLinked){ - const doContinue = await Dialog.confirm({ - title: game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Title"), - content: lib.dialogWarning(game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Content")), - defaultYes: false - }); - if (!doContinue) { - isEnabled = false; - $(this).prop("checked", false); - } + const existingData = lib.getItemPileData(self.document); + if(isEnabled && !existingData?.enabled) { + const isLinked = self.document instanceof Actor + ? self.document.data.token.actorLink + : self.document.isLinked; + if(isLinked){ + const doContinue = await Dialog.confirm({ + title: game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Title"), + content: lib.dialogWarning(game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Content")), + defaultYes: false + }); + if (!doContinue) { + $(this).prop("checked", false); } } } - firstTime = false; - html.find('.tab-body').find('input, button, select').not($(this)).each(function () { - $(this).prop('disabled', !isEnabled); - $(this).closest('.form-group').toggleClass("item-pile-disabled", !isEnabled); - }); - }).change(); + }) scaleCheckbox.change(function () { let isDisabled = !$(this).is(":checked") || !displayOneCheckbox.is(":checked"); @@ -100,32 +91,33 @@ export class ItemPileConfig extends FormApplication { }).change(); displayOneCheckbox.change(function () { - let isDisabled = !$(this).is(":checked"); - html.find('.item-pile-display-one-settings').children().each(function () { - $(this).toggleClass("item-pile-disabled", isDisabled); - $(this).find('input, button').prop("disabled", isDisabled); - scaleCheckbox.change(); - }); + let isEnabled = $(this).is(":checked"); + let isScale = scaleCheckbox.is(":checked"); + let isContainer = containerCheckbox.is(":checked"); + + slider.prop('disabled', !isEnabled || !isScale); + input.prop('disabled', !isEnabled || !isScale); + slider.parent().toggleClass("item-pile-disabled", !isEnabled || !isScale); + + scaleCheckbox.prop('disabled', !isEnabled); + scaleCheckbox.parent().toggleClass("item-pile-disabled", !isEnabled); + + html.find(".display-one-warning").css("display", isEnabled && isContainer ? "block" : "none"); + self.setPosition(); }).change(); - const containerSettings = html.find('.item-pile-container-settings'); containerCheckbox.change(function () { - let isDisabled = !$(this).is(":checked"); - containerSettings.children().each(function () { - $(this).toggleClass("item-pile-disabled", isDisabled); - $(this).find('input, button, select').prop("disabled", isDisabled); - }); + let isEnabled = $(this).is(":checked"); + let isDisplayOne = displayOneCheckbox.is(":checked"); + html.find(".display-one-warning").css("display", isEnabled && isDisplayOne ? "block" : "none"); }).change(); overrideAttributesEnabledCheckbox.change(function () { let isChecked = $(this).is(":checked"); - html.find(".item-pile-config-configure-override-attributes") - .toggleClass("item-pile-disabled", !isChecked) - .prop("disabled", !isChecked); if (isChecked) { self.pileData.overrideAttributes = game.settings.get(CONSTANTS.MODULE_NAME, "dynamicAttributes"); } - }).change(); + }) html.find(".item-pile-config-configure-override-attributes").click(function () { self.showAttributeEditor(); diff --git a/scripts/formapplications/itemPileInventory.js b/scripts/formapplications/itemPileInventory.js index b7370697..e7d8db7b 100644 --- a/scripts/formapplications/itemPileInventory.js +++ b/scripts/formapplications/itemPileInventory.js @@ -82,7 +82,7 @@ export class ItemPileInventory extends FormApplication { const foundItem = self.pile.actor.items.get(id) || lib.getSimilarItem(pileItems, { itemName: name, itemType: type }); let itemQuantity; - let maxQuantity; + let quantity; itemQuantity = $(this).find('input').val(); @@ -91,15 +91,15 @@ export class ItemPileInventory extends FormApplication { name = foundItem.name; img = foundItem.data.img; type = getProperty(foundItem.data, API.ITEM_TYPE_ATTRIBUTE); - maxQuantity = Number(getProperty(foundItem.data, API.ITEM_QUANTITY_ATTRIBUTE)); + quantity = Number(getProperty(foundItem.data, API.ITEM_QUANTITY_ATTRIBUTE)); }else{ itemQuantity = 0; - maxQuantity = 0; + quantity = 0; } - const currentQuantity = Math.min(maxQuantity, Math.max(itemQuantity, 1)); + const currentQuantity = Math.min(quantity, Math.max(itemQuantity, 1)); - self.items.push({ id, name, type, img, currentQuantity, maxQuantity }); + self.items.push({ id, name, type, img, currentQuantity, quantity }); }); @@ -118,7 +118,7 @@ export class ItemPileInventory extends FormApplication { type: item.type, img: item.data?.img ?? "", currentQuantity: 1, - maxQuantity: Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1) + quantity: Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1) }; }) } @@ -135,11 +135,11 @@ export class ItemPileInventory extends FormApplication { const img = $(this).find('.item-piles-img').attr('src'); const itemQuantity = $(this).find('input').val(); - const maxQuantity = Number(getProperty(self.pile.actor.data, path)) ?? 0; + const quantity = Number(getProperty(self.pile.actor.data, path)) ?? 0; - const currentQuantity = Math.min(maxQuantity, Math.max(itemQuantity, 0)); + const currentQuantity = Math.min(quantity, Math.max(itemQuantity, 0)); - self.attributes.push({ name, path, img, currentQuantity, maxQuantity }); + self.attributes.push({ name, path, img, currentQuantity, quantity }); }); @@ -149,23 +149,18 @@ export class ItemPileInventory extends FormApplication { } - validAttribute(attribute) { - return lib.hasNonzeroAttribute(this.pile.actor, attribute) && (!this.recipientActor || hasProperty(this.recipientActor.data, attribute)); - } - getPileAttributeData() { const attributes = API.getItemPileAttributes(this.pile); - return attributes.map(attribute => { - if (!this.validAttribute(attribute.path)) return false; - const localizedName = game.i18n.has(attribute.name) ? game.i18n.localize(attribute.name) : attribute.name; - return { - name: localizedName, - path: attribute.path, - img: attribute.img, - currentQuantity: 1, - maxQuantity: Number(getProperty(this.pile.actor.data, attribute.path) ?? 1) - } - }).filter(Boolean); + return attributes + .filter(attribute => { + return !this.recipientActor || (this.recipientActor && hasProperty(this.recipientActor.data, attribute.path)); + }) + .map(attribute => { + return { + ...attribute, + currentQuantity: 1 + } + }); } _onDrop(event) { @@ -276,16 +271,16 @@ export class ItemPileInventory extends FormApplication { this.saveItems(); this.saveAttributes(); const item = this.pile.actor.items.get(itemId); - const maxQuantity = Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1); - const quantity = Math.min(inputQuantity, maxQuantity); + let quantity = Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1); + quantity = Math.min(inputQuantity, quantity); await API.transferItems(this.pile, this.recipient, [{ _id: itemId, quantity }]); } async takeAttribute(attribute, inputQuantity) { this.saveItems(); this.saveAttributes(); - const maxQuantity = Number(getProperty(this.pile.actor.data, attribute) ?? 0); - const quantity = Math.min(inputQuantity, maxQuantity); + let quantity = Number(getProperty(this.pile.actor.data, attribute) ?? 0); + quantity = Math.min(inputQuantity, quantity); await API.transferAttributes(this.pile, this.recipient, { [attribute]: quantity }); } diff --git a/scripts/lib/lib.js b/scripts/lib/lib.js index 43c04485..245441a0 100644 --- a/scripts/lib/lib.js +++ b/scripts/lib/lib.js @@ -190,14 +190,11 @@ export async function updateItemPile(inDocument, flagData, tokenData){ await canvas.scene.updateEmbeddedDocuments("Token", updates); - const newPrototypeTokenData = foundry.utils.mergeObject(tokenData, { - "img": getItemPileTokenImage(documentActor, flagData), - "scale": getItemPileTokenScale(documentActor, flagData), - }); - return documentActor.update({ [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: flagData, - ...newPrototypeTokenData + "token": { + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: flagData, + } }); } @@ -208,17 +205,35 @@ export function getItemPileTokenImage(pileDocument, data = false) { data = getItemPileData(pileDocument); } - let img = pileDocument instanceof TokenDocument && pileDocument.data.actorLink - ? pileDocument.actor.data.token.img - : pileDocument.data.img; + let originalImg; + if(pileDocument instanceof TokenDocument){ + originalImg = pileDocument.data.actorLink + ? pileDocument.actor.data.token.img + : pileDocument.data.img; + }else{ + originalImg = pileDocument.data.token.img; + } - if(!isValidItemPile(pileDocument)) return img; + if(!isValidItemPile(pileDocument)) return originalImg; const items = getItemPileItems(pileDocument); + const attributes = getItemPileAttributes(pileDocument); + + const numItems = items.length + attributes.length; + + let img; + + if (data.displayOne && numItems === 1) { + + img = items.length > 0 + ? items[0].data.img + : attributes[0].img; + + } if (data.isContainer) { - img = data.lockedImage || data.closedImage || data.openedImage || data.emptyImage || img; + img = data.lockedImage || data.closedImage || data.openedImage || data.emptyImage; if (data.locked && data.lockedImage) { img = data.lockedImage; @@ -230,13 +245,9 @@ export function getItemPileTokenImage(pileDocument, data = false) { img = data.openedImage; } - } else if (data.displayOne && items.length === 1) { - - img = items[0].data.img; - } - return img; + return img || originalImg; } @@ -246,17 +257,23 @@ export function getItemPileTokenScale(pileDocument, data) { data = getItemPileData(pileDocument); } - const baseScale = pileDocument instanceof TokenDocument && pileDocument.data.actorLink - ? pileDocument.actor.data.token.scale - : pileDocument.data.scale; - - if(!isValidItemPile(pileDocument, data)) return baseScale; + let baseScale; + if(pileDocument instanceof TokenDocument){ + baseScale = pileDocument.data.actorLink + ? pileDocument.actor.data.token.scale + : pileDocument.data.scale; + }else{ + baseScale = pileDocument.data.token.scale; + } const items = getItemPileItems(pileDocument); + const attributes = getItemPileAttributes(pileDocument); - return data.displayOne && data.overrideSingleItemScale && items.length === 1 - ? data.singleItemScale - : baseScale; + const numItems = items.length + attributes.length; + + if(!isValidItemPile(pileDocument, data) || data.isContainer || !data.displayOne || !data.overrideSingleItemScale || numItems > 1) return baseScale; + + return data.singleItemScale; } @@ -299,17 +316,30 @@ export function isItemPileEmpty(target) { if(!targetActor) return false; const hasNoItems = getItemPileItems(target).length === 0; - - const attributes = getItemPileAttributes(target); - const hasEmptyAttributes = attributes.filter(attribute => { - return hasNonzeroAttribute(targetActor, attribute.path) - }).length === 0; + const hasEmptyAttributes = getItemPileAttributes(target).length === 0; return hasNoItems && hasEmptyAttributes; } -export function getItemPileAttributes(target){ +export function getItemPileAttributeList(target){ const pileData = getItemPileData(target); return pileData.overrideAttributes || API.DYNAMIC_ATTRIBUTES; } + +export function getItemPileAttributes(target) { + let targetActor = target?.actor ?? target; + const attributes = getItemPileAttributeList(targetActor); + return attributes + .filter(attribute => { + return hasProperty(targetActor.data, attribute.path) && Number(getProperty(targetActor.data, attribute.path)) > 0; + }).map(attribute => { + const localizedName = game.i18n.has(attribute.name) ? game.i18n.localize(attribute.name) : attribute.name; + return { + name: localizedName, + path: attribute.path, + img: attribute.img, + quantity: Number(getProperty(targetActor.data, attribute.path) ?? 1) + } + }); +} \ No newline at end of file diff --git a/templates/item-pile-config.html b/templates/item-pile-config.html index a258df50..76dd5120 100644 --- a/templates/item-pile-config.html +++ b/templates/item-pile-config.html @@ -82,6 +82,8 @@
+ +