From e84ecf877195f3114a06b183953c0a0d4668240a Mon Sep 17 00:00:00 2001 From: Jeff Hitchcock Date: Sat, 29 Oct 2022 20:53:22 -0700 Subject: [PATCH] [#729] Add list of contents to container sheet --- dnd5e.css | 220 ++++++++++++++---------- less/actors.less | 101 ----------- less/dnd5e.less | 1 + less/inventory.less | 141 +++++++++++++++ less/variables.less | 12 ++ module/applications/item/item-sheet.mjs | 135 ++++++++++++++- module/data/item/container.mjs | 103 ++++++++--- module/data/shared/currency.mjs | 17 ++ module/documents/item.mjs | 2 + templates/items/backpack.hbs | 70 +++++++- 10 files changed, 583 insertions(+), 219 deletions(-) create mode 100644 less/inventory.less diff --git a/dnd5e.css b/dnd5e.css index 44512292b6..61f1d50992 100644 --- a/dnd5e.css +++ b/dnd5e.css @@ -22,6 +22,16 @@ /* ----------------------------------------- */ /* Sheet Styles */ /* ----------------------------------------- */ +:root { + /* ----------------------------------------- */ + /* Encumbrance & Capacity Bars */ + /* ----------------------------------------- */ + --dnd5e-capacity-container-background-color: #7a7971; + --dnd5e-capacity-container-border-color: #191813; + --dnd5e-capacity-background-color: #6c8aa5; + --dnd5e-capacity-border-color: #cde4ff; + --dnd5e-capacity-text-color: #eeeeee; +} /* ----------------------------------------- */ /* Flexbox */ /* ----------------------------------------- */ @@ -1083,95 +1093,6 @@ h5 { flex: 0 0 20px; justify-content: flex-end; } -.dnd5e.sheet.actor .inventory-filters .currency { - flex: 0 0 100%; - list-style: none; - margin: 4px 0 8px; - padding: 0; - font-size: 12px; -} -.dnd5e.sheet.actor .inventory-filters .currency label { - flex: 0; - margin-left: 8px; - text-align: right; - line-height: 20px; - color: #7a7971; -} -.dnd5e.sheet.actor .inventory-filters .currency input[type="text"], -.dnd5e.sheet.actor .inventory-filters .currency input[type="number"] { - flex: 0 0 48px; - text-align: center; - margin-left: 8px; - border-bottom: 2px groove #eeede0; -} -.dnd5e.sheet.actor .inventory-list { - padding: 0 5px; -} -.dnd5e.sheet.actor .inventory-list .item .item-name { - cursor: pointer; -} -.dnd5e.sheet.actor .inventory-list .item .item-name.rollable:hover .item-image { - background-image: url("../../icons/svg/d20-grey.svg") !important; -} -.dnd5e.sheet.actor .inventory-list .item .item-name.rollable .item-image:hover { - background-image: url("../../icons/svg/d20-black.svg") !important; -} -.dnd5e.sheet.actor .inventory-list .item .item-name i.attuned { - color: #7a7971; -} -.dnd5e.sheet.actor .inventory-list .item .item-name i.not-attuned { - color: #44191A; -} -.dnd5e.sheet.actor .inventory-list .item .item-uses input { - width: 24px; - text-align: center; -} -.dnd5e.sheet.actor .inventory-list .item .item-properties { - margin-top: 3px; -} -.dnd5e.sheet.actor .inventory-list .item .item-recharge { - flex: 0 0 80px; - text-align: right; - font-size: 11px; - white-space: nowrap; -} -.dnd5e.sheet.actor .inventory-list .inventory-header .item-controls a.item-create { - flex: 0 0 100%; -} -.dnd5e.sheet.actor .inventory-list .item-detail { - flex: 0 0 70px; - font-size: 12px; - text-align: center; - border-right: 1px solid #c9c7b8; - word-break: break-word; - white-space: nowrap; - overflow: hidden; -} -.dnd5e.sheet.actor .inventory-list .item-detail:last-child { - border-right: none; -} -.dnd5e.sheet.actor .inventory-list .item-detail.item-action { - flex: 0 0 100px; -} -.dnd5e.sheet.actor .inventory-list .item-detail.attunement { - flex: 0 0 24px; -} -.dnd5e.sheet.actor .inventory-list .item-weight { - flex: 0 0 60px; - border-left: 1px solid #c9c7b8; - border-right: 1px solid #c9c7b8; -} -.dnd5e.sheet.actor .inventory-list .item-controls { - flex: 0 0 44px; -} -.dnd5e.sheet.actor .inventory-list .item-summary { - flex: 0 0 100%; - font-size: 12px; - line-height: 16px; - padding: 0.25em 0.5em; - color: #191813; - border-top: 1px solid #c9c7b8; -} .dnd5e.sheet.actor .encumbrance { flex: 0 0 12px; background: #7a7971; @@ -1721,6 +1642,127 @@ h5 { .dnd5e.sheet.item .loot-header { margin-bottom: 10px; } +.inventory-list { + padding: 0 5px; +} +.inventory-list .item .item-name { + cursor: pointer; +} +.inventory-list .item .item-name.rollable:hover .item-image { + background-image: url("../../icons/svg/d20-grey.svg") !important; +} +.inventory-list .item .item-name.rollable .item-image:hover { + background-image: url("../../icons/svg/d20-black.svg") !important; +} +.inventory-list .item .item-name i.attuned { + color: #7a7971; +} +.inventory-list .item .item-name i.not-attuned { + color: #44191A; +} +.inventory-list .item .item-uses input { + width: 24px; + text-align: center; +} +.inventory-list .item .item-properties { + margin-top: 3px; +} +.inventory-list .item .item-recharge { + flex: 0 0 80px; + text-align: right; + font-size: 11px; + white-space: nowrap; +} +.inventory-list .inventory-header .item-controls a.item-create { + flex: 0 0 100%; +} +.inventory-list .item-detail { + flex: 0 0 70px; + font-size: 12px; + text-align: center; + border-right: 1px solid #c9c7b8; + word-break: break-word; + white-space: nowrap; + overflow: hidden; +} +.inventory-list .item-detail:last-child { + border-right: none; +} +.inventory-list .item-detail.item-action { + flex: 0 0 100px; +} +.inventory-list .item-detail.attunement { + flex: 0 0 24px; +} +.inventory-list .item-weight { + flex: 0 0 60px; + border-left: 1px solid #c9c7b8; + border-right: 1px solid #c9c7b8; +} +.inventory-list .item-controls { + flex: 0 0 44px; +} +.inventory-list .item-summary { + flex: 0 0 100%; + font-size: 12px; + line-height: 16px; + padding: 0.25em 0.5em; + color: #191813; + border-top: 1px solid #c9c7b8; +} +.capacity { + flex: 0 0 12px; + margin-inline-end: 18px; + contain: content; +} +.capacity meter { + width: 100%; + background: var(--dnd5e-capacity-container-background-color); + border: 1px solid var(--dnd5e-capacity-container-border-color); + border-radius: 3px; +} +.capacity meter::-moz-meter-bar, +.capacity meter::-webkit-meter-bar { + height: calc(100% - 2px); + background: var(--dnd5e-capacity-background-color); + border: 1px solid var(--dnd5e-capacity-border-color); + border-radius: 2px; +} +.capacity label { + position: absolute; + top: 0; + right: 0; + padding-inline: 5px; + text-align: right; + color: var(--dnd5e-capacity-text-color); + text-shadow: 0 0 5px #000; +} +.inventory .inventory-header { + margin: 0 8px; + flex: 0 0 20px; + justify-content: flex-end; +} +.inventory .currency { + flex: 0 0 100%; + justify-content: flex-end; + margin: 4px 0 8px; + padding: 0; + font-size: var(--font-size-12); +} +.inventory .currency label { + flex: 0; + margin-left: 8px; + text-align: right; + line-height: 20px; + color: #7a7971; +} +.inventory .currency input[type="text"], +.inventory .currency input[type="number"] { + flex: 0 0 48px; + text-align: center; + margin-left: 8px; + border-bottom: 2px groove #eeede0; +} /* ----------------------------------------- */ /* SRD Compendium */ /* ----------------------------------------- */ diff --git a/less/actors.less b/less/actors.less index ec1db60b8f..d6ea33746c 100644 --- a/less/actors.less +++ b/less/actors.less @@ -470,107 +470,6 @@ margin: 0 8px; flex: 0 0 20px; justify-content: flex-end; - - .currency { - flex: 0 0 100%; - list-style: none; - margin: 4px 0 8px; - padding: 0; - font-size: 12px; - - label { - flex: 0; - margin-left: 8px; - text-align: right; - line-height: 20px; - color: @colorTan; - } - input[type="text"], input[type="number"] { - flex: 0 0 48px; - text-align: center; - margin-left: 8px; - border-bottom: @borderGroove; - } - } - } - - // Inventory item lists - .inventory-list { - padding: 0 5px; - .item { - .item-name { - cursor: pointer; - &.rollable:hover .item-image { - background-image: url("../../icons/svg/d20-grey.svg") !important; - } - &.rollable .item-image:hover { - background-image: url("../../icons/svg/d20-black.svg") !important; - } - i.attuned { color: @colorTan; } - i.not-attuned { color: @colorCrimson; } - } - - // Item uses - .item-uses input { - width: 24px; - text-align: center; - } - - // Item Dropdown Properties - .item-properties { - margin-top: 3px; - } - - // Charged - .item-recharge { - flex: 0 0 80px; - text-align: right; - font-size: 11px; - white-space: nowrap; - } - } - - // Inventory Header - .inventory-header { - .item-controls a.item-create { - flex: 0 0 100%; - } - } - - // Item Detail Sections - .item-detail { - flex: 0 0 70px; - font-size: 12px; - text-align: center; - border-right: 1px solid @colorFaint; - word-break: break-word; - white-space: nowrap; - overflow: hidden; - &:last-child { border-right: none; } - &.item-action {flex: 0 0 100px} - &.attunement {flex: 0 0 24px} - } - - .item-weight { - flex: 0 0 60px; - border-left: 1px solid @colorFaint; - border-right: 1px solid @colorFaint; - } - - // Item Control Buttons - .item-controls { - flex: 0 0 44px; - } - - // Item Dropdown Summary - .item-summary { - flex: 0 0 100%; - font-size: 12px; - line-height: 16px; - padding: 0.25em 0.5em; - color: @colorDark; - border-top: 1px solid @colorFaint; - } } /* Encumbrance Bar */ diff --git a/less/dnd5e.less b/less/dnd5e.less index 99e3616dd0..870e10c424 100644 --- a/less/dnd5e.less +++ b/less/dnd5e.less @@ -3,6 +3,7 @@ @import "actors.less"; @import "advancement.less"; @import "items.less"; +@import "inventory.less"; @import "journal.less"; @import "chat.less"; @import "character.less"; diff --git a/less/inventory.less b/less/inventory.less new file mode 100644 index 0000000000..01d796663c --- /dev/null +++ b/less/inventory.less @@ -0,0 +1,141 @@ +// Inventory item lists +.inventory-list { + padding: 0 5px; + .item { + .item-name { + cursor: pointer; + &.rollable:hover .item-image { + background-image: url("../../icons/svg/d20-grey.svg") !important; + } + &.rollable .item-image:hover { + background-image: url("../../icons/svg/d20-black.svg") !important; + } + i.attuned { color: @colorTan; } + i.not-attuned { color: @colorCrimson; } + } + + // Item uses + .item-uses input { + width: 24px; + text-align: center; + } + + // Item Dropdown Properties + .item-properties { + margin-top: 3px; + } + + // Charged + .item-recharge { + flex: 0 0 80px; + text-align: right; + font-size: 11px; + white-space: nowrap; + } + } + + // Inventory Header + .inventory-header { + .item-controls a.item-create { + flex: 0 0 100%; + } + } + + // Item Detail Sections + .item-detail { + flex: 0 0 70px; + font-size: 12px; + text-align: center; + border-right: 1px solid @colorFaint; + word-break: break-word; + white-space: nowrap; + overflow: hidden; + &:last-child { border-right: none; } + &.item-action {flex: 0 0 100px} + &.attunement {flex: 0 0 24px} + } + + .item-weight { + flex: 0 0 60px; + border-left: 1px solid @colorFaint; + border-right: 1px solid @colorFaint; + } + + // Item Control Buttons + .item-controls { + flex: 0 0 44px; + } + + // Item Dropdown Summary + .item-summary { + flex: 0 0 100%; + font-size: 12px; + line-height: 16px; + padding: 0.25em 0.5em; + color: @colorDark; + border-top: 1px solid @colorFaint; + } +} + +.capacity { + flex: 0 0 12px; + margin-inline-end: 18px; + contain: content; + + meter { + width: 100%; + + background: var(--dnd5e-capacity-container-background-color); + border: 1px solid var(--dnd5e-capacity-container-border-color); + border-radius: 3px; + + &::-moz-meter-bar, &::-webkit-meter-bar { + height: calc(100% - 2px); + + background: var(--dnd5e-capacity-background-color); + border: 1px solid var(--dnd5e-capacity-border-color); + border-radius: 2px; + } + } + + label { + position: absolute; + top: 0; + right: 0; + padding-inline: 5px; + + text-align: right; + color: var(--dnd5e-capacity-text-color); + text-shadow: 0 0 5px #000; + } +} + +.inventory { + .inventory-header { + margin: 0 8px; + flex: 0 0 20px; + justify-content: flex-end; + } + + .currency { + flex: 0 0 100%; + justify-content: flex-end; + margin: 4px 0 8px; + padding: 0; + font-size: var(--font-size-12); + + label { + flex: 0; + margin-left: 8px; + text-align: right; + line-height: 20px; + color: @colorTan; + } + input[type="text"], input[type="number"] { + flex: 0 0 48px; + text-align: center; + margin-left: 8px; + border-bottom: @borderGroove; + } + } +} diff --git a/less/variables.less b/less/variables.less index 9399ef773b..6e9fefd40f 100644 --- a/less/variables.less +++ b/less/variables.less @@ -40,6 +40,18 @@ @borderGroove: 2px groove #eeede0; @sheetBackground: url("ui/parchment.jpg") repeat; +:root { + /* ----------------------------------------- */ + /* Encumbrance & Capacity Bars */ + /* ----------------------------------------- */ + + --dnd5e-capacity-container-background-color: #7a7971; + --dnd5e-capacity-container-border-color: #191813; + --dnd5e-capacity-background-color: #6c8aa5; + --dnd5e-capacity-border-color: #cde4ff; + --dnd5e-capacity-text-color: #eeeeee; +} + /* ----------------------------------------- */ /* Flexbox */ /* ----------------------------------------- */ diff --git a/module/applications/item/item-sheet.mjs b/module/applications/item/item-sheet.mjs index e5d22b65c6..00da1c47d6 100644 --- a/module/applications/item/item-sheet.mjs +++ b/module/applications/item/item-sheet.mjs @@ -10,13 +10,18 @@ export default class ItemSheet5e extends ItemSheet { constructor(...args) { super(...args); - // Expand the default size of the class sheet - if ( this.object.type === "class" ) { - this.options.width = this.position.width = 600; - this.options.height = this.position.height = 680; - } - else if ( this.object.type === "subclass" ) { - this.options.height = this.position.height = 540; + switch ( this.object.type ) { + case "backpack": + this.options.width = this.position.width = 600; + this.options.height = this.position.height = 540; + break; + case "class": + this.options.width = this.position.width = 600; + this.options.height = this.position.height = 680; + break; + case "subclass": + this.options.height = this.position.height = 540; + break; } } @@ -109,6 +114,38 @@ export default class ItemSheet5e extends ItemSheet { effects: ActiveEffect5e.prepareActiveEffectCategories(item.effects) }); + // Container contents + if ( item.system.contents ) { + const contents = await item.system.containedItems; + context.items = Array.from(contents).map(i => i.toObject(false)); + + context.capacity = { max: item.system.capacity.value }; + if ( item.system.capacity.type === "weight" ) { + context.capacity.value = await item.system.containedItemsWeight; + context.capacity.units = "lbs."; // TODO: Localize & support metric + } else { + context.capacity.value = await item.system.containedItemsCount; + context.capacity.units = "items"; // TODO: Localize + } + + const containedItems = new Set(); + for ( let i of context.items ) { + const item = contents.find(item => i._id === item.id); + if ( item.type === "backpack" ) { + item.system.contents.forEach(id => containedItems.add(id)); + i.totalWeight = await item.system.totalWeight; + } else { + i.totalWeight = (i.system.quantity * i.system.weight).toNearest(0.1); + i.isStack = i.system.quantity > 1; + } + } + + context.items = context.items + .filter(i => !containedItems.has(i._id)) + .sort((a, b) => (a.sort || 0) - (b.sort || 0)); + // TODO: Split contained containers from rest of contents + } + /** @deprecated */ Object.defineProperty(context, "data", { get() { @@ -453,6 +490,10 @@ export default class ItemSheet5e extends ItemSheet { /** @inheritDoc */ activateListeners(html) { super.activateListeners(html); + + html.find(".item .item-name.rollable h4").click(event => this._onItemSummary(event)); + html.find(".item-edit").click(this._onItemEdit.bind(this)); + if ( this.isEditable ) { html.find(".damage-control").click(this._onDamageControl.bind(this)); html.find(".trait-selector").click(this._onConfigureTraits.bind(this)); @@ -464,6 +505,12 @@ export default class ItemSheet5e extends ItemSheet { const t = event.currentTarget; if ( t.dataset.action ) this._onAdvancementAction(t, t.dataset.action); }); + html.find(".item-delete").click(this._onItemDelete.bind(this)); + } + if ( this.item.isEmbedded && this.item.actor.isOwner ) { + html.find(".rollable .item-image").click(event => this._onItemUse(event)); + } else { + html.find(".rollable").each((i, el) => el.classList.remove("rollable")); } // Advancement context menu @@ -675,6 +722,80 @@ export default class ItemSheet5e extends ItemSheet { /* -------------------------------------------- */ + /** + * Handle using an item from the Actor sheet, obtaining the Item instance, and dispatching to its use method. + * @param {Event} event The triggering click event. + * @protected + */ + async _onItemUse(event) { + event.preventDefault(); + const itemId = event.currentTarget.closest(".item").dataset.itemId; + const item = (await this.item.system.containedItems).find(i => i.id === itemId); + // TODO: Add contained items cache to Item5e with better method to find by ID + item?.use({}, {event}); + } + + /* -------------------------------------------- */ + + /** + * Handle toggling and items expanded description. + * @param {Event} event Triggering event. + * @protected + */ + async _onItemSummary(event) { + event.preventDefault(); + const li = event.currentTarget.closest(".item"); + const item = (await this.item.system.containedItems).find(i => i.id === li.dataset.itemId); + const chatData = await item?.getChatData({secrets: this.item.actor?.isOwner ?? false}); + + if ( li.classList.contains("expanded") ) { + let summary = $(li.querySelector(".item-summary")); + summary.slideUp(200, () => summary.remove()); + li.classList.remove("expanded"); + } else { + // TODO: Replace with template to support inline container contents list + const div = $(`
${chatData.description.value}
`); + const props = $('
'); + chatData.properties.forEach(p => props.append(`${p}`)); + div.append(props); + $(li).append(div.hide()); + div.slideDown(200); // TODO: Replace with non-jQuery implementation + li.classList.add("expanded"); + } + } + + /* -------------------------------------------- */ + + /** + * Handle editing an item in a container. + * @param {Event} event The originating click event. + * @protected + */ + async _onItemEdit(event) { + event.preventDefault(); + const itemId = event.currentTarget.closest(".item").dataset.itemId; + const item = (await this.item.system.containedItems).find(i => i.id === itemId); + item?.sheet.render(true); + // TODO: Editing an item should also update the container's sheet + } + + /* -------------------------------------------- */ + + /** + * Handle deleting an item from a container. + * @param {Event} event The originating click event. + * @protected + */ + async _onItemDelete(event) { + event.preventDefault(); + const itemId = event.currentTarget.closest(".item").dataset.itemId; + const item = (await this.item.system.containedItems).find(i => i.id === itemId); + item?.deleteDialog(); + // TODO: Deleting an item needs to also delete it from the container's contents + } + + /* -------------------------------------------- */ + /** @inheritdoc */ async _onSubmit(...args) { if ( this._tabs[0].active === "details" ) this.position.height = "auto"; diff --git a/module/data/item/container.mjs b/module/data/item/container.mjs index fedd7e0a87..d538d7f405 100644 --- a/module/data/item/container.mjs +++ b/module/data/item/container.mjs @@ -39,56 +39,117 @@ export default class ContainerData extends SystemDataModel.mixin( }); } + /* -------------------------------------------- */ + /* Getters */ + /* -------------------------------------------- */ + + /** + * Get all of the items contained in this container. A promise if item is within a compendium. + * @type {Set|Promise>} + */ + get containedItems() { + if ( !this.parent || !this.contents.size ) return new Set(); + if ( this.parent.pack ) return this._containedItems(); + + return this.contents.reduce((set, id) => { + const document = this.parent.isEmbedded ? this.parent.actor.items.get(id) : game.items.get(id); + if ( document ) set.add(document); + return set; + }, new Set()); + } + + /* -------------------------------------------- */ + + /** + * Asynchronous helper method for fetching contained items from a compendium. + * @returns {Promise>} + * @private + */ + async _containedItems() { + const pack = game.packs.get(this.parent.pack); + return this.contents.reduce(async (promise, id) => { + const document = await pack.getDocument(id); + const set = await promise; + if ( document ) set.add(document); + return set; + }, new Set()); + } + /* -------------------------------------------- */ /** - * Get all of the items contained in this container. - * @returns {Set|Promise>} Set of each item, promised if item is in a compendium. + * Get all of the items in this container and any sub-containers. A promise if item is within a compendium. + * @type {Set|Promise>} */ - getContents() { + get allContainedItems() { if ( !this.parent || !this.contents.size ) return new Set(); + if ( this.parent.pack ) return this._allContainedItems(); - // If on an actor, fetch directly from the actor - if ( this.parent.isEmbedded ) return new Set(this.contents.map(id => this.parent.actor.items.get(id))); + return this.containedItems.reduce((set, item) => { + set.add(item); + if ( item.type === "backpack" ) item.system.allContainedItems.forEach(i => set.add(i)); + return set; + }, new Set()); + } - // If in a compendium, fetch each item from the compendium - if ( this.parent.pack ) { - const pack = game.packs.get(this.parent.pack); - return Promise.all(this.contents.map(id => pack.getDocument(id))).then(a => new Set(a)); - } + /* -------------------------------------------- */ - // If on the sidebar, fetch from the world items - return new Set(this.contents.map(id => game.items.get(id))); + /** + * Asynchronous helper method for fetching all contained items from a compendium. + * @returns {Promise>} + * @private + */ + async _allContainedItems() { + return (await this.containedItems).reduce(async (promise, item) => { + const set = await promise; + set.add(item); + if ( item.type === "backpack" ) (await item.system.allContainedItems).forEach(i => set.add(i)); + return set; + }, new Set()); + } + + /* -------------------------------------------- */ + + /** + * Number of items contained in this container including items in sub-containers. Result is a promise if item + * is within a compendium. + * @type {number|Promise} + */ + get containedItemsCount() { + const reducer = (count, item) => count + item.system.quantity; + const items = this.allContainedItems; + if ( items instanceof Promise ) return items.then(items => items.reduce(reducer, 0)); + return items.reduce(reducer, 0); } /* -------------------------------------------- */ /** - * Weight of the items in this container. + * Weight of the items in this container. Result is a promise if item is within a compendium. * @type {number|Promise} */ - get contentsWeight() { + get containedItemsWeight() { const reducer = (weight, item) => { const system = item.system; const itemWeight = item.type === "backpack" ? system.totalWeight : (system.weight * system.quantity); if ( itemWeight instanceof Promise ) return itemWeight.then(i => weight + i); return weight + itemWeight; }; - const contents = this.getContents(); - if ( contents instanceof Promise ) return contents.then(c => c.reduce(reducer, 0)); - return contents.reduce(reducer, 0); + const contents = this.containedItems; + if ( contents instanceof Promise ) return contents.then(c => c.reduce(reducer, this.currency.weight)); + return contents.reduce(reducer, this.currency.weight); } /* -------------------------------------------- */ /** - * The weight of this container with all of its contents. + * The weight of this container with all of its contents. Result is a promise if item is within a compendium. * @type {number|Promise} */ get totalWeight() { if ( this.weightless ) return this.weight; - const contentsWeight = this.contentsWeight; - if ( contentsWeight instanceof Promise ) return contentsWeight.then(c => this.weight + c); - return this.weight + contentsWeight; + const containedWeight = this.containedItemsWeight; + if ( containedWeight instanceof Promise ) return containedWeight.then(c => this.weight + c); + return this.weight + containedWeight; } } diff --git a/module/data/shared/currency.mjs b/module/data/shared/currency.mjs index 2d463877e8..21d09a6101 100644 --- a/module/data/shared/currency.mjs +++ b/module/data/shared/currency.mjs @@ -15,4 +15,21 @@ export default class CurrencyTemplate extends foundry.abstract.DataModel { }), {initialKeys: CONFIG.DND5E.currencies, label: "DND5E.Currency"}) }; } + + /* -------------------------------------------- */ + /* Getters */ + /* -------------------------------------------- */ + + /** + * Get the weight of all of the currency. Always returns 0 if currency weight is disabled in settings. + * @returns {number} + */ + get weight() { + if ( !game.settings.get("dnd5e", "currencyWeight") ) return 0; + const count = Object.values(this).reduce((count, value) => count + value, 0); + const currencyPerWeight = game.settings.get("dnd5e", "metricWeightUnits") + ? CONFIG.DND5E.encumbrance.currencyPerWeight.metric + : CONFIG.DND5E.encumbrance.currencyPerWeight.imperial; + return count / currencyPerWeight; + } } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index b3999c6af2..19e83ae2b4 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -20,6 +20,8 @@ export default class Item5e extends Item { /* Item Properties */ /* -------------------------------------------- */ + // TODO: Add getter to retrieve container item + /** * Which ability score modifier is used by this item? * @type {string|null} diff --git a/templates/items/backpack.hbs b/templates/items/backpack.hbs index e5b58286d1..dbcd2bdac8 100644 --- a/templates/items/backpack.hbs +++ b/templates/items/backpack.hbs @@ -29,12 +29,80 @@ {{!-- Item Sheet Navigation --}} {{!-- Item Sheet Body --}} -
+
{{log this}} + + {{!-- Contents Tab --}} +
+
+
+ {{#each system.currency as |v k|}} + + {{numberInput v name=(concat "system.currency." k)}} + {{/each}} +
+
+
    +
  1. +

    Contents

    + +
    {{localize "DND5E.Weight"}}
    + + {{#if @root.editable}} +
    + {{/if}} +
  2. + + {{#each items}} +
  3. +
    +
    +

    {{this.name}} {{~#if this.isStack}} ({{this.system.quantity}}){{/if}}

    + {{#if this.attunement}} +
    + +
    + {{/if}} +
    + +
    + {{#if this.totalWeight}} +
    + {{this.totalWeight}} {{@root.weightUnit}} +
    + {{/if}} +
    + + {{#if @root.editable}} + + {{/if}} +
  4. + {{/each}} +
+ +
+ + {{capacity.value}} / {{capacity.max}} {{capacity.units}} + + +
+
{{!-- Description Tab --}} {{> "dnd5e.item-description"}}