Skip to content

Commit

Permalink
SWADE and treat attributes as numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
Haxxer committed Jan 8, 2022
1 parent 6928e8e commit 776c1f8
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 20 deletions.
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Like what we've done? Buy us a coffee!

---

![Item Piles in a nutshell](./docs/images/intro.jpg)

## What is Item Piles?

This module enables dropping items onto the canvas, which then get represented as a pile of items. In order to work in all systems without breaking or messing too much with the core functionality of Foundry, this **creates an unlinked token & actor** to hold these items. When a player double-clicks on an item pile token, it opens a custom UI to show what the pile contains and players can then take items from it.
Expand Down Expand Up @@ -69,6 +71,7 @@ This module leverages the [Advanced Macros](https://github.com/League-of-Foundry
- [Pathfinder 1e](https://foundryvtt.com/packages/pf1)
- [Dungeon Slayers 4](https://foundryvtt.com/packages/ds4)
- [D&D 3.5e SRD](https://foundryvtt.com/packages/D35E)
- [Savage Worlds Adventure Edition](https://foundryvtt.com/packages/swade)


## Not Supported Systems
Expand All @@ -91,29 +94,35 @@ Installing this module allows anyone to drag & drop items to the canvas, which c

As a GM, you can duplicate the default item pile to create new versions of that pile, or turn existing actors into new item piles through the `Item Pile` button on the actor sheets' header bar. This UI has a wide range of customization, which allows you to control exactly how your players interact with the item pile.

![Item Pile Token Configuration](https://raw.githubusercontent.com/fantasycalendar/FoundryVTT-ItemPiles/master/docs/images/configs.png)
![Item Pile Token Configuration](./docs/images/configs.png)

### Warning!

Be sure to set any new Item Piles as **unlinked from its actor data**! Unless you know what you're doing, keeping this enabled will be confusing as **all tokens of this pile will share the same images and inventory**.

![Item Pile Token Linked](./docs/images/disable-link.jpg)

### Interaction

Any player or GM can drag & drop items - if you drag & drop an item from an actor's inventory, you will be prompted how many of that item that you wish to drop (if they have more than 1 of the item). Holding ALT before dragging & dropping an item, you will automatically drop 1 of the items into a new pile without a prompt. **You can also drag and drop items onto an existing pile**.

When players double-click on the item pile (or Left Control + double-click for GMs to inspect, or to inspect as someone else select two tokens, and repeat), they will get a custom UI where they can choose what they want to take from the item pile, or all of it (if they're loot goblins).

![Item Pile Inventory UI](https://raw.githubusercontent.com/fantasycalendar/FoundryVTT-ItemPiles/master/docs/images/inventory-ui.png)
![Item Pile Inventory UI](./docs/images/inventory-ui.png)

### Extra UI and settings

In addition, item piles have a few extra buttons on the right click Token HUD to open, close, lock, and unlock containers, and a quick way to access the configuration of that token.

![Item Pile Token HUD buttons](https://raw.githubusercontent.com/fantasycalendar/FoundryVTT-ItemPiles/master/docs/images/token-buttons.png)
![Item Pile Token HUD buttons](./docs/images/token-buttons.png)

In the module settings, you can configure all sorts of things, such as whether empty piles auto-delete once they're empty, and which item types are allowed to be picked up. These are by default configured to the D&D5e system, so adjust them accordingly for your own system.

### Currencies and attributes

For most systems, currencies or other physical things aren't considered "items" in Foundry, but rather just numbers on the sheet, so dragging and dropping them is hard. However, Item Piles still allow you to pick up such items from piles using its flexible "Dynamic Attributes" feature. With this feature, you can configure what types of these "numbers only" things that can still be picked from piles up by players.

![Item Pile Dynamic Attribute Settings](https://raw.githubusercontent.com/fantasycalendar/FoundryVTT-ItemPiles/master/docs/images/attributes.png)
![Item Pile Dynamic Attribute Settings](./docs/images/attributes.png)

Each row represents a field on a character sheet that may be picked up. As you can see, the D&D5e system denotes the number of gold coins a character has with the `actor.data.data.currency.gp` field, so by putting `data.currency.gp` as a valid field in this UI, Item Piles figures out that if an actor has more than 0 in this field, it can be "picked up" and transferred to the character who picked it up.

Expand Down
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Item Piles Changelog

## Version 1.1.1
- Added support for the Savage Worlds Adventure Edition: <https://foundryvtt.com/packages/swade>
- Fixed linked token actors not acting like they are linked - now all tokens on the canvas with the same linked actor share the same state and image
- Fixed dynamic attributes not being treated as numbers, which caused problems in some systems (such as SWADE) if they were stored as strings

## Version 1.1.0
- Added support for the D&D 3.5 system: <https://foundryvtt.com/packages/D35E>
- Added the ability to click on item names to inspect the items - this can be disabled in the item pile's settings
Expand Down
Binary file added docs/images/disable-link.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/intro.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions scripts/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -891,12 +891,12 @@ export default class API {

const item = lib.getSimilarItem(targetActorItems, { itemId: itemData._id, itemName: itemData.name, itemType: itemData.type });

const incomingQuantity = getProperty(itemData, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1;
const incomingQuantity = Number(getProperty(itemData, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1);

const itemAdded = item ? item.toObject() : foundry.utils.duplicate(itemData);

if (item) {
const currentQuantity = getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE);
const currentQuantity = Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE));
const newQuantity = currentQuantity + incomingQuantity;
itemsToUpdate.push({
"_id": item.id,
Expand Down Expand Up @@ -998,9 +998,9 @@ export default class API {
const actorItem = targetActor.items.get(itemId);
const removedItem = actorItem.toObject();

const currentQuantity = getProperty(actorItem.data, API.ITEM_QUANTITY_ATTRIBUTE);
const currentQuantity = Number(getProperty(actorItem.data, API.ITEM_QUANTITY_ATTRIBUTE));

const quantityToRemove = getProperty(item, API.ITEM_QUANTITY_ATTRIBUTE) ?? item.quantity ?? currentQuantity;
const quantityToRemove = Number(getProperty(item, API.ITEM_QUANTITY_ATTRIBUTE) ?? item.quantity ?? currentQuantity);

const newQuantity = Math.max(0, currentQuantity - quantityToRemove);

Expand Down Expand Up @@ -1249,7 +1249,7 @@ export default class API {

for (const [attribute, quantityToAdd] of Object.entries(attributes)) {

const currentQuantity = getProperty(targetActor.data, attribute);
const currentQuantity = Number(getProperty(targetActor.data, attribute));

updates[attribute] = currentQuantity + quantityToAdd;
attributesAdded[attribute] = currentQuantity + quantityToAdd;
Expand Down Expand Up @@ -1336,13 +1336,13 @@ export default class API {

if (Array.isArray(attributes)) {
attributes = Object.fromEntries(attributes.map(attribute => {
return [attribute, getProperty(targetActor.data, attribute)];
return [attribute, Number(getProperty(targetActor.data, attribute))];
}))
}

for (const [attribute, quantityToRemove] of Object.entries(attributes)) {

const currentQuantity = getProperty(targetActor.data, attribute);
const currentQuantity = Number(getProperty(targetActor.data, attribute));
const newQuantity = Math.max(0, currentQuantity - quantityToRemove);

updates[attribute] = newQuantity;
Expand Down Expand Up @@ -1515,7 +1515,7 @@ export default class API {

const attributesToTransfer = sourceAttributes.filter(attribute => {
return hasProperty(sourceActor.data, attribute.path)
&& getProperty(sourceActor.data, attribute.path) > 0
&& Number(getProperty(sourceActor.data, attribute.path)) > 0
&& hasProperty(targetActor.data, attribute.path);
}).map(attribute => attribute.path);

Expand Down
2 changes: 1 addition & 1 deletion scripts/formapplications/dropDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class DropDialog extends FormApplication {
data.dropObjects = this.dropObjects;
data.itemPileAtLocation = this.dropObjects.length > 0;
data.droppedItem = this.droppedItem;
data.itemQuantity = getProperty(this.droppedItem, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1;
data.itemQuantity = Number(getProperty(this.droppedItem, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1);
data.itemQuantityMoreThanOne = data.itemQuantity > 1;

return data;
Expand Down
12 changes: 6 additions & 6 deletions scripts/formapplications/itemPileInventory.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class ItemPileInventory extends FormApplication {
name = foundItem.name;
img = foundItem.data.img;
type = getProperty(foundItem.data, API.ITEM_TYPE_ATTRIBUTE);
maxQuantity = getProperty(foundItem.data, API.ITEM_QUANTITY_ATTRIBUTE);
maxQuantity = Number(getProperty(foundItem.data, API.ITEM_QUANTITY_ATTRIBUTE));
}else{
itemQuantity = 0;
maxQuantity = 0;
Expand All @@ -118,7 +118,7 @@ export class ItemPileInventory extends FormApplication {
type: item.type,
img: item.data?.img ?? "",
currentQuantity: 1,
maxQuantity: getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1
maxQuantity: Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1)
};
})
}
Expand All @@ -135,7 +135,7 @@ export class ItemPileInventory extends FormApplication {
const img = $(this).find('.item-piles-img').attr('src');

const itemQuantity = $(this).find('input').val();
const maxQuantity = getProperty(self.pile.actor.data, path) ?? 0;
const maxQuantity = Number(getProperty(self.pile.actor.data, path)) ?? 0;

const currentQuantity = Math.min(maxQuantity, Math.max(itemQuantity, 0));

Expand Down Expand Up @@ -163,7 +163,7 @@ export class ItemPileInventory extends FormApplication {
path: attribute.path,
img: attribute.img,
currentQuantity: 1,
maxQuantity: getProperty(this.pile.actor.data, attribute.path) ?? 1
maxQuantity: Number(getProperty(this.pile.actor.data, attribute.path) ?? 1)
}
}).filter(Boolean);
}
Expand Down Expand Up @@ -277,15 +277,15 @@ export class ItemPileInventory extends FormApplication {
this.saveItems();
this.saveAttributes();
const item = this.pile.actor.items.get(itemId);
const maxQuantity = getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1;
const maxQuantity = Number(getProperty(item.data, API.ITEM_QUANTITY_ATTRIBUTE) ?? 1);
const quantity = Math.min(inputQuantity, maxQuantity);
await API.transferItems(this.pile, this.recipient, [{ _id: itemId, quantity }]);
}

async takeAttribute(attribute, inputQuantity) {
this.saveItems();
this.saveAttributes();
const maxQuantity = getProperty(this.pile.actor.data, attribute) ?? 0;
const maxQuantity = Number(getProperty(this.pile.actor.data, attribute) ?? 0);
const quantity = Math.min(inputQuantity, maxQuantity);
await API.transferAttributes(this.pile, this.recipient, { [attribute]: quantity });
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/lib/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export function hasNonzeroAttribute(target, attribute){
const actor = target instanceof TokenDocument
? target.actor
: target;
const attributeValue = getProperty(actor.data, attribute) ?? 0;
const attributeValue = Number(getProperty(actor.data, attribute) ?? 0);
return hasProperty(actor.data, attribute) && attributeValue > 0;
}

Expand Down
2 changes: 2 additions & 0 deletions scripts/systems.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dnd5e from "./systems/dnd5e.js";
import pf1 from "./systems/pf1.js";
import ds4 from "./systems/ds4.js";
import d35e from "./systems/ds4.js";
import swade from "./systems/swade.js";
// ↑ IMPORT SYSTEMS HERE ↑

/**
Expand All @@ -18,6 +19,7 @@ export const SYSTEMS = {
pf1,
ds4,
d35e,
swade,
// ↑ ADD SYSTEMS HERE ↑
}?.[game.system.id];
}
Expand Down
20 changes: 20 additions & 0 deletions scripts/systems/swade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default {
// The actor class type is the type of actor that will be used for the default item pile actor that is created on first item drop.
"ACTOR_CLASS_TYPE": "character",

// The item quantity attribute is the path to the attribute on items that denote how many of that item that exists
"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",

// Dynamic attributes are things like currencies or transferable powers that exist as editable number fields on character sheets
"DYNAMIC_ATTRIBUTES": [
{
name: "SWADE.Currency",
path: "data.details.currency",
img: "icons/svg/coins.svg"
}
]
}

0 comments on commit 776c1f8

Please sign in to comment.