diff --git a/changelog.md b/changelog.md index 3f562fdd..26d40731 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,18 @@ # Item Piles Changelog +## Version 2.8.5 + +- Finally ACTUALLY fixed quantity not showing up properly on the Custom System Builder system +- Fixed missing location on the trade button in the bottom left + +## Version 2.8.4 + +- Added support for the Shadow of the Demonlord system (thank you to brantai on github!) +- Updated French, German, Portuguese (Brazil), and Polish localization (thank you everyone for contributing on weblate!) +- Fixed minor Simple Calendar incompatibility +- Fixed faulty migration code causing errors with other modules +- Fixed migration being loud when it should have been silent (it logged to console) + ## Version 2.8.3 - Fixed Custom System Builder item quantity not working properly diff --git a/docs/README.md b/docs/README.md index 778338ad..bb60142d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -101,6 +101,7 @@ Item Piles is designed to work in all systems, but may require some setup for it - [Alien RPG](https://foundryvtt.com/packages/alienrpg) - [Pirate Borg](https://foundryvtt.com/packages/pirateborg) - [Star Wars FFG](https://foundryvtt.com/packages/starwarsffg) +- [Shadow of the Demonlord](https://foundryvtt.com/packages/demonlord) ## Externally support systems diff --git a/src/helpers/utilities.js b/src/helpers/utilities.js index 87079552..1c3d390e 100644 --- a/src/helpers/utilities.js +++ b/src/helpers/utilities.js @@ -3,6 +3,7 @@ import CONSTANTS from "../constants/constants.js"; import SETTINGS from "../constants/settings.js"; import { getItemFlagData } from "./pile-utilities.js"; import { deletedActorCache } from "./caches.js"; +import { SYSTEMS } from "../systems.js"; export function getActor(target) { if (target instanceof Actor) return target; @@ -166,9 +167,10 @@ export function getItemTypesThatCanStack() { itemTypesWithQuantities = new Set(); if (game.system.id === "custom-system-builder") { + const quantityAttribute = game.itempiles.API.ITEM_QUANTITY_ATTRIBUTE.split(".").pop(); const itemTemplates = game.items .filter(_i => _i?.templateSystem?.isTemplate && _i?.templateSystem?.getKeys) - .filter(_i => _i.templateSystem.getKeys().has(_i.system.body.contents)); + .filter(_i => _i.templateSystem.getKeys().has(quantityAttribute)); for (const item of itemTemplates) { itemTypesWithQuantities.add(item.name); } diff --git a/src/stores/pile-item.js b/src/stores/pile-item.js index dff6c76b..ec8e6a13 100644 --- a/src/stores/pile-item.js +++ b/src/stores/pile-item.js @@ -11,297 +11,297 @@ import * as CompendiumUtilities from "../helpers/compendium-utilities.js"; class PileBaseItem { - constructor(store, data, isCurrency = false, isSecondaryCurrency = false) { - this.store = store; - this.subscriptions = []; - this.isCurrency = isCurrency; - this.isSecondaryCurrency = isSecondaryCurrency; - this.setup(data); - } - - setupStores() { - this.category = writable({ service: false, type: "", label: "" }); - this.quantity = writable(1); - this.currentQuantity = writable(1); - this.quantityLeft = writable(1); - this.filtered = writable(true); - this.presentFromTheStart = writable(false); - this.rarityColor = writable(false); - } - - setupSubscriptions() { - // Higher order implementation - } - - setup(data) { - this.unsubscribe(); - this.setupStores(data); - this.setupSubscriptions(data); - } - - subscribeTo(target, callback) { - this.subscriptions.push(target.subscribe(callback)); - } - - unsubscribe() { - this.subscriptions.forEach(unsubscribe => unsubscribe()); - this.subscriptions = []; - } - - preview() { - } + constructor(store, data, isCurrency = false, isSecondaryCurrency = false) { + this.store = store; + this.subscriptions = []; + this.isCurrency = isCurrency; + this.isSecondaryCurrency = isSecondaryCurrency; + this.setup(data); + } + + setupStores() { + this.category = writable({ service: false, type: "", label: "" }); + this.quantity = writable(1); + this.currentQuantity = writable(1); + this.quantityLeft = writable(1); + this.filtered = writable(true); + this.presentFromTheStart = writable(false); + this.rarityColor = writable(false); + } + + setupSubscriptions() { + // Higher order implementation + } + + setup(data) { + this.unsubscribe(); + this.setupStores(data); + this.setupSubscriptions(data); + } + + subscribeTo(target, callback) { + this.subscriptions.push(target.subscribe(callback)); + } + + unsubscribe() { + this.subscriptions.forEach(unsubscribe => unsubscribe()); + this.subscriptions = []; + } + + preview() { + } } export class PileItem extends PileBaseItem { - setupStores(item) { - super.setupStores(); - this.item = item; - this.itemDocument = new TJSDocument(this.item); - this.canStack = PileUtilities.canItemStack(this.item, this.actor); - this.presentFromTheStart.set(Utilities.getItemQuantity(this.item) > 0 || !this.canStack); - this.quantity.set(this.canStack ? Utilities.getItemQuantity(this.item) : 1); - this.currentQuantity.set(Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity))); - this.id = this.item.id; - this.type = this.item.type; - const itemData = CompendiumUtilities.findSimilarItemInCompendiumSync(this.item); - this.name = writable(itemData?.name ?? this.item.name); - this.img = writable(itemData?.img ?? this.item.img); - this.abbreviation = writable(""); - this.identifier = randomID(); - this.itemFlagData = writable(PileUtilities.getItemFlagData(this.item)); - } - - setupSubscriptions() { - super.setupSubscriptions() - - this.subscribeTo(this.store.pileData, () => { - this.setupProperties(); - }); - this.subscribeTo(this.store.pileCurrencies, () => { - this.setupProperties(); - }); - - this.subscribeTo(this.store.shareData, () => { - if (!this.toShare) { - this.quantityLeft.set(get(this.quantity)); - return; - } - const quantityLeft = SharingUtilities.getItemSharesLeftForActor(this.store.actor, this.item, this.store.recipient); - this.quantityLeft.set(quantityLeft); - }); - - this.subscribeTo(this.itemDocument, () => { - const { data } = this.itemDocument.updateOptions; - const itemData = CompendiumUtilities.findSimilarItemInCompendiumSync(this.item); - this.name = writable(itemData?.name ?? this.item.name); - this.img = writable(itemData?.img ?? this.item.img); - this.similarities = Utilities.setSimilarityProperties({}, this.item); - if (PileUtilities.canItemStack(this.item, this.store.actor) && Utilities.hasItemQuantity(data)) { - this.quantity.set(Utilities.getItemQuantity(data)); - const quantity = Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity)); - this.currentQuantity.set(quantity); - } - if (hasProperty(data, CONSTANTS.FLAGS.ITEM)) { - this.itemFlagData.set(PileUtilities.getItemFlagData(this.item)); - this.updateCategory(); - this.store.refreshItems(); - } - if (Plugins["rarity-colors"].data) { - this.rarityColor.set(Plugins["rarity-colors"].data.getItemColor(this.item)); - } - }); - - this.updateCategory(); - - this.subscribeTo(this.quantity, this.filter.bind(this)); - this.subscribeTo(this.store.search, this.filter.bind(this)); - this.subscribeTo(this.category, this.filter.bind(this)); - } - - setupProperties() { - const actorIsItemPile = PileUtilities.isValidItemPile(this.store.actor, get(this.store.pileData)); - const pileActor = actorIsItemPile ? this.store.actor : this.store.recipient; - const pileActorData = actorIsItemPile ? this.store.pileData : this.store.recipientPileData; - const pileCurrencies = actorIsItemPile ? get(this.store.pileCurrencies) : get(this.store.recipientCurrencies); - this.isCurrency = PileUtilities.isItemCurrency(this.item, { - target: pileActor, - actorCurrencies: pileCurrencies - }); - const currency = this.isCurrency ? PileUtilities.getItemCurrencyData(this.item, { - target: pileActor, - actorCurrencies: pileCurrencies - }) : {}; - this.isSecondaryCurrency = !!currency?.secondary; - this.abbreviation.set(currency?.abbreviation ?? ""); - this.similarities = Utilities.setSimilarityProperties({}, this.item); - this.name.set(this.isCurrency ? currency.name : this.item.name); - this.img.set(this.isCurrency ? currency.img : this.item.img); - this.toShare = this.isCurrency - ? get(pileActorData).shareCurrenciesEnabled && !!this.store.recipient - : get(pileActorData).shareItemsEnabled && !!this.store.recipient; - } - - updateCategory() { - const pileData = get(this.store.pileData); - const itemFlagData = get(this.itemFlagData); - this.category.update(cat => { - cat.service = itemFlagData?.isService; - if (itemFlagData.customCategory) { - cat.type = itemFlagData.customCategory.toLowerCase(); - cat.label = itemFlagData.customCategory; - } else if (cat.service && pileData.enabled && pileData.type === CONSTANTS.PILE_TYPES.MERCHANT) { - cat.type = "item-piles-service"; - cat.label = "ITEM-PILES.Merchant.Service"; - } else { - cat.type = this.type; - cat.label = CONFIG.Item.typeLabels[this.type]; - } - return cat; - }); - } - - filter() { - const name = get(this.name).trim(); - const search = get(this.store.search).trim(); - const presentFromTheStart = get(this.presentFromTheStart); - const quantity = get(this.quantity); - if (quantity === 0 && !presentFromTheStart) { - this.filtered.set(true); - } else if (search) { - this.filtered.set(!name.toLowerCase().includes(search.toLowerCase())); - } else { - this.filtered.set(!presentFromTheStart && quantity === 0); - } - } - - take() { - const quantity = Math.min(get(this.currentQuantity), get(this.quantityLeft)); - if (!quantity) return; - return game.itempiles.API.transferItems( - this.store.actor, - this.store.recipient, - [{ _id: this.id, quantity }], - { interactionId: this.store.interactionId } - ); - } - - async remove() { - return game.itempiles.API.removeItems(this.store.actor, [this.id]); - } - - updateQuantity(quantity, add = false) { - let total = typeof quantity === "string" ? (new Roll(quantity).evaluate({ async: false })).total : quantity; - if (add) { - total += get(this.quantity); - } - this.quantity.set(total); - return this.item.update(Utilities.setItemQuantity({}, total)); - } - - async updateFlags() { - await this.item.update({ - [CONSTANTS.FLAGS.ITEM]: get(this.itemFlagData), - [CONSTANTS.FLAGS.VERSION]: Helpers.getModuleVersion() - }) - } - - preview() { - const pileData = get(this.store.pileData); - if (!pileData.canInspectItems && !game.user.isGM) return; - if (SYSTEMS.DATA?.PREVIEW_ITEM_TRANSFORMER) { - if (!SYSTEMS.DATA?.PREVIEW_ITEM_TRANSFORMER(this.item)) { - return; - } - } - if (game.user.isGM || this.item.permission[game.user.id] === 3) { - return this.item.sheet.render(true); - } - const cls = this.item._getSheetClass(); - const sheet = new cls(this.item, { editable: false }); - return sheet._render(true); - } + setupStores(item) { + super.setupStores(); + this.item = item; + this.itemDocument = new TJSDocument(this.item); + this.canStack = PileUtilities.canItemStack(this.item, this.actor); + this.presentFromTheStart.set(Utilities.getItemQuantity(this.item) > 0 || !this.canStack); + this.quantity.set(this.canStack ? Utilities.getItemQuantity(this.item) : 1); + this.currentQuantity.set(Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity))); + this.id = this.item.id; + this.type = this.item.type; + const itemData = CompendiumUtilities.findSimilarItemInCompendiumSync(this.item); + this.name = writable(itemData?.name ?? this.item.name); + this.img = writable(itemData?.img ?? this.item.img); + this.abbreviation = writable(""); + this.identifier = randomID(); + this.itemFlagData = writable(PileUtilities.getItemFlagData(this.item)); + } + + setupSubscriptions() { + super.setupSubscriptions() + + this.subscribeTo(this.store.pileData, () => { + this.setupProperties(); + }); + this.subscribeTo(this.store.pileCurrencies, () => { + this.setupProperties(); + }); + + this.subscribeTo(this.store.shareData, () => { + if (!this.toShare) { + this.quantityLeft.set(get(this.quantity)); + return; + } + const quantityLeft = SharingUtilities.getItemSharesLeftForActor(this.store.actor, this.item, this.store.recipient); + this.quantityLeft.set(quantityLeft); + }); + + this.subscribeTo(this.itemDocument, () => { + const { data } = this.itemDocument.updateOptions; + const itemData = CompendiumUtilities.findSimilarItemInCompendiumSync(this.item); + this.name = writable(itemData?.name ?? this.item.name); + this.img = writable(itemData?.img ?? this.item.img); + this.similarities = Utilities.setSimilarityProperties({}, this.item); + if (PileUtilities.canItemStack(this.item, this.store.actor) && Utilities.hasItemQuantity(data)) { + this.quantity.set(Utilities.getItemQuantity(data)); + const quantity = Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity)); + this.currentQuantity.set(quantity); + } + if (hasProperty(data, CONSTANTS.FLAGS.ITEM)) { + this.itemFlagData.set(PileUtilities.getItemFlagData(this.item)); + this.updateCategory(); + this.store.refreshItems(); + } + if (Plugins["rarity-colors"].data) { + this.rarityColor.set(Plugins["rarity-colors"].data.getItemColor(this.item)); + } + }); + + this.updateCategory(); + + this.subscribeTo(this.quantity, this.filter.bind(this)); + this.subscribeTo(this.store.search, this.filter.bind(this)); + this.subscribeTo(this.category, this.filter.bind(this)); + } + + setupProperties() { + const actorIsItemPile = PileUtilities.isValidItemPile(this.store.actor, get(this.store.pileData)); + const pileActor = actorIsItemPile ? this.store.actor : this.store.recipient; + const pileActorData = actorIsItemPile ? this.store.pileData : this.store.recipientPileData; + const pileCurrencies = actorIsItemPile ? get(this.store.pileCurrencies) : get(this.store.recipientCurrencies); + this.isCurrency = PileUtilities.isItemCurrency(this.item, { + target: pileActor, + actorCurrencies: pileCurrencies + }); + const currency = this.isCurrency ? PileUtilities.getItemCurrencyData(this.item, { + target: pileActor, + actorCurrencies: pileCurrencies + }) : {}; + this.isSecondaryCurrency = !!currency?.secondary; + this.abbreviation.set(currency?.abbreviation ?? ""); + this.similarities = Utilities.setSimilarityProperties({}, this.item); + this.name.set(this.isCurrency ? currency.name : this.item.name); + this.img.set(this.isCurrency ? currency.img : this.item.img); + this.toShare = this.isCurrency + ? get(pileActorData).shareCurrenciesEnabled && !!this.store.recipient + : get(pileActorData).shareItemsEnabled && !!this.store.recipient; + } + + updateCategory() { + const pileData = get(this.store.pileData); + const itemFlagData = get(this.itemFlagData); + this.category.update(cat => { + cat.service = itemFlagData?.isService; + if (itemFlagData.customCategory) { + cat.type = itemFlagData.customCategory.toLowerCase(); + cat.label = itemFlagData.customCategory; + } else if (cat.service && pileData.enabled && pileData.type === CONSTANTS.PILE_TYPES.MERCHANT) { + cat.type = "item-piles-service"; + cat.label = "ITEM-PILES.Merchant.Service"; + } else { + cat.type = this.type; + cat.label = CONFIG.Item.typeLabels[this.type]; + } + return cat; + }); + } + + filter() { + const name = get(this.name).trim(); + const search = get(this.store.search).trim(); + const presentFromTheStart = get(this.presentFromTheStart); + const quantity = get(this.quantity); + if (quantity === 0 && !presentFromTheStart) { + this.filtered.set(true); + } else if (search) { + this.filtered.set(!name.toLowerCase().includes(search.toLowerCase())); + } else { + this.filtered.set(!presentFromTheStart && quantity === 0); + } + } + + take() { + const quantity = Math.min(get(this.currentQuantity), get(this.quantityLeft)); + if (!quantity) return; + return game.itempiles.API.transferItems( + this.store.actor, + this.store.recipient, + [{ _id: this.id, quantity }], + { interactionId: this.store.interactionId } + ); + } + + async remove() { + return game.itempiles.API.removeItems(this.store.actor, [this.id]); + } + + updateQuantity(quantity, add = false) { + let total = typeof quantity === "string" ? (new Roll(quantity).evaluate({ async: false })).total : quantity; + if (add) { + total += get(this.quantity); + } + this.quantity.set(total); + return this.item.update(Utilities.setItemQuantity({}, total)); + } + + async updateFlags() { + await this.item.update({ + [CONSTANTS.FLAGS.ITEM]: get(this.itemFlagData), + [CONSTANTS.FLAGS.VERSION]: Helpers.getModuleVersion() + }) + } + + preview() { + const pileData = get(this.store.pileData); + if (!pileData.canInspectItems && !game.user.isGM) return; + if (SYSTEMS.DATA?.PREVIEW_ITEM_TRANSFORMER) { + if (!SYSTEMS.DATA?.PREVIEW_ITEM_TRANSFORMER(this.item)) { + return; + } + } + if (game.user.isGM || this.item.permission[game.user.id] === 3) { + return this.item.sheet.render(true); + } + const cls = this.item._getSheetClass(); + const sheet = new cls(this.item, { editable: false }); + return sheet._render(true); + } } export class PileAttribute extends PileBaseItem { - setupStores(attribute) { - super.setupStores(); - this.attribute = attribute; - this.path = this.attribute.path; - this.name = writable(this.attribute.name); - this.img = writable(this.attribute.img); - this.abbreviation = writable(this.attribute.abbreviation); - this.identifier = randomID() - const startingQuantity = Number(getProperty(this.store.actor, this.path) ?? 0); - this.presentFromTheStart.set(startingQuantity > 0); - this.quantity.set(startingQuantity); - this.currentQuantity.set(Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity))); - this.category.set({ type: "currency", label: "ITEM-PILES.Currency" }); - } - - setupSubscriptions() { - super.setupSubscriptions(); - - this.subscribeTo(this.store.pileData, this.setupProperties.bind(this)); - - this.subscribeTo(this.store.shareData, (val) => { - if (!this.toShare) { - this.quantityLeft.set(get(this.quantity)); - return; - } - const quantityLeft = SharingUtilities.getAttributeSharesLeftForActor(this.store.actor, this.path, this.store.recipient); - this.quantityLeft.set(quantityLeft); - }); - - this.subscribeTo(this.store.document, () => { - const { data } = this.store.document.updateOptions; - this.path = this.attribute.path; - this.name.set(this.attribute.name); - this.img.set(this.attribute.img); - if (hasProperty(data, this.path)) { - this.quantity.set(Number(getProperty(data, this.path) ?? 0)); - this.currentQuantity.set(Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity))); - this.store.refreshItems(); - } - }); - - this.subscribeTo(this.quantity, this.filter.bind(this)); - this.subscribeTo(this.store.search, this.filter.bind(this)); - } - - setupProperties() { - this.toShare = get(this.store.pileData).shareCurrenciesEnabled && !!this.store.recipient; - } - - filter() { - const name = get(this.name); - const search = get(this.store.search); - const presentFromTheStart = get(this.presentFromTheStart); - const quantity = get(this.quantity); - if (quantity === 0 && !presentFromTheStart) { - this.filtered.set(true); - } else if (search) { - this.filtered.set(!name.toLowerCase().includes(search.toLowerCase())); - } else { - this.filtered.set(!presentFromTheStart && quantity === 0); - } - } - - take() { - const quantity = Math.min(get(this.currentQuantity), get(this.quantityLeft)); - return game.itempiles.API.transferAttributes( - this.store.actor, - this.store.recipient, - { [this.path]: quantity }, - { interactionId: this.store.interactionId } - ); - } - - updateQuantity() { - return this.store.actor.update({ - [this.path]: get(this.quantity) - }); - } + setupStores(attribute) { + super.setupStores(); + this.attribute = attribute; + this.path = this.attribute.path; + this.name = writable(this.attribute.name); + this.img = writable(this.attribute.img); + this.abbreviation = writable(this.attribute.abbreviation); + this.identifier = randomID() + const startingQuantity = Number(getProperty(this.store.actor, this.path) ?? 0); + this.presentFromTheStart.set(startingQuantity > 0); + this.quantity.set(startingQuantity); + this.currentQuantity.set(Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity))); + this.category.set({ type: "currency", label: "ITEM-PILES.Currency" }); + } + + setupSubscriptions() { + super.setupSubscriptions(); + + this.subscribeTo(this.store.pileData, this.setupProperties.bind(this)); + + this.subscribeTo(this.store.shareData, (val) => { + if (!this.toShare) { + this.quantityLeft.set(get(this.quantity)); + return; + } + const quantityLeft = SharingUtilities.getAttributeSharesLeftForActor(this.store.actor, this.path, this.store.recipient); + this.quantityLeft.set(quantityLeft); + }); + + this.subscribeTo(this.store.document, () => { + const { data } = this.store.document.updateOptions; + this.path = this.attribute.path; + this.name.set(this.attribute.name); + this.img.set(this.attribute.img); + if (hasProperty(data, this.path)) { + this.quantity.set(Number(getProperty(data, this.path) ?? 0)); + this.currentQuantity.set(Math.min(get(this.currentQuantity), get(this.quantityLeft), get(this.quantity))); + this.store.refreshItems(); + } + }); + + this.subscribeTo(this.quantity, this.filter.bind(this)); + this.subscribeTo(this.store.search, this.filter.bind(this)); + } + + setupProperties() { + this.toShare = get(this.store.pileData).shareCurrenciesEnabled && !!this.store.recipient; + } + + filter() { + const name = get(this.name); + const search = get(this.store.search); + const presentFromTheStart = get(this.presentFromTheStart); + const quantity = get(this.quantity); + if (quantity === 0 && !presentFromTheStart) { + this.filtered.set(true); + } else if (search) { + this.filtered.set(!name.toLowerCase().includes(search.toLowerCase())); + } else { + this.filtered.set(!presentFromTheStart && quantity === 0); + } + } + + take() { + const quantity = Math.min(get(this.currentQuantity), get(this.quantityLeft)); + return game.itempiles.API.transferAttributes( + this.store.actor, + this.store.recipient, + { [this.path]: quantity }, + { interactionId: this.store.interactionId } + ); + } + + updateQuantity() { + return this.store.actor.update({ + [this.path]: get(this.quantity) + }); + } }