From 6198146c6cbcbdbf06ea496812e7e554b89f5d6c Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Fri, 19 Apr 2024 19:06:15 -0600 Subject: [PATCH 01/14] Make it so that Materials can only be in unknown location or your inventory --- module/documents/Item/Material.mjs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/module/documents/Item/Material.mjs b/module/documents/Item/Material.mjs index 15d2a58..c6c5b72 100644 --- a/module/documents/Item/Material.mjs +++ b/module/documents/Item/Material.mjs @@ -5,4 +5,11 @@ export class Material extends DotDungeonItem { let affects = game.settings.get(`dotdungeon`, `materialsAffectCapacity`); return affects ? super.usedCapacity : 0; }; + + get availableLocations() { + return [ + { value: null, label: `dotdungeon.location.unknown` }, + { value: `inventory`, label: `dotdungeon.location.inventory` }, + ]; + }; }; From af4f0f60a40285e572940313026135f5c87d06b7 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 20 Apr 2024 15:40:40 -0600 Subject: [PATCH 02/14] Add layout to the effects tab (closes #145) --- styles/v3/layouts/items/untyped/v2.scss | 5 +++++ templates/items/untyped/v2/tabs/effects.v2.untyped.hbs | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/styles/v3/layouts/items/untyped/v2.scss b/styles/v3/layouts/items/untyped/v2.scss index ce6300f..d94bf49 100644 --- a/styles/v3/layouts/items/untyped/v2.scss +++ b/styles/v3/layouts/items/untyped/v2.scss @@ -44,6 +44,7 @@ %flex-col { display: flex; flex-direction: column; + gap: 8px; } @include utils.tab("details") { @@ -56,6 +57,10 @@ } } + @include utils.tab("effects") { + @extend %flex-col; + } + @include utils.tab("settings") { @extend %flex-col; diff --git a/templates/items/untyped/v2/tabs/effects.v2.untyped.hbs b/templates/items/untyped/v2/tabs/effects.v2.untyped.hbs index 9206962..630bce2 100644 --- a/templates/items/untyped/v2/tabs/effects.v2.untyped.hbs +++ b/templates/items/untyped/v2/tabs/effects.v2.untyped.hbs @@ -1,3 +1,9 @@
- Effects Tab +
+ Am Effect, Hewwo +
+
From 002438398b4f98e09d8266c3a34434776d5b5c74 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 20 Apr 2024 16:52:37 -0600 Subject: [PATCH 03/14] Implement the image context menu functionality (closes #148) --- module/sheets/Items/UntypedItemSheet.mjs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/module/sheets/Items/UntypedItemSheet.mjs b/module/sheets/Items/UntypedItemSheet.mjs index a62b7dd..4c81cb5 100644 --- a/module/sheets/Items/UntypedItemSheet.mjs +++ b/module/sheets/Items/UntypedItemSheet.mjs @@ -29,15 +29,20 @@ export class UntypedItemSheet extends GenericItemSheet { new GenericContextMenu(html, `.photo.panel`, [ { name: `View Larger`, - callback: (html) => { - console.log(`.dungeon | View Larger`); + callback: () => { + (new ImagePopout(this.item.img)).render(true); }, }, { name: `Change Photo`, condition: () => this.isEditable, - callback: (html) => { - console.log(`.dungeon | Change Photo`); + callback: () => { + const fp = new FilePicker({ + callback: (path) => { + this.item.update({"img": path}); + }, + }); + fp.render(true); }, }, ]); From 6b80f3530d434e9feda65f3e765f30bb629878b1 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 20 Apr 2024 21:29:27 -0600 Subject: [PATCH 04/14] Localize the photo dropdown options --- langs/en-ca.2.json | 4 +++- module/sheets/Items/UntypedItemSheet.mjs | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json index c1d67b1..6eb38bc 100644 --- a/langs/en-ca.2.json +++ b/langs/en-ca.2.json @@ -81,9 +81,11 @@ "send-to-chat": "Send to Chat", "edit": "Edit", "delete": "Delete", + "reset": "Reset", "empty": "---", "help": "Help", - "gm": "Server" + "gm": "Server", + "view-larger": "View Larger" }, "sheet-names": { "*DataSheet": "Data Sheet" diff --git a/module/sheets/Items/UntypedItemSheet.mjs b/module/sheets/Items/UntypedItemSheet.mjs index 4c81cb5..1154842 100644 --- a/module/sheets/Items/UntypedItemSheet.mjs +++ b/module/sheets/Items/UntypedItemSheet.mjs @@ -1,5 +1,6 @@ import { GenericContextMenu } from "../../utils/GenericContextMenu.mjs"; import { GenericItemSheet } from "./GenericItemSheet.mjs"; +import { localizer } from "../../utils/localizer.mjs"; export class UntypedItemSheet extends GenericItemSheet { static get defaultOptions() { @@ -28,13 +29,13 @@ export class UntypedItemSheet extends GenericItemSheet { new GenericContextMenu(html, `.photo.panel`, [ { - name: `View Larger`, + name: localizer(`dotdungeon.common.view-larger`), callback: () => { (new ImagePopout(this.item.img)).render(true); }, }, { - name: `Change Photo`, + name: localizer(`dotdungeon.common.edit`), condition: () => this.isEditable, callback: () => { const fp = new FilePicker({ @@ -45,6 +46,13 @@ export class UntypedItemSheet extends GenericItemSheet { fp.render(true); }, }, + { + name: localizer(`dotdungeon.common.reset`), + condition: () => this.isEditable, + callback: () => { + console.log(`.dungeon | Reset Item Image`) + }, + } ]); if (!this.isEditable) return; From 8aff8b0fda77b6defc2ab47c3f08225122829643 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 20 Apr 2024 21:38:42 -0600 Subject: [PATCH 05/14] Remove icon preloading from the systems in favour of the async web component --- module/dotdungeon.mjs | 3 -- module/handlebars.mjs | 54 ------------------------ module/sheets/GenericActorSheet.mjs | 2 +- module/sheets/Items/GenericItemSheet.mjs | 2 +- 4 files changed, 2 insertions(+), 59 deletions(-) diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index 09f4d39..f49d120 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -110,9 +110,6 @@ Hooks.once(`init`, async () => { hbs.registerHandlebarsHelpers(); hbs.preloadHandlebarsTemplates(); registerCustomComponents(); - - CONFIG.CACHE ??= {}; - CONFIG.CACHE.icons = await hbs.preloadIcons(); }); diff --git a/module/handlebars.mjs b/module/handlebars.mjs index de34f62..cdaa1b7 100644 --- a/module/handlebars.mjs +++ b/module/handlebars.mjs @@ -43,25 +43,6 @@ export const preAliasedPartials = { "dotdungeon.pc.v2.foil": "actors/char-sheet/v2/partials/inventory/items/untyped.v2.pc.hbs", }; -export const icons = [ - `caret-right.svg`, - `caret-down.svg`, - `garbage-bin.svg`, - `chat-bubble.svg`, - `dice/d4.svg`, - `dice/d6.svg`, - `dice/d8.svg`, - `dice/d10.svg`, - `dice/d12.svg`, - `dice/d20.svg`, - `create.svg`, - `close.svg`, - `edit.svg`, - `sheet.svg`, - `minus.svg`, -]; - - export async function registerHandlebarsHelpers() { Handlebars.registerHelper(helpers); }; @@ -97,38 +78,3 @@ export async function preloadHandlebarsTemplates() { console.groupEnd(); return loadTemplates(paths); }; - -/** - * Loads all of the icons that are needed in the handlebars templating to make - * the sheet look nicer. - * - * @returns An object containing icon names to the corresponding HTML data for - * displaying the icon - */ -export async function preloadIcons() { - const pathPrefix = `systems/dotdungeon/assets/` - const parsedIcons = {}; - - for (const icon of icons) { - const iconName = icon.split(`/`).slice(-1)[0].slice(0, -4); - if (icon.endsWith(`.svg`)) { - try { - const response = await fetchWithTimeout(`${pathPrefix}${icon}`); - if (response.status !== 200) { continue }; - const svgData = await response.text(); - parsedIcons[iconName] = svgData; - } catch { - console.error(`.dungeon | Failed to fetch/parse icon: ${icon}`); - continue; - }; - } - else if (icon.endsWith(`.png`)) { - parsedIcons[iconName] = ``; - } - else { - console.warn(`.dungeon | Icon "${icon}" failed to be handled by a loader`) - }; - }; - - return parsedIcons; -}; diff --git a/module/sheets/GenericActorSheet.mjs b/module/sheets/GenericActorSheet.mjs index 50dcada..2010304 100644 --- a/module/sheets/GenericActorSheet.mjs +++ b/module/sheets/GenericActorSheet.mjs @@ -40,7 +40,7 @@ export class GenericActorSheet extends ActorSheet { ctx.actor = this.actor; ctx.config = DOTDUNGEON; - ctx.icons = CONFIG.CACHE.icons; + ctx.icons = {}; return ctx; }; diff --git a/module/sheets/Items/GenericItemSheet.mjs b/module/sheets/Items/GenericItemSheet.mjs index 883050e..31821e1 100644 --- a/module/sheets/Items/GenericItemSheet.mjs +++ b/module/sheets/Items/GenericItemSheet.mjs @@ -32,7 +32,7 @@ export class GenericItemSheet extends ItemSheet { ctx.flags = this.item.flags; ctx.config = DOTDUNGEON; - ctx.icons = CONFIG.CACHE.icons; + ctx.icons = {}; return ctx; }; From cfaed0d2306c5fb13dd1a9a34d4b003ccb459bec Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 20 Apr 2024 22:08:23 -0600 Subject: [PATCH 06/14] Add a generic ActiveEffect class + proxy --- .../ActiveEffect/GenericActiveEffect.mjs | 7 ++++ module/documents/ActiveEffect/_proxy.mjs | 42 +++++++++++++++++++ module/dotdungeon.mjs | 2 + 3 files changed, 51 insertions(+) create mode 100644 module/documents/ActiveEffect/GenericActiveEffect.mjs create mode 100644 module/documents/ActiveEffect/_proxy.mjs diff --git a/module/documents/ActiveEffect/GenericActiveEffect.mjs b/module/documents/ActiveEffect/GenericActiveEffect.mjs new file mode 100644 index 0000000..8ee70f3 --- /dev/null +++ b/module/documents/ActiveEffect/GenericActiveEffect.mjs @@ -0,0 +1,7 @@ +export class DotDungeonActiveEffect extends ActiveEffect { + + // Invert the logic of the disabled property so it's easier to modify via + // embedded controls + get enabled() { return !this.disabled }; + set enabled(newValue) { this.disabled = !newValue }; +}; diff --git a/module/documents/ActiveEffect/_proxy.mjs b/module/documents/ActiveEffect/_proxy.mjs new file mode 100644 index 0000000..4b51b54 --- /dev/null +++ b/module/documents/ActiveEffect/_proxy.mjs @@ -0,0 +1,42 @@ +import { DotDungeonActiveEffect } from "./GenericActiveEffect.mjs"; + +const classes = {}; + +const defaultClass = DotDungeonActiveEffect; + +export const ActiveEffectProxy = new Proxy(function () {}, { + construct(target, args) { + const [data] = args; + + if (!classes.hasOwnProperty(data.type)) { + return new defaultClass(...args); + } + + return new classes[data.type](...args); + }, + get(target, prop, receiver) { + + if (["create", "createDocuments"].includes(prop)) { + return function (data, options) { + if (data.constructor === Array) { + return data.map(i => ActiveEffectProxy.create(i, options)) + } + + if (!classes.hasOwnProperty(data.type)) { + return defaultClass.create(data, options); + } + + return classes[data.type].create(data, options); + }; + }; + + if (prop == Symbol.hasInstance) { + return function (instance) { + if (instance instanceof defaultClass) return true; + return Object.values(classes).some(i => instance instanceof i); + }; + }; + + return defaultClass[prop]; + }, +}); diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index f49d120..c76f259 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -10,6 +10,7 @@ import { SyncData } from "./models/Actor/Sync.mjs"; import { MobData } from "./models/Actor/Mob.mjs"; // Main Documents +import { ActiveEffectProxy } from "./documents/ActiveEffect/_proxy.mjs"; import { ActorProxy } from "./documents/Actor/_proxy.mjs"; import { ItemProxy } from "./documents/Item/_proxy.mjs"; @@ -55,6 +56,7 @@ Hooks.once(`init`, async () => { CONFIG.Item.dataModels.pet = PetItemData; CONFIG.Actor.documentClass = ActorProxy; CONFIG.Item.documentClass = ItemProxy; + CONFIG.ActiveEffect.documentClass = ActiveEffectProxy; CONFIG.DOTDUNGEON = DOTDUNGEON; From c466e0e53975b553d5ac23f9a3a708a1ca7326ec Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 20 Apr 2024 23:12:38 -0600 Subject: [PATCH 07/14] Some ActiveEffect shenanigans with training levels --- module/documents/Actor/Player.mjs | 11 ++++++++++- module/sheets/Actors/PC/PlayerSheetV2.mjs | 10 +++++++--- .../actors/char-sheet/v2/partials/stats.v2.pc.hbs | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/module/documents/Actor/Player.mjs b/module/documents/Actor/Player.mjs index ff54481..6140ecb 100644 --- a/module/documents/Actor/Player.mjs +++ b/module/documents/Actor/Player.mjs @@ -1,8 +1,17 @@ import { DotDungeonActor } from "./GenericActor.mjs"; -import { DotDungeonItem } from "../Item/GenericItem.mjs"; export class Player extends DotDungeonActor { + applyActiveEffects() { + super.applyActiveEffects(); + + // These are the (groups of) fields that ActiveEffects may modify safely + // and remain editable in the sheet. This needs to be done because of default + // Foundry behaviour that otherwise prevents these fields from being edited + delete this.overrides.system.stats; + delete this.overrides.system.skills; + }; + async createCustomPet() { const body = new URLSearchParams({ number: 1, diff --git a/module/sheets/Actors/PC/PlayerSheetV2.mjs b/module/sheets/Actors/PC/PlayerSheetV2.mjs index e39f7e3..142744b 100644 --- a/module/sheets/Actors/PC/PlayerSheetV2.mjs +++ b/module/sheets/Actors/PC/PlayerSheetV2.mjs @@ -74,6 +74,7 @@ export class PlayerSheetv2 extends GenericActorSheet { /** @type {ActorHandler} */ const actor = this.actor; + ctx.original = actor.toObject().system; ctx.system = actor.system; ctx.flags = actor.flags; ctx.items = this.actor.itemTypes; @@ -92,11 +93,13 @@ export class PlayerSheetv2 extends GenericActorSheet { get #statData() { const stats = []; - const usedDice = new Set(Object.values(this.actor.system.stats)); - for (const statName in this.actor.system.stats) { + const original = this.actor.toObject().system; + const usedDice = new Set(Object.values(original.stats)); + for (const statName in original.stats) { const stat = { key: statName, name: localizer(`dotdungeon.stat.${statName}`), + original: original.stats[statName], value: this.actor.system.stats[statName], }; @@ -111,7 +114,7 @@ export class PlayerSheetv2 extends GenericActorSheet { return { value: die, label: localizer(`dotdungeon.die.${die}`, { stat: statName }), - disabled: usedDice.has(die) && this.actor.system.stats[statName] !== die, + disabled: usedDice.has(die) && original.stats[statName] !== die, }; }) ]; @@ -127,6 +130,7 @@ export class PlayerSheetv2 extends GenericActorSheet { key: skill, name: game.i18n.format(`dotdungeon.skills.${skill}`), value, + original: original.skills[statName][skill], formula: `1` + stat.value + modifierToString(value, { spaces: true }), rollDisabled: value === -1, }); diff --git a/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs b/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs index 5a5a5f1..6353120 100644 --- a/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs +++ b/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs @@ -7,7 +7,7 @@ name="system.stats.{{stat.key}}" class="e-2dp dice-select" > - {{{dd-options stat.value stat.dieOptions}}} + {{{dd-options stat.original stat.dieOptions}}} From 2c2c4cc83f35a14043d7b1efe7f7999a7e717d62 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 23 Apr 2024 23:19:23 -0600 Subject: [PATCH 11/14] Make the AE deletion context menu option actually work --- langs/en-ca.2.json | 6 ++++++ module/sheets/Items/UntypedItemSheet.mjs | 26 ++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json index cc81d0a..dce0efa 100644 --- a/langs/en-ca.2.json +++ b/langs/en-ca.2.json @@ -95,6 +95,12 @@ "title": "What is Calculated Capacity?", "content": "

The calculated capacity is how much space in your inventory that the item will take up, the way it is calculated is determined by the item. Usually the main thing that affects the capacity is the item's quantity, but this can be turned off by the @dotdungeon.common.gm, which means that no matter the quantity it will only use up one capacity. The @dotdungeon.common.gm can also entirely disable capacity usage which will make the used capacity always be zero.

" } + }, + "delete": { + "ActiveEffect": { + "title": "Delete Effect", + "content": "

Are you sure you would like to delete the active effect: {name}

" + } } }, "TYPES": { diff --git a/module/sheets/Items/UntypedItemSheet.mjs b/module/sheets/Items/UntypedItemSheet.mjs index 99f6862..5ae0f24 100644 --- a/module/sheets/Items/UntypedItemSheet.mjs +++ b/module/sheets/Items/UntypedItemSheet.mjs @@ -1,4 +1,5 @@ import { GenericContextMenu } from "../../utils/GenericContextMenu.mjs"; +import { DialogManager } from "../../utils/DialogManager.mjs"; import { GenericItemSheet } from "./GenericItemSheet.mjs"; import { localizer } from "../../utils/localizer.mjs"; @@ -75,8 +76,29 @@ export class UntypedItemSheet extends GenericItemSheet { }, { name: localizer(`dotdungeon.common.delete`), - callback: async () => { - (await fromUuid(html.closest(`.effect`)[0].dataset.embeddedId))?.delete(true); + callback: async (html) => { + const target = html.closest(`.effect`)[0]; + const data = target.dataset; + const id = data.embeddedId; + const doc = await fromUuid(id); + DialogManager.createOrFocus( + `${doc.uuid}-delete`, + { + title: localizer(`dotdungeon.delete.ActiveEffect.title`, doc), + content: localizer(`dotdungeon.delete.ActiveEffect.content`, doc), + buttons: { + yes: { + label: localizer(`Yes`), + callback() { + doc.delete(); + }, + }, + no: { + label: localizer(`No`), + } + } + } + ); }, } ]); From e2579e12f881d95781899333dca38398a98b71c2 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Tue, 23 Apr 2024 23:19:59 -0600 Subject: [PATCH 12/14] Ignore all ref files in the root rather than just the foundry.js file --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4c1e822..4beb05c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ references/ /.*/ !/.vscode/ !/.github/ -/foundry.js +/*.ref.* *.lock *.zip From f8364888f2238fce5a971d1accdb9d0166df4691 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 27 Apr 2024 00:37:30 -0600 Subject: [PATCH 13/14] Fix issues with the localizer's replacement to be more reliable --- module/utils/localizer.mjs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/module/utils/localizer.mjs b/module/utils/localizer.mjs index 550c1cb..7cfebb0 100644 --- a/module/utils/localizer.mjs +++ b/module/utils/localizer.mjs @@ -18,12 +18,20 @@ export function localizer(key, args = {}, depth = 0) { return localized; }; + /* + Helps prevent recursion on the same key so that we aren't doing excess work. + */ + const localizedSubkeys = new Map(); for (const match of subkeys) { const subkey = match.groups.key; - localized = - localized.slice(0, match.index) - + localizer(subkey, args, depth + 1) - + localized.slice(match.index + subkey.length + 1) + if (localizedSubkeys.has(subkey)) continue; + localizedSubkeys.set(subkey, localizer(subkey, args, depth + 1)); }; - return localized; + + return localized.replace( + localizerConfig.subKeyPattern, + (_fullMatch, subkey) => { + return localizedSubkeys.get(subkey); + } + ); }; From cf13b986d4ceeca7a17bc882f04d88c1da75e239 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sat, 27 Apr 2024 00:37:49 -0600 Subject: [PATCH 14/14] Change how embedded ActiveEffects are created --- module/sheets/Actors/PC/PlayerSheetV2.mjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/module/sheets/Actors/PC/PlayerSheetV2.mjs b/module/sheets/Actors/PC/PlayerSheetV2.mjs index 0968a6e..ac1a378 100644 --- a/module/sheets/Actors/PC/PlayerSheetV2.mjs +++ b/module/sheets/Actors/PC/PlayerSheetV2.mjs @@ -39,9 +39,8 @@ export class PlayerSheetv2 extends GenericActorSheet { html.find(`.create-ae`).on(`click`, async ($e) => { console.debug(`Creating an ActiveEffect?`); - ActiveEffect.implementation.create({ - name: "Default AE", - }, { parent: this.actor, renderSheet: true }); + const ae = this.actor.createEmbeddedDocuments(`ActiveEffect`, [{name: "Default AE"}]); + ae.sheet.render(true); }); html.find(`[data-filter-toggle]`).on(`change`, ($e) => { const target = $e.delegateTarget;