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 diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json index c1d67b1..dce0efa 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" @@ -93,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": { @@ -113,6 +121,9 @@ "legendaryItem": "Legendary Item", "spell": "Spell", "untyped": "Custom" + }, + "ActiveEffect": { + "base": "Effect" } } } \ No newline at end of file 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/documents/Actor/GenericActor.mjs b/module/documents/Actor/GenericActor.mjs index d52b185..171af63 100644 --- a/module/documents/Actor/GenericActor.mjs +++ b/module/documents/Actor/GenericActor.mjs @@ -1,4 +1,15 @@ export class DotDungeonActor extends Actor { + + /* + Using this to take a "snapshot" of the system data prior to applying AE's so + that the inputs can still have the non-modified value in them, while we still + provide all that data to AE's without needing to disable any inputs. + */ + prepareEmbeddedDocuments() { + this.preAE = foundry.utils.deepClone(this.system); + super.prepareEmbeddedDocuments(); + }; + async createEmbeddedItem(defaults, opts = {}) { let items = await this.createEmbeddedDocuments(`Item`, defaults); if (!Array.isArray(items)) items = items ? [items] : []; diff --git a/module/documents/Actor/Player.mjs b/module/documents/Actor/Player.mjs index ff54481..2c5840d 100644 --- a/module/documents/Actor/Player.mjs +++ b/module/documents/Actor/Player.mjs @@ -1,8 +1,22 @@ 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. + The deletes must use optional chaining otherwise they can cause issues + during the document preparation lifecycle as an actor with no AE's affecting + anything in one of these areas will result in these paths being undefined. + */ + delete this.overrides.system?.stats; + delete this.overrides.system?.skills; + }; + async createCustomPet() { const body = new URLSearchParams({ number: 1, 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` }, + ]; + }; }; diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index daa0530..8731314 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"; @@ -56,6 +57,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; @@ -111,9 +113,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] = `