From 4ccfc03e593112f4f253ef51eb6917c5de15d931 Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Sun, 16 Feb 2025 11:41:32 -0700 Subject: [PATCH] RC-110 | Prevent Armour/Shield Equipping based on slots --- langs/en-ca.json | 2 +- module/Apps/ItemSheets/AllItemSheetV1.mjs | 21 +++++++ .../data/Item/{Protector.mjs => Armour.mjs} | 61 +++++++++++++++---- module/data/Item/Shield.mjs | 19 ++++++ module/documents/item.mjs | 2 +- module/hooks/init.mjs | 7 ++- 6 files changed, 94 insertions(+), 18 deletions(-) rename module/data/Item/{Protector.mjs => Armour.mjs} (58%) create mode 100644 module/data/Item/Shield.mjs diff --git a/langs/en-ca.json b/langs/en-ca.json index 737906c..61a77e6 100644 --- a/langs/en-ca.json +++ b/langs/en-ca.json @@ -135,7 +135,7 @@ }, "notifs": { "error": { - "cannot-equip-not-embedded": "Cannot equip an {itemType} when it isn't within an Actor", + "cannot-equip": "Cannot equip the {itemType}, see console for more details.", "invalid-delta": "The delta for \"{name}\" is not a number, cannot finish processing the action." }, "warn": { diff --git a/module/Apps/ItemSheets/AllItemSheetV1.mjs b/module/Apps/ItemSheets/AllItemSheetV1.mjs index 0744e57..3ac26ba 100644 --- a/module/Apps/ItemSheets/AllItemSheetV1.mjs +++ b/module/Apps/ItemSheets/AllItemSheetV1.mjs @@ -43,6 +43,27 @@ export class AllItemSheetV1 extends GenericAppMixin(HandlebarsApplicationMixin(I Logger.debug(`Context:`, ctx); return ctx; }; + + async _onRender() { + // remove the flag if it exists when we render the sheet + delete this.document?.system?.forceRerender; + }; + + /** + * Used to make it so that items that don't get updated because of the + * _preUpdate hook removing/changing the data submitted, can still get + * re-rendered when the diff is empty. If the document does get updated, + * this rerendering does not happen. + * + * @override + */ + async _processSubmitData(...args) { + await super._processSubmitData(...args); + + if (this.document.system.forceRerender) { + await this.render(false); + }; + }; // #endregion // #region Actions diff --git a/module/data/Item/Protector.mjs b/module/data/Item/Armour.mjs similarity index 58% rename from module/data/Item/Protector.mjs rename to module/data/Item/Armour.mjs index 871c79e..34d964d 100644 --- a/module/data/Item/Protector.mjs +++ b/module/data/Item/Armour.mjs @@ -1,13 +1,14 @@ import { CommonItemData } from "./Common.mjs"; import { gameTerms } from "../../gameTerms.mjs"; import { localizer } from "../../utils/Localizer.mjs"; +import { Logger } from "../../utils/Logger.mjs"; import { requiredInteger } from "../helpers.mjs"; const { fields } = foundry.data; -const { hasProperty, mergeObject } = foundry.utils; +const { hasProperty, diffObject, mergeObject } = foundry.utils; /** Used for Armour and Shields */ -export class ProtectorData extends CommonItemData { +export class ArmourData extends CommonItemData { // MARK: Schema static defineSchema() { return { @@ -45,22 +46,56 @@ export class ProtectorData extends CommonItemData { // #region Lifecycle async _preUpdate(changes, options, user) { + // return false if (options.force && game.settings.get(`ripcrypt`, `devMode`)) { return }; - let valid = super._preUpdate(changes, options, user); - if (hasProperty(changes, `system.equipped`) && !this.parent.isEmbedded) { - ui.notifications.error(localizer( - `RipCrypt.notifs.error.cannot-equip-not-embedded`, - { itemType: `@TYPES.Item.${this.parent.type}` }, - )); - mergeObject( - changes, - { "-=system.equipped": null }, - { inplace: true, performDeletions: true }, + // Ensure changes is a diffed object + const diff = diffObject(this.parent._source, changes); + let valid = await super._preUpdate(changes, options, user); + + if (hasProperty(diff, `system.equipped`) && !this._canEquip()) { + ui.notifications.error( + localizer( + `RipCrypt.notifs.error.cannot-equip`, + { itemType: `@TYPES.Item.${this.parent.type}` }, + ), + { console: false }, ); + + // Don't stop the update, but don't allow changing the equipped status + mergeObject(changes, { + "system.equipped": false, + }); + + // Set a flag so that we can tell the sheet that it needs to rerender + this.forceRerender = true; + }; + + return valid; + }; + + /** Used to tell the preUpdate logic whether or not to prevent the */ + _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; }; - return valid; + + if (this.location.size === 0) { + Logger.error(`Unable to equip an item without any locations`); + return false; + }; + + const slots = parent.parent.system.equippedArmour ?? {}; + Logger.debug(`slots`, slots); + for (const locationTag of this.location) { + if (slots[locationTag.toLowerCase()] != null) { + Logger.error(`Unable to equip multiple items in the same slot`); + return false; + }; + }; + return true; }; // #endregion diff --git a/module/data/Item/Shield.mjs b/module/data/Item/Shield.mjs new file mode 100644 index 0000000..aed37b7 --- /dev/null +++ b/module/data/Item/Shield.mjs @@ -0,0 +1,19 @@ +import { ArmourData } from "./Armour.mjs"; +import { Logger } from "../../utils/Logger.mjs"; + +export class ShieldData extends ArmourData { + _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 shield = parent.parent.system.equippedShield; + if (shield) { + Logger.error(`Unable to equip multiple shields`); + return false; + }; + return true; + }; +}; diff --git a/module/documents/item.mjs b/module/documents/item.mjs index db7c968..ac1d188 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -2,7 +2,7 @@ export class RipCryptItem extends Item { get quantifiedName() { if (this.system.quantity != null && this.system.quantity !== 1) { return `${this.name} (${this.system.quantity})`; - } + }; return this.name; }; }; diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs index 35c434d..812c6e2 100644 --- a/module/hooks/init.mjs +++ b/module/hooks/init.mjs @@ -8,10 +8,11 @@ import { RipCryptCombatTracker } from "../Apps/sidebar/CombatTracker.mjs"; // Data Models import { AmmoData } from "../data/Item/Ammo.mjs"; +import { ArmourData } from "../data/Item/Armour.mjs"; import { CraftData } from "../data/Item/Craft.mjs"; import { GoodData } from "../data/Item/Good.mjs"; import { HeroData } from "../data/Actor/Hero.mjs"; -import { ProtectorData } from "../data/Item/Protector.mjs"; +import { ShieldData } from "../data/Item/Shield.mjs"; import { SkillData } from "../data/Item/Skill.mjs"; import { WeaponData } from "../data/Item/Weapon.mjs"; @@ -49,10 +50,10 @@ Hooks.once(`init`, () => { // #region Datamodels CONFIG.Actor.dataModels.hero = HeroData; CONFIG.Item.dataModels.ammo = AmmoData, - CONFIG.Item.dataModels.armour = ProtectorData; + CONFIG.Item.dataModels.armour = ArmourData; CONFIG.Item.dataModels.craft = CraftData; CONFIG.Item.dataModels.good = GoodData; - CONFIG.Item.dataModels.shield = ProtectorData; + CONFIG.Item.dataModels.shield = ShieldData; CONFIG.Item.dataModels.skill = SkillData; CONFIG.Item.dataModels.weapon = WeaponData; // #endregion