diff --git a/lang/en.json b/lang/en.json index 190ded4673..fc2e7a66af 100644 --- a/lang/en.json +++ b/lang/en.json @@ -134,6 +134,7 @@ "DND5E.AdvancementItemGrantRecursiveWarning": "You cannot grant an item in its own advancement.", "DND5E.AdvancementItemGrantOptional": "Optional", "DND5E.AdvancementItemGrantOptionalHint": "If optional, players will be given the option to opt out of any of the following items, otherwise all of them are granted.", +"DND5E.AdvancementItemTypeInvalidWarning": "{type} items cannot be added with this advancement type.", "DND5E.AdvancementLevelHeader": "Level {level}", "DND5E.AdvancementLevelAnyHeader": "Any Level", "DND5E.AdvancementLevelNoneHeader": "No Level", diff --git a/module/advancement/types/item-choice.mjs b/module/advancement/types/item-choice.mjs index a1be348712..d160961957 100644 --- a/module/advancement/types/item-choice.mjs +++ b/module/advancement/types/item-choice.mjs @@ -40,9 +40,9 @@ export class ItemChoiceAdvancement extends Advancement { /** * The item types that are supported in Item Choice. This order will be how they are displayed * in the configuration interface. - * @enum {object} + * @type {Set} */ - static VALID_TYPES = ["feat", "spell", "consumable", "backpack", "equipment", "loot", "tool", "weapon"]; + static VALID_TYPES = new Set(["feat", "spell", "consumable", "backpack", "equipment", "loot", "tool", "weapon"]); /* -------------------------------------------- */ /* Instance Properties */ @@ -145,6 +145,44 @@ export class ItemChoiceAdvancement extends Advancement { return { items }; } + /* -------------------------------------------- */ + + /** + * Verify that the provided item can be used with this advancement based on the configuration. + * @param {Item5e} item Item that needs to be tested. + * @param {object} options + * @param {boolean} [options.warn=true] Display UI notifications with warning messages. + * @returns {boolean} Can this item be used? + */ + _validateItemType(item, { warn=true }={}) { + // Type restriction is set and the item type does not match the selected type + const restriction = this.data.configuration.type; + if ( restriction && (restriction !== item.type) ) { + const type = game.i18n.localize(`ITEM.Type${restriction.capitalize()}`); + if ( warn ) ui.notifications.warn(game.i18n.format("DND5E.AdvancementItemChoiceTypeWarning", { type })); + return false; + } + + // Item is not one of the valid types + if ( !this.constructor.VALID_TYPES.has(item.type) ) { + const type = game.i18n.localize(`ITEM.Type${item.type.capitalize()}`); + if ( warn ) ui.notifications.warn(game.i18n.format("DND5E.AdvancementItemTypeInvalidWarning", { type })); + return false; + } + + // If spell level is restricted, ensure the spell is of the appropriate level + const l = parseInt(this.data.configuration.spell?.level); + if ( (restriction === "spell") && Number.isNumeric(l) && (item.system.level !== l) ) { + if ( warn ) ui.notifications.warn(game.i18n.format( + "DND5E.AdvancementItemChoiceSpellLevelSpecificWarning", { level: CONFIG.DND5E.spellLevels[l] } + )); + return false; + } + + // Everything's great! + return true; + } + } @@ -189,13 +227,8 @@ export class ItemChoiceConfig extends AdvancementConfig { /** @inheritdoc */ _verifyDroppedItem(event, item) { - const type = this.advancement.data.configuration.type; - if ( !type || (type === item.type) ) return true; - const typeName = game.i18n.localize(`ITEM.Type${type.capitalize()}`); - ui.notifications.warn(game.i18n.format("DND5E.AdvancementItemChoiceTypeWarning", { type: typeName })); - return false; + return this.advancement._validateItemType(item); } - } @@ -341,12 +374,8 @@ export class ItemChoiceFlow extends AdvancementFlow { if ( data.type !== "Item" ) return false; const item = await Item.implementation.fromDropData(data); - // If there is a type restriction, verify it against the dropped type - const type = this.advancement.data.configuration.type; - if ( type && (type !== item.type) ) { - const typeName = game.i18n.localize(`ITEM.Type${type.capitalize()}`); - return ui.notifications.warn(game.i18n.format("DND5E.AdvancementItemChoiceTypeWarning", { type: typeName })); - } + const valid = this.advancement._validateItemType(item); + if ( !valid ) return; // If the item is already been marked as selected, no need to go further if ( this.selected.has(item.uuid) ) return false; diff --git a/module/advancement/types/item-grant.mjs b/module/advancement/types/item-grant.mjs index 5fe14c9e81..359fe02c85 100644 --- a/module/advancement/types/item-grant.mjs +++ b/module/advancement/types/item-grant.mjs @@ -29,6 +29,14 @@ export class ItemGrantAdvancement extends Advancement { }); } + /* -------------------------------------------- */ + + /** + * The item types that are supported in Item Grant. + * @type {Set} + */ + static VALID_TYPES = new Set(["feat", "spell", "consumable", "backpack", "equipment", "loot", "tool", "weapon"]); + /* -------------------------------------------- */ /* Display Methods */ /* -------------------------------------------- */ @@ -144,12 +152,21 @@ export class ItemGrantConfig extends AdvancementConfig { async getData() { const data = super.getData(); - // TODO: Only need the index here, replace with simplified index fetching in V10 - data.items = await Promise.all(data.data.configuration.items.map(fromUuid)); + data.items = await Promise.all(data.data.configuration.items.map(fromUuidSync)); data.showSpellConfig = data.items.some(i => i.type === "spell"); return data; } + /* -------------------------------------------- */ + + /** @inheritdoc */ + _verifyDroppedItem(event, item) { + if ( this.advancement.constructor.VALID_TYPES.has(item.type) ) return true; + const type = game.i18n.localize(`ITEM.Type${item.type.capitalize()}`); + ui.notifications.warn(game.i18n.format("DND5E.AdvancementItemTypeInvalidWarning", { type })); + return false; + } + }