From 98b429f941ee725fa9fca1a711ff34a9e124c032 Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sun, 5 Oct 2025 13:31:31 -0600 Subject: [PATCH 1/6] Finish making the intellisense work properly --- augments.d.ts | 14 ++++++++++++++ eslint.config.mjs | 2 +- jsconfig.json | 22 +++++++++++++++------- 3 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 augments.d.ts diff --git a/augments.d.ts b/augments.d.ts new file mode 100644 index 0000000..dc091c2 --- /dev/null +++ b/augments.d.ts @@ -0,0 +1,14 @@ +declare global { + class Hooks extends foundry.helpers.Hooks {}; + const fromUuid = foundry.utils.fromUuid; +} + +interface Actor { + /** The system-specific data */ + system: any; +}; + +interface Item { + /** The system-specific data */ + system: any; +}; \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index a0d500d..c18baac 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,7 +4,7 @@ import stylistic from "@stylistic/eslint-plugin"; export default [ // Tell eslint to ignore files that I don't mind being formatted slightly differently - { ignores: [ `scripts/` ] }, + { ignores: [ `scripts/`, `foundry/` ] }, { languageOptions: { globals: globals.browser, diff --git a/jsconfig.json b/jsconfig.json index 8b0d1fc..47b5b55 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,11 +1,19 @@ { "compilerOptions": { - "module": "ES2020", - "target": "ES2020" + "module": "es2022", + "target": "es2022", + "types": [ + "./augments.d.ts" + ], + "paths": { + "@client/*": ["./foundry/client/*"], + "@common/*": ["./foundry/common/*"], + } }, - "exclude": ["node_modules", "**/node_modules/*"], - "include": ["module/**/*", "foundry.v13.link/client/**/*.js", "foundry.v13.link/**/*.mjs"], - "typeAcquisition": { - "include": ["jquery"] - } + "include": [ + "module/**/*", + "foundry/client/client.mjs", + "foundry/client/global.d.mts", + "foundry/common/primitives/global.d.mts" + ] } \ No newline at end of file From 2497c492bfcb6e20ca05d78e673ba3610c6d7249 Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sun, 5 Oct 2025 15:24:11 -0600 Subject: [PATCH 2/6] Update the way that the force rerender calls the render method --- module/Apps/ItemSheets/AllItemSheetV1.mjs | 2 +- module/Apps/ItemSheets/ArmourSheet.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/module/Apps/ItemSheets/AllItemSheetV1.mjs b/module/Apps/ItemSheets/AllItemSheetV1.mjs index 3ac26ba..0608fde 100644 --- a/module/Apps/ItemSheets/AllItemSheetV1.mjs +++ b/module/Apps/ItemSheets/AllItemSheetV1.mjs @@ -61,7 +61,7 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I await super._processSubmitData(...args); if (this.document.system.forceRerender) { - await this.render(false); + await this.render(); }; }; // #endregion diff --git a/module/Apps/ItemSheets/ArmourSheet.mjs b/module/Apps/ItemSheets/ArmourSheet.mjs index 9201e16..7570413 100644 --- a/module/Apps/ItemSheets/ArmourSheet.mjs +++ b/module/Apps/ItemSheets/ArmourSheet.mjs @@ -55,7 +55,7 @@ export class ArmourSheet extends GenericAppMixin(HandlebarsApplicationMixin(Item await super._processSubmitData(...args); if (this.document.system.forceRerender) { - await this.render(false); + await this.render(); }; }; From dfc896429662e91d41e26c2b64d70ee0fb0f5538 Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sun, 5 Oct 2025 15:24:37 -0600 Subject: [PATCH 3/6] Add a getter for retrieving equipped weapons easily --- module/data/Actor/Entity.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/module/data/Actor/Entity.mjs b/module/data/Actor/Entity.mjs index 7a424d9..89f2b7f 100644 --- a/module/data/Actor/Entity.mjs +++ b/module/data/Actor/Entity.mjs @@ -155,6 +155,11 @@ export class EntityData extends foundry.abstract.TypeDataModel { }; // #region Getters + get equippedWeapons() { + const weapons = this.parent.itemTypes.weapon; + return weapons.filter(w => w.system.equipped); + }; + get equippedArmour() { const armours = this.parent.itemTypes.armour; const slots = Object.fromEntries( From f29ab8bdaab286c635793f901f73819b000d1d34 Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sun, 5 Oct 2025 15:25:23 -0600 Subject: [PATCH 4/6] Add a prompt for equipping an item on drag and drop --- module/data/Item/Armour.mjs | 39 ++++++++++++------------ module/data/Item/Weapon.mjs | 61 +++++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 35 deletions(-) diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 726c929..a8ead74 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -4,12 +4,13 @@ import { localizer } from "../../utils/Localizer.mjs"; import { Logger } from "../../utils/Logger.mjs"; import { requiredInteger } from "../helpers.mjs"; +const { diffObject, getProperty, setProperty } = foundry.utils; +const { DialogV2 } = foundry.applications.api; const { fields } = foundry.data; -const { getProperty, diffObject, mergeObject } = foundry.utils; /** Used for Armour and Shields */ export class ArmourData extends CommonItemData { - // MARK: Schema + // #region Schema static defineSchema() { return { ...super.defineSchema(), @@ -39,20 +40,22 @@ export class ArmourData extends CommonItemData { }), }; }; - - // MARK: Base Data - prepareBaseData() { - super.prepareBaseData(); - }; - - // MARK: Derived Data - prepareDerivedData() { - super.prepareDerivedData(); - }; + // #endregion Schema // #region Lifecycle + async _preCreate(item) { + if (this.parent.isEmbedded && this._canEquip()) { + const shouldEquip = await DialogV2.confirm({ + window: { title: `Equip Item?` }, + content: `Do you want to equip ${item.name}?`, + }); + if (shouldEquip) { + this.updateSource({ "equipped": true }); + }; + }; + }; + async _preUpdate(changes, options, user) { - // return false if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; // Ensure changes is a diffed object @@ -69,9 +72,7 @@ export class ArmourData extends CommonItemData { ); // Don't stop the update, but don't allow changing the equipped status - mergeObject(changes, { - "system.equipped": false, - }); + setProperty(changes, `system.equipped`, false); // Set a flag so that we can tell the sheet that it needs to rerender this.forceRerender = true; @@ -79,7 +80,9 @@ export class ArmourData extends CommonItemData { return valid; }; + // #endregion Lifecycle + // #region Helpers /** * Used to tell the preUpdate logic whether or not to prevent the item from * being equipped or not. @@ -106,11 +109,9 @@ export class ArmourData extends CommonItemData { }; return true; }; - // #endregion - // #region Getters get locationString() { return [...this.location].join(`, `); }; - // #endregion + // #endregion Helpers }; diff --git a/module/data/Item/Weapon.mjs b/module/data/Item/Weapon.mjs index a4b77ff..8d50f78 100644 --- a/module/data/Item/Weapon.mjs +++ b/module/data/Item/Weapon.mjs @@ -2,12 +2,14 @@ import { barAttribute, optionalInteger, requiredInteger } from "../helpers.mjs"; import { CommonItemData } from "./Common.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { localizer } from "../../utils/Localizer.mjs"; +import { Logger } from "../../utils/Logger.mjs"; +const { diffObject, getProperty, setProperty } = foundry.utils; +const { DialogV2 } = foundry.applications.api; const { fields } = foundry.data; -const { hasProperty, mergeObject } = foundry.utils; export class WeaponData extends CommonItemData { - // MARK: Schema + // #region Schema static defineSchema() { return { ...super.defineSchema(), @@ -41,18 +43,28 @@ export class WeaponData extends CommonItemData { }), }; }; - - // MARK: Base Data - prepareBaseData() { - super.prepareBaseData(); - }; - - // MARK: Derived Data - prepareDerivedData() { - super.prepareDerivedData(); - }; + // #endregion Schema // #region Lifecycle + async _preCreate(item) { + if (this.parent.isEmbedded && this._canEquip()) { + const shouldEquip = await DialogV2.confirm({ + window: { title: `Equip Item?` }, + content: `Do you want to equip ${item.name}?`, + }); + if (shouldEquip) { + this.updateSource({ "equipped": true }); + }; + }; + }; + + /** + * + * @param {*} changes The expanded object that was used for the update + * @param {*} options + * @param {*} user + * @returns + */ async _preUpdate(changes, options, user) { if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; let valid = super._preUpdate(changes, options, user); @@ -71,9 +83,26 @@ export class WeaponData extends CommonItemData { }; return valid; }; - // #endregion + // #endregion Lifecycle + + // #region Helpers + /** + * Used to tell the preUpdate logic whether or not to prevent the item from + * being equipped or not. + */ + _canEquip() { + const parent = this.parent; + if (!parent.isEmbedded || !(parent.parent instanceof Actor)) { + Logger.error(`Unable to equip item when it's not embedded`); + return false; + }; + const actor = this.parent.parent.system; + if (actor.equippedWeapons?.length >= actor.limit.weapons) { + return false; + }; + return true; + }; - // #region Getters get traitString() { return [...this.traits].join(`, `); }; @@ -84,7 +113,7 @@ export class WeaponData extends CommonItemData { }; return String(this.range.short ?? this.range.long ?? ``); }; - // #endregion + // #endregion Helpers // #region Sheet Data async getFormFields(_ctx) { @@ -225,5 +254,5 @@ export class WeaponData extends CommonItemData { return fields; }; - // #endregion + // #endregion Sheet Data }; From 08278655dcb125725354403d617f7799293f7219 Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sun, 5 Oct 2025 15:25:52 -0600 Subject: [PATCH 5/6] Update the preUpdate handling to not block the entire update, bringing it more inline with Armour/Shield --- module/data/Item/Weapon.mjs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/module/data/Item/Weapon.mjs b/module/data/Item/Weapon.mjs index 8d50f78..3fa1e68 100644 --- a/module/data/Item/Weapon.mjs +++ b/module/data/Item/Weapon.mjs @@ -67,19 +67,21 @@ export class WeaponData extends CommonItemData { */ async _preUpdate(changes, options, user) { if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; + + const diff = diffObject(this.parent._source, changes); let valid = super._preUpdate(changes, options, user); - if (hasProperty(changes, `system.equipped`) && !this.parent.isEmbedded) { + if (getProperty(diff, `system.equipped`) && !this._canEquip()) { ui.notifications.error(localizer( - `RipCrypt.notifs.error.cannot-equip-not-embedded`, + `RipCrypt.notifs.error.cannot-equip`, { itemType: `@TYPES.Item.${this.parent.type}` }, )); - mergeObject( - changes, - { "-=system.equipped": null }, - { inplace: true, performDeletions: true }, - ); - return false; + + // Don't stop the update, but don't allow changing the equipped status + setProperty(changes, `system.equipped`, false); + + // Set a flag so that we can tell the sheet that it needs to rerender + this.forceRerender = true; }; return valid; }; From cc61a0c3acee5329f75422ace288bb43219abd8f Mon Sep 17 00:00:00 2001 From: Eldritch-Oliver Date: Sun, 5 Oct 2025 15:48:15 -0600 Subject: [PATCH 6/6] Add a way to skip the equip prompt for API-based creation when it's not desired --- module/data/Item/Armour.mjs | 5 +++-- module/data/Item/Weapon.mjs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index a8ead74..f2a1b0a 100644 --- a/module/data/Item/Armour.mjs +++ b/module/data/Item/Armour.mjs @@ -43,8 +43,9 @@ export class ArmourData extends CommonItemData { // #endregion Schema // #region Lifecycle - async _preCreate(item) { - if (this.parent.isEmbedded && this._canEquip()) { + async _preCreate(item, options) { + const showEquipPrompt = options.showEquipPrompt ?? true; + if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) { const shouldEquip = await DialogV2.confirm({ window: { title: `Equip Item?` }, content: `Do you want to equip ${item.name}?`, diff --git a/module/data/Item/Weapon.mjs b/module/data/Item/Weapon.mjs index 3fa1e68..420149d 100644 --- a/module/data/Item/Weapon.mjs +++ b/module/data/Item/Weapon.mjs @@ -46,8 +46,9 @@ export class WeaponData extends CommonItemData { // #endregion Schema // #region Lifecycle - async _preCreate(item) { - if (this.parent.isEmbedded && this._canEquip()) { + async _preCreate(item, options) { + const showEquipPrompt = options.showEquipPrompt ?? true; + if (showEquipPrompt && this.parent.isEmbedded && this._canEquip()) { const shouldEquip = await DialogV2.confirm({ window: { title: `Equip Item?` }, content: `Do you want to equip ${item.name}?`,