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 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(); }; }; 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( diff --git a/module/data/Item/Armour.mjs b/module/data/Item/Armour.mjs index 726c929..f2a1b0a 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,23 @@ export class ArmourData extends CommonItemData { }), }; }; - - // MARK: Base Data - prepareBaseData() { - super.prepareBaseData(); - }; - - // MARK: Derived Data - prepareDerivedData() { - super.prepareDerivedData(); - }; + // #endregion Schema // #region Lifecycle + 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}?`, + }); + 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 +73,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 +81,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 +110,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..420149d 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,39 +43,69 @@ export class WeaponData extends CommonItemData { }), }; }; - - // MARK: Base Data - prepareBaseData() { - super.prepareBaseData(); - }; - - // MARK: Derived Data - prepareDerivedData() { - super.prepareDerivedData(); - }; + // #endregion Schema // #region Lifecycle + 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}?`, + }); + 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 }; + + 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; }; - // #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 +116,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 +257,5 @@ export class WeaponData extends CommonItemData { return fields; }; - // #endregion + // #endregion Sheet Data };