diff --git a/LICENSE b/LICENSE index d2e5beaf..5350ff0e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Repository Owner +Copyright (c) 2020 Fantasy Computerworks Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,3 +19,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Credits: +- Manuel Vögele for his "isResponsibleGM" function implementation and wonderful SocketLib module \ No newline at end of file diff --git a/changelog.md b/changelog.md index 6950a2ab..e49fd1d9 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Item Piles Changelog +## Version 1.2.6 +- Added `Item Filters` setting - now you can more accurately filter items you do not want to show up in item piles, such as natural weapons +- Updated all supported systems to support the above and added migrations to convert existing settings to the new system - reset your Item Piles module settings to ensure you have the latest system configurations +- Removed `Item Type Attribute` and `Item Type Filters` as the above feature covers these cases +- Added debounce to the token image refresh so that it doesn't try to change its image too often +- Further fixes to `ItemPiles.API.addItems` +- Fixed unlinked item piles not retaining their setup when created from the actors directory + ## Version 1.2.5 - Added missing handlebars method for Foundry v0.8.9 @@ -90,8 +98,8 @@ ## Version 1.0.6 - Added API endpoints: - - `ItemPiles.API.getDocumentItemTypeFilters(TokenDocument|Actor)` - Returns the item type filters for a given item pile - - `ItemPiles.API.getDocumentItems(TokenDocument|Actor, Array|Boolean)` - Returns the items the item pile contains and can transfer + - `ItemPiles.API.getDocumentItemFilters(TokenDocument|Actor)` - Returns the item type filters for a given item pile + - `ItemPiles.API.getValidDocumentItems(TokenDocument|Actor, Array|Boolean)` - Returns the items the item pile contains and can transfer - Updated japanese localization - Fixed item piles not respecting item type filters - Fixed issue with `ItemPiles.API.turnTokenIntoItemPile` not actually turning the token into an item pile diff --git a/docs/api.md b/docs/api.md index 550e50a4..21f25783 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,21 +1,3 @@ -## How to use this API - -In your scripts, you can write `ItemPiles.API.` and then any of the entries below. - -### Example: Turning selected tokens into item piles -```js -const selectedTokens = canvas.tokens.controlled; -if(selectedTokens.length === 0) return; -ItemPiles.API.turnTokensIntoItemPiles(selectedTokens); -``` - -### Example: Reverting selected tokens from item piles back to normal tokens -```js -const selectedTokens = canvas.tokens.controlled; -if(selectedTokens.length === 0) return; -ItemPiles.API.revertTokensFromItemPiles(selectedTokens); -``` - ## Members
@@ -28,16 +10,14 @@ ItemPiles.API.revertTokensFromItemPiles(selectedTokens);
ITEM_QUANTITY_ATTRIBUTEstring

The attribute used to track the quantity of items in this system

-
ITEM_TYPE_ATTRIBUTEstring
-

The attribute used to track the item type in this system

-
-
ITEM_TYPE_FILTERSArray
+
ITEM_FILTERSArray

The filters for item types eligible for interaction within this system

## Functions +### Settings methods
setActorClassType(inClassType)Promise

Sets the actor class type used for the original item pile actor in this system

@@ -48,40 +28,42 @@ ItemPiles.API.revertTokensFromItemPiles(selectedTokens);
setItemQuantityAttribute(inAttribute)Promise

Sets the inAttribute used to track the quantity of items in this system

-
setItemTypeAttribute(inAttribute)string
-

Sets the attribute used to track the item type in this system

-
-
setItemTypeFilters(inFilters)Promise
-

Sets the filters for item types eligible for interaction within this system

+
setItemFilters(inFilters)Promise
+

Sets the items filters for interaction within this system

+
+ +### Item Pile Methods + +
createItemPile(position, { items, pileActorName })Promise

Creates the default item pile token at a location.

-
turnTokensIntoItemPiles(targets, { pileSettings, tokenSettings })Promise.<Array>
+
turnTokensIntoItemPiles(targets, pileSettings, tokenSettings)Promise.<Array>

Turns tokens and its actors into item piles

-
revertTokensFromItemPiles(targets, { tokenSettings })Promise.<Array>
+
revertTokensFromItemPiles(targets, tokenSettings)Promise.<Array>

Reverts tokens from an item pile into a normal token and actor

-
openItemPile(target, interactingToken)Promise
+
openItemPile(target, [interactingToken])Promise

Opens a pile if it is enabled and a container

-
closeItemPile(target, interactingToken)Promise
+
closeItemPile(target, [interactingToken])Promise

Closes a pile if it is enabled and a container

-
toggleItemPileClosed(target, interactingToken)Promise
+
toggleItemPileClosed(target, [interactingToken])Promise

Toggles a pile's closed state if it is enabled and a container

-
lockItemPile(target, interactingToken)Promise
+
lockItemPile(target, [interactingToken])Promise

Locks a pile if it is enabled and a container

-
unlockItemPile(target, interactingToken)Promise
+
unlockItemPile(target, [interactingToken])Promise

Unlocks a pile if it is enabled and a container

-
toggleItemPileLocked(target, interactingToken)Promise
+
toggleItemPileLocked(target, [interactingToken])Promise

Toggles a pile's locked state if it is enabled and a container

-
rattleItemPile(target, interactingToken)Promise.<boolean>
+
rattleItemPile(target, [interactingToken])Promise.<boolean>

Causes the item pile to play a sound as it was attempted to be opened, but was locked

isItemPileLocked(target)boolean
@@ -99,18 +81,16 @@ ItemPiles.API.revertTokensFromItemPiles(selectedTokens);
deleteItemPile(target)Promise

Deletes a pile, calling the relevant hooks.

-
openItemPileInventory(target, userId, inspectingTarget, useDefaultCharacter)Promise
-
+
openItemPileInventory(target, userIds, { inspectingTarget, useDefaultCharacter })Promise
+

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.

+
isValidItemPile(document)boolean

Whether a given document is a valid pile or not

isItemPileEmpty(target)boolean

Whether the item pile is empty

-
getItemPileItemTypeFilters(target)Array
-

Returns the item type filters for a given item pile

-
-
getItemPileItems(target, [itemTypeFilters])Array
+
getItemPileItems(target, [itemFilters])Array

Returns the items this item pile can transfer

getItemPileAttributes(target)array
@@ -122,6 +102,9 @@ ItemPiles.API.revertTokensFromItemPiles(selectedTokens);
rerenderItemPileInventoryApplication(inPileUuid, [deleted])Promise

Causes all connected users to re-render a specific pile's inventory UI

+ +### Item & Attribute Methods +
addItems(target, items, { interactionId })Promise.<array>

Adds item to an actor, increasing item quantities if matches were found

@@ -131,7 +114,7 @@ ItemPiles.API.revertTokensFromItemPiles(selectedTokens);
transferItems(source, target, items, { interactionId })Promise.<object>

Transfers items from the source to the target, subtracting a number of quantity from the source's item and adding it to the target's item, deleting items from the source if their quantity reaches 0

-
transferAllItems(source, target, [itemTypeFilters], { interactionId })Promise.<array>
+
transferAllItems(source, target, { itemFilters, interactionId })Promise.<array>

Transfers all items between the source and the target.

addAttributes(target, attributes, { interactionId })Promise.<object>
@@ -146,59 +129,48 @@ ItemPiles.API.revertTokensFromItemPiles(selectedTokens);
transferAllAttributes(source, target, { interactionId })Promise.<object>

Transfers all dynamic attributes from a source to a target, removing it or subtracting from the source and adding them to the target

-
transferEverything(source, target, { itemTypeFilters, interactionId })Promise.<object>
+
transferEverything(source, target, { itemFilters, interactionId })Promise.<object>

Transfers all items and attributes between the source and the target.

rerenderTokenHud()Promise

Causes every user's token HUD to rerender

-
isItemTypeDisallowed(item, itemTypeFilters)boolean/string
-

Checks whether an item (or item data) is of a type that is not allowed. If an array whether that type is allowed -or not, returning the type if it is NOT allowed.

-
--- -## ACTOR\_CLASS\_TYPE ⇒ string +### ACTOR\_CLASS\_TYPE ⇒ string The actor class type used for the original item pile actor in this system --- -## DYNAMIC\_ATTRIBUTES ⇒ array +### DYNAMIC\_ATTRIBUTES ⇒ array The attributes used to track dynamic attributes in this system --- -## ITEM\_QUANTITY\_ATTRIBUTE ⇒ string +### ITEM\_QUANTITY\_ATTRIBUTE ⇒ string The attribute used to track the quantity of items in this system - - ---- - -## ITEM\_TYPE\_ATTRIBUTE ⇒ string -The attribute used to track the item type in this system - - + --- -## ITEM\_TYPE\_FILTERS ⇒ Array +### ITEM\_FILTERS ⇒ Array The filters for item types eligible for interaction within this system --- -## setActorClassType(inClassType) ⇒ Promise -Sets the actor class type used for the original item pile actor in this system +### setActorClassType(inClassType) ⇒ Promise +Sets the actor class type used for the original item pile actor in this system | Param | Type | | --- | --- | @@ -208,8 +180,8 @@ Sets the actor class type used for the original item pile actor in this system --- -## setDynamicAttributes(inAttributes) ⇒ Promise -Sets the attributes used to track dynamic attributes in this system +### setDynamicAttributes(inAttributes) ⇒ Promise +Sets the attributes used to track dynamic attributes in this system | Param | Type | | --- | --- | @@ -219,41 +191,30 @@ Sets the attributes used to track dynamic attributes in this system --- -## setItemQuantityAttribute(inAttribute) ⇒ Promise -Sets the inAttribute used to track the quantity of items in this system - -| Param | Type | -| --- | --- | -| inAttribute | string | - - - ---- - -## setItemTypeAttribute(inAttribute) ⇒ string -Sets the attribute used to track the item type in this system +### setItemQuantityAttribute(inAttribute) ⇒ Promise +Sets the inAttribute used to track the quantity of items in this system | Param | Type | | --- | --- | | inAttribute | string | - + --- -## setItemTypeFilters(inFilters) ⇒ Promise -Sets the filters for item types eligible for interaction within this system +### setItemFilters(inFilters) ⇒ Promise +Sets the items filters for interaction within this system | Param | Type | | --- | --- | -| inFilters | string/array | +| inFilters | array | --- -## createItemPile(position, { items, pileActorName }) ⇒ Promise -Creates the default item pile token at a location. +### createItemPile(position, { items, pileActorName }) ⇒ Promise +Creates the default item pile token at a location. | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -265,8 +226,8 @@ Creates the default item pile token at a location. --- -## turnTokensIntoItemPiles(targets, { pileSettings, tokenSettings }) ⇒ Promise.<Array> -Turns tokens and its actors into item piles +### turnTokensIntoItemPiles(targets, pileSettings, tokenSettings) ⇒ Promise.<Array> +Turns tokens and its actors into item piles **Returns**: Promise.<Array> - The uuids of the targets after they were turned into item piles | Param | Type | Description | @@ -279,8 +240,8 @@ Turns tokens and its actors into item piles --- -## revertTokensFromItemPiles(targets, { tokenSettings }) ⇒ Promise.<Array> -Reverts tokens from an item pile into a normal token and actor +### revertTokensFromItemPiles(targets, tokenSettings) ⇒ Promise.<Array> +Reverts tokens from an item pile into a normal token and actor **Returns**: Promise.<Array> - The uuids of the targets after they were reverted from being item piles | Param | Type | Description | @@ -292,8 +253,8 @@ Reverts tokens from an item pile into a normal token and actor --- -## openItemPile(target, interactingToken) ⇒ Promise -Opens a pile if it is enabled and a container +### openItemPile(target, [interactingToken]) ⇒ Promise +Opens a pile if it is enabled and a container | Param | Type | Default | | --- | --- | --- | @@ -304,8 +265,8 @@ Opens a pile if it is enabled and a container --- -## closeItemPile(target, interactingToken) ⇒ Promise -Closes a pile if it is enabled and a container +### closeItemPile(target, [interactingToken]) ⇒ Promise +Closes a pile if it is enabled and a container | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -316,8 +277,8 @@ Closes a pile if it is enabled and a container --- -## toggleItemPileClosed(target, interactingToken) ⇒ Promise -Toggles a pile's closed state if it is enabled and a container +### toggleItemPileClosed(target, [interactingToken]) ⇒ Promise +Toggles a pile's closed state if it is enabled and a container | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -328,8 +289,8 @@ Toggles a pile's closed state if it is enabled and a container --- -## lockItemPile(target, interactingToken) ⇒ Promise -Locks a pile if it is enabled and a container +### lockItemPile(target, [interactingToken]) ⇒ Promise +Locks a pile if it is enabled and a container | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -340,8 +301,8 @@ Locks a pile if it is enabled and a container --- -## unlockItemPile(target, interactingToken) ⇒ Promise -Unlocks a pile if it is enabled and a container +### unlockItemPile(target, [interactingToken]) ⇒ Promise +Unlocks a pile if it is enabled and a container | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -352,8 +313,8 @@ Unlocks a pile if it is enabled and a container --- -## toggleItemPileLocked(target, interactingToken) ⇒ Promise -Toggles a pile's locked state if it is enabled and a container +### toggleItemPileLocked(target, [interactingToken]) ⇒ Promise +Toggles a pile's locked state if it is enabled and a container | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -364,8 +325,8 @@ Toggles a pile's locked state if it is enabled and a container --- -## rattleItemPile(target, interactingToken) ⇒ Promise.<boolean> -Causes the item pile to play a sound as it was attempted to be opened, but was locked +### rattleItemPile(target, [interactingToken]) ⇒ Promise.<boolean> +Causes the item pile to play a sound as it was attempted to be opened, but was locked | Param | Type | Default | | --- | --- | --- | @@ -376,8 +337,8 @@ Causes the item pile to play a sound as it was attempted to be opened, but was l --- -## isItemPileLocked(target) ⇒ boolean -Whether an item pile is locked. If it is not enabled or not a container, it is always false. +### isItemPileLocked(target) ⇒ boolean +Whether an item pile is locked. If it is not enabled or not a container, it is always false. | Param | Type | | --- | --- | @@ -387,8 +348,8 @@ Whether an item pile is locked. If it is not enabled or not a container, it is a --- -## isItemPileClosed(target) ⇒ boolean -Whether an item pile is closed. If it is not enabled or not a container, it is always false. +### isItemPileClosed(target) ⇒ boolean +Whether an item pile is closed. If it is not enabled or not a container, it is always false. | Param | Type | | --- | --- | @@ -398,8 +359,8 @@ Whether an item pile is closed. If it is not enabled or not a container, it is a --- -## isItemPileContainer(target) ⇒ boolean -Whether an item pile is a container. If it is not enabled, it is always false. +### isItemPileContainer(target) ⇒ boolean +Whether an item pile is a container. If it is not enabled, it is always false. | Param | Type | | --- | --- | @@ -409,8 +370,8 @@ Whether an item pile is a container. If it is not enabled, it is always false. --- -## updateItemPile(target, newData, { interactingToken, tokenSettings }) ⇒ Promise -Updates a pile with new data. +### updateItemPile(target, newData, { interactingToken, tokenSettings }) ⇒ Promise +Updates a pile with new data. | Param | Type | Default | | --- | --- | --- | @@ -423,101 +384,89 @@ Updates a pile with new data. --- -## deleteItemPile(target) ⇒ Promise -Deletes a pile, calling the relevant hooks. +### deleteItemPile(target) ⇒ Promise +Deletes a pile, calling the relevant hooks. | Param | Type | | --- | --- | | target | Token/TokenDocument | - --- -## openItemPileInventory(target, userIds, { inspectingTarget, useDefaultCharacter }) ⇒ Promise -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. +### openItemPileInventory(target, userIds, { inspectingTarget, useDefaultCharacter }) ⇒ Promise +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 | Type | Description | | --- | --- | --- | | target | Token/TokenDocument/Actor | The item pile actor or token whose inventory to open | -| userIds | array<string> | The IDs of the users that should open this item pile inventory | -| inspectingTarget | boolean/Token/TokenDocument/Actor | This will force the users to inspect this item pile as a specific character | -| useDefaultCharacter | boolean | Causes the users to inspect the item pile inventory as their default character | +| userIds | array.<string> | The IDs of the users that should open this item pile inventory | +| [inspectingTarget] | boolean/Token/TokenDocument/Actor | This will force the users to inspect this item pile as a specific character | +| [useDefaultCharacter] | boolean | Causes the users to inspect the item pile inventory as their default character | --- -## isValidItemPile(document) ⇒ boolean -Whether a given document is a valid pile or not +### isValidItemPile(document) ⇒ boolean +Whether a given document is a valid pile or not | Param | Type | | --- | --- | -| document | TokenDocument \| Actor | +| document | Token/TokenDocument \| Actor | --- -## isItemPileEmpty(target) ⇒ boolean -Whether the item pile is empty +### isItemPileEmpty(target) ⇒ boolean +Whether the item pile is empty | Param | Type | | --- | --- | -| target | TokenDocument \| Actor | - - - ---- - -## getItemPileItemTypeFilters(target) ⇒ Array -Returns the item type filters for a given item pile - -| Param | -| --- | -| target | +| target | Token/TokenDocument \| Actor | --- -## getItemPileItems(target, [itemTypeFilters]) ⇒ Array -Returns the items this item pile can transfer +### getItemPileItems(target, [itemFilters]) ⇒ Array +Returns the items this item pile can transfer | Param | Type | Default | Description | | --- | --- | --- | --- | -| target | TokenDocument \| Actor | | | -| [itemTypeFilters] | array/boolean | false | Array of item types disallowed - will default to pile settings or module settings if none provided | +| target | Token/TokenDocument \| Actor | | | +| [itemFilters] | array/boolean | false | Array of item types disallowed - will default to pile settings or module settings if none provided | --- -## getItemPileAttributes(target) ⇒ array -Returns the attributes this item pile can transfer +### getItemPileAttributes(target) ⇒ array +Returns the attributes this item pile can transfer | Param | Type | | --- | --- | -| target | TokenDocument \| Actor | +| target | Token/TokenDocument \| Actor | --- -## refreshItemPile(target) ⇒ Promise -Refreshes the target image of an item pile, ensuring it remains in sync +### refreshItemPile(target) ⇒ Promise +Refreshes the target image of an item pile, ensuring it remains in sync -| Param | -| --- | -| target | +| Param | Type | +| --- | --- | +| target | Token/TokenDocument \| Actor | --- -## rerenderItemPileInventoryApplication(inPileUuid, [deleted]) ⇒ Promise -Causes all connected users to re-render a specific pile's inventory UI +### rerenderItemPileInventoryApplication(inPileUuid, [deleted]) ⇒ Promise +Causes all connected users to re-render a specific pile's inventory UI | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -528,8 +477,8 @@ Causes all connected users to re-render a specific pile's inventory UI --- -## addItems(target, items, { interactionId }) ⇒ Promise.<array> -Adds item to an actor, increasing item quantities if matches were found +### addItems(target, items, { interactionId }) ⇒ Promise.<array> +Adds item to an actor, increasing item quantities if matches were found **Returns**: Promise.<array> - An array of objects, each containing the item that was added or updated, and the quantity that was added | Param | Type | Default | Description | @@ -542,8 +491,8 @@ Adds item to an actor, increasing item quantities if matches were found --- -## removeItems(target, items, { interactionId }) ⇒ Promise.<array> -Subtracts the quantity of items on an actor. If the quantity of an item reaches 0, the item is removed from the actor. +### removeItems(target, items, { interactionId }) ⇒ Promise.<array> +Subtracts the quantity of items on an actor. If the quantity of an item reaches 0, the item is removed from the actor. **Returns**: Promise.<array> - An array of objects, each containing the item that was removed or updated, the quantity that was removed, and whether the item was deleted | Param | Type | Default | Description | @@ -556,8 +505,8 @@ Subtracts the quantity of items on an actor. If the quantity of an item reaches --- -## transferItems(source, target, items, { interactionId }) ⇒ Promise.<object> -Transfers items from the source to the target, subtracting a number of quantity from the source's item and adding it to the target's item, deleting items from the source if their quantity reaches 0 +### transferItems(source, target, items, { interactionId }) ⇒ Promise.<object> +Transfers items from the source to the target, subtracting a number of quantity from the source's item and adding it to the target's item, deleting items from the source if their quantity reaches 0 **Returns**: Promise.<object> - An array of objects, each containing the item that was added or updated, and the quantity that was transferred | Param | Type | Default | Description | @@ -571,24 +520,24 @@ Transfers items from the source to the target, subtracting a number of quantity --- -## transferAllItems(source, target, { itemTypeFilters, interactionId }) ⇒ Promise.<array> -Transfers all items between the source and the target. +### transferAllItems(source, target, { itemFilters, interactionId }) ⇒ Promise.<array> +Transfers all items between the source and the target. **Returns**: Promise.<array> - An array containing all of the items that were transferred to the target | Param | Type | Default | Description | | --- | --- | --- | --- | | source | Actor/Token/TokenDocument | | The actor to transfer all items from | | target | Actor/Token/TokenDocument | | The actor to receive all the items | -| [itemTypeFilters] | array/boolean | false | Array of item types disallowed - will default to module settings if none provided | +| [itemFilters] | array/boolean | false | Array of item types disallowed - will default to module settings if none provided | | [interactionId] | string/boolean | false | The ID of this interaction | --- -## addAttributes(target, attributes, { interactionId }) ⇒ Promise.<object> -Adds to attributes on an actor -**Returns**: Promise.<object> - Returns an array containing a key value pair of the attribute path and the quantity of that attribute that was removed +### addAttributes(target, attributes, { interactionId }) ⇒ Promise.<object> +Adds to attributes on an actor +**Returns**: Promise.<object> - An array containing a key value pair of the attribute path and the quantity of that attribute that was removed | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -600,9 +549,9 @@ Adds to attributes on an actor --- -## removeAttributes(target, attributes, { interactionId }) ⇒ Promise.<object> -Subtracts attributes on the target -**Returns**: Promise.<object> - Returns an array containing a key value pair of the attribute path and the quantity of that attribute that was removed +### removeAttributes(target, attributes, { interactionId }) ⇒ Promise.<object> +Subtracts attributes on the target +**Returns**: Promise.<object> - An array containing a key value pair of the attribute path and the quantity of that attribute that was removed | Param | Type | Default | Description | | --- | --- | --- | --- | @@ -614,8 +563,8 @@ Subtracts attributes on the target --- -## transferAttributes(source, target, attributes, { interactionId }) ⇒ Promise.<object> -Transfers a set quantity of an attribute from a source to a target, removing it or subtracting from the source and adds it the target +### transferAttributes(source, target, attributes, { interactionId }) ⇒ Promise.<object> +Transfers a set quantity of an attribute from a source to a target, removing it or subtracting from the source and adds it the target **Returns**: Promise.<object> - 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 | Param | Type | Default | Description | @@ -629,8 +578,8 @@ Transfers a set quantity of an attribute from a source to a target, removing it --- -## transferAllAttributes(source, target, { interactionId }) ⇒ Promise.<object> -Transfers all dynamic attributes from a source to a target, removing it or subtracting from the source and adding them to the target +### transferAllAttributes(source, target, { interactionId }) ⇒ Promise.<object> +Transfers all dynamic attributes from a source to a target, removing it or subtracting from the source and adding them to the target **Returns**: Promise.<object> - 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 | Param | Type | Default | Description | @@ -643,33 +592,20 @@ Transfers all dynamic attributes from a source to a target, removing it or subtr --- -## transferEverything(source, target, { itemTypeFilters, interactionId }) ⇒ Promise.<object> -Transfers all items and attributes between the source and the target. +### transferEverything(source, target, { itemFilters, interactionId }) ⇒ Promise.<object> +Transfers all items and attributes between the source and the target. **Returns**: Promise.<object> - An object containing all items and attributes transferred to the target | Param | Type | Default | Description | | --- | --- | --- | --- | | source | Actor/Token/TokenDocument | | The actor to transfer all items and attributes from | | target | Actor/Token/TokenDocument | | The actor to receive all the items and attributes | -| [itemTypeFilters] | array/boolean | false | Array of item types disallowed - will default to module settings if none provided | +| [itemFilters] | array/boolean | false | Array of item types disallowed - will default to module settings if none provided | | [interactionId] | string/boolean | false | The ID of this interaction | --- -## rerenderTokenHud() ⇒ Promise -Causes every user's token HUD to rerender - - ---- - -## isItemTypeDisallowed(item, itemTypeFilters) ⇒ boolean/string -Checks whether an item (or item data) is of a type that is not allowed. If an array whether that type is allowed -or not, returning the type if it is NOT allowed. - -| Param | Type | Default | -| --- | --- | --- | -| item | Item/Object | | -| [itemTypeFilters] | array/boolean | false | - +### rerenderTokenHud() ⇒ Promise +Causes every user's token HUD to rerender diff --git a/languages/de.json b/languages/de.json index 9f656bf7..5c9cb743 100644 --- a/languages/de.json +++ b/languages/de.json @@ -5,9 +5,9 @@ "Title": "Untersuche Gegenstandstapel", "AsActor": "Du untersuchst diesen Stapel als {actorName}", "NoActor": "Du untersuchst den Inhalt dieses Stapels ohne ein Token zu kontrollieren. Dies bedeutet du kannst keine Gegenstände vom Stapel nehmen.", - "TakeAll": "Alles nehmen", "Empty": "Dieser Stapel ist leer.", "Destroyed": "Dieser Stapel existiert nicht mehr und liegt brach.", + "TakeAll": "Alles nehmen", "Take": "Nehmen", "Close": "Kiste schliessen", "Leave": "Verlassen" @@ -51,54 +51,76 @@ "Title": "Dynamischer Attribute Editor", "Explanation": "Hier kannst du festlegen, welche Attribute von Charakteren von Gegenstandsstapeln abgeholt werden können, z.B. Währungen. In D&D5e gibt es Währungen für Akteure mit dem Attributspfad \"actor.data.data.currency.gp\", also fügst du deine eigene mit dem Namen \"Goldmünzen\" und dem Attributspfad \"data.currency.gp\" hinzu.", "Name": "Attributsname", - "AttributePath": "Attributspfad", "Icon": "Attribut Icon", "AddNew": "Neues Attribut zufügen", "Submit": "Attribut übermitteln" }, + "FilterEditor": { + "Title": "Item Filters Editor", + "Explanation": "Here you can define multiple types of filters that will exclude certain types of items. Based on the attribute path given, the item pile could find the \"type\" of an item and based on the filters, it hides those items in the item pile inventory UI.", + "Filters": "Filters", + "AddNew": "Add new filter", + "Submit": "Submit Filters" + }, + + "AttributePath": "Attributspfad", + "Defaults": { "Title": "Gegenstandsstapel Konfiguration", "Configure": "Gegenstandsstapel", - "MainSettings": "Hauptmenü", - "SingleItemSettings": "Einstellungen einzelner Gegenstand", - "ContainerSettings": "Einstellungen Container", "Update": "Gegenstandsstapel aktualisieren", - "EnabledPile": "Aktiviert", - "EnabledPileExplanation": "Bestimmt ob sich dies als Gegenstandsstapel verhalten soll.", - "InspectItems": "Gegenstände prüfbar aktivieren", - "InspectItemsExplanation": "Durch Klick auf den Namen des Gegenständes wird das Gegenstandsfenster geöffnet.", - "Distance": "Interaktionsreichweite", - "GridUnits": "Gittereinheiten (leer lassen für unbegrenzt)", - "ItemTypeFilter": "Gegenstandstyp Filter", - "ItemTypeFilterExplanation": "Artikel dieser Typen werden in keiner Artikelstapel-Benutzeroberfläche angezeigt, wobei jeder Typ durch ein Komma getrennt wird. Leer lassen, um die Standardwerte des Moduls zu verwenden.", - "Macro": "Macro bei Interaktion", - "MacroExplanation": "Name des Makros, das ausgeführt werden soll, wenn mit diesem Stapel interagiert wird.", - "MacroPlaceholder": "Macro Name einfügen", - "OverrideAttributes": "Dynamische Attribute außer Kraft setzen", - "OverrideAttributesExplanation": "Legen Sie fest, ob dieser Stapel andere Attribute als die Standardattribute übertragen können soll.", - "ConfigureOverrideAttributes": "Konfiguriere: Dynamische Attribute außer Kraft setzen", - "DeleteWhenEmpty": "Löschen wenn leer", - "DeleteWhenEmptyExplanation": "Bewirkt, dass sich der Gegenstandsstapel selbst löscht, sobald er leer ist.", - "DeleteWhenEmptyDefault": "Standardeinstellung des Moduls", - "DeleteWhenEmptyYes": "Ja, löschen, wenn leer", - "DeleteWhenEmptyNo": "Nein, nicht löschen wenn leer", - "DisplayOne": "Einzelnes Artikelbild anzeigen", - "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", - "ClosedImagePath": "Pfad Bild Geschlossener Container", - "EmptyImagePath": "Pfad Bild Leerer Container", - "OpenedImagePath": "Pfad Bild Geöffneter Container", - "LockedImagePath": "Pfad Bild Verschlossener Container", - "CloseSoundPath": "Pfad Geräusch Schliessen", - "OpenSoundPath": "Pfad Geräusch Öffnen", - "LockedSoundPath": "Pfad Geräusch Verschlossen" + + "Main": { + "Title": "Hauptmenü", + "EnabledPile": "Aktiviert", + "EnabledPileExplanation": "Bestimmt ob sich dies als Gegenstandsstapel verhalten soll.", + "InspectItems": "Gegenstände prüfbar aktivieren", + "InspectItemsExplanation": "Durch Klick auf den Namen des Gegenständes wird das Gegenstandsfenster geöffnet.", + "Distance": "Interaktionsreichweite", + "GridUnits": "Gittereinheiten (leer lassen für unbegrenzt)", + "Macro": "Macro bei Interaktion", + "MacroExplanation": "Name des Makros, das ausgeführt werden soll, wenn mit diesem Stapel interagiert wird.", + "MacroPlaceholder": "Macro Name einfügen", + "DeleteWhenEmpty": "Löschen wenn leer", + "DeleteWhenEmptyExplanation": "Bewirkt, dass sich der Gegenstandsstapel selbst löscht, sobald er leer ist.", + "DeleteWhenEmptyDefault": "Standardeinstellung des Moduls", + "DeleteWhenEmptyYes": "Ja, löschen, wenn leer", + "DeleteWhenEmptyNo": "Nein, nicht löschen wenn leer", + "OverrideAttributes": "Dynamische Attribute außer Kraft setzen", + "OverrideAttributesExplanation": "Legen Sie fest, ob dieser Stapel andere Attribute als die Standardattribute übertragen können soll.", + "ConfigureOverrideAttributes": "Konfiguriere: Dynamische Attribute außer Kraft setzen", + "OverrideItemFilters": "Override Item Filters", + "OverrideItemFiltersExplanation": "Configure if this pile should be able to transfer other types items than the default.", + "ConfigureOverrideItemFilters": "Configure Override Item Filters" + }, + + "SingleItem": { + "Title": "Einstellungen einzelner Gegenstand", + "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.", + "DisplayOne": "Einzelnes Artikelbild anzeigen", + "DisplayOneExplanation": "Besteht der Stapel aus einem einzigen Gegenstand, wird das Bild des Stapels auf das Bild des Gegenstandes gesetzt.", + "OverrideScale": "Einzelne Gegenstands-Token-Skalierung außer Kraft setzen", + "Scale": "Gegenstands-Token-Skalierung", + "ItemName": "Use Item Name", + "ItemNameExplanation": "Causes the item pile to be named after the single item it contains." + }, + + "Container": { + "Title": "Einstellungen Container", + "IsContainer": "Ist Container", + "Locked": "Ist verschlossen", + "Closed": "Ist geschlossen", + "ClosedImagePath": "Pfad Bild Geschlossener Container", + "EmptyImagePath": "Pfad Bild Leerer Container", + "OpenedImagePath": "Pfad Bild Geöffneter Container", + "LockedImagePath": "Pfad Bild Verschlossener Container", + "CloseSoundPath": "Pfad Geräusch Schliessen", + "OpenSoundPath": "Pfad Geräusch Öffnen", + "LockedSoundPath": "Pfad Geräusch Verschlossen" + } }, + "HUD": { "ToggleLocked": "Setze verschlossen", "ToggleClosed": "Setze geschlossen", @@ -140,13 +162,10 @@ "Label": "Attribute konfigurieren", "Hint": "Mit dieser Einstellung werden die Attribute festgelegt, die für die Abholung in Artikelpfaden in Frage kommen, z. B. Währungen oder Stärken, bei denen es sich nicht um tatsächliche Artikel handeln muss." }, - "ItemType": { - "Title": "Gegenstand Attribut Artikeltyp", - "Label": "Diese Einstellung legt den Attributspfad fest, der bestimmt, wo sich die Gegenstandstypen befinden. In D&D5e heißt dieser einfach \"type\", weil der Typ direkt in \"item.data.type\" definiert ist, da jeder Gegenstand seinen Typ mit diesem Attribut kennzeichnet." - }, - "ItemTypeFilters": { - "Title": "Gegenstand Typ Filter", - "Label": "Hier kannst du konfigurieren, welche Gegenstandstypen ignoriert und nicht in den Gegenstandsstapeldialogen aufgeführt werden sollen. Zum Beispiel willst du in D&D5e wahrscheinlich keine Zauber, Feats und Klassen anzeigen, also würdest du \"spell, feat, class\" eintragen." + "ItemFilters": { + "Title": "Item filters", + "Label": "Configure Item Filters", + "Hint": "Here you can configure what items are ignored and not listed in the item pile dialogs." }, "OutputToChat": { "Title": "Ausgabe im Chat", diff --git a/languages/en.json b/languages/en.json index 3e829ba3..74efc153 100644 --- a/languages/en.json +++ b/languages/en.json @@ -5,9 +5,9 @@ "Title": "Inspecting Pile Contents", "AsActor": "You're inspecting this pile as {actorName}", "NoActor": "You are inspecting the contents of this pile without controlling a token, which means you can't take items from it.", - "TakeAll": "Take All Items", "Empty": "This pile is empty.", "Destroyed": "This pile no longer exists and lies barren.", + "TakeAll": "Take All Items", "Take": "Take", "Close": "Close Lid", "Leave": "Leave" @@ -51,53 +51,75 @@ "Title": "Dynamic Attributes Editor", "Explanation": "Here you can define which attributes on characters that can be picked up from item piles, such as currencies. In D&D5e, currencies exist on actors on the attribute path \"actor.data.data.currency.gp\", so you'd add your own with the name \"Gold Coins\" and attribute path \"data.currency.gp\".", "Name": "Attribute name", - "AttributePath": "Attribute path", "Icon": "Attribute icon", "AddNew": "Add new attribute", "Submit": "Submit Attributes" }, + "FilterEditor": { + "Title": "Item Filters Editor", + "Explanation": "Here you can define multiple types of filters that will exclude certain types of items. Based on the attribute path given, the item pile could find the \"type\" of an item and based on the filters, it hides those items in the item pile inventory UI.", + "Filters": "Filters", + "AddNew": "Add new filter", + "Submit": "Submit Filters" + }, + + "AttributePath": "Attribute path", + "Defaults": { "Title": "Item Pile Configuration", "Configure": "Item Pile", - "MainSettings": "Main Settings", - "SingleItemSettings": "Single Item Settings", - "ContainerSettings": "Container Settings", "Update": "Update Item Pile", - "EnabledPile": "Enabled", - "EnabledPileExplanation": "Whether this should act as an item pile.", - "InspectItems": "Enable Item Inspect", - "InspectItemsExplanation": "Clicking item names will open the item's sheet.", - "Distance": "Interaction Distance", - "GridUnits": "Grid Units (leave empty for infinite)", - "ItemTypeFilter": "Item Type Filter", - "ItemTypeFilterExplanation": "Items of these types will not be shown in any item pile UI, each type separated by a comma. Leave blank to use module defaults.", - "Macro": "On Interact Macro", - "MacroExplanation": "Name of macro to execute when this pile is interacted with.", - "MacroPlaceholder": "Insert macro name", - "OverrideAttributes": "Override Dynamic Attributes", - "OverrideAttributesExplanation": "Configure if this pile should be able to transfer other attributes than the default.", - "ConfigureOverrideAttributes": "Configure Override Dynamic Attributes", - "DeleteWhenEmpty": "Delete when empty", - "DeleteWhenEmptyExplanation": "Causes the item pile to auto-delete itself once it's empty.", - "DeleteWhenEmptyDefault": "Default module setting", - "DeleteWhenEmptyYes": "Yes, delete when empty", - "DeleteWhenEmptyNo": "No, don't delete when empty", - "DisplayOne": "Display Single Item Image", - "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", - "ClosedImagePath": "Closed Image Path", - "EmptyImagePath": "Empty Image Path", - "OpenedImagePath": "Opened Image Path", - "LockedImagePath": "Locked Image Path", - "CloseSoundPath": "Closing Sound Path", - "OpenSoundPath": "Opening Sound Path", - "LockedSoundPath": "Locked Sound Path" + + "Main": { + "Title": "Main", + "EnabledPile": "Enabled", + "EnabledPileExplanation": "Whether this should act as an item pile.", + "InspectItems": "Enable Item Inspect", + "InspectItemsExplanation": "Clicking item names will open the item's sheet.", + "Distance": "Interaction Distance", + "GridUnits": "Grid Units (leave empty for infinite)", + "Macro": "On Interact Macro", + "MacroExplanation": "Name of macro to execute when this pile is interacted with.", + "MacroPlaceholder": "Insert macro name", + "DeleteWhenEmpty": "Delete when empty", + "DeleteWhenEmptyExplanation": "Causes the item pile to auto-delete itself once it's empty.", + "DeleteWhenEmptyDefault": "Default module setting", + "DeleteWhenEmptyYes": "Yes, delete when empty", + "DeleteWhenEmptyNo": "No, don't delete when empty", + "OverrideAttributes": "Override Dynamic Attributes", + "OverrideAttributesExplanation": "Configure if this pile should be able to transfer other attributes than the default.", + "ConfigureOverrideAttributes": "Configure Override Dynamic Attributes", + "OverrideItemFilters": "Override Item Filters", + "OverrideItemFiltersExplanation": "Configure if this pile should be able to transfer other types items than the default.", + "ConfigureOverrideItemFilters": "Configure Override Item Filters" + }, + + "SingleItem": { + "Title": "Single Item", + "DisplayOneContainerWarning": "Warning! You have both \"Display Single Item Image\" and \"Is Container\" enabled. In this case, the container images takes priority.", + "DisplayOne": "Display Single Item Image", + "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.", + "OverrideScale": "Override single item token scale", + "Scale": "Single item token scale", + "ItemName": "Use Item Name", + "ItemNameExplanation": "Causes the item pile to be named after the single item it contains." + }, + + "Container": { + "Title": "Container Settings", + "IsContainer": "Is Container", + "Locked": "Is Locked", + "Closed": "Is Closed", + "ClosedImagePath": "Closed Image Path", + "EmptyImagePath": "Empty Image Path", + "OpenedImagePath": "Opened Image Path", + "LockedImagePath": "Locked Image Path", + "CloseSoundPath": "Closing Sound Path", + "OpenSoundPath": "Opening Sound Path", + "LockedSoundPath": "Locked Sound Path" + } + }, "HUD": { @@ -141,13 +163,10 @@ "Label": "Configure Attributes", "Hint": "This setting define the attributes that are eligible for pickup in item paths, such as currencies or power, which may not be actual items." }, - "ItemType": { - "Title": "Item type attribute", - "Label": "This setting defines the attribute path for determining where the item types live. In D&D5e, it's just \"type\" because the type is defined directly in \"item.data.type\", because each item distinguishes its type with that attribute." - }, - "ItemTypeFilters": { - "Title": "Item type filters", - "Label": "Here you can configure what item types are ignored and not listed in the item pile dialogs. For example, in D&D5e we probably don't want to show spells, feats, and classes, so you'd put \"spell, feat, class\"." + "ItemFilters": { + "Title": "Item filters", + "Label": "Configure Item Filters", + "Hint": "Here you can configure what items are ignored and not listed in the item pile dialogs." }, "OutputToChat": { "Title": "Output to chat", diff --git a/languages/fr.json b/languages/fr.json index 5e64e094..15cba2db 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -5,9 +5,9 @@ "Title": "Inspecter le contenu des piles", "AsActor": "Vous inspectez cette pile en tant que {actorName}", "NoActor": "Vous inspectez le contenu de cette pile sans contrôler un token, ce qui signifie que vous ne pouvez pas y prendre d'objets.", - "TakeAll": "Prendre tous les articles", "Empty": "Cette pile est vide.", "Destroyed": "Cette pile n'existe plus.", + "TakeAll": "Prendre tous les articles", "Take": "Prendre", "Close": "Fermer", "Leave": "Partir" @@ -51,53 +51,74 @@ "Title": "Éditeur d'attributs dynamiques", "Explanation": "Ici, vous pouvez définir les attributs des personnages qui peuvent être récupérés dans les piles d'objets, comme les devises. Dans D&D5e, les monnaies existent sur les acteurs dans le chemin d'attribut \"actor.data.data.currency.gp\", donc vous ajouteriez la vôtre avec le nom \"Gold Coins\" et le chemin d'attribut \"data.currency.gp\".", "Name": "Nom de l'attribut", - "AttributePath": "Chemin de l'attribut", "Icon": "Icône de l'attribut", "AddNew": "Ajouter un nouvel attribut", "Submit": "Soumettre les attributs" }, + "FilterEditor": { + "Title": "Item Filters Editor", + "Explanation": "Here you can define multiple types of filters that will exclude certain types of items. Based on the attribute path given, the item pile could find the \"type\" of an item and based on the filters, it hides those items in the item pile inventory UI.", + "Filters": "Filters", + "AddNew": "Add new filter", + "Submit": "Submit Filters" + }, + + "AttributePath": "Chemin de l'attribut", + "Defaults": { - "Title": "Configuration de la pile d'article", - "Configure": "Pile d'articles", - "MainSettings": "Paramètres principaux", - "SingleItemSettings": "Paramètres objet unique", - "ContainerSettings": "Paramètres du conteneur", - "Update": "Mise à jour de la pile d'articles", - "EnabledPile": "Activé", - "EnabledPileExplanation": "Si cela doit agir comme une pile d'articles.", - "InspectItems": "Activer l'inspection des éléments", - "InspectItemsExplanation": "En cliquant sur les noms des éléments, vous ouvrirez la fiche en question.", - "Distance": "Distance d'interaction", - "GridUnits": "Unités de grille (laissez vide pour l'infini)", - "ItemTypeFilter": "Filtre du type d'article", - "ItemTypeFilterExplanation": "Les éléments de ces types ne seront pas affichés dans l'interface de la pile d'éléments, chaque type étant séparé par une virgule. Laissez vide pour utiliser les valeurs par défaut du module.", - "Macro": "Macro à l'interaction", - "MacroExplanation": "Nom de la macro à exécuter lors de l'interaction avec cette pile.", - "MacroPlaceholder": "Insérer le nom de la macro", - "OverrideAttributes": "Remplacer les attributs dynamiques", - "OverrideAttributesExplanation": "Configurez si cette pile doit être capable d'utiliser d'autres attributs que ceux par défaut.", - "ConfigureOverrideAttributes": "Configurer les attributs dynamiques de remplacement", - "DeleteWhenEmpty": "Supprimer lorsque vide", - "DeleteWhenEmptyExplanation": "Permet à la pile d'objets de s'effacer automatiquement lorsqu'elle est vide.", - "DeleteWhenEmptyDefault": "Réglage par défaut du module", - "DeleteWhenEmptyYes": "Oui, supprimer quand c'est vide", - "DeleteWhenEmptyNo": "Non, ne pas supprimer quand c'est vide", - "DisplayOne": "Afficher l'image d'un seul élément", - "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é", - "ClosedImagePath": "Chemin de l'image quand fermé", - "EmptyImagePath": "Chemin de l'image quand vide", - "OpenedImagePath": "Chemin de l'image quand ouvert", - "LockedImagePath": "Chemin de l'image quand verrouillé", - "CloseSoundPath": "Chemin du son quand fermé", - "OpenSoundPath": "Chemin du son quand ouvert", - "LockedSoundPath": "Chemin du son quand verrouillé" + "Title": "Configuration de la pile d'article", + "Configure": "Pile d'articles", + "Update": "Mise à jour de la pile d'articles", + + "Main": { + "Title": "Paramètres principaux", + "EnabledPile": "Activé", + "EnabledPileExplanation": "Si cela doit agir comme une pile d'articles.", + "InspectItems": "Activer l'inspection des éléments", + "InspectItemsExplanation": "En cliquant sur les noms des éléments, vous ouvrirez la fiche en question.", + "Distance": "Distance d'interaction", + "GridUnits": "Unités de grille (laissez vide pour l'infini)", + "Macro": "Macro à l'interaction", + "MacroExplanation": "Nom de la macro à exécuter lors de l'interaction avec cette pile.", + "MacroPlaceholder": "Insérer le nom de la macro", + "DeleteWhenEmpty": "Supprimer lorsque vide", + "DeleteWhenEmptyExplanation": "Permet à la pile d'objets de s'effacer automatiquement lorsqu'elle est vide.", + "DeleteWhenEmptyDefault": "Réglage par défaut du module", + "DeleteWhenEmptyYes": "Oui, supprimer quand c'est vide", + "DeleteWhenEmptyNo": "Non, ne pas supprimer quand c'est vide", + "OverrideAttributes": "Remplacer les attributs dynamiques", + "OverrideAttributesExplanation": "Configurez si cette pile doit être capable d'utiliser d'autres attributs que ceux par défaut.", + "ConfigureOverrideAttributes": "Configurer les attributs dynamiques de remplacement", + "OverrideItemFilters": "Override Item Filters", + "OverrideItemFiltersExplanation": "Configure if this pile should be able to transfer other types items than the default.", + "ConfigureOverrideItemFilters": "Configure Override Item Filters" + }, + + "SingleItem": { + "Title": "Paramètres objet unique", + "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.", + "DisplayOne": "Afficher l'image d'un seul élément", + "DisplayOneExplanation": "Si la pile est composée d'un seul objet, l'image du token de la pile sera l'image de l'objet.", + "OverrideScale": "Remplacer l'échelle du Token objet unique", + "Scale": "Échelle du Token", + "ItemName": "Use Item Name", + "ItemNameExplanation": "Causes the item pile to be named after the single item it contains." + }, + + "Container": { + "Title": "Paramètres objet unique", + "IsContainer": "Est un conteneur", + "Locked": "est verrouillé", + "Closed": "Est fermé", + "ClosedImagePath": "Chemin de l'image quand fermé", + "EmptyImagePath": "Chemin de l'image quand vide", + "OpenedImagePath": "Chemin de l'image quand ouvert", + "LockedImagePath": "Chemin de l'image quand verrouillé", + "CloseSoundPath": "Chemin du son quand fermé", + "OpenSoundPath": "Chemin du son quand ouvert", + "LockedSoundPath": "Chemin du son quand verrouillé" + } }, "HUD": { @@ -141,14 +162,11 @@ "Label": "Configurer les attributs", "Hint": "Ce paramètre définit les attributs qui peuvent être ramassés dans les chemins d'accès aux articles, tels que les devises ou les pouvoirs, qui peuvent ne pas être des articles réels." }, - "ItemType": { - "Title": "Attribut Type d'article", - "Label": "Ce paramètre définit le chemin de l'attribut pour déterminer où se trouvent les types d'objets. Dans D&D5e, c'est juste \"type\" parce que le type est défini directement dans \"item.data.type\", parce que chaque objet distingue son type avec cet attribut." - }, - "ItemTypeFilters": { - "Title": "Filtres de type d'article", - "Label": "Ici, vous pouvez configurer quels types d'objets sont ignorés et ne sont pas listés dans les dialogues de pile d'objets. Par exemple, dans D&D5e, nous ne voulons probablement pas montrer les sorts, les dons et les classes, donc vous mettrez \"sort, dons, classe\"." - }, + "ItemFilters": { + "Title": "Item filters", + "Label": "Configure Item Filters", + "Hint": "Here you can configure what items are ignored and not listed in the item pile dialogs." + }, "OutputToChat": { "Title": "Afficher dans le chat", "Label": "Chaque fois qu'un joueur ramasse des articles d'une pile d'objets, ceci enverra un message indiquant ce qui a été ramassé", @@ -175,4 +193,4 @@ } } } -} +} \ No newline at end of file diff --git a/languages/ja.json b/languages/ja.json index af14648d..0ff01ded 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -5,9 +5,9 @@ "Title": "お宝の中身", "AsActor": "{actorName}としてお宝を確認中", "NoActor": "選択中のコマが居ない状態で中身を確認しています。アイテムを取得する先が存在ませんのでアイテムの取得ができません。", - "TakeAll": "すべて取得", "Empty": "このお宝は空です", "Destroyed": "このお宝はもはや存在せず……", + "TakeAll": "すべて取得", "Take": "取得", "Close": "閉じる", "Leave": "離れる" @@ -51,54 +51,76 @@ "Title": "参照データ設定", "Explanation": "お宝として指定されたコマから取得可能なリソースや内部データをここで定義できます。例えばD&D5版の場合、所持しているGPの数は\"actor.data.data.currency.gp\"に保存されています。\"金貨\"という名前とともに\"data.currency.gp\"のデータパスを設定してあげればお宝からGPを取得可能になります。", "Name": "データ名", - "AttributePath": "データパス", "Icon": "データアイコン", "AddNew": "新規データ追加", "Submit": "データ追加" }, + "FilterEditor": { + "Title": "アイテムフィルター設定", + "Explanation": "ここではお宝の中に表示されないアイテムの種別を設定できます。設定したパスからアイテムの「種別」が定められ、UI上に非表示になります。", + "Filters": "フィルター", + "AddNew": "新規フィルター追加", + "Submit": "フィルター確定" + }, + + "AttributePath": "データパス", + "Defaults": { "Title": "Item Pile(お宝)設定", "Configure": "お宝", - "MainSettings": "全体設定", - "SingleItemSettings": "単体設定", - "ContainerSettings": "コンテナ設定", "Update": "お宝更新", - "EnabledPile": "有効化", - "EnabledPileExplanation": "このキャラクターがお宝として振る舞うかどうかを設定します。", - "InspectItems": "アイテム詳細表示", - "InspectItemsExplanation": "アイテム名をクリックすると、そのアイテムの詳細が表示されます。", - "Distance": "操作可能範囲", - "GridUnits": "グリッド単位(空で無限)", - "ItemTypeFilter": "アイテム種別フィルタ", - "ItemTypeFilterExplanation": "この種別のアイテムは", - "Macro": "操作時のマクロ", - "MacroExplanation": "このお宝を誰かが操作したときにマクロを実行します(効果音やその他の挙動を自分で追加したい時)", - "MacroPlaceholder": "マクロ名", - "OverrideAttributes": "参照データ上書き", - "OverrideAttributesExplanation": "ここを設定すればデフォルト以外のデータを参照し、中身を取得できるようになります。", - "ConfigureOverrideAttributes": "参照データ設定", - "DeleteWhenEmpty": "空のお宝を自動削除", - "DeleteWhenEmptyExplanation": "中身が空になったら、お宝のコマがシーン上から消滅します。", - "DeleteWhenEmptyDefault": "デフォルト設定", - "DeleteWhenEmptyYes": "はい、空になったら削除してください", - "DeleteWhenEmptyNo": "いいえ、空になっても削除しないでください", - "DisplayOne": "単体時、アイテム画像を表示", - "DisplayOneExplanation": "お宝の中身が単体のアイテムの場合、そのアイテムの画像がお宝の画像そのものになります。", - "OverrideSingleItemScale": "単体アイテムのコマサイズ調整", - "SingleItemScale": "単体アイテムコマサイズ", - "DisplayOneContainerWarning": "警告:\"単体時、アイテム画像を表示\"と\"コンテナである\"の両方の設定が有効化されています。この場合、コンテナ画像のほうが優先されますのでご注意ください。", - "IsContainer": "コンテナである", - "Locked": "ロックされている", - "Closed": "閉まっている", - "ClosedImagePath": "閉まっている時の画像", - "EmptyImagePath": "空の時の画像", - "OpenedImagePath": "開いている時の画像", - "LockedImagePath": "ロック中の画像", - "CloseSoundPath": "閉めた時の効果音", - "OpenSoundPath": "開いた時の効果音", - "LockedSoundPath": "ロック中の効果音" + + "Main": { + "Title": "全体設定", + "EnabledPile": "有効化", + "EnabledPileExplanation": "このキャラクターがお宝として振る舞うかどうかを設定します。", + "InspectItems": "アイテム詳細表示", + "InspectItemsExplanation": "アイテム名をクリックすると、そのアイテムの詳細が表示されます。", + "Distance": "操作可能範囲", + "GridUnits": "グリッド単位(空で無限)", + "Macro": "操作時のマクロ", + "MacroExplanation": "このお宝を誰かが操作したときにマクロを実行します(効果音やその他の挙動を自分で追加したい時)", + "MacroPlaceholder": "マクロ名", + "DeleteWhenEmpty": "空のお宝を自動削除", + "DeleteWhenEmptyExplanation": "中身が空になったら、お宝のコマがシーン上から消滅します。", + "DeleteWhenEmptyDefault": "デフォルト設定", + "DeleteWhenEmptyYes": "はい、空になったら削除してください", + "DeleteWhenEmptyNo": "いいえ、空になっても削除しないでください", + "OverrideAttributes": "参照データ上書き", + "OverrideAttributesExplanation": "ここを設定すればデフォルト以外のデータを参照し、中身を取得できるようになります。", + "ConfigureOverrideAttributes": "参照データ設定", + "OverrideItemFilters": "アイテムフィルター上書き", + "OverrideItemFiltersExplanation": "設定を上書きし、デフォルトのアイテム種別以外を受け取れるようになります。", + "ConfigureOverrideItemFilters": "アイテムフィルター上書き設定" + }, + + "SingleItem": { + "Title": "単体設定", + "DisplayOneContainerWarning": "警告:\"単体時、アイテム画像を表示\"と\"コンテナである\"の両方の設定が有効化されています。この場合、コンテナ画像のほうが優先されますのでご注意ください。", + "DisplayOne": "単体時、アイテム画像を表示", + "DisplayOneExplanation": "お宝の中身が単体のアイテムの場合、そのアイテムの画像がお宝の画像そのものになります。", + "OverrideScale": "単体アイテムのコマサイズ調整", + "Scale": "単体アイテムコマサイズ", + "ItemName": "アイテム名使用", + "ItemNameExplanation": "お宝の中身が単体アイテムの場合はそのアイテムの名前を使用します。" + }, + + "Container": { + "Title": "コンテナ設定", + "IsContainer": "コンテナである", + "Locked": "ロックされている", + "Closed": "閉まっている", + "ClosedImagePath": "閉まっている時の画像", + "EmptyImagePath": "空の時の画像", + "OpenedImagePath": "開いている時の画像", + "LockedImagePath": "ロック中の画像", + "CloseSoundPath": "閉めた時の効果音", + "OpenSoundPath": "開いた時の効果音", + "LockedSoundPath": "ロック中の効果音" + } }, + "HUD": { "ToggleLocked": "ロック切替", "ToggleClosed": "開閉", @@ -140,13 +162,10 @@ "Label": "データ設定", "Hint": "(上級設定)ここでは所持金や能力の使用回数など、必ずしもアイテムではないものを設定し中から取得できるようにします。" }, - "ItemType": { - "Title": "アイテム種別設定", - "Label": "(上級設定)ここではデータ上アイテムとなるものがどんなパスを持つのかを設定します。D&D5版の場合、\"item.data.type\"という形で定義されているので、ここには\"type\"とだけ記入します。" - }, - "ItemTypeFilters": { - "Title": "アイテム種別フィルタ", - "Label": "お宝として取得不可能なアイテムの種別をここで定義します。それらのアイテムは中に存在しても、ダイアログの中には出現しなくなります。D&D5版では、呪文、特徴、クラスをアイテムとして置きたくないかもしれないので\"spell, feat, class\"を記入します(英語名であることに注意)。" + "ItemFilters": { + "Title": "アイテムフィルター", + "Label": "アイテムフィルター設定", + "Hint": "ここではお宝の中に表示されないアイテムの種別を設定できます。" }, "OutputToChat": { "Title": "チャット表示", diff --git a/scripts/api.js b/scripts/api.js index 1a62a971..2063a3da 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -35,22 +35,13 @@ export default class API { return game.settings.get(CONSTANTS.MODULE_NAME, "itemQuantityAttribute"); } - /** - * The attribute used to track the item type in this system - * - * @returns {string} - */ - static get ITEM_TYPE_ATTRIBUTE() { - return game.settings.get(CONSTANTS.MODULE_NAME, "itemTypeAttribute"); - } - /** * The filters for item types eligible for interaction within this system * * @returns {Array} */ - static get ITEM_TYPE_FILTERS() { - return game.settings.get(CONSTANTS.MODULE_NAME, "itemTypeFilters").split(',').map(str => str.trim().toLowerCase()); + static get ITEM_FILTERS() { + return lib.cleanItemFilters(game.settings.get(CONSTANTS.MODULE_NAME, "itemFilters")); } /** @@ -107,38 +98,24 @@ export default class API { } /** - * Sets the attribute used to track the item type in this system + * Sets the items filters for interaction within this system * - * @param {string} inAttribute - * @returns {string} - */ - static async setItemTypeAttribute(inAttribute) { - if (typeof inAttribute !== "string") { - throw lib.custom_error("setItemTypeAttribute | inAttribute must be of type string"); - } - return game.settings.set(CONSTANTS.MODULE_NAME, "itemTypeAttribute", inAttribute); - } - - /** - * Sets the filters for item types eligible for interaction within this system - * - * @param {string/array} inFilters + * @param {array} inFilters * @returns {Promise} */ - static async setItemTypeFilters(inFilters) { + static async setItemFilters(inFilters) { if (!Array.isArray(inFilters)) { - if (typeof inFilters !== "string") { - throw lib.custom_error("setItemTypeFilters | inFilters must be of type string or array"); - } - inFilters = inFilters.split(',') - } else { - inFilters.forEach(filter => { - if (typeof filter !== "string") { - throw lib.custom_error("setItemTypeFilters | each entry in inFilters must be of type string"); - } - }) + throw lib.custom_error("setItemFilters | inFilters must be of type string or array"); } - return game.settings.set(CONSTANTS.MODULE_NAME, "itemTypeFilters", inFilters.join(',')); + inFilters.forEach(filter => { + if (typeof filter?.path !== "string") { + throw lib.custom_error("setItemFilters | each entry in inFilters must have a \"path\" property with a value that is of type string"); + } + if (typeof filter?.filters !== "string") { + throw lib.custom_error("setItemFilters | each entry in inFilters must have a \"filters\" property with a value that is of type string"); + } + }); + return game.settings.set(CONSTANTS.MODULE_NAME, "itemFilters", inFilters); } /** @@ -222,6 +199,7 @@ export default class API { tokenSettings = foundry.utils.mergeObject(tokenSettings, { "img": lib.getItemPileTokenImage(target, pileSettings), "scale": lib.getItemPileTokenScale(target, pileSettings), + "name": lib.getItemPileName(target, pileSettings) }); const sceneId = targetUuid.split('.')[1]; @@ -234,8 +212,8 @@ export default class API { tokenUpdateGroups[sceneId].push({ "_id": tokenId, ...tokenSettings, - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: pileSettings, - [`actorData.flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: pileSettings + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: pileSettings, + [`actorData.flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: pileSettings }); } @@ -304,8 +282,8 @@ export default class API { tokenUpdateGroups[sceneId].push({ "_id": tokenId, ...tokenSettings, - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: pileSettings, - [`actorData.flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: pileSettings + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: pileSettings, + [`actorData.flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: pileSettings }); } @@ -575,7 +553,7 @@ export default class API { await lib.wait(15); - await lib.updateItemPile(target, data, tokenSettings); + await lib.updateItemPileData(target, data, tokenSettings); if (data.enabled && data.isContainer) { if (diff?.closed === true) { @@ -671,10 +649,10 @@ export default class 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 {boolean/Token/TokenDocument/Actor} inspectingTarget This will force the users to inspect this item pile as a specific character - * @param {boolean} useDefaultCharacter Causes the users to inspect the item pile inventory as their default character + * @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 {boolean/Token/TokenDocument/Actor} [inspectingTarget=false] This will force the users to inspect this item pile as a specific character + * @param {boolean} [useDefaultCharacter=false] Causes the users to inspect the item pile inventory as their default character * @returns {Promise} */ static async openItemPileInventory(target, userIds = [ game.user.id ], { inspectingTarget = false, useDefaultCharacter = false }={}){ @@ -750,25 +728,15 @@ export default class API { return lib.isItemPileEmpty(target); } - /** - * Returns the item type filters for a given item pile - * - * @param {Token/TokenDocument|Actor} target - * @returns {Array} - */ - static getItemPileItemTypeFilters(target){ - return lib.getDocumentItemTypeFilters(target); - } - /** * Returns the items this item pile can transfer * * @param {Token/TokenDocument|Actor} target - * @param {array/boolean} [itemTypeFilters=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} */ - static getItemPileItems(target, itemTypeFilters = false){ - return lib.getDocumentItems(target, itemTypeFilters); + static getItemPileItems(target, itemFilters = false){ + return lib.getValidDocumentItems(target, itemFilters); } /** @@ -814,6 +782,7 @@ export default class API { await _target.update({ "img": lib.getItemPileTokenImage(targetDocument), "scale": lib.getItemPileTokenScale(targetDocument), + "name": lib.getItemPileName(targetDocument) }) } resolve(); @@ -859,15 +828,19 @@ export default class API { if (!targetUuid) throw lib.custom_error(`AddItems | Could not determine the UUID, please provide a valid target`, true) items = items.map(itemData => { - let item; - if(itemData.item instanceof Item){ + + let item = itemData; + if(itemData instanceof Item){ + item = itemData.toObject(); + }else if(itemData.item instanceof Item){ item = itemData.item.toObject(); - }else{ + }else if(itemData.item){ item = itemData.item; } + return { item: item, - quantity: itemData?.quantity ?? getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) + quantity: itemData?.quantity ?? getProperty(item?.data ?? item ?? {}, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1 } }); @@ -900,7 +873,7 @@ export default class API { const foundItem = lib.getSimilarItem(targetActorItems, { itemId: item._id, itemName: item.name, - itemType: getProperty(item, API.ITEM_TYPE_ATTRIBUTE) + itemType: item.type }); const incomingQuantity = Number(itemData?.quantity ?? getProperty(itemData, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1); @@ -1186,14 +1159,14 @@ export default class 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} [itemTypeFilters=false] Array of item types disallowed - will default to module settings if none provided + * @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 array containing all of the items that were transferred to the target */ - static async transferAllItems(source, target, { itemTypeFilters = false, interactionId = false } = {}) { + static async transferAllItems(source, target, { itemFilters = false, interactionId = false } = {}) { - const hookResult = Hooks.call(HOOKS.ITEM.PRE_TRANSFER_ALL, source, target, itemTypeFilters, interactionId); + const hookResult = Hooks.call(HOOKS.ITEM.PRE_TRANSFER_ALL, source, target, itemFilters, interactionId); if (hookResult === false) return; const sourceUuid = lib.getUuid(source); @@ -1202,9 +1175,9 @@ export default class API { const targetUuid = lib.getUuid(target); if (!targetUuid) throw lib.custom_error(`TransferAllItems | Could not determine the UUID, please provide a valid target`, true) - if (itemTypeFilters) { - itemTypeFilters.forEach(filter => { - if (typeof filter !== "string") throw lib.custom_error(`TransferAllItems | entries in the itemTypeFilters must be of type string`); + if (itemFilters) { + itemFilters.forEach(filter => { + if (typeof filter !== "string") throw lib.custom_error(`TransferAllItems | entries in the itemFilters must be of type string`); }) } @@ -1212,17 +1185,17 @@ export default class API { throw lib.custom_error(`TransferAllItems | interactionId must be of type string or false`); } - return itemPileSocket.executeAsGM(SOCKET_HANDLERS.TRANSFER_ALL_ITEMS, sourceUuid, targetUuid, game.user.id, { itemTypeFilters, interactionId }); + return itemPileSocket.executeAsGM(SOCKET_HANDLERS.TRANSFER_ALL_ITEMS, sourceUuid, targetUuid, game.user.id, { itemFilters, interactionId }); } /** * @private */ - static async _transferAllItems(sourceUuid, targetUuid, userId, { itemTypeFilters = false, interactionId = false, isEverything = false } = {}) { + static async _transferAllItems(sourceUuid, targetUuid, userId, { itemFilters = false, interactionId = false, isEverything = false } = {}) { const source = await fromUuid(sourceUuid); - const itemsToRemove = API.getItemPileItems(source, itemTypeFilters).map(item => item.toObject()); + const itemsToRemove = API.getItemPileItems(source, itemFilters).map(item => item.toObject()); const itemsRemoved = await API._removeItems(sourceUuid, itemsToRemove, userId, { isTransfer: true, interactionId: interactionId }); const itemAdded = await API._addItems(targetUuid, itemsRemoved, userId, { isTransfer: true, interactionId: interactionId }); @@ -1637,14 +1610,14 @@ export default class 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} [itemTypeFilters=false] Array of item types disallowed - will default to module settings if none provided + * @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 */ - static async transferEverything(source, target, { itemTypeFilters = false, interactionId = false } = {}) { + static async transferEverything(source, target, { itemFilters = false, interactionId = false } = {}) { - const hookResult = Hooks.call(HOOKS.PRE_TRANSFER_EVERYTHING, source, target, itemTypeFilters, interactionId); + const hookResult = Hooks.call(HOOKS.PRE_TRANSFER_EVERYTHING, source, target, itemFilters, interactionId); if (hookResult === false) return; const sourceUuid = lib.getUuid(source); @@ -1653,9 +1626,9 @@ export default class API { const targetUuid = lib.getUuid(target); if (!targetUuid) throw lib.custom_error(`TransferEverything | Could not determine the UUID, please provide a valid target`, true) - if (itemTypeFilters) { - itemTypeFilters.forEach(filter => { - if (typeof filter !== "string") throw lib.custom_error(`TransferEverything | entries in the itemTypeFilters must be of type string`); + if (itemFilters) { + itemFilters.forEach(filter => { + if (typeof filter !== "string") throw lib.custom_error(`TransferEverything | entries in the itemFilters must be of type string`); }) } @@ -1663,16 +1636,16 @@ export default class API { throw lib.custom_error(`TransferEverything | interactionId must be of type string or false`); } - return itemPileSocket.executeAsGM(SOCKET_HANDLERS.TRANSFER_EVERYTHING, sourceUuid, targetUuid, game.user.id, { itemTypeFilters, interactionId }) + return itemPileSocket.executeAsGM(SOCKET_HANDLERS.TRANSFER_EVERYTHING, sourceUuid, targetUuid, game.user.id, { itemFilters, interactionId }) } /** * @private */ - static async _transferEverything(sourceUuid, targetUuid, userId, { itemTypeFilters = false, interactionId = false } = {}) { + static async _transferEverything(sourceUuid, targetUuid, userId, { itemFilters = false, interactionId = false } = {}) { - const itemsTransferred = await API._transferAllItems(sourceUuid, targetUuid, userId, { itemTypeFilters, interactionId, isEverything: true }); + const itemsTransferred = await API._transferAllItems(sourceUuid, targetUuid, userId, { itemFilters, interactionId, isEverything: true }); const attributesTransferred = await API._transferAllAttributes(sourceUuid, targetUuid, userId, { interactionId, isEverything: true }); await itemPileSocket.executeForEveryone(SOCKET_HANDLERS.CALL_HOOK, HOOKS.TRANSFER_EVERYTHING, sourceUuid, targetUuid, itemsTransferred, attributesTransferred, userId, interactionId); @@ -1724,24 +1697,6 @@ export default class API { return true; } - /** - * Checks whether an item (or item data) is of a type that is not allowed. If an array whether that type is allowed - * or not, returning the type if it is NOT allowed. - * - * @param {Item/Object} item - * @param {array/boolean} [itemTypeFilters=false] - * @return {boolean/string} - */ - static isItemTypeDisallowed(item, itemTypeFilters = false) { - if (!API.ITEM_TYPE_ATTRIBUTE) return false; - if (!Array.isArray(itemTypeFilters)) itemTypeFilters = API.ITEM_TYPE_FILTERS; - const itemType = getProperty(item, API.ITEM_TYPE_ATTRIBUTE); - if (itemTypeFilters.includes(itemType)) { - return itemType; - } - return false; - } - /* -------- PRIVATE ITEM PILE METHODS -------- */ /** @@ -1868,23 +1823,6 @@ export default class API { position: false } - const disallowedType = API.isItemTypeDisallowed(itemData); - if (disallowedType) { - if (!game.user.isGM) { - return lib.custom_warning(game.i18n.format("ITEM-PILES.Errors.DisallowedItemDrop", { type: disallowedType }), true) - } - if (!hotkeyState.shiftDown) { - const force = await Dialog.confirm({ - title: game.i18n.localize("ITEM-PILES.Dialogs.DropTypeWarning.Title"), - content: `

${game.i18n.format("ITEM-PILES.Dialogs.DropTypeWarning.Content", { type: disallowedType })}

`, - defaultYes: false - }); - if (!force) { - return; - } - } - } - if (data.tokenId) { dropData.source = canvas.tokens.get(data.tokenId).actor; } else if (data.actorId) { @@ -1947,6 +1885,23 @@ export default class API { } } + const disallowedType = lib.isItemInvalid(droppableDocuments?.[0], itemData); + if (disallowedType) { + if (!game.user.isGM) { + return lib.custom_warning(game.i18n.format("ITEM-PILES.Errors.DisallowedItemDrop", { type: disallowedType }), true) + } + if (!hotkeyState.shiftDown) { + const force = await Dialog.confirm({ + title: game.i18n.localize("ITEM-PILES.Dialogs.DropTypeWarning.Title"), + content: `

${game.i18n.format("ITEM-PILES.Dialogs.DropTypeWarning.Content", { type: disallowedType })}

`, + defaultYes: false + }); + if (!force) { + return; + } + } + } + if (hotkeyState.altDown) { if (droppableDocuments.length) { @@ -2071,6 +2026,7 @@ export default class API { pileDataDefaults.enabled = true; pileDataDefaults.deleteWhenEmpty = true; pileDataDefaults.displayOne = true; + pileDataDefaults.showItemName = true; pileDataDefaults.overrideSingleItemScale = true; pileDataDefaults.singleItemScale = 0.75; @@ -2078,7 +2034,7 @@ export default class API { name: "Default Item Pile", type: game.settings.get(CONSTANTS.MODULE_NAME, "actorClassType"), img: "icons/svg/item-bag.svg", - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: pileDataDefaults + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: pileDataDefaults }); await pileActor.update({ @@ -2088,7 +2044,7 @@ export default class API { bar1: { attribute: "" }, vision: false, displayName: 50, - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: pileDataDefaults + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: pileDataDefaults } }) @@ -2110,32 +2066,6 @@ export default class API { overrideData['actorData'] = { items } - const pileConfig = lib.getItemPileData(pileActor); - const attributes = lib.getItemPileAttributes(pileActor); - - 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; - } - } - - 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/chathandler.js b/scripts/chathandler.js index 15fd7444..1a4ec486 100644 --- a/scripts/chathandler.js +++ b/scripts/chathandler.js @@ -123,7 +123,7 @@ const chatHandler = { const targetActor = target?.actor ?? target; for(let message of messages){ - const flags = message.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.FLAG_NAME); + const flags = message.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.PILE_DATA); if(!flags || flags.interactionId !== interactionId) continue; return this._updateExistingMessage(message, sourceActor, targetActor, items, attributes) } @@ -141,7 +141,7 @@ const chatHandler = { content: chatCardHtml, flavor: "Item Piles", speaker: ChatMessage.getSpeaker({ alias: game.user.name }), - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: { + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: { interactionId: interactionId, items: items, attributes: attributes @@ -183,7 +183,7 @@ const chatHandler = { async _updateExistingMessage(message, sourceActor, targetActor, items, attributes) { - const flags = message.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.FLAG_NAME); + const flags = message.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.PILE_DATA); const newItems = this._matchEntries(flags.items, items); const newAttributes = this._matchEntries(flags.attributes, attributes); @@ -199,10 +199,10 @@ const chatHandler = { attributes: newAttributes }); - message.update({ + return message.update({ content: chatCardHtml, - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}.items`]: newItems, - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}.attributes`]: newAttributes, + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}.items`]: newItems, + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}.attributes`]: newAttributes, }); } diff --git a/scripts/constants.js b/scripts/constants.js index d57e4998..ab8e7fcb 100644 --- a/scripts/constants.js +++ b/scripts/constants.js @@ -1,17 +1,26 @@ const CONSTANTS = { MODULE_NAME: "item-piles", - FLAG_NAME: "data", + PILE_DATA: "data", + SHARING_DATA: "sharing", PILE_DEFAULTS: { + // Core settings enabled: false, distance: 1, - itemTypeFilters: "", - overrideAttributes: false, - canInspectItems: true, macro: "", deleteWhenEmpty: "default", + canInspectItems: true, + + // Overrides + overrideItemFilters: false, + overrideAttributes: false, + + // Token settings displayOne: false, + showItemName: false, overrideSingleItemScale: false, singleItemScale: 1.0, + + // Container settings isContainer: false, closed: false, locked: false, @@ -22,7 +31,7 @@ const CONSTANTS = { closeSound: "", openSound: "", lockedSound: "", - unlockedSound: "", + unlockedSound: "" } } diff --git a/scripts/flagManager.js b/scripts/flagManager.js new file mode 100644 index 00000000..3a20583f --- /dev/null +++ b/scripts/flagManager.js @@ -0,0 +1,88 @@ +import * as lib from "./lib/lib.js"; +import CONSTANTS from "./constants.js"; +import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; + +const flagManager = { + + _latestFlagVersion: false, + updateStack: new Map(), + + get latestFlagVersion() { + if (!this._latestFlagVersion) { + const versions = Object.keys(this.migrations); + versions.sort((a, b) => { + return isNewerVersion(a, b) ? -1 : 1; + }) + this._latestFlagVersion = versions[0]; + } + return this._latestFlagVersion; + }, + + /** + * Sanitizes the effect data, accounting for changes to the structure in previous versions + * + * @param inDocument + * @returns {array/boolean} + */ + getFlags(inDocument) { + + let pileData = inDocument.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.PILE_DATA); + + if (!pileData) return false; + + const pileVersion = pileData?.flagVersion || "1.0.0"; + + if (pileVersion === this.latestFlagVersion) return pileData; + + for (let [version, migration] of Object.entries(this.migrations)) { + + if (!isNewerVersion(version, pileVersion)) continue; + + pileData = migration(inDocument, pileData); + + } + + pileData.flagVersion = this.latestFlagVersion; + + itemPileSocket.executeAsGM(SOCKET_HANDLERS.MIGRATE_PILE, lib.getUuid(inDocument), pileData); + + return pileData; + + }, + + migrations: { + "1.2.6": (inDocument, pileData) => { + pileData.overrideItemFilters = pileData?.itemTypeFilters?.length + ? pileData.overrideItemFilters = [{ + path: "type", + filters: pileData.itemTypeFilters + }] : false; + delete pileData.itemTypeFilters; + + return pileData; + } + }, + + addDocumentToMigrate(inUuid, pileData){ + this.updateStack.set(inUuid, pileData); + this.runUpdates(); + }, + + runUpdates: foundry.utils.debounce(() => flagManager._runUpdates(), 100), + + async _runUpdates(){ + const stack = Array.from(this.updateStack); + this.updateStack = new Map(); + for(const [uuid, pileData] of stack){ + const pileDocument = await fromUuid(uuid); + if(!pileDocument) continue; + lib.debug(`Updated pile with UUID "${uuid}"`) + await lib.updateItemPileData(pileDocument, pileData); + } + } + +} + + + +export default flagManager; \ No newline at end of file diff --git a/scripts/formapplications/itemPileAttributeEditor.js b/scripts/formapplications/itemPileAttributeEditor.js index 26bb3a8e..af4783f6 100644 --- a/scripts/formapplications/itemPileAttributeEditor.js +++ b/scripts/formapplications/itemPileAttributeEditor.js @@ -25,7 +25,7 @@ export class ItemPileAttributeEditor extends FormApplication { const promise = new Promise(_resolve => { resolve = _resolve; }); - return [promise, new ItemPileAttributeEditor(pileAttributes, resolve).render(true)] + return [promise, new ItemPileAttributeEditor(foundry.utils.duplicate(pileAttributes), resolve).render(true)] } async getData(options) { @@ -83,4 +83,11 @@ export class ItemPileAttributeEditor extends FormApplication { } + async close(...args){ + if(this.resolve){ + this.resolve() + } + return super.close(...args) + } + } \ No newline at end of file diff --git a/scripts/formapplications/itemPileConfig.js b/scripts/formapplications/itemPileConfig.js index 1a5e8365..63fe77b1 100644 --- a/scripts/formapplications/itemPileConfig.js +++ b/scripts/formapplications/itemPileConfig.js @@ -2,6 +2,7 @@ import CONSTANTS from "../constants.js"; import API from "../api.js"; import { ItemPileAttributeEditor } from "./itemPileAttributeEditor.js"; import * as lib from "../lib/lib.js"; +import { ItemPileFiltersEditor } from "./itemPileFiltersEditor.js"; export class ItemPileConfig extends FormApplication { @@ -10,6 +11,7 @@ export class ItemPileConfig extends FormApplication { this.document = actor?.token ?? actor; this.pileData = foundry.utils.mergeObject(CONSTANTS.PILE_DEFAULTS, lib.getItemPileData(this.document)); this.attributeEditor = false; + this.itemFiltersEditor = false; } /** @inheritdoc */ @@ -43,9 +45,6 @@ export class ItemPileConfig extends FormApplication { async getData(options) { let data = super.getData(options); data.flagData = foundry.utils.mergeObject(foundry.utils.duplicate(data), foundry.utils.duplicate(this.pileData)); - data.defaultItemTypeFilters = API.ITEM_TYPE_FILTERS.length - ? "Defaults: " + Array.from(API.ITEM_TYPE_FILTERS).join(', ') - : "Input item type filters..."; return data; } @@ -56,6 +55,7 @@ export class ItemPileConfig extends FormApplication { const scaleCheckbox = html.find('input[name="overrideSingleItemScale"]'); const displayOneCheckbox = html.find('input[name="displayOne"]'); const containerCheckbox = html.find('input[name="isContainer"]'); + const overrideItemFiltersEnabledCheckbox = html.find('.item-pile-config-override-item-filters-checkbox'); const overrideAttributesEnabledCheckbox = html.find('.item-pile-config-override-attributes-checkbox'); const slider = html.find(".item-piles-scaleRange"); @@ -100,6 +100,9 @@ export class ItemPileConfig extends FormApplication { scaleCheckbox.prop('disabled', !isEnabled); scaleCheckbox.parent().toggleClass("item-pile-disabled", !isEnabled); + html.find('input[name="showItemName"]').prop('disabled', !isEnabled); + html.find('input[name="showItemName"]').parent().toggleClass("item-pile-disabled", !isEnabled); + html.find(".display-one-warning").css("display", isEnabled && isContainer ? "block" : "none"); self.setPosition(); }).change(); @@ -115,12 +118,25 @@ export class ItemPileConfig extends FormApplication { if (isChecked) { self.pileData.overrideAttributes = game.settings.get(CONSTANTS.MODULE_NAME, "dynamicAttributes"); } + html.find(".item-pile-config-configure-override-attributes").prop('disabled', !isChecked); }) html.find(".item-pile-config-configure-override-attributes").click(function () { self.showAttributeEditor(); }) + overrideItemFiltersEnabledCheckbox.change(function() { + let isChecked = $(this).is(":checked"); + if (isChecked) { + self.pileData.overrideItemFilters = game.settings.get(CONSTANTS.MODULE_NAME, "itemFilters"); + } + html.find(".item-pile-config-configure-override-item-filters").prop('disabled', !isChecked); + }) + + html.find(".item-pile-config-configure-override-item-filters").click(function () { + self.showItemFiltersEditor(); + }) + slider.on("input", function () { input.val($(this).val()); }) @@ -137,7 +153,24 @@ export class ItemPileConfig extends FormApplication { this.attributeEditor = UI; promise.then(newSettings => { this.attributeEditor = false; - this.pileData.overrideAttributes = newSettings; + if(newSettings) { + this.pileData.overrideAttributes = newSettings; + } + }); + } + + async showItemFiltersEditor() { + if (this.itemFiltersEditor) { + return this.itemFiltersEditor.render(false, { focus: true }); + } + const [promise, UI] = ItemPileFiltersEditor.showForPile(this.pileData.overrideItemFilters); + this.itemFiltersEditor = UI; + promise.then(newSettings => { + this.itemFiltersEditor = false; + if(newSettings) { + console.log(newSettings) + this.pileData.overrideItemFilters = newSettings; + } }); } @@ -147,9 +180,11 @@ export class ItemPileConfig extends FormApplication { const data = foundry.utils.mergeObject(defaults, formData); - const checked = this.element.find('.item-pile-config-override-attributes-checkbox').is(":checked"); + const overrideAttributesChecked = this.element.find('.item-pile-config-override-attributes-checkbox').is(":checked"); + data.overrideAttributes = overrideAttributesChecked ? this.pileData.overrideAttributes : false; - data.overrideAttributes = checked ? this.pileData.overrideAttributes : false; + const overrideItemFiltersChecked = this.element.find('.item-pile-config-override-item-filters-checkbox').is(":checked"); + data.overrideItemFilters = overrideItemFiltersChecked ? this.pileData.overrideItemFilters : false; data.deleteWhenEmpty = { "default": "default", @@ -168,6 +203,9 @@ export class ItemPileConfig extends FormApplication { if (this.attributeEditor) { this.attributeEditor.close(); } + if (this.itemFiltersEditor) { + this.itemFiltersEditor.close(); + } } } \ No newline at end of file diff --git a/scripts/formapplications/itemPileFiltersEditor.js b/scripts/formapplications/itemPileFiltersEditor.js new file mode 100644 index 00000000..5614b50b --- /dev/null +++ b/scripts/formapplications/itemPileFiltersEditor.js @@ -0,0 +1,91 @@ +import CONSTANTS from "../constants.js"; + +export class ItemPileFiltersEditor extends FormApplication { + + constructor(pileAttributes = false, resolve = false) { + super(); + this.resolve = resolve; + this.filters = pileAttributes || game.settings.get(CONSTANTS.MODULE_NAME, "itemFilters"); + } + + /** @inheritdoc */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + title: game.i18n.localize("ITEM-PILES.AttributeEditor.Title"), + classes: ["sheet", "item-pile-filters-editor"], + template: `${CONSTANTS.PATH}templates/filter-editor.html`, + width: 630, + height: "auto", + resizable: false + }); + } + + static showForPile(pileAttributes) { + let resolve; + const promise = new Promise(_resolve => { + resolve = _resolve; + }); + return [promise, new ItemPileFiltersEditor(foundry.utils.duplicate(pileAttributes), resolve).render(true)] + } + + async getData(options) { + const data = super.getData(options); + data.filters = this.filters; + return data; + } + + /* -------------------------------------------- */ + + activateListeners(html) { + super.activateListeners(html); + const self = this; + html.find('.item-pile-filters-remove').click(function () { + const index = Number($(this).closest('.item-pile-filters-row').attr("data-filter-index")); + self.filters.splice(index, 1); + $(this).closest('.item-pile-filters-row').remove(); + self.rerender(); + }); + html.find('button[name="newFilter"]').click(function () { + self.filters.push({ + path: "", + filters: "" + }) + self.rerender(); + }); + } + + rerender(){ + const self = this; + this.element.find('.item-pile-filters-row').each(function(index){ + if(index === 0) return; + self.filters[index-1] = { + path: $(this).find('.item-pile-filters-path-input').val(), + filters: $(this).find('.item-pile-filters-input').val() + } + }); + return this.render(true); + } + + async _updateObject(event, formData) { + + const newSettings = []; + for (let [path, value] of Object.entries(formData)) { + setProperty(newSettings, path, value) + } + + if (!this.resolve) { + game.settings.set(CONSTANTS.MODULE_NAME, "itemFilters", newSettings); + } else { + this.resolve(newSettings); + } + + } + + async close(...args){ + if(this.resolve){ + this.resolve() + } + return super.close(...args) + } + +} \ No newline at end of file diff --git a/scripts/formapplications/itemPileInventory.js b/scripts/formapplications/itemPileInventory.js index 15077142..5103eb01 100644 --- a/scripts/formapplications/itemPileInventory.js +++ b/scripts/formapplications/itemPileInventory.js @@ -22,6 +22,7 @@ export class ItemPileInventory extends FormApplication { this.deleted = false; this.interactionId = randomID(); this.overrides = overrides; + this.pileData = lib.getItemPileData(this.pile); Hooks.callAll(HOOKS.PILE.OPEN_INVENTORY, this, pile, recipient); } @@ -65,6 +66,7 @@ export class ItemPileInventory extends FormApplication { const app = ItemPileInventory.getActiveAppFromPile(pileUuid, recipientUuid); if (app) { + app.pileData = lib.getItemPileData(app.pile); return app.render(true, { focus: true }); } @@ -102,7 +104,7 @@ export class ItemPileInventory extends FormApplication { id = foundItem.id; name = foundItem.name; img = foundItem.data.img; - type = getProperty(foundItem.data, API.ITEM_TYPE_ATTRIBUTE); + type = foundItem.data.type; quantity = Number(getProperty(foundItem.data, API.ITEM_QUANTITY_ATTRIBUTE)); }else{ itemQuantity = 0; @@ -193,28 +195,57 @@ export class ItemPileInventory extends FormApplication { getData(options) { const data = super.getData(options); - data.overrides = this.overrides; - data.isDeleted = this.deleted; data.name = !data.isDeleted ? this.pile.name : "Nonexistent pile"; + if (this.deleted) { + return data; + } + + data.overrides = this.overrides; + data.hasRecipient = !!this.recipient; data.recipient = this.recipient; data.isContainer = false; - if (!data.isDeleted) { - const pileData = lib.getItemPileData(this.pile); - data.isContainer = pileData.isContainer; - data.items = this.items.length ? this.items : this.getPileItemData(); - data.attributes = this.attributes.length ? this.attributes : this.getPileAttributeData(); - data.hasItems = API.getItemPileItems(this.pile).length > 0; - data.hasAttributes = data?.attributes?.length; - data.canInspectItems = pileData.canInspectItems || game.user.isGM; - } + const pileData = lib.getItemPileData(this.pile); + data.isContainer = pileData.isContainer; + data.items = this.items.length ? this.items : this.getPileItemData(); + data.attributes = this.attributes.length ? this.attributes : this.getPileAttributeData(); + data.hasItems = API.getItemPileItems(this.pile).length > 0; + data.hasAttributes = data?.attributes?.length; + data.canInspectItems = pileData.canInspectItems || game.user.isGM; + + data.hasThings = data?.hasItems || data?.hasAttributes; data.isEmpty = lib.isItemPileEmpty(this.pile); + data.buttons = []; + + if(data.hasRecipient){ + + data.buttons.push({ + value: "takeAll", + icon: "fas fa-fist-raised", + text: game.i18n.localize("ITEM-PILES.Inspect.TakeAll") + }); + + if(pileData.isContainer && !this.overrides.remote){ + data.buttons.push({ + value: "close", + icon: "fas fa-box", + text: game.i18n.localize("ITEM-PILES.Inspect.Close") + }) + } + } + + data.buttons.push({ + value: "leave", + icon: "fas fa-sign-out-alt", + text: game.i18n.localize("ITEM-PILES.Inspect.Leave") + }); + return data; } @@ -301,11 +332,12 @@ export class ItemPileInventory extends FormApplication { async _updateObject(event, formData) { if (event.submitter.value === "takeAll") { - return await API.transferEverything(this.pile, this.recipient, { interactionId: this.interactionId }); + API.transferEverything(this.pile, this.recipient, { interactionId: this.interactionId }); + return; } if (event.submitter.value === "close") { - return isPileInventoryOpenForOthers.query(this.pile).then((result) => { + isPileInventoryOpenForOthers.query(this.pile).then((result) => { if (!result) API.closeItemPile(this.pile, this.recipient); }); } diff --git a/scripts/lib/lib.js b/scripts/lib/lib.js index 4e1e86a2..3e6b890e 100644 --- a/scripts/lib/lib.js +++ b/scripts/lib/lib.js @@ -1,5 +1,6 @@ import CONSTANTS from "../constants.js"; import API from "../api.js"; +import flagManager from "../flagManager.js"; export function isGMConnected(){ return !!Array.from(game.users).find(user => user.isGM && user.active); @@ -156,13 +157,11 @@ export function dialogWarning(message, icon = "fas fa-exclamation-triangle"){ export function getItemPileData(target){ let inDocument = getDocument(target); - if(inDocument instanceof TokenDocument && inDocument?.data?.actorLink){ + if(inDocument instanceof TokenDocument){ inDocument = inDocument?.actor; - }else if(inDocument instanceof Actor && inDocument.token){ - inDocument = inDocument?.token; } try{ - let data = inDocument.getFlag(CONSTANTS.MODULE_NAME, CONSTANTS.FLAG_NAME); + let data = foundry.utils.duplicate(flagManager.getFlags(inDocument)); if(!data) return {}; let defaults = foundry.utils.duplicate(CONSTANTS.PILE_DEFAULTS); return foundry.utils.mergeObject(defaults, data); @@ -171,54 +170,6 @@ export function getItemPileData(target){ } } -export async function updateItemPile(target, flagData, tokenData){ - - const inDocument = getDocument(target); - - if(!tokenData) tokenData = {}; - - let documentActor; - let documentTokens = []; - - if(inDocument instanceof Actor){ - documentActor = inDocument; - if(inDocument.token) { - documentToken.push(inDocument?.token); - }else{ - documentTokens = canvas.tokens.placeables.filter(token => token.document.actor === documentActor).map(token => token.document); - } - }else{ - documentActor = inDocument.actor; - if(inDocument.isLinked){ - documentTokens = canvas.tokens.placeables.filter(token => token.document.actor === documentActor).map(token => token.document); - }else{ - documentTokens.push(inDocument); - } - } - - const updates = documentTokens.map(tokenDocument => { - const newTokenData = foundry.utils.mergeObject(tokenData, { - "img": getItemPileTokenImage(tokenDocument, flagData), - "scale": getItemPileTokenScale(tokenDocument, flagData), - }); - return { - "_id": tokenDocument.id, - ...newTokenData, - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: flagData - } - }); - - await canvas.scene.updateEmbeddedDocuments("Token", updates); - - return documentActor.update({ - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: flagData, - "token": { - [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.FLAG_NAME}`]: flagData, - } - }); - -} - export function getItemPileTokenImage(target, data = false) { const pileDocument = getDocument(target); @@ -238,7 +189,7 @@ export function getItemPileTokenImage(target, data = false) { if(!isValidItemPile(pileDocument)) return originalImg; - const items = getDocumentItems(pileDocument); + const items = getValidDocumentItems(pileDocument); const attributes = getItemPileAttributes(pileDocument); const numItems = items.length + attributes.length; @@ -288,47 +239,104 @@ export function getItemPileTokenScale(target, data) { baseScale = pileDocument.data.token.scale; } - const items = getDocumentItems(pileDocument); + const items = getValidDocumentItems(pileDocument); const attributes = getItemPileAttributes(pileDocument); const numItems = items.length + attributes.length; - if(!isValidItemPile(pileDocument, data) || data.isContainer || !data.displayOne || !data.overrideSingleItemScale || numItems > 1) return baseScale; + if(!isValidItemPile(pileDocument, data) || data.isContainer || !data.displayOne || !data.overrideSingleItemScale || numItems > 1 || numItems === 0) return baseScale; return data.singleItemScale; } -export function getDocumentItems(target, itemTypeFilters = false){ +export function getItemPileName(target, data){ - const inDocument = getDocument(target); + const pileDocument = getDocument(target); - const pileItemFilters = Array.isArray(itemTypeFilters) - ? new Set(itemTypeFilters) - : new Set(getDocumentItemTypeFilters(target)); + if (!data) { + data = getItemPileData(pileDocument); + } + const items = getValidDocumentItems(pileDocument); + const attributes = getItemPileAttributes(pileDocument); + + const numItems = items.length + attributes.length; + + if(!isValidItemPile(pileDocument, data) || data.isContainer || !data.displayOne || !data.showItemName || numItems > 1 || numItems === 0) return pileDocument.name; + + const item = items.length > 0 + ? items[0] + : attributes[0]; + + return item.name; + +} + + +export function getValidDocumentItems(target, itemFilters = false){ + + const pileItemFilters = itemFilters || getDocumentItemFilters(target); + + const inDocument = getDocument(target); const targetActor = inDocument instanceof TokenDocument ? inDocument.actor : inDocument; - return Array.from(targetActor.items).filter(item => { - const itemType = getProperty(item.data, API.ITEM_TYPE_ATTRIBUTE); - return !pileItemFilters.has(itemType); - }) + return Array.from(targetActor.items).filter(item => !isItemInvalid(inDocument, item, pileItemFilters)); + } -export function isValidItemPile(target, data = false){ - const inDocument = getDocument(target); - const documentActor = inDocument instanceof TokenDocument ? inDocument.actor : inDocument; - return inDocument && !inDocument.destroyed && documentActor && (data || getItemPileData(inDocument))?.enabled; +export function isActiveGM(user) { + return user.active && user.isGM; +} + +export function isResponsibleGM() { + if (!game.user.isGM) return false; + const connectedGMs = game.users.filter(isActiveGM); + return !connectedGMs.some(other => other.data._id < game.user.data._id); } -export function getDocumentItemTypeFilters(target){ +export function isItemInvalid(target, item, itemFilters = false){ + const pileItemFilters = itemFilters || getDocumentItemFilters(target); + const itemData = item instanceof Item ? item.data : item; + for(const filter of pileItemFilters){ + if(!hasProperty(itemData, filter.path)) continue; + const attributeValue = getProperty(itemData, filter.path); + if (filter.filters.has(attributeValue)) { + return attributeValue; + } + } + return false; +} + +export function getDocumentItemFilters(target){ + if(!target) return API.ITEM_FILTERS; const inDocument = getDocument(target); const pileData = getItemPileData(inDocument); - return isValidItemPile(inDocument) && pileData?.itemTypeFilters - ? pileData.itemTypeFilters.split(',').map(str => str.trim().toLowerCase()) - : API.ITEM_TYPE_FILTERS; + return isValidItemPile(inDocument) && pileData?.overrideItemFilters + ? cleanItemFilters(pileData.overrideItemFilters) + : API.ITEM_FILTERS; +} + +/** + * Cleans item filters and prepares them for use in above functions + * + * @param {Array} itemFilters + * @returns {Array} + */ +export function cleanItemFilters(itemFilters){ + return itemFilters ? foundry.utils.duplicate(itemFilters).map(filter => { + filter.path = filter.path.trim().toLowerCase(); + filter.filters = new Set(filter.filters.split(',').map(string => string.trim().toLowerCase())); + return filter; + }) : []; +} + +export function isValidItemPile(target, data = false){ + const inDocument = getDocument(target); + const documentActor = inDocument instanceof TokenDocument ? inDocument.actor : inDocument; + return inDocument && !inDocument.destroyed && documentActor && (data || getItemPileData(inDocument))?.enabled; } export function isItemPileEmpty(target) { @@ -341,7 +349,7 @@ export function isItemPileEmpty(target) { if(!targetActor) return false; - const hasNoItems = getDocumentItems(inDocument).length === 0; + const hasNoItems = getValidDocumentItems(inDocument).length === 0; const hasEmptyAttributes = getItemPileAttributes(inDocument).length === 0; return hasNoItems && hasEmptyAttributes; @@ -370,4 +378,60 @@ export function getItemPileAttributes(target) { quantity: Number(getProperty(targetActor.data, attribute.path) ?? 1) } }); +} + +function getRelevantTokensAndActor(target){ + + const inDocument = getDocument(target); + + let documentActor; + let documentTokens = []; + + if(inDocument instanceof Actor){ + documentActor = inDocument; + if(inDocument.token) { + documentToken.push(inDocument?.token); + }else{ + documentTokens = canvas.tokens.placeables.filter(token => token.document.actor === documentActor).map(token => token.document); + } + }else{ + documentActor = inDocument.actor; + if(inDocument.isLinked){ + documentTokens = canvas.tokens.placeables.filter(token => token.document.actor === documentActor).map(token => token.document); + }else{ + documentTokens.push(inDocument); + } + } + + return [documentActor, documentTokens] + +} + +export async function updateItemPileData(target, flagData, tokenData){ + + if(!tokenData) tokenData = {}; + + const [documentActor, documentTokens] = getRelevantTokensAndActor(target); + + const updates = documentTokens.map(tokenDocument => { + const newTokenData = foundry.utils.mergeObject(tokenData, { + "img": getItemPileTokenImage(tokenDocument, flagData), + "scale": getItemPileTokenScale(tokenDocument, flagData), + }); + return { + "_id": tokenDocument.id, + ...newTokenData, + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: flagData + } + }); + + await canvas.scene.updateEmbeddedDocuments("Token", updates); + + return documentActor.update({ + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: flagData, + "token": { + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: flagData, + } + }); + } \ No newline at end of file diff --git a/scripts/module.js b/scripts/module.js index a9b56e60..2c8b2270 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -1,13 +1,15 @@ import CONSTANTS from "./constants.js"; -import registerSettings, { checkSystem, registerHandlebarHelpers } from "./settings.js"; -import { registerSocket } from "./socket.js"; +import HOOKS from "./hooks.js"; + +import chatHandler from "./chathandler.js"; import API from "./api.js"; import * as lib from "./lib/lib.js"; + import { ItemPileConfig } from "./formapplications/itemPileConfig.js"; +import { registerSettings, checkSystem, migrateSettings, registerHandlebarHelpers } from "./settings.js"; +import { registerSocket } from "./socket.js"; import { registerLibwrappers } from "./libwrapper.js"; -import HOOKS from "./hooks.js"; import { registerHotkeysPre, registerHotkeysPost } from "./hotkeys.js"; -import chatHandler from "./chathandler.js"; Hooks.once("init", () => { @@ -72,9 +74,26 @@ Hooks.once("ready", () => { checkSystem(); registerHotkeysPost(); registerHandlebarHelpers(); + migrateSettings(); Hooks.callAll(HOOKS.READY); }); +const debounceManager = { + + debounces: {}, + + setDebounce(id, method){ + if(this.debounces[id]){ + return this.debounces[id]; + } + this.debounces[id] = debounce(function(...args){ + delete debounceManager.debounces[id]; + return method(...args); + }, 50); + return this.debounces[id]; + } +}; + const module = { async _pileAttributeChanged(actor, changes) { @@ -85,10 +104,13 @@ const module = { return hasProperty(changes, attribute.path); }); if (!validProperty) return; - const deleted = await API._checkItemPileShouldBeDeleted(target.uuid); - await API._rerenderItemPileInventoryApplication(target.uuid, deleted); - if (deleted || !game.user.isGM) return; - return API._refreshItemPile(target.uuid); + const targetUuid = target.uuid; + return debounceManager.setDebounce(targetUuid, async function(uuid){ + const deleted = await API._checkItemPileShouldBeDeleted(uuid); + await API._rerenderItemPileInventoryApplication(uuid, deleted); + if (deleted || !lib.isResponsibleGM()) return; + return API._refreshItemPile(uuid); + })(targetUuid); }, async _pileInventoryChanged(item) { @@ -96,17 +118,20 @@ const module = { if (!target) return; target = target?.token ?? target; if (!lib.isValidItemPile(target)) return; - const deleted = await API._checkItemPileShouldBeDeleted(target.uuid); - await API._rerenderItemPileInventoryApplication(target.uuid, deleted); - if (deleted || !game.user.isGM) return; - return API._refreshItemPile(target.uuid); + const targetUuid = target.uuid; + return debounceManager.setDebounce(targetUuid, async function(uuid){ + const deleted = await API._checkItemPileShouldBeDeleted(uuid); + await API._rerenderItemPileInventoryApplication(uuid, deleted); + if (deleted || !lib.isResponsibleGM()) return; + return API._refreshItemPile(uuid); + })(targetUuid); }, async _canvasReady(canvas) { const tokens = [...canvas.tokens.placeables].map(token => token.document); for (const doc of tokens) { await API._initializeItemPile(doc); - if(game.user.isGM) { + if(lib.isResponsibleGM()) { await API._refreshItemPile(doc.uuid); } } @@ -114,9 +139,12 @@ const module = { async _preCreatePile(token){ if (!lib.isValidItemPile(token)) return; + const itemPileConfig = lib.getItemPileData(token.actor) token.data.update({ - "img": lib.getItemPileTokenImage(token), - "scale": lib.getItemPileTokenScale(token) + "img": lib.getItemPileTokenImage(token, itemPileConfig), + "scale": lib.getItemPileTokenScale(token, itemPileConfig), + "name": lib.getItemPileName(token, itemPileConfig), + [`flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.PILE_DATA}`]: itemPileConfig }); }, diff --git a/scripts/settings.js b/scripts/settings.js index 77a810ec..06ac9afc 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -2,6 +2,7 @@ import CONSTANTS from "./constants.js"; import { ItemPileAttributeEditor } from "./formapplications/itemPileAttributeEditor.js"; import { SYSTEMS } from "./systems.js"; import * as lib from "./lib/lib.js"; +import { ItemPileFiltersEditor } from "./formapplications/itemPileFiltersEditor.js"; function defaultSettings(apply = false) { return { @@ -11,6 +12,12 @@ function defaultSettings(apply = false) { default: apply && SYSTEMS.DATA ? SYSTEMS.DATA.DYNAMIC_ATTRIBUTES : [], type: Array }, + "itemFilters": { + scope: "world", + config: false, + default: apply && SYSTEMS.DATA ? SYSTEMS.DATA.ITEM_FILTERS : [], + type: Array + }, "actorClassType": { name: "ITEM-PILES.Setting.ActorClass.Title", hint: "ITEM-PILES.Setting.ActorClass.Label", @@ -26,27 +33,11 @@ function defaultSettings(apply = false) { config: true, default: apply && SYSTEMS.DATA ? SYSTEMS.DATA.ITEM_QUANTITY_ATTRIBUTE : "", type: String - }, - "itemTypeAttribute": { - name: "ITEM-PILES.Setting.ItemType.Title", - hint: "ITEM-PILES.Setting.ItemType.Label", - scope: "world", - config: true, - default: apply && SYSTEMS.DATA ? SYSTEMS.DATA.ITEM_TYPE_ATTRIBUTE : "", - type: String - }, - "itemTypeFilters": { - name: "ITEM-PILES.Setting.ItemTypeFilters.Title", - hint: "ITEM-PILES.Setting.ItemTypeFilters.Label", - scope: "world", - config: true, - default: apply && SYSTEMS.DATA ? SYSTEMS.DATA.ITEM_TYPE_FILTERS : "", - type: String } } } -export default function registerSettings() { +export function registerSettings() { game.settings.registerMenu(CONSTANTS.MODULE_NAME, "resetAllSettings", { name: "ITEM-PILES.Setting.Reset.Title", @@ -66,6 +57,15 @@ export default function registerSettings() { restricted: true }); + game.settings.registerMenu(CONSTANTS.MODULE_NAME, "openItemFiltersEditor", { + name: "ITEM-PILES.Setting.ItemFilters.Title", + label: "ITEM-PILES.Setting.ItemFilters.Label", + hint: "ITEM-PILES.Setting.ItemFilters.Hint", + icon: "fas fa-list-ul", + type: ItemPileFiltersEditor, + restricted: true + }); + const settings = defaultSettings(); for (const [name, data] of Object.entries(settings)) { game.settings.register(CONSTANTS.MODULE_NAME, name, data); @@ -153,6 +153,29 @@ export default function registerSettings() { } +export async function migrateSettings(){ + + const itemTypeAttribute = game.settings.storage.get("world").getSetting(`${CONSTANTS.MODULE_NAME}.itemTypeAttribute`); + const itemTypeFilters = game.settings.storage.get("world").getSetting(`${CONSTANTS.MODULE_NAME}.itemTypeFilters`); + + if(itemTypeAttribute && itemTypeFilters){ + + const itemTypeAttributeValue = JSON.parse(itemTypeAttribute.data.value) + const itemTypeFiltersValue = JSON.parse(itemTypeFilters.data.value) + + game.settings.set(CONSTANTS.MODULE_NAME, "itemFilters", [ + { + "path": itemTypeAttributeValue, + "filters": itemTypeFiltersValue + } + ]) + + await itemTypeAttribute.delete(); + await itemTypeFilters.delete(); + + } +} + const debounceReload = foundry.utils.debounce(() => { window.location.reload(); }, 100); diff --git a/scripts/socket.js b/scripts/socket.js index d52c2485..2f3ed50f 100644 --- a/scripts/socket.js +++ b/scripts/socket.js @@ -3,6 +3,7 @@ import CONSTANTS from "./constants.js"; import API from "./api.js"; import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; import chatHandler from "./chathandler.js"; +import flagManager from "./flagManager.js"; export const SOCKET_HANDLERS = { /** @@ -11,6 +12,7 @@ export const SOCKET_HANDLERS = { CALL_HOOK: "callHook", PICKUP_CHAT_MESSAGE: "pickupChatMessage", + /** * Item pile sockets */ @@ -21,6 +23,7 @@ export const SOCKET_HANDLERS = { TURN_INTO_PILE: "turnIntoPiles", REVERT_FROM_PILE: "revertFromPiles", REFRESH_PILE: "refreshItemPile", + MIGRATE_PILE: "migrateItemPileFlags", /** * UI sockets @@ -68,6 +71,7 @@ export function registerSocket() { itemPileSocket.register(SOCKET_HANDLERS.TURN_INTO_PILE, (...args) => API._turnTokensIntoItemPiles(...args)) itemPileSocket.register(SOCKET_HANDLERS.REVERT_FROM_PILE, (...args) => API._revertTokensFromItemPiles(...args)) itemPileSocket.register(SOCKET_HANDLERS.REFRESH_PILE, (...args) => API._refreshItemPile(...args)) + itemPileSocket.register(SOCKET_HANDLERS.MIGRATE_PILE, (...args) => flagManager.addDocumentToMigrate(...args)) /** * UI sockets @@ -121,7 +125,14 @@ export const isPileInventoryOpenForOthers = { itemPileSocket.executeForOthers(SOCKET_HANDLERS.QUERY_PILE_INVENTORY_OPEN, game.user.id, lib.getUuid(inPile)); - setTimeout(this.resolve, 200); + if(this.usersToRespond.size > 0) { + setTimeout(this.resolve, 200); + }else{ + this.resolve(false); + this.usersToRespond = new Set(); + this.isOpen = false; + this.resolve = () => {}; + } return promise; }, @@ -138,8 +149,7 @@ export const isPileInventoryOpenForOthers = { this.resolve(this.isOpen); this.usersToRespond = new Set(); this.isOpen = false; - this.resolve = () => { - }; + this.resolve = () => {}; } } \ No newline at end of file diff --git a/scripts/systems/d35e.js b/scripts/systems/d35e.js index b0e1757a..62a18cc7 100644 --- a/scripts/systems/d35e.js +++ b/scripts/systems/d35e.js @@ -6,8 +6,12 @@ export default { "ITEM_QUANTITY_ATTRIBUTE": "data.quantity", // Item types and the filters actively remove items from the item pile inventory UI that users cannot loot, such as spells, feats, and classes - "ITEM_TYPE_ATTRIBUTE": "type", - "ITEM_TYPE_FILTERS": "spell,feat,class,race,attack,full-attack,buff,aura,alignment,enhancement,damage-type,material", + "ITEM_FILTERS": [ + { + "path": "type", + "filters": "spell,feat,class,race,attack,full-attack,buff,aura,alignment,enhancement,damage-type,material" + } + ], // Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets "DYNAMIC_ATTRIBUTES": [ diff --git a/scripts/systems/dnd5e.js b/scripts/systems/dnd5e.js index d5aa1eb3..1499fae5 100644 --- a/scripts/systems/dnd5e.js +++ b/scripts/systems/dnd5e.js @@ -6,8 +6,16 @@ export default { "ITEM_QUANTITY_ATTRIBUTE": "data.quantity", // Item types and the filters actively remove items from the item pile inventory UI that users cannot loot, such as spells, feats, and classes - "ITEM_TYPE_ATTRIBUTE": "type", - "ITEM_TYPE_FILTERS": "spell,feat,class", + "ITEM_FILTERS": [ + { + "path": "type", + "filters": "spell,feat,class" + }, + { + "path": "data.weaponType", + "filters": "natural" + } + ], // Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets "DYNAMIC_ATTRIBUTES": [ diff --git a/scripts/systems/ds4.js b/scripts/systems/ds4.js index 41d510e8..9b6bdeb5 100644 --- a/scripts/systems/ds4.js +++ b/scripts/systems/ds4.js @@ -8,8 +8,12 @@ export default { "ITEM_QUANTITY_ATTRIBUTE": "data.quantity", // Item types and the filters actively remove items from the item pile inventory UI that users cannot loot, such as spells, feats, and classes - "ITEM_TYPE_ATTRIBUTE": "types", - "ITEM_TYPE_FILTERS": "spell,talent,racialAbility,language,alphabet,specialCreatureAbility", + "ITEM_FILTERS": [ + { + "path": "type", + "filters": "spell,talent,racialAbility,language,alphabet,specialCreatureAbility" + } + ], // Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets "DYNAMIC_ATTRIBUTES": [ diff --git a/scripts/systems/pf1.js b/scripts/systems/pf1.js index c89b850e..80c40f38 100644 --- a/scripts/systems/pf1.js +++ b/scripts/systems/pf1.js @@ -6,8 +6,12 @@ export default { "ITEM_QUANTITY_ATTRIBUTE": "data.quantity", // Item types and the filters actively remove items from the item pile inventory UI that users cannot loot, such as spells, feats, and classes - "ITEM_TYPE_ATTRIBUTE": "type", - "ITEM_TYPE_FILTERS": "attack,buff,class,feat,race,spell", + "ITEM_FILTERS": [ + { + "path": "type", + "filters": "attack,buff,class,feat,race,spell" + } + ], // Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets "DYNAMIC_ATTRIBUTES": [ diff --git a/scripts/systems/swade.js b/scripts/systems/swade.js index 924048c4..ff9049e0 100644 --- a/scripts/systems/swade.js +++ b/scripts/systems/swade.js @@ -6,8 +6,12 @@ export default { "ITEM_QUANTITY_ATTRIBUTE": "data.quantity", // Item types and the filters actively remove items from the item pile inventory UI that users cannot loot, such as spells, feats, and classes - "ITEM_TYPE_ATTRIBUTE": "type", - "ITEM_TYPE_FILTERS": "edge,hindrance,skill,power,ability", + "ITEM_FILTERS": [ + { + "path": "type", + "filters": "edge,hindrance,skill,power,ability" + } + ], // Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets "DYNAMIC_ATTRIBUTES": [ diff --git a/scripts/systems/tormenta20.js b/scripts/systems/tormenta20.js index 69efae73..236fa899 100644 --- a/scripts/systems/tormenta20.js +++ b/scripts/systems/tormenta20.js @@ -6,8 +6,7 @@ export default { "ITEM_QUANTITY_ATTRIBUTE": "data.qtd", // Item types and the filters actively remove items from the item pile inventory UI that users cannot loot, such as spells, feats, and classes - "ITEM_TYPE_ATTRIBUTE": "type", - "ITEM_TYPE_FILTERS": "", + "ITEM_FILTERS": [], // Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets "DYNAMIC_ATTRIBUTES": [ diff --git a/templates/filter-editor.html b/templates/filter-editor.html new file mode 100644 index 00000000..c54ab5e1 --- /dev/null +++ b/templates/filter-editor.html @@ -0,0 +1,40 @@ +
+ + + +

{{localize "ITEM-PILES.FilterEditor.Explanation"}}

+ +
+
+
+
{{localize "ITEM-PILES.AttributePath"}}
+
{{localize "ITEM-PILES.FilterEditor.Filters"}}
+
+ {{#each filters as |filter id|}} +
+
+
+
+
+ {{/each}} + +
+
+ +
+ +
\ No newline at end of file diff --git a/templates/item-pile-config.html b/templates/item-pile-config.html index 4d48e692..14858b28 100644 --- a/templates/item-pile-config.html +++ b/templates/item-pile-config.html @@ -1,9 +1,9 @@

@@ -14,80 +14,84 @@
- +
- + {{localize "ITEM-PILES.Defaults.Main.OverrideAttributes"}}
+

{{localize "ITEM-PILES.Defaults.Main.OverrideAttributesExplanation"}}

+
+ +
+ +
+

- +
- +
- +
@@ -96,14 +100,22 @@
+ +
+ +
+
@@ -116,24 +128,24 @@
- +
- +
- +
- +
{{filePicker target="closedImage" type="imagevideo"}} @@ -141,7 +153,7 @@
- +
{{filePicker target="openedImage" type="imagevideo"}} @@ -149,7 +161,7 @@
- +
{{filePicker target="emptyImage" type="imagevideo"}} @@ -157,7 +169,7 @@
- +
{{filePicker target="lockedImage" type="imagevideo"}} @@ -165,7 +177,7 @@
- +
{{filePicker target="closeSound" type="audio"}} @@ -173,7 +185,7 @@
- +
{{filePicker target="openSound" type="audio"}} @@ -181,7 +193,7 @@
- +
{{filePicker target="lockedSound" type="audio"}} @@ -191,9 +203,10 @@
- +
+
diff --git a/templates/item-pile-inventory.html b/templates/item-pile-inventory.html index 4a5d0a45..8668ecb8 100644 --- a/templates/item-pile-inventory.html +++ b/templates/item-pile-inventory.html @@ -2,10 +2,6 @@
-
- -
- {{#if hasRecipient}}

{{localize "ITEM-PILES.Inspect.AsActor" actorName=recipient.name }}

{{else}} @@ -44,11 +40,9 @@ {{/if}}
{{/each}} - {{#if hasAttributes}} - {{#if hasItems}} + {{#if hasThings}}
{{/if}} - {{/if}} {{#each attributes as |attribute id|}}
@@ -69,19 +63,9 @@ {{/if}}