diff --git a/package.json b/package.json index 18af797..bb4c229 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ ], "scripts": { "build": "vite build", + "build:watch": "vite build --watch", "dev": "vite", "eslint": "eslint .", "prepare": "husky install", diff --git a/src/module.js b/src/module.js index 73bfd05..9ac5a61 100644 --- a/src/module.js +++ b/src/module.js @@ -4,6 +4,7 @@ import Logger from "./scripts/lib/Logger.js"; import { MagicItemActor } from "./scripts/magicitemactor.js"; import { MagicItemSheet } from "./scripts/magicitemsheet.js"; import { MagicItemTab } from "./scripts/magicItemtab.js"; +import { MagicItem } from "./scripts/magic-item/MagicItem.js"; //CONFIG.debug.hooks = true; @@ -66,40 +67,130 @@ Hooks.once("createToken", (token) => { } }); -Hooks.once('tidy5e-sheet.ready', (api) => { - const myTab = new api.models.HandlebarsTab({ - title: 'Magic Items', - tabId: 'magic-items', - path: '/modules/magic-items-2/templates/magic-item-tab.hbs', - enabled: (data) => { return ["weapon", "equipment", "consumable", "tool", "backpack", "feat"].includes(data.item.type) }, +let tidyApi; +Hooks.once("tidy5e-sheet.ready", (api) => { + tidyApi = api; + + // Register Tidy Item Sheet Tab + const magicItemsTab = new api.models.HandlebarsTab({ + title: "Magic Item", + tabId: "magic-items", + path: "/modules/magic-items-2/templates/magic-item-tab.hbs", + enabled: (data) => { + return MagicItemTab.isAcceptedItemType(data.item) && MagicItemTab.isAllowedToShow(); + }, + getData(data) { + return new MagicItem(data.item.flags.magicitems); + }, onRender(params) { - if (!game.user.isGM && game.settings.get(CONSTANTS.MODULE_ID, "hideFromPlayers")) { - return; + const html = $(params.element); + + if (params.data.editable) { + const magicItem = new MagicItem(params.data.item.flags.magicitems); + MagicItemTab.activateTabContentsListeners({ + html: html, + item: params.data.item, + magicItem: magicItem, + }); + params.element.querySelector(`.magic-items-content`).addEventListener("drop", (event) => { + MagicItemTab.onDrop({ event, item: params.data.item, magicItem: magicItem }); + }); + } else { + MagicItemTab.disableMagicItemTabInputs(html); } - var htmlArr = []; - htmlArr.push($(params.element)); - MagicItemTab.bind(params.app, htmlArr, params.data); }, }); - Logger.logObject(myTab); - api.registerItemTab(myTab); + api.registerItemTab(magicItemsTab); + + // Register character and NPC spell tab custom content + api.registerActorContent( + new api.models.HandlebarsContent({ + path: `modules/${CONSTANTS.MODULE_ID}/templates/magic-item-spell-sheet.html`, + injectParams: { + position: "afterbegin", + selector: `[data-tab-contents-for="${api.constants.TAB_ID_CHARACTER_SPELLBOOK}"] .scroll-container`, + }, + enabled(data) { + const actor = MagicItemActor.get(data.actor.id); + // Required for Tidy to have accurate item data + actor.buildItems(); + return ["character", "npc"].includes(data.actor.type) && actor?.hasItemsSpells(); + }, + getData(data) { + return MagicItemActor.get(data.actor.id); + }, + }) + ); + + // Register character and NPC feature tab custom content + const npcAbilitiesTabContainerSelector = `[data-tidy-sheet-part="${api.constants.SHEET_PARTS.NPC_ABILITIES_CONTAINER}"]`; + const characterFeaturesContainerSelector = `[data-tab-contents-for="${api.constants.TAB_ID_CHARACTER_FEATURES}"] [data-tidy-sheet-part="${api.constants.SHEET_PARTS.ITEMS_CONTAINER}"]`; + const magicItemFeatureTargetSelector = [npcAbilitiesTabContainerSelector, characterFeaturesContainerSelector].join( + ", " + ); + api.registerActorContent( + new api.models.HandlebarsContent({ + path: `modules/${CONSTANTS.MODULE_ID}/templates/magic-item-feat-sheet.html`, + injectParams: { + position: "afterbegin", + selector: magicItemFeatureTargetSelector, + }, + enabled(data) { + const actor = MagicItemActor.get(data.actor.id); + // Required for Tidy to have accurate item data + actor.buildItems(); + return ["character", "npc"].includes(data.actor.type) && actor?.hasItemsFeats(); + }, + getData(data) { + return MagicItemActor.get(data.actor.id); + }, + }) + ); }); +// Wire Tidy events and register iterated, data-dependent content +Hooks.on("tidy5e-sheet.renderActorSheet", (app, element, data) => { + // Place wand for visible magic items + const actor = MagicItemActor.get(data.actor.id); + const html = $(element); + actor?.items + .filter((item) => item.visible) + .forEach((item) => { + let itemEl = html.find( + `[data-tidy-sheet-part="${tidyApi.constants.SHEET_PARTS.ITEM_TABLE_ROW}"][data-item-id="${item.id}"]` + ); + let itemNameContainer = itemEl.find(`[data-tidy-sheet-part=${tidyApi.constants.SHEET_PARTS.ITEM_NAME}]`); + let iconHtml = tidyApi.useHandlebarsRendering(CONSTANTS.HTML.MAGIC_ITEM_ICON); + itemNameContainer.append(iconHtml); + }); + + // Wire events for custom tidy actor sheet content + MagicItemSheet.handleEvents(html, actor); +}); Hooks.on(`renderItemSheet5e`, (app, html, data) => { - if (app.constructor.name !== "Tidy5eKgarItemSheet") { - if (!game.user.isGM && game.settings.get(CONSTANTS.MODULE_ID, "hideFromPlayers")) { - return; - } - MagicItemTab.bind(app, html, data); + if (tidyApi?.isTidy5eItemSheet(app)) { + return; + } + + if (!MagicItemTab.isAllowedToShow()) { + return; } + + MagicItemTab.bind(app, html, data); }); Hooks.on(`renderActorSheet5eCharacter`, (app, html, data) => { + if (tidyApi?.isTidy5eCharacterSheet(app)) { + return; + } MagicItemSheet.bind(app, html, data); }); Hooks.on(`renderActorSheet5eNPC`, (app, html, data) => { + if (tidyApi?.isTidy5eNpcSheet(app)) { + return; + } MagicItemSheet.bind(app, html, data); }); diff --git a/src/scripts/constants/constants.js b/src/scripts/constants/constants.js index 2c12e2b..5204013 100644 --- a/src/scripts/constants/constants.js +++ b/src/scripts/constants/constants.js @@ -4,6 +4,10 @@ const CONSTANTS = { PREFIX_LABEL: "MAGICITEMS", PREFIX_FLAG: "magicitems", FLAGS: {}, + HTML: { + MAGIC_ITEM_ICON: '', + }, }; CONSTANTS.PATH = `modules/${CONSTANTS.MODULE_NAME}/`; + export default CONSTANTS; diff --git a/src/scripts/magic-item-entry/MagicItemSpell.js b/src/scripts/magic-item-entry/MagicItemSpell.js index 08cb241..9892c16 100644 --- a/src/scripts/magic-item-entry/MagicItemSpell.js +++ b/src/scripts/magic-item-entry/MagicItemSpell.js @@ -1,13 +1,14 @@ import { AbstractMagicItemEntry } from "./AbstractMagicItemEntry"; +import { NumberUtils } from "../utils/number"; export class MagicItemSpell extends AbstractMagicItemEntry { constructor(data) { super(data); - this.baseLevel = parseInt(this.baseLevel); - this.level = parseInt(this.level); - this.consumption = parseInt(this.consumption); - this.upcast = this.upcast ? parseInt(this.upcast) : this.level; - this.upcastCost = this.upcastCost ? parseInt(this.upcastCost) : 1; + this.baseLevel = NumberUtils.parseIntOrGetDefault(this.baseLevel, 0); + this.level = NumberUtils.parseIntOrGetDefault(this.level, 0); + this.consumption = NumberUtils.parseIntOrGetDefault(this.consumption, 0); + this.upcast = this.upcast ? NumberUtils.parseIntOrGetDefault(this.upcast, 0) : this.level; + this.upcastCost = this.upcastCost ? NumberUtils.parseIntOrGetDefault(this.upcastCost, 0) : 1; this.dc = this.flatDc && this.dc ? this.dc : ""; } diff --git a/src/scripts/magic-item/MagicItem.js b/src/scripts/magic-item/MagicItem.js index 6888b43..04c3497 100644 --- a/src/scripts/magic-item/MagicItem.js +++ b/src/scripts/magic-item/MagicItem.js @@ -3,6 +3,7 @@ import { MagicItemFeat } from "../magic-item-entry/MagicItemFeat.js"; import { MagicItemSpell } from "../magic-item-entry/MagicItemSpell.js"; import { MagicItemTable } from "../magic-item-entry/MagicItemTable.js"; import { MagicItemHelpers } from "../magic-item-helpers.js"; +import { NumberUtils } from "../utils/number.js"; export class MagicItem { constructor(flags) { @@ -11,7 +12,7 @@ export class MagicItem { this.enabled = data.enabled; this.equipped = data.equipped; this.attuned = data.attuned; - this.charges = parseInt(data.charges); + this.charges = NumberUtils.parseIntOrGetDefault(data.charges, 0); this.chargeType = data.chargeType; this.rechargeable = data.rechargeable; this.recharge = data.recharge; @@ -23,7 +24,6 @@ export class MagicItem { this.destroyFlavorText = data.destroyFlavorText; this.sorting = data.sorting; this.sortingModes = { l: "MAGICITEMS.SheetSortByLevel", a: "MAGICITEMS.SheetSortAlphabetically" }; - this.updateDestroyTarget(); this.spells = Object.values(data.spells ? data.spells : {}) .filter((spell) => spell !== "null") @@ -44,6 +44,12 @@ export class MagicItem { this.savedSpells = this.spells.length; this.savedFeats = this.feats.length; this.savedTables = this.tables.length; + + this.sort(); + + if (!this.enabled) { + this.clear(); + } } sort() { @@ -55,11 +61,10 @@ export class MagicItem { } } - updateDestroyTarget() { - this.destroyTarget = - this.chargeType === "c1" - ? game.i18n.localize("MAGICITEMS.SheetObjectTarget") - : game.i18n.localize("MAGICITEMS.SheetSpellTarget"); + get destroyTarget() { + return this.chargeType === "c1" + ? game.i18n.localize("MAGICITEMS.SheetObjectTarget") + : game.i18n.localize("MAGICITEMS.SheetSpellTarget"); } defaultData() { @@ -313,26 +318,13 @@ export class MagicItem { return this.items.filter((item) => item.id === itemId)[0]; } - async renderSheet(spellId) { - let spellFounded = this.findByUuid(spellId); - if (!byUuid) { - spellFounded = this.findById(spellId); + async renderSheet(itemId) { + let item = this.findByUuid(itemId); + if (!item) { + item = this.findById(itemId); } - // let uuid = null; - // if (spellFounded.uuid) { - // uuid = spellFounded.uuid; - // } else { - // uuid = MagiItemHelpers.retrieveUuid({ - // documentName: spellFounded.name, - // documentId: spellFounded.id, - // documentCollectionType: "Item", - // documentPack: spellFounded.pack, - // }); - // } - // const itemTmp = await fromUuid(uuid); - // itemTmp.sheet.render(true); - await spellFounded.renderSheet(); + await item.renderSheet(); } cleanup() { diff --git a/src/scripts/magic-item/OwnedMagicItem.js b/src/scripts/magic-item/OwnedMagicItem.js index 35f7912..f8eb8d6 100644 --- a/src/scripts/magic-item/OwnedMagicItem.js +++ b/src/scripts/magic-item/OwnedMagicItem.js @@ -58,7 +58,9 @@ export class OwnedMagicItem extends MagicItem { active = active && this.item.system.equipped; } if (this.attuned) { - let isAttuned = this.item.system.attunement ? this.item.system.attunement === 2 : this.item.system.attuned; + let isAttuned = + this.item.system.attunement === 2 || + this.item.system.attuned === true; /* this.item.system.attuned is a legacy property; can be undefined */ active = active && isAttuned; } return active; diff --git a/src/scripts/magicItemtab.js b/src/scripts/magicItemtab.js index 9131857..22c40d3 100644 --- a/src/scripts/magicItemtab.js +++ b/src/scripts/magicItemtab.js @@ -1,88 +1,77 @@ -import { MAGICITEMS } from "./config.js"; import CONSTANTS from "./constants/constants.js"; -import Logger from "./lib/Logger.js"; -import { MagicItemHelpers } from "./magic-item-helpers.js"; import { MagicItem } from "./magic-item/MagicItem.js"; const magicItemTabs = []; export class MagicItemTab { static bind(app, html, item) { - let acceptedTypes = ["weapon", "equipment", "consumable", "tool", "backpack", "feat"]; - if (acceptedTypes.includes(item.document.type)) { + if (MagicItemTab.isAcceptedItemType(item.document)) { let tab = magicItemTabs[app.id]; if (!tab) { tab = new MagicItemTab(app); magicItemTabs[app.id] = tab; } - tab.init(html, item); + tab.init(html, item, app); } } constructor(app) { - this.app = app; - this.item = app.item; - - this.hack(this.app); - + this.hack(app); this.activate = false; } - init(html, data) { - - if (this.app.constructor.name !== "Tidy5eKgarItemSheet") { - if (html[0].localName !== "div") { - html = $(html[0].parentElement.parentElement); - } - let tabs = html.find(`form nav.sheet-navigation.tabs`); - if (tabs.find("a[data-tab=magicitems]").length > 0) { - return; // already initialized, duplication bug! - } - - tabs.append($('Magic Item')); + init(html, data, app) { + this.item = app.item; + this.html = html; + this.editable = data.editable; - $(html.find(`.sheet-body`)).append( - $('
') - ); + if (html[0].localName !== "div") { + html = $(html[0].parentElement.parentElement); } - else { - html = $(html[0]); - Logger.logObject(html); - let tabs = html.find(`form nav.tidy-tabs`); - if (tabs.find("a[data-tab-id=magic-items]").length > 0) { - return; - } + let tabs = html.find(`form nav.sheet-navigation.tabs`); + if (tabs.find("a[data-tab=magicitems]").length > 0) { + return; // already initialized, duplication bug! } - this.html = html; - this.editable = data.editable; + tabs.append($('Magic Item')); + + $(html.find(`.sheet-body`)).append( + $('
') + ); if (this.editable) { const dragDrop = new DragDrop({ dropSelector: ".tab.magic-items", permissions: { - dragstart: this._canDragStart.bind(this.app), - drop: this._canDragDrop.bind(this.app), + dragstart: this._canDragStart.bind(app), + drop: this._canDragDrop.bind(app), }, callbacks: { - dragstart: this.app._onDragStart.bind(this.app), - dragover: this.app._onDragOver.bind(this.app), - drop: this._onDrop.bind(this), + dragstart: app._onDragStart.bind(app), + dragover: app._onDragOver.bind(app), + drop: (event) => { + this.activate = true; + MagicItemTab.onDrop({ + event: event, + item: this.item, + magicItem: this.magicItem, + }); + }, }, }); - this.app._dragDrop.push(dragDrop); - dragDrop.bind(this.app.form); + app._dragDrop.push(dragDrop); + dragDrop.bind(app.form); } - this.magicItem = new MagicItem(this.item.flags.magicitems); + this.magicItem = new MagicItem(app.item.flags.magicitems); - this.render(); + this.render(app); } hack(app) { let tab = this; - app.setPosition = function(position = {}) { + app.setPosition = function (position = {}) { position.height = tab.isActive() && !position.height ? "auto" : position.height; let that = this; for (let i = 0; i < 100; i++) { @@ -95,8 +84,7 @@ export class MagicItemTab { }; } - async render() { - this.magicItem.sort(); + async render(app) { let template = await renderTemplate(`modules/${CONSTANTS.MODULE_ID}/templates/magic-item-tab.hbs`, this.magicItem); let el = this.html.find(`.magic-items-content`); if (el.length) { @@ -105,202 +93,70 @@ export class MagicItemTab { this.html.find(".tab.magic-items").append(template); } - let magicItemEnabled = this.html.find(".magic-item-enabled"); - if (this.magicItem.enabled) { - magicItemEnabled.show(); - } else { - magicItemEnabled.hide(); - } - - let magicItemDestroyType = this.html.find('select[name="flags.magicitems.destroyType"]'); - if (this.magicItem.chargeType === "c1") { - magicItemDestroyType.show(); - } else { - magicItemDestroyType.hide(); - } - - let magicItemDestroyCheck = this.html.find('select[name="flags.magicitems.destroyCheck"]'); - let magicItemFlavorText = this.html.find(".magic-item-destroy-flavor-text"); - if (this.magicItem.destroy) { - magicItemDestroyCheck.prop("disabled", false); - magicItemDestroyType.prop("disabled", false); - magicItemFlavorText.show(); + if (this.editable) { + this.activateTabManagementListeners(); + MagicItemTab.activateTabContentsListeners({ + html: this.html, + item: this.item, + magicItem: this.magicItem, + onItemUpdatingCallback: () => { + this.activate = true; + }, + }); } else { - magicItemDestroyCheck.prop("disabled", true); - magicItemDestroyType.prop("disabled", true); - magicItemFlavorText.hide(); + MagicItemTab.disableMagicItemTabInputs(this.html); } - let magicItemRecharge = this.html.find(".form-group.magic-item-recharge"); - if (this.magicItem.rechargeable) { - magicItemRecharge.show(); - } else { - magicItemRecharge.hide(); + if (this.activate && !this.isActive()) { + app._tabs[0].activate("magicitems"); + app.setPosition(); } - let rechargeField = this.html.find('input[name="flags.magicitems.recharge"]'); - if (this.magicItem.rechargeType === MAGICITEMS.FORMULA_FULL) { - rechargeField.prop("disabled", true); - } else { - rechargeField.prop("disabled", false); - } + this.activate = false; + } - if (this.editable) { - this.handleEvents(); - } else { - this.html.find("input").prop("disabled", true); - this.html.find("select").prop("disabled", true); - } + isActive() { + return $(this.html).find('a.item[data-tab="magicitems"]').hasClass("active"); + } - this.app.setPosition(); + _canDragDrop() { + return true; + } - if (this.app.constructor.name !== "Tidy5eKgarItemSheet" && this.activate && !this.isActive()) { - this.app._tabs[0].activate("magicitems"); - this.activate = false; - } else { - this.activate = false; - } + _canDragStart() { + return true; } - handleEvents() { - this.html.find('.magic-items-content input[type="text"]').change((evt) => { + activateTabManagementListeners() { + this.html.find(".magic-items-content").on("change", ":input", (evt) => { this.activate = true; - this.render(); - }); - this.html.find(".magic-items-content select").change((evt) => { - this.activate = true; - this.render(); }); + } - this.html.find('input[name="flags.magicitems.enabled"]').click((evt) => { - this.magicItem.toggleEnabled(evt.target.checked); - this.render(); - }); - this.html.find('input[name="flags.magicitems.equipped"]').click((evt) => { - this.magicItem.equipped = evt.target.checked; - this.render(); - }); - this.html.find('input[name="flags.magicitems.attuned"]').click((evt) => { - this.magicItem.attuned = evt.target.checked; - this.render(); - }); - this.html.find('input[name="flags.magicitems.charges"]').change((evt) => { - this.magicItem.charges = MagicItemHelpers.numeric(evt.target.value, this.magicItem.charges); - this.render(); - }); - this.html.find('select[name="flags.magicitems.chargeType"]').change((evt) => { - this.magicItem.chargeType = evt.target.value; - this.magicItem.updateDestroyTarget(); - this.render(); - }); - this.html.find('input[name="flags.magicitems.rechargeable"]').change((evt) => { - this.magicItem.toggleRechargeable(evt.target.checked); - this.render(); - }); - this.html.find('input[name="flags.magicitems.recharge"]').change((evt) => { - this.magicItem.recharge = evt.target.value; - this.render(); - }); - this.html.find('select[name="flags.magicitems.rechargeType"]').change((evt) => { - this.magicItem.rechargeType = evt.target.value; - this.render(); - }); - this.html.find('select[name="flags.magicitems.rechargeUnit"]').change((evt) => { - this.magicItem.rechargeUnit = evt.target.value; - this.render(); - }); - this.html.find('input[name="flags.magicitems.destroy"]').change((evt) => { - this.magicItem.destroy = evt.target.checked; - this.render(); - }); - this.html.find('select[name="flags.magicitems.destroyCheck"]').change((evt) => { - this.magicItem.destroyCheck = evt.target.value; - this.render(); - }); - this.html.find('select[name="flags.magicitems.destroyType"]').change((evt) => { - this.magicItem.destroyType = evt.target.value; - this.render(); - }); - this.html.find('input[name="flags.magicitems.destroyFlavorText"]').change((evt) => { - this.magicItem.destroyFlavorText = evt.target.value; - this.render(); - }); - this.html.find('input[name="flags.magicitems.sorting"]').change((evt) => { - this.magicItem.sorting = evt.target.value; - this.magicItem.sort(); - this.render(); - }); - this.html.find(".item-delete.item-spell").click((evt) => { - this.magicItem.removeSpell(evt.target.getAttribute("data-spell-idx")); - this.render(); - }); - this.html.find(".item-delete.item-feat").click((evt) => { - this.magicItem.removeFeat(evt.target.getAttribute("data-feat-idx")); - this.render(); - }); - this.html.find(".item-delete.item-table").click((evt) => { - this.magicItem.removeTable(evt.target.getAttribute("data-table-idx")); - this.render(); - }); - this.magicItem.spells.forEach((spell, idx) => { - this.html.find(`select[name="flags.magicitems.spells.${idx}.level"]`).change((evt) => { - spell.level = parseInt(evt.target.value); - this.render(); - }); - this.html.find(`input[name="flags.magicitems.spells.${idx}.consumption"]`).change((evt) => { - spell.consumption = MagicItemHelpers.numeric(evt.target.value, spell.consumption); - this.render(); - }); - this.html.find(`select[name="flags.magicitems.spells.${idx}.upcast"]`).change((evt) => { - spell.upcast = parseInt(evt.target.value); - this.render(); - }); - this.html.find(`input[name="flags.magicitems.spells.${idx}.upcastCost"]`).change((evt) => { - spell.upcastCost = MagicItemHelpers.numeric(evt.target.value, spell.cost); - this.render(); - }); - this.html.find(`input[name="flags.magicitems.spells.${idx}.flatDc"]`).click((evt) => { - spell.flatDc = evt.target.checked; - this.render(); - }); - this.html.find(`input[name="flags.magicitems.spells.${idx}.dc"]`).change((evt) => { - spell.dc = evt.target.value; - this.render(); - }); - this.html.find(`a[data-spell-idx="${idx}"]`).click((evt) => { - spell.renderSheet(); - }); - }); - this.magicItem.feats.forEach((feat, idx) => { - this.html.find(`select[name="flags.magicitems.feats.${idx}.effect"]`).change((evt) => { - feat.effect = evt.target.value; - this.render(); - }); - this.html.find(`input[name="flags.magicitems.feats.${idx}.consumption"]`).change((evt) => { - feat.consumption = MagicItemHelpers.numeric(evt.target.value, feat.consumption); - this.render(); - }); - this.html.find(`a[data-feat-idx="${idx}"]`).click((evt) => { - feat.renderSheet(); - }); - }); - this.magicItem.tables.forEach((table, idx) => { - this.html.find(`input[name="flags.magicitems.tables.${idx}.consumption"]`).change((evt) => { - table.consumption = MagicItemHelpers.numeric(evt.target.value, table.consumption); - }); - this.html.find(`a[data-table-idx="${idx}"]`).click((evt) => { - table.renderSheet(); - }); - }); + /** + * Disable all relevant inputs in the magic items tab. + */ + static disableMagicItemTabInputs(html) { + html.find(".magic-items-content input").prop("disabled", true); + html.find(".magic-items-content select").prop("disabled", true); } - async _onDrop(evt) { - evt.preventDefault(); + /** + * Handles drop event for compatible magic item source (for example, a spell). + * + * @param {object} params Parameters needed to handle item drops to the magic item tab. + * @param {DragEvent} params.event The drop event. + * @param {Item5e} params.item The target item. + * @param {MagicItem} params.magicItem The relevant magic item associated with the target item. + * @returns + */ + static async onDrop({ event, item, magicItem }) { + event.preventDefault(); let data; try { - data = JSON.parse(evt.dataTransfer.getData("text/plain")); - if (!this.magicItem.support(data.type)) { + data = JSON.parse(event.dataTransfer.getData("text/plain")); + if (!magicItem.support(data.type)) { return; } } catch (err) { @@ -310,23 +166,84 @@ export class MagicItemTab { const entity = await fromUuid(data.uuid); const pack = entity.pack ? entity.pack : "world"; - if (entity && this.magicItem.compatible(entity)) { - this.magicItem.addEntity(entity, pack); - this.render(); + if (entity && magicItem.compatible(entity)) { + magicItem.addEntity(entity, pack); + item.update({ + flags: { + magicitems: magicItem.serializeData(), + }, + }); } } - isActive() { - return this.app.constructor.name !== "Tidy5eKgarItemSheet" ? - $(this.html).find('a.item[data-tab="magicitems"]').hasClass("active") : - $(this.html).find('a.item[data-tab-id="magic-items"]').hasClass("active") + /** + * Activates listeners related to tab contents. + * + * @param {object} params The parameters for wiring up tab content event handling. + * @param {jQuery} params.html The sheet HTML jQuery element + * @param {Item5e} params.item The item which is to be changed. + * @param {MagicItem} params.magicItem A Magic Item instance + * @param {Function} params.onItemUpdatingCallback A callback for handling when item updates are about to be applied. This is useful for current tab management. + */ + static activateTabContentsListeners({ + html, + item, + magicItem, + onItemUpdatingCallback: onMagicItemUpdatingCallback = null, + }) { + html.find(".item-delete.item-spell").click((evt) => { + magicItem.removeSpell(evt.target.getAttribute("data-spell-idx")); + onMagicItemUpdatingCallback?.(); + item.update({ + flags: { + magicitems: magicItem.serializeData(), + }, + }); + }); + html.find(".item-delete.item-feat").click((evt) => { + magicItem.removeFeat(evt.target.getAttribute("data-feat-idx")); + onMagicItemUpdatingCallback?.(); + item.update({ + flags: { + magicitems: magicItem.serializeData(), + }, + }); + }); + html.find(".item-delete.item-table").click((evt) => { + magicItem.removeTable(evt.target.getAttribute("data-table-idx")); + onMagicItemUpdatingCallback?.(); + item.update({ + flags: { + magicitems: magicItem.serializeData(), + }, + }); + }); + magicItem.spells.forEach((spell, idx) => { + html.find(`a[data-spell-idx="${idx}"]`).click((evt) => { + spell.renderSheet(); + }); + }); + magicItem.feats.forEach((feat, idx) => { + html.find(`a[data-feat-idx="${idx}"]`).click((evt) => { + feat.renderSheet(); + }); + }); + magicItem.tables.forEach((table, idx) => { + html.find(`a[data-table-idx="${idx}"]`).click((evt) => { + table.renderSheet(); + }); + }); } - _canDragDrop() { - return true; + static get acceptedItemTypes() { + return ["weapon", "equipment", "consumable", "tool", "backpack", "feat"]; } - _canDragStart() { - return true; + static isAcceptedItemType(document) { + return MagicItemTab.acceptedItemTypes.includes(document?.type); + } + + static isAllowedToShow() { + return game.user.isGM || !game.settings.get(CONSTANTS.MODULE_ID, "hideFromPlayers"); } } diff --git a/src/scripts/magicitemactor.js b/src/scripts/magicitemactor.js index 43662a2..b1b4316 100644 --- a/src/scripts/magicitemactor.js +++ b/src/scripts/magicitemactor.js @@ -268,25 +268,11 @@ export class MagicItemActor { * @param ownedItemId */ async renderSheet(itemId, ownedItemId) { - let found = this.items.filter((item) => { + let item = this.items.find((item) => { return item.id === itemId || item.uuid === itemId; }); - if (found.length) { - let item = found[0]; + if (item) { item.renderSheet(ownedItemId); - // let uuid = null; - // if (item.uuid) { - // uuid = item.uuid; - // } else { - // uuid = MagiItemHelpers.retrieveUuid({ - // documentName: item.name, - // documentId: item.id, - // documentCollectionType: "Item", - // documentPack: item.pack, - // }); - // } - // const itemTmp = await fromUuid(uuid); - // itemTmp.sheet.render(true); } } diff --git a/src/scripts/magicitemsheet.js b/src/scripts/magicitemsheet.js index e713cfb..ac27e5d 100644 --- a/src/scripts/magicitemsheet.js +++ b/src/scripts/magicitemsheet.js @@ -68,11 +68,11 @@ export class MagicItemSheet { let itemEl = this.html.find(`.inventory-list .item-list .item[data-item-id="${item.id}"]`); let h4 = itemEl.find("h4"); if (!h4.find("i.fa-magic").length) { - h4.append(''); + h4.append(CONSTANTS.HTML.MAGIC_ITEM_ICON); } }); - this.handleEvents(); + MagicItemSheet.handleEvents(this.html, this.actor); } /** @@ -96,48 +96,54 @@ export class MagicItemSheet { /** * */ - handleEvents() { - this.html.find(".item div.magic-item-image").click((evt) => this.onItemRoll(evt)); - this.html.find(".item h4.spell-name").click((evt) => this.onItemShow(evt)); - this.actor.items.forEach((item) => { - this.html.find(`input[data-item-id="magicitems.${item.id}.uses"]`).change((evt) => { + static handleEvents(html, actor) { + html.find(".item div.magic-item-image").click((evt) => MagicItemSheet.onItemRoll(evt, actor)); + html.find(".item h4.spell-name").click((evt) => MagicItemSheet.onItemShow(evt)); + MagicItemSheet.handleActorItemUsesChangeEvents(html, actor); + MagicItemSheet.handleMagicItemDragStart(html, actor); + } + + static handleMagicItemDragStart(html, actor) { + html.find(`li.item.magic-item`).each((i, li) => { + li.addEventListener("dragstart", (evt) => MagicItemSheet.onDragItemStart(evt, actor)); + }); + } + + static handleActorItemUsesChangeEvents(html, actor) { + actor.items.forEach((item) => { + html.find(`input[data-item-id="magicitems.${item.id}.uses"]`).change((evt) => { item.setUses(MagicItemHelpers.numeric(evt.currentTarget.value, item.uses)); item.update(); }); item.ownedEntries.forEach((entry) => { - this.html.find(`input[data-item-id="magicitems.${item.id}.${entry.id}.uses"]`).change((evt) => { + html.find(`input[data-item-id="magicitems.${item.id}.${entry.id}.uses"]`).change((evt) => { entry.uses = MagicItemHelpers.numeric(evt.currentTarget.value, entry.uses); item.update(); }); }); }); - this.html.find(`li.item.magic-item`).each((i, li) => { - li.addEventListener("dragstart", this.onDragItemStart.bind(this), false); - }); } /** * * @param evt */ - onItemRoll(evt) { + static async onItemRoll(evt, actor) { evt.preventDefault(); let dataset = evt.currentTarget.closest(".item").dataset; let magicItemId = dataset.magicItemId; let itemId = dataset.itemId; - this.actor.roll(magicItemId, itemId).then(() => { - this.render(); - }); + await actor.roll(magicItemId, itemId); + // this.render(); } /** * * @param evt */ - async onItemShow(evt) { + static async onItemShow(evt) { evt.preventDefault(); let dataset = evt.currentTarget.closest(".item").dataset; - let magicItemId = dataset.magicItemId; let itemId = dataset.itemId; let itemUuid = dataset.itemUuid; let itemPack = dataset.itemPack; @@ -155,19 +161,17 @@ export class MagicItemSheet { } const itemTmp = await fromUuid(uuid); itemTmp.sheet.render(true); - // TODO TO REMOVE ??? OR UPDATE SOMEHOW ? - await this.actor.renderSheet(magicItemId, itemId); } /** * * @param evt */ - onDragItemStart(evt) { + static onDragItemStart(evt, actor) { const li = evt.currentTarget; let magicItemId = li.dataset.magicItemId; let itemId = li.dataset.itemId; - let magicItem = this.actor.magicItem(magicItemId); + let magicItem = actor.magicItem(magicItemId); let item = magicItem.entryBy(itemId); const dragData = { diff --git a/src/scripts/utils/number.js b/src/scripts/utils/number.js new file mode 100644 index 0000000..ecb3da0 --- /dev/null +++ b/src/scripts/utils/number.js @@ -0,0 +1,6 @@ +export class NumberUtils { + static parseIntOrGetDefault(value, defaultValue) { + const parsedValue = parseInt(value); + return !isNaN(parsedValue) ? parsedValue : defaultValue; + } +} diff --git a/src/styles/magicitems.css b/src/styles/magicitems.css index 3ead743..73d6e95 100644 --- a/src/styles/magicitems.css +++ b/src/styles/magicitems.css @@ -11,22 +11,6 @@ border-bottom: 1px solid #c9c7b8; } -.dnd5e.sheet.item .magic-item-list .inventory-header { - margin: 2px 0; - padding: 0; - background: rgba(0, 0, 0, 0.05); - border: 2px groove #eeede0; - font-weight: bold; - line-height: 24px; -} - -.dnd5e.sheet.item .magic-item-list .inventory-header h3 { - margin: 0 -5px 0 0; - padding-left: 5px; - font-size: 13px; - font-weight: bold; -} - .dnd5e.sheet.item .magic-item-list .item .item-name { cursor: pointer; } @@ -35,132 +19,19 @@ border-bottom: none; } -.dnd5e.sheet.item .magic-item-list .item-list { - list-style: none; - margin: 0; - padding: 0; -} - .dnd5e.sheet.item .spell-slots, .dnd5e.sheet.item .spell-comps { - flex: 0 0 60px; - font-size: 12px; - text-align: center; - color: #7a7971; - border-right: 1px solid #c9c7b8; -} - -.dnd5e.sheet.item .spell-level-head, -.dnd5e.sheet.item .spell-upcast-head, -.dnd5e.sheet.item .spell-consumption-head, -.dnd5e.sheet.item .spell-cost-head, -.dnd5e.sheet.item .spell-dc-head { - font-size: 12px; - color: #7a7971; - margin-right: 4px; - text-align: center; - border-right: 1px solid #c9c7b8; -} - -.dnd5e.sheet.item .spell-upcast-head { - flex: 0 0 80px; -} - -.dnd5e.sheet.item .spell-level-head, -.dnd5e.sheet.item .spell-consumption-head, -.dnd5e.sheet.item .spell-cost-head { - flex: 0 0 50px; -} - -.dnd5e.sheet.item .spell-dc-head { - flex: 0 0 64px; -} - -.dnd5e.sheet.item .spell-controls-head { - flex: 0 0 20px; -} - -.dnd5e.sheet.item .table-usage-head, -.dnd5e.sheet.item .table-consumption-head { - flex: 0 0 80px; font-size: 12px; - color: #7a7971; - margin-right: 4px; text-align: center; - border-right: 1px solid #c9c7b8; -} - -.dnd5e.sheet.item .feat-effect-head, -.dnd5e.sheet.item .feat-consumption-head { - flex: 0 0 80px; - font-size: 12px; color: #7a7971; - margin-right: 4px; - text-align: center; border-right: 1px solid #c9c7b8; } -.dnd5e.sheet.item .magic-item-list .item .item-name .item-image { - flex: 0 0 30px; - background-size: 30px; - margin-right: 5px; -} - -.dnd5e.sheet.item .magic-item-list .item .item-name h4 { - margin: 0; -} - -.dnd5e.sheet.item .magic-item-list .item-controls { - text-align: right; - flex: 0 0 20px; -} - -.dnd5e.sheet.item .magic-item-list .item-controls a { - flex: 0 0 22px; - font-size: 10px; - text-align: center; - color: #666; - margin-right: 5px; -} - -.dnd5e.sheet.item .magic-item-list .item .spell-upcast { - flex: 0 0 80px; - margin-right: 4px; -} - -.dnd5e.sheet.item .magic-item-list .item .spell-level, -.dnd5e.sheet.item .magic-item-list .item .spell-consumption, -.dnd5e.sheet.item .magic-item-list .item .spell-cost { - flex: 0 0 50px; - margin-right: 4px; -} - -.dnd5e.sheet.item .magic-item-list .item .spell-flat-dc { - flex: 0 0 20px; -} - -.dnd5e.sheet.item .magic-item-list .item .spell-dc { - flex: 0 0 38px; - margin-right: 2px; -} - -.dnd5e.sheet.item .magic-item-list .item .table-usage, -.dnd5e.sheet.item .magic-item-list .item .table-consumption { - flex: 0 0 80px; - margin-right: 4px; -} - .dnd5e.sheet.item .magic-item-list .item .spell-dc:disabled { color: #9a9a9a; border-color: #dad9d9; } -.dnd5e.sheet.item .magic-item-list .item .feat-effect, -.dnd5e.sheet.item .magic-item-list .item .feat-consumption { - flex: 0 0 80px; - margin-right: 4px; -} - .dnd5e.sheet.item .magic-items-content input[type="text"], .dnd5e.sheet.item .magic-items-content select { height: 24px; @@ -180,6 +51,16 @@ border: 2px #828180 dashed; } +.dnd5e.sheet.item .magic-items-content .item-controls { + flex-basis: 2rem; + display: flex; + justify-content: center; +} + +.dnd5e.sheet.item .magic-items-content .item-detail select { + width: 100%; +} + .dnd5e.sheet.actor .magic-items-head { margin-top: 10px; } @@ -216,10 +97,6 @@ margin-right: 5px; } -.dnd5e.sheet.actor .inventory-list .item .magic-item-image:hover { - background-image: url(/icons/svg/d20-black.svg) !important; -} - .dnd5e.sheet.item .magic-items-content select.magic-item-charges-type { flex: 0.5; } @@ -248,10 +125,12 @@ border-color: #dad9d9; } -.dnd5e.sheet .items-list .item .item-name .item-image .fa-dice-d20 { +:not(.tidy5e).sheet .items-list .item .item-name .item-image .fa-dice-d20 { display: none; } +/* Original Tidy Styles */ + .tidy5e.sheet.actor .magic-items-head { display: none; } @@ -285,3 +164,103 @@ .tidy5e.sheet.actor .list-layout .items-list .item.magic-item.prepared { box-shadow: none !important; } + +.tidy5e.sheet.item .magic-items-content .item-detail select { + width: 100%; +} + +.tidy5e.sheet.item .magic-items-content .item-controls { + flex-basis: 2rem; + display: flex; + justify-content: center; +} + +.tidy5e.sheet.item .magic-items-content .spell-flat-and-dc, +.tidy5e.sheet.item .magic-items-content .spell-dc-head { + flex: 0 0 60px; +} + +/* This style intentionally overrides Tidy specificity to patch in the correct look. */ +.tidy5e.sheet.item + .sheet-body + .tab.magic-items + .magic-items-content + .magic-item-list + .spell-level-head.spell-level-head, +.tidy5e.sheet.item .sheet-body .tab.magic-items .magic-items-content .magic-item-list .spell-level.spell-level { + flex: 0 0 70px; +} + +.tidy5e.sheet.item .magic-items-content .items-header { + background: var(--t5e-faint-color); + box-shadow: 0 0 5px inset var(--t5e-light-color); + border-bottom: 1px solid var(--t5e-light-color); +} + +.tidy5e.sheet.item .magic-items-content h3.item-name { + margin: 0 -5px 0 0; + padding-left: 5px; + font-size: 13px; + font-weight: bold; + border: none; +} + +/* New Tidy Styles */ +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] .spell-rechargeable { + flex: 0 0 11.25rem; + text-align: center; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] .spell-level { + flex: 0 0 3.75rem; + text-align: center; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] .spell-consumption { + flex: 0 0 5rem; + text-align: center; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] .spell-upcast { + flex: 0 0 3.75rem; + text-align: center; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] .magic-items-spells-content .spell-slots > * { + flex: 0; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] + :is(.magic-items-spells-content, .magic-items-feats-content) + .magic-items-head + h3 { + font-size: 0.8125rem; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Actor"] + :is(.magic-items-spells-content, .magic-items-feats-content) + .magic-items-head { + margin: 0 0 0.5rem 0.5rem; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Item"] .magic-items-content .item-detail select { + width: 100%; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Item"] .magic-items-content .item-controls { + flex-basis: 2rem; + display: flex; + justify-content: center; +} + +[data-sheet-module="tidy5e-sheet"][data-document-name="Item"] .magic-items-content .spell-drag-content { + margin: 0.25rem; + height: 4.375rem; + text-align: center; + color: var(--t5e-primary-font-color); + color: var(--t5ek-primary-font-color); + line-height: 5; + border: 0.125rem dashed; + border-color: var(--t5e-primary-font-color); + border-color: var(--t5ek-primary-font-color); +} diff --git a/src/templates/magic-item-feat-sheet.html b/src/templates/magic-item-feat-sheet.html index dfb62e9..0422639 100644 --- a/src/templates/magic-item-feat-sheet.html +++ b/src/templates/magic-item-feat-sheet.html @@ -1,53 +1,60 @@ {{#if hasVisibleItems}}
-
- -
- {{#each items as |item i|}} - {{#if (and visible active hasFeats)}} -
  • -

    {{name}}

    -
    - - / - -
    -
    {{rechargeableLabel}}
    -
    {{localize "MAGICITEMS.SheetFeatOnUsage"}}
    -
  • -
      - - {{#each feats as |feat|}} -
    1. -
      -
      - -
      -

      {{feat.displayName}}

      -
      -
      {{feat.consumptionLabel}}
      -
    2. - {{/each}} - - {{#if hasTableAsFeats}} - {{#each tableAsFeats as |table|}} -
    3. -
      -
      -
      -

      {{table.displayName}}

      -
      -
      {{table.consumption}}
      -
    4. - {{/each}} - {{/if}} -
    - {{/if}} - {{/each}} +
    + +
    + {{#each items as |item i|}} {{#if (and visible active hasFeats)}} +
      +
    1. +

      {{name}}

      +
      + + / + +
      +
      {{rechargeableLabel}}
      +
      {{localize "MAGICITEMS.SheetFeatOnUsage"}}
      +
    2. +
        + + {{#each feats as |feat|}} +
      1. +
        +
        +

        {{feat.displayName}}

        +
        +
        {{feat.consumptionLabel}}
        +
      2. + {{/each}} + + {{#if hasTableAsFeats}} {{#each tableAsFeats as |table|}} +
      3. +
        +
        +

        {{table.displayName}}

        +
        +
        {{table.consumption}}
        +
      4. + {{/each}} {{/if}} +
      +
    + {{/if}} {{/each}}
    {{/if}} diff --git a/src/templates/magic-item-spell-sheet.html b/src/templates/magic-item-spell-sheet.html index 5a51661..70cc736 100644 --- a/src/templates/magic-item-spell-sheet.html +++ b/src/templates/magic-item-spell-sheet.html @@ -1,76 +1,85 @@ {{#if hasVisibleItems}}
    -
    - -
    - {{#each items as |item i|}} - {{#if (and visible active hasSpells)}} -
  • -

    {{name}}

    - {{#if chargesOnWholeItem}} -
    - - / - -
    -
    {{rechargeableLabel}}
    - {{/if}} -
    {{localize "MAGICITEMS.SheetLevel"}}
    -
    {{localize "MAGICITEMS.SheetConsumption"}}
    -
    {{localize "MAGICITEMS.SheetCanUpcast"}}
    -
  • -
      - {{#each spells as |spell|}} -
    1. -
      -
      - -
      -

      {{spell.displayName}}

      -
      - {{#if ../chargesPerSpell}} -
      - - / - -
      -
      {{../rechargeableLabel}}
      - {{/if}} -
      {{spell.level}}
      -
      {{spell.consumption}}
      -
      {{spell.canUpcastLabel}}
      -
    2. - {{/each}} - - {{#if hasTableAsSpells}} - {{#each tableAsSpells as |table|}} -
    3. -
      -
      -
      -

      {{table.displayName}}

      -
      - {{#if ../chargesPerSpell}} -
      - - / - -
      -
      {{../rechargeableLabel}}
      - {{/if}} -
      -
      -
      {{table.consumption}}
      -
      -
      -
    4. - {{/each}} - {{/if}} - -
    - {{/if}} - {{/each}} +
    + +
    + {{#each items as |item i|}} {{#if (and visible active hasSpells)}} +
      +
    1. +

      {{name}}

      + {{#if chargesOnWholeItem}} +
      + + / + +
      +
      {{rechargeableLabel}}
      + {{/if}} +
      {{localize "MAGICITEMS.SheetLevel"}}
      +
      {{localize "MAGICITEMS.SheetConsumption"}}
      +
      {{localize "MAGICITEMS.SheetCanUpcast"}}
      +
    2. +
        + {{#each spells as |spell|}} +
      1. +
        +
        +

        {{spell.displayName}}

        +
        + {{#if ../chargesPerSpell}} +
        + + / + +
        +
        {{../rechargeableLabel}}
        + {{/if}} +
        {{spell.level}}
        +
        {{spell.consumption}}
        +
        {{spell.canUpcastLabel}}
        +
      2. + {{/each}} {{#if hasTableAsSpells}} {{#each tableAsSpells as |table|}} +
      3. +
        +
        +

        {{table.displayName}}

        +
        + {{#if ../chargesPerSpell}} +
        + + / + +
        +
        {{../rechargeableLabel}}
        + {{/if}} +
        -
        +
        {{table.consumption}}
        +
        -
        +
      4. + {{/each}} {{/if}} +
      +
    + {{/if}} {{/each}}
    -{{/if}} \ No newline at end of file +{{/if}} diff --git a/src/templates/magic-item-tab.hbs b/src/templates/magic-item-tab.hbs index df14f91..0ca79ad 100644 --- a/src/templates/magic-item-tab.hbs +++ b/src/templates/magic-item-tab.hbs @@ -1,241 +1,280 @@
    +
    + + +
    +
    - - + + {{localize "MAGICITEMS.SheetActivationEquipped"}} + + {{localize "MAGICITEMS.SheetActivationAttuned"}} +
    -
    - - {{localize "MAGICITEMS.SheetActivationEquipped"}} - - {{localize "MAGICITEMS.SheetActivationAttuned"}} - + +
    + + +
    - -
    - - -
    + + + + +
    +
    + +
    +
    - - - +
    +
    + +
    + + - + {{#select rechargeUnit}} + {{#each rechargeUnits as |name key|}} + {{/each}} - {{/select}} + {{/select}} +
    -
    - - -
    -
    - - -
    -
    - -
    - - - -
    +
    + +
    + {{radioBoxes "flags.magicitems.sorting" sortingModes checked=sorting localize=true}} +
    - {{!--
    - -
    - {{radioBoxes "flags.magicitems.sorting" sortingModes checked=sorting localize=true}} -
    -
    --}} {{#if spells.length}} -
      -
    1. -
      -

      {{localize "MAGICITEMS.SheetSpell"}}

      -
      -
      {{localize "MAGICITEMS.SheetLevel"}}
      -
      {{localize "MAGICITEMS.SheetConsumption"}}
      -
      {{localize "MAGICITEMS.SheetUpcast"}}
      -
      {{localize "MAGICITEMS.SheetCost"}}
      -
      Save DC
      -
       
      +
        +
      1. +

        {{localize "MAGICITEMS.SheetSpell"}}

        +
        {{localize "MAGICITEMS.SheetLevel"}}
        +
        {{localize "MAGICITEMS.SheetConsumption"}}
        +
        {{localize "MAGICITEMS.SheetUpcast"}}
        +
        {{localize "MAGICITEMS.SheetCost"}}
        +
        Save DC
        +
         
        +
      2. +
          + {{#each spells as |item i|}} +
        1. +
          +
          +

          {{item.displayName}}

          + + + + + + +
          +
          + +
          +
          + +
          +
          + +
          +
          + +
          +
          + + +
          +
          + + + +
        2. - -
            - {{#each spells as |item i|}} -
          1. -
            -
            -

            {{item.displayName}}

            - - - - - - -
            - - - - - - - - -
            - - - -
            -
          2. - {{/each}} -
          + {{/each}}
        +
      {{/if}} {{#if feats.length}} -
        -
      1. -
        -

        {{localize "MAGICITEMS.SheetFeat"}}

        -
        -
        {{localize "MAGICITEMS.SheetEffect"}}
        -
        {{localize "MAGICITEMS.SheetConsumption"}}
        -
         
        +
          +
        1. +

          {{localize "MAGICITEMS.SheetFeat"}}

          +
          {{localize "MAGICITEMS.SheetEffect"}}
          +
          {{localize "MAGICITEMS.SheetConsumption"}}
          +
           
          +
        2. +
            + {{#each feats as |item i|}} +
          1. +
            +
            +

            {{item.displayName}}

            + + + + + +
            +
            + +
            +
            + +
            +
            + + + +
          2. - -
              - {{#each feats as |item i|}} -
            1. -
              -
              -

              {{item.displayName}}

              - - - - - -
              - - -
              - - - -
              -
            2. - {{/each}} -
            + {{/each}}
          +
        {{/if}} {{#if tables.length}} -
          -
        1. -
          -

          {{localize "MAGICITEMS.SheetTable"}}

          -
          -
          {{localize "MAGICITEMS.SheetUsage"}}
          -
          {{localize "MAGICITEMS.SheetConsumption"}}
          -
           
          +
            +
          1. +

            {{localize "MAGICITEMS.SheetTable"}}

            +
            {{localize "MAGICITEMS.SheetUsage"}}
            +
            {{localize "MAGICITEMS.SheetConsumption"}}
            +
             
            +
          2. +
              + {{#each tables as |item i|}} +
            1. +
              +
              {{{@root/rollIcon}}}
              +

              {{item.displayName}}

              + + + + + +
              +
              + +
              +
              + +
              +
              + + + +
            2. - -
                - {{#each tables as |item i|}} -
              1. -
                -
                -

                {{item.displayName}}

                - - - - - -
                - - -
                - - - -
                -
              2. - {{/each}} -
              + {{/each}}
            +
          {{/if}} - {{#each spellsGarbage as |index i|}} - - {{/each}} - - {{#each featsGarbage as |index i|}} - - {{/each}} - - {{#each tablesGarbage as |index i|}} - - {{/each}} - {{#if empty}} -
        2. -
          {{localize "MAGICITEMS.SheetDragContent"}}
          -
        3. +
        4. +
          {{localize "MAGICITEMS.SheetDragContent"}}
          +
        5. {{/if}} -
    -
    +
    +
    \ No newline at end of file