diff --git a/lang/en.json b/lang/en.json index 6916e4ba0d..ac3881f89d 100644 --- a/lang/en.json +++ b/lang/en.json @@ -20,6 +20,9 @@ "TYPES.Actor.group": "Group", "TYPES.Actor.groupPl": "Groups", +"TYPES.ActiveEffect.enchantment": "Enchantment", +"TYPES.ActiveEffect.enchantmentPl": "Enchantments", + "ITEM.TypeBackground": "Background", "ITEM.TypeBackgroundPl": "Backgrounds", "ITEM.TypeContainer": "Container", @@ -1098,20 +1101,83 @@ "DND5E.EffectApplyWarningConcentration": "Applying an effect that is being concentrated on by another character requires GM permissions.", "DND5E.EffectApplyWarningOwnership": "Effects cannot be applied to tokens you are not the owner of.", "DND5E.EffectsSearch": "Search effects", -"DND5E.Enchantment": { + +"DND5E.ENCHANT": { + "Title": "Enchant", + "FIELDS": { + "effects": { + "FIELDS": { + "level": { + "label": "Level Limit", + "hint": "Range of levels required to use this enchantment.", + "max": { + "label": "Maximum Level" + }, + "min": { + "label": "Minimum Level" + } + }, + "riders": { + "label": "Attached", + "effect": { + "label": "Additional Effects", + "hint": "These additional effects will be added to the enchanted item when this enchantment is added, and removed when the enchantment is removed." + }, + "item": { + "label": "Additional Items", + "hint": "These additional items will be added to the creature when one of its items is enchanted, and will be removed if the enchantment is ever removed." + } + } + } + }, + "enchant": { + "label": "Enchantment Configuration", + "identifier": { + "label": "Class Identifier", + "hint": "Identifier used to determine whether the character level or a specific class level should be used for enchantment level limits." + } + }, + "restrictions": { + "label": "Restrictions", + "hint": "Restrictions on the type of item to which this enchantment can be applied.", + "allowMagical": { + "label": "Allow Magical", + "hint": "Allow items that are already magical to be enchanted." + }, + "type": { + "label": "Item Type", + "hint": "Type of item to which this enchantment can be applied.", + "Any": "Any Enchantable Type" + } + } + }, + "SECTIONS": { + "Enchanting": "Enchanting", + "Enchantments": "Enchantments", + "Restrictions": "Restrictions" + }, + "Enchantment": { + "Action": { + "Create": "Create Enchantment", + "Delete": "Delete Enchantment" + }, + "Empty": "No associated enchantments, use the button below to create one or select an existing enchantment from the control above." + } +}, + +"DND5E.ENCHANTMENT": { "Action": { "Apply": "Apply Enchantment", - "Configure": "Configure Enchantment", - "Create": "Create Enchantment", - "Delete": "Delete Enchantment", "Disable": "Disable Enchantment", "Edit": "Edit Enchantment", "Enable": "Enable Enchantment", "Remove": "Remove Enchantment" - }, + } +}, + +"DND5E.Enchantment": { "Category": { "Active": "Active Enchantments", - "Empty": "No enchantments have been created, use the button above to create one.", "General": "Enchantments", "Inactive": "Inactive Enchantments" }, @@ -1121,9 +1187,6 @@ "FIELDS": { "enchantment": { "label": "Enchantment Configuration", - "classIdentifier": { - "hint": "Identifier used to determine whether the character level or a specific class level should be used for enchantment level limits." - }, "items": { "max": { "label": "Item Limit", @@ -1133,38 +1196,13 @@ "label": "Replacement Period", "hint": "How frequently the enchantments of this type can be re-bound to different items." } - }, - "restrictions": { - "label": "Restrictions", - "hint": "Restrictions on the type of item to which this enchantment can be applied.", - "allowMagical": { - "label": "Allow Magical", - "hint": "Allow items that are already magical to be enchanted." - }, - "type": { - "label": "Item Type", - "hint": "Type of item to which this enchantment can be applied." - } } } }, "Label": "Enchantment", - "Level": { - "Hint": "Range of levels required to use this enchantment." - }, "Items": { "Entry": "{item} on {actor}" }, - "Riders": { - "Effect": { - "Label": "Additional Effects", - "Hint": "These additional effects will be added to the enchanted item when this enchantment is added, and removed when the enchantment is removed." - }, - "Item": { - "Label": "Additional Items", - "Hint": "These additional items will be added to the creature when one of its items is enchanted, and will be removed if the enchantment is ever removed." - } - }, "Warning": { "ConcentrationEnded": "Cannot apply this enchantment because concentration has ended.", "NoMagicalItems": "Items that are already magical cannot be enchanted.", @@ -2119,11 +2157,11 @@ "level": { "label": "Level Limit", "hint": "Range of levels required to use this profile.", - "min": { - "label": "Minimum Level" - }, "max": { "label": "Maximum Level" + }, + "min": { + "label": "Minimum Level" } }, "name": { diff --git a/module/applications/activity/_module.mjs b/module/applications/activity/_module.mjs index 23a19afcf3..c731d5c8de 100644 --- a/module/applications/activity/_module.mjs +++ b/module/applications/activity/_module.mjs @@ -1,5 +1,6 @@ export {default as ActivitySheet} from "./activity-sheet.mjs"; export {default as AttackSheet} from "./attack-sheet.mjs"; +export {default as EnchantSheet} from "./enchant-sheet.mjs"; export {default as SummonSheet} from "./summon-sheet.mjs"; export {default as UtilitySheet} from "./utility-sheet.mjs"; diff --git a/module/applications/activity/activity-sheet.mjs b/module/applications/activity/activity-sheet.mjs index 19acfdeaa6..0bc76e987a 100644 --- a/module/applications/activity/activity-sheet.mjs +++ b/module/applications/activity/activity-sheet.mjs @@ -116,6 +116,10 @@ export default class ActivitySheet extends Application5e { */ #expandedSections = new Map(); + get expandedSections() { + return this.#expandedSections; + } + /* -------------------------------------------- */ /** @@ -334,12 +338,15 @@ export default class ActivitySheet extends Application5e { if ( context.activity.effects ) { const appliedEffects = new Set(context.activity.effects?.map(e => e._id) ?? []); - context.allEffects = this.item.effects.map(effect => ({ - value: effect.id, label: effect.name, selected: appliedEffects.has(effect.id) - })); + context.allEffects = this.item.effects + .filter(e => e.type !== "enchantment") + .map(effect => ({ + value: effect.id, label: effect.name, selected: appliedEffects.has(effect.id) + })); context.appliedEffects = context.activity.effects.map((data, index) => { const effect = { data, + collapsed: this.expandedSections.get(`effects.${data._id}`) ? "" : "collapsed", effect: data.effect, fields: this.activity.schema.fields.effects.element.fields, prefix: `effects.${index}.`, @@ -541,14 +548,25 @@ export default class ActivitySheet extends Application5e { * @param {HTMLElement} target Button that was clicked. */ static async #addEffect(event, target) { - const effectData = { + const effectData = this._addEffectData(); + const [created] = await this.item.createEmbeddedDocuments("ActiveEffect", [effectData]); + this.activity.update({ effects: [...this.activity.toObject().effects, { _id: created.id }] }); + } + + /* -------------------------------------------- */ + + /** + * The data for a newly created applied effect. + * @returns {object} + * @protected + */ + _addEffectData() { + return { name: this.item.name, img: this.item.img, origin: this.item.uuid, transfer: false }; - const [created] = await this.item.createEmbeddedDocuments("ActiveEffect", [effectData]); - this.activity.update({ effects: [...this.activity.toObject().effects, { _id: created.id }] }); } /* -------------------------------------------- */ @@ -646,7 +664,7 @@ export default class ActivitySheet extends Application5e { target.classList.toggle("collapsed"); this.#expandedSections.set( target.closest("[data-expand-id]")?.dataset.expandId, - !event.currentTarget.classList.contains("collapsed") + !target.classList.contains("collapsed") ); } diff --git a/module/applications/activity/enchant-sheet.mjs b/module/applications/activity/enchant-sheet.mjs new file mode 100644 index 0000000000..94a40db960 --- /dev/null +++ b/module/applications/activity/enchant-sheet.mjs @@ -0,0 +1,104 @@ +import ActivitySheet from "./activity-sheet.mjs"; + +/** + * Sheet for the enchant activity. + */ +export default class EnchantSheet extends ActivitySheet { + + /** @inheritDoc */ + static DEFAULT_OPTIONS = { + classes: ["enchant-activity"] + }; + + /* -------------------------------------------- */ + + /** @inheritDoc */ + static PARTS = { + ...super.PARTS, + effect: { + template: "systems/dnd5e/templates/activity/enchant-effect.hbs", + templates: [ + "systems/dnd5e/templates/activity/parts/enchant-enchantments.hbs", + "systems/dnd5e/templates/activity/parts/enchant-restrictions.hbs" + ] + } + }; + + /* -------------------------------------------- */ + + /** @override */ + tabGroups = { + sheet: "identity", + activation: "time", + effect: "enchantments" + }; + + /* -------------------------------------------- */ + /* Rendering */ + /* -------------------------------------------- */ + + /** @override */ + _prepareAppliedEffectContext(context, effect) { + effect.effectOptions = context.allEffects.map(e => ({ + ...e, selected: effect.data.riders.effect.has(e.value) + })); + return effect; + } + + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _prepareEffectContext(context) { + context = await super._prepareEffectContext(context); + + const appliedEnchantments = new Set(context.activity.effects?.map(e => e._id) ?? []); + context.allEnchantments = this.item.effects + .filter(e => e.type === "enchantment") + .map(effect => ({ + value: effect.id, label: effect.name, selected: appliedEnchantments.has(effect.id) + })); + const enchantableTypes = this.activity.enchantableTypes; + context.typeOptions = [ + { value: "", label: game.i18n.localize("DND5E.ENCHANT.FIELDS.restrictions.type.Any") }, + ...Object.keys(CONFIG.Item.dataModels) + .filter(t => enchantableTypes.has(t)) + .map(value => ({ value, label: game.i18n.localize(CONFIG.Item.typeLabels[value]) })) + ]; + + return context; + } + + /* -------------------------------------------- */ + + /** @inheritDoc */ + _getTabs() { + const tabs = super._getTabs(); + tabs.effect.label = "DND5E.ENCHANT.SECTIONS.Enchanting"; + tabs.effect.icon = "fa-solid fa-wand-sparkles"; + tabs.effect.tabs = this._markTabs({ + enchantments: { + id: "enchantments", group: "effect", icon: "fa-solid fa-star", + label: "DND5E.ENCHANT.SECTIONS.Enchantments" + }, + restrictions: { + id: "restrictions", group: "effect", icon: "fa-solid fa-ban", + label: "DND5E.ENCHANT.SECTIONS.Restrictions" + } + }); + return tabs; + } + + /* -------------------------------------------- */ + /* Event Listeners and Handlers */ + /* -------------------------------------------- */ + + /** @override */ + _addEffectData() { + return { + type: "enchantment", + name: this.item.name, + img: this.item.img, + disabled: true + }; + } +} diff --git a/module/applications/activity/summon-sheet.mjs b/module/applications/activity/summon-sheet.mjs index 3c7e789de3..43402195be 100644 --- a/module/applications/activity/summon-sheet.mjs +++ b/module/applications/activity/summon-sheet.mjs @@ -1,7 +1,7 @@ import ActivitySheet from "./activity-sheet.mjs"; /** - * Default sheet for activities. + * Sheet for the summon activity. */ export default class SummonSheet extends ActivitySheet { @@ -67,6 +67,7 @@ export default class SummonSheet extends ActivitySheet { ]; context.profiles = this.activity.profiles.map((data, index) => ({ data, index, + collapsed: this.expandedSections.get(`profiles.${effect.id}`) ? "" : "collapsed", fields: this.activity.schema.fields.profiles.element.fields, prefix: `profiles.${index}.`, source: context.source.profiles[index] ?? data, diff --git a/module/applications/components/effects.mjs b/module/applications/components/effects.mjs index 26b6ad996c..9655db6eed 100644 --- a/module/applications/components/effects.mjs +++ b/module/applications/components/effects.mjs @@ -129,7 +129,7 @@ export default class EffectsElement extends HTMLElement { if ( e.disabled ) categories.enchantmentInactive.effects.push(e); else categories.enchantmentActive.effects.push(e); } - else if ( e.getFlag("dnd5e", "type") === "enchantment" ) categories.enchantment.effects.push(e); + else if ( e.type === "enchantment" ) categories.enchantment.effects.push(e); else if ( e.isSuppressed ) categories.suppressed.effects.push(e); else if ( e.disabled ) categories.inactive.effects.push(e); else if ( e.isTemporary ) categories.temporary.effects.push(e); @@ -280,12 +280,12 @@ export default class EffectsElement extends HTMLElement { const isActor = this.document instanceof Actor; const isEnchantment = li.dataset.effectType.startsWith("enchantment"); return this.document.createEmbeddedDocuments("ActiveEffect", [{ + type: isEnchantment ? "enchantment" : "base", name: isActor ? game.i18n.localize("DND5E.EffectNew") : this.document.name, icon: isActor ? "icons/svg/aura.svg" : this.document.img, origin: isEnchantment ? undefined : this.document.uuid, "duration.rounds": li.dataset.effectType === "temporary" ? 1 : undefined, - disabled: ["inactive", "enchantmentInactive"].includes(li.dataset.effectType), - "flags.dnd5e.type": isEnchantment ? "enchantment" : undefined + disabled: ["inactive", "enchantmentInactive"].includes(li.dataset.effectType) }]); } diff --git a/module/applications/item/enchantment-config.mjs b/module/applications/item/enchantment-config.mjs index c0431ed1e3..1474758aa8 100644 --- a/module/applications/item/enchantment-config.mjs +++ b/module/applications/item/enchantment-config.mjs @@ -1,151 +1,10 @@ -import { EnchantmentData } from "../../data/item/fields/enchantment-field.mjs"; - /** * Application for configuring enchantment information for an item. */ export default class EnchantmentConfig extends DocumentSheet { - - /** @inheritDoc */ - static get defaultOptions() { - return foundry.utils.mergeObject(super.defaultOptions, { - classes: ["dnd5e", "enchantment-config"], - dragDrop: [{ dropSelector: "form" }], - template: "systems/dnd5e/templates/apps/enchantment-config.hbs", - width: 500, - height: "auto", - sheetConfig: false, - closeOnSubmit: false, - submitOnChange: true, - submitOnClose: true - }); - } - - /* -------------------------------------------- */ - /* Properties */ - /* -------------------------------------------- */ - - /** - * Expanded states for each enchantment. - * @type {Map} - */ - expandedEnchantments = new Map(); - - /* -------------------------------------------- */ - - /** @inheritDoc */ - get title() { - return `${game.i18n.localize("DND5E.Enchantment.Configuration")}: ${this.document.name}`; - } - - /* -------------------------------------------- */ - /* Rendering */ - /* -------------------------------------------- */ - - /** @inheritDoc */ - async getData(options={}) { - const context = await super.getData(options); - - context.enchantableTypes = EnchantmentData.enchantableTypes.reduce((obj, k) => { - obj[k] = game.i18n.localize(CONFIG.Item.typeLabels[k]); - return obj; - }, {}); - context.enchantment = this.document.system.enchantment; - context.isSpell = this.document.type === "spell"; - context.source = this.document.toObject().system.enchantment; - - const effects = []; - context.enchantments = []; - for ( const effect of this.document.effects ) { - if ( effect.getFlag("dnd5e", "type") !== "enchantment" ) effects.push(effect); - else if ( !effect.isAppliedEnchantment ) context.enchantments.push(effect); - } - context.enchantments = context.enchantments.map(effect => ({ - id: effect.id, - uuid: effect.uuid, - name: effect.name, - flags: effect.flags, - collapsed: this.expandedEnchantments.get(effect.id) ? "" : "collapsed", - riderEffects: effects.map(({ id, name }) => ({ - id, name, selected: effect.flags.dnd5e?.enchantment?.riders?.effect?.includes(id) ? "selected" : "" - })), - riderItems: effect.flags.dnd5e?.enchantment?.riders?.item?.join(",") ?? "" - })); - - return context; - } - - /* -------------------------------------------- */ - /* Event Handling */ - /* -------------------------------------------- */ - - /** @inheritDoc */ - activateListeners(jQuery) { - super.activateListeners(jQuery); - const html = jQuery[0]; - - for ( const element of html.querySelectorAll("[data-action]") ) { - element.addEventListener("click", event => this.submit({ updateData: { - action: event.target.dataset.action, - enchantmentId: event.target.closest("[data-enchantment-id]")?.dataset.enchantmentId - } })); - } - - for ( const element of html.querySelectorAll("multi-select") ) { - element.addEventListener("change", this._onChangeInput.bind(this)); - } - - for ( const element of html.querySelectorAll(".collapsible") ) { - element.addEventListener("click", event => { - const id = event.target.closest("[data-enchantment-id]")?.dataset.enchantmentId; - if ( event.target.closest(".collapsible-content") || !id ) return; - event.currentTarget.classList.toggle("collapsed"); - this.expandedEnchantments.set(id, !event.currentTarget.classList.contains("collapsed")); - }); - } - } - - /* -------------------------------------------- */ - - /** @inheritDoc */ - async _updateObject(event, formData) { - const { action, effects, enchantmentId, ...data } = foundry.utils.expandObject(formData); - - await this.document.update({"system.enchantment": data}); - - const riderIds = new Set(); - const effectsChanges = Object.entries(effects ?? {}).map(([_id, changes]) => { - const updates = { _id, ...changes }; - // Fix bug with in V11 - if ( !foundry.utils.hasProperty(updates, "flags.dnd5e.enchantment.riders.effect") ) { - foundry.utils.setProperty(updates, "flags.dnd5e.enchantment.riders.effect", []); - } - // End bug fix - riderIds.add(...(foundry.utils.getProperty(updates, "flags.dnd5e.enchantment.riders.effect") ?? [])); - return updates; - }); - for ( const effect of this.document.effects ) { - if ( effect.getFlag("dnd5e", "type") === "enchantment" ) continue; - if ( riderIds.has(effect.id) ) effectsChanges.push({ _id: effect.id, "flags.dnd5e.rider": true }); - else effectsChanges.push({ _id: effect.id, "flags.dnd5e.-=rider": null }); - } - if ( effectsChanges.length ) await this.document.updateEmbeddedDocuments("ActiveEffect", effectsChanges); - - const enchantment = this.document.effects.get(enchantmentId); - switch ( action ) { - case "add-enchantment": - const effect = await ActiveEffect.implementation.create({ - name: this.document.name, - icon: this.document.img, - "flags.dnd5e.type": "enchantment" - }, { parent: this.document }); - effect.sheet.render(true); - break; - case "delete-enchantment": - enchantment?.deleteDialog(); - break; - case "edit-enchantment": - enchantment?.sheet.render(true); - break; - } + constructor() { + throw new Error( + "EnchantmentConfig has been deprecated. Configuring enchanting should now be performed through the Enchant activity." + ); } } diff --git a/module/applications/item/item-sheet.mjs b/module/applications/item/item-sheet.mjs index 9fc6ddfc0b..32c454d51e 100644 --- a/module/applications/item/item-sheet.mjs +++ b/module/applications/item/item-sheet.mjs @@ -9,7 +9,6 @@ import AdvancementMigrationDialog from "../advancement/advancement-migration-dia import Accordion from "../accordion.mjs"; import EffectsElement from "../components/effects.mjs"; import SourceConfig from "../source-config.mjs"; -import EnchantmentConfig from "./enchantment-config.mjs"; import StartingEquipmentConfig from "./starting-equipment-config.mjs"; /** @@ -538,9 +537,6 @@ export default class ItemSheet5e extends ItemSheet { const button = event.currentTarget; let app; switch ( button.dataset.action ) { - case "enchantment": - app = new EnchantmentConfig(this.item); - break; case "movement": app = new ActorMovementConfig(this.item, { keyPath: "system.movement" }); break; @@ -706,7 +702,7 @@ export default class ItemSheet5e extends ItemSheet { let keepOrigin = false; // Validate against the enchantment's restraints on the origin item - if ( effect.getFlag("dnd5e", "type") === "enchantment" ) { + if ( effect.type === "enchantment" ) { const errors = effect.parent.system.enchantment?.canEnchant(this.item); if ( errors?.length ) { errors.forEach(err => ui.notifications.error(err.message)); diff --git a/module/config.mjs b/module/config.mjs index e42ad3fac6..645729c240 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -3236,6 +3236,9 @@ DND5E.activityTypes = { attack: { documentClass: activities.AttackActivity }, + enchant: { + documentClass: activities.EnchantActivity + }, save: { documentClass: activities.SaveActivity }, diff --git a/module/data/activity/_module.mjs b/module/data/activity/_module.mjs index f4a783cbfc..c65af89ff9 100644 --- a/module/data/activity/_module.mjs +++ b/module/data/activity/_module.mjs @@ -1,6 +1,7 @@ export {default as BaseActivityData} from "./base-activity.mjs"; export {default as AttackActivityData} from "./attack-data.mjs"; +export {default as EnchantActivityData} from "./enchant-data.mjs"; export {default as SaveActivityData} from "./save-data.mjs"; export {default as SummonActivityData} from "./summon-data.mjs"; export {default as UtilityActivityData} from "./utility-data.mjs"; diff --git a/module/data/activity/enchant-data.mjs b/module/data/activity/enchant-data.mjs new file mode 100644 index 0000000000..9c3a41804a --- /dev/null +++ b/module/data/activity/enchant-data.mjs @@ -0,0 +1,52 @@ +import IdentifierField from "../fields/identifier-field.mjs"; +import BaseActivityData from "./base-activity.mjs"; +import AppliedEffectField from "./fields/applied-effect-field.mjs"; + +const { + ArrayField, BooleanField, DocumentIdField, DocumentUUIDField, NumberField, SchemaField, SetField, StringField +} = foundry.data.fields; + +/** + * @typedef {EffectApplicationData} EnchantEffectApplicationData + * @property {object} level + * @property {number} level.min Minimum level at which this profile can be used. + * @property {number} level.max Maximum level at which this profile can be used. + * @property {object} riders + * @property {string[]} riders.effect IDs of other effects on this item that will be added with this enchantment. + * @property {string[]} riders.item UUIDs of items that will be added with this enchantment. + */ + +/** + * Data model for a enchant activity. + * + * @property {object} enchant + * @property {string} enchant.identifier Class identifier that will be used to determine applicable level. + * @property {object} restrictions + * @property {boolean} restrictions.allowMagical Allow enchantments to be applied to items that are already magical. + * @property {string} restrictions.type Item type to which this enchantment can be applied. + */ +export default class EnchantActivityData extends BaseActivityData { + /** @inheritDoc */ + static defineSchema() { + return { + ...super.defineSchema(), + effects: new ArrayField(new AppliedEffectField({ + level: new SchemaField({ + min: new NumberField({ min: 0, integer: true }), + max: new NumberField({ min: 0, integer: true }) + }), + riders: new SchemaField({ + effect: new SetField(new DocumentIdField()), + item: new SetField(new DocumentUUIDField()) + }) + })), + enchant: new SchemaField({ + identifier: new IdentifierField() + }), + restrictions: new SchemaField({ + allowMagical: new BooleanField(), + type: new StringField() + }) + }; + } +} diff --git a/module/data/item/fields/enchantment-field.mjs b/module/data/item/fields/enchantment-field.mjs index d597623931..e77d8efc3d 100644 --- a/module/data/item/fields/enchantment-field.mjs +++ b/module/data/item/fields/enchantment-field.mjs @@ -87,7 +87,7 @@ export class EnchantmentData extends foundry.abstract.DataModel { * @type {ActiveEffect5e[]} */ get enchantments() { - return this.item.effects.filter(ae => ae.getFlag("dnd5e", "type") === "enchantment"); + return this.item.effects.filter(ae => ae.type === "enchantment"); } /* -------------------------------------------- */ @@ -159,7 +159,7 @@ export class EnchantmentData extends foundry.abstract.DataModel { : "details.level"; const level = foundry.utils.getProperty(item.getRollData(), keyPath) ?? 0; return item.effects.filter(e => { - if ( (e.getFlag("dnd5e", "type") !== "enchantment") || e.isAppliedEnchantment ) return false; + if ( (e.type !== "enchantment") || e.isAppliedEnchantment ) return false; const { min, max } = e.getFlag("dnd5e", "enchantment.level") ?? {}; return ((min ?? -Infinity) <= level) && (level <= (max ?? Infinity)); }); diff --git a/module/documents/active-effect.mjs b/module/documents/active-effect.mjs index b9760e6430..79c807fa07 100644 --- a/module/documents/active-effect.mjs +++ b/module/documents/active-effect.mjs @@ -40,8 +40,7 @@ export default class ActiveEffect5e extends ActiveEffect { * @type {boolean} */ get isAppliedEnchantment() { - return (this.getFlag("dnd5e", "type") === "enchantment") - && !!this.origin && (this.origin !== this.parent.uuid); + return (this.type === "enchantment") && !!this.origin && (this.origin !== this.parent.uuid); } /* -------------------------------------------- */ @@ -73,6 +72,20 @@ export default class ActiveEffect5e extends ActiveEffect { return super._fromStatusEffect?.(statusId, effectData, options) ?? new this(effectData, options); } + /* -------------------------------------------- */ + /* Data Migration */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + _initializeSource(data, options={}) { + if ( data.flags?.dnd5e?.type === "enchantment" ) { + data.type = "enchantment"; + delete data.flags.dnd5e.type; + } + + return super._initializeSource(data, options); + } + /* -------------------------------------------- */ /* Effect Application */ /* -------------------------------------------- */ @@ -271,7 +284,7 @@ export default class ActiveEffect5e extends ActiveEffect { */ determineSuppression() { this.isSuppressed = false; - if ( this.getFlag("dnd5e", "type") === "enchantment" ) return; + if ( this.type === "enchantment" ) return; if ( this.parent instanceof dnd5e.documents.Item5e ) this.isSuppressed = this.parent.areEffectsSuppressed; } @@ -410,7 +423,7 @@ export default class ActiveEffect5e extends ActiveEffect { if ( options.keepOrigin === false ) this.updateSource({ origin: this.parent.uuid }); // Enchantments cannot be added directly to actors - if ( (this.getFlag("dnd5e", "type") === "enchantment") && (this.parent instanceof Actor) ) { + if ( (this.type === "enchantment") && (this.parent instanceof Actor) ) { ui.notifications.error("DND5E.Enchantment.Warning.NotOnActor", { localize: true }); return false; } @@ -736,7 +749,7 @@ export default class ActiveEffect5e extends ActiveEffect { else if ( this.disabled ) properties.push("DND5E.EffectType.Inactive"); else if ( this.isTemporary ) properties.push("DND5E.EffectType.Temporary"); else properties.push("DND5E.EffectType.Passive"); - if ( this.getFlag("dnd5e", "type") === "enchantment" ) properties.push("DND5E.Enchantment.Label"); + if ( this.type === "enchantment" ) properties.push("DND5E.Enchantment.Label"); return { content: await renderTemplate( diff --git a/module/documents/activity/_module.mjs b/module/documents/activity/_module.mjs index 2d3b497ae4..de7c2ebe94 100644 --- a/module/documents/activity/_module.mjs +++ b/module/documents/activity/_module.mjs @@ -1,6 +1,7 @@ export {default as ActivityMixin} from "./mixin.mjs"; export {default as AttackActivity} from "./attack.mjs"; +export {default as EnchantActivity} from "./enchant.mjs"; export {default as SaveActivity} from "./save.mjs"; export {default as SummonActivity} from "./summon.mjs"; export {default as UtilityActivity} from "./utility.mjs"; diff --git a/module/documents/activity/enchant.mjs b/module/documents/activity/enchant.mjs new file mode 100644 index 0000000000..ce470975c8 --- /dev/null +++ b/module/documents/activity/enchant.mjs @@ -0,0 +1,50 @@ +import EnchantSheet from "../../applications/activity/enchant-sheet.mjs"; +import EnchantActivityData from "../../data/activity/enchant-data.mjs"; +import ActivityMixin from "./mixin.mjs"; + +/** + * Activity for enchanting items. + */ +export default class EnchantActivity extends ActivityMixin(EnchantActivityData) { + /* -------------------------------------------- */ + /* Model Configuration */ + /* -------------------------------------------- */ + + /** @inheritDoc */ + static LOCALIZATION_PREFIXES = [...super.LOCALIZATION_PREFIXES, "DND5E.ENCHANT"]; + + /* -------------------------------------------- */ + + /** @inheritDoc */ + static metadata = Object.freeze( + foundry.utils.mergeObject(super.metadata, { + type: "enchant", + img: "systems/dnd5e/icons/svg/activity/enchant.svg", + title: "DND5E.ENCHANT.Title", + sheetClass: EnchantSheet + }, { inplace: false }) + ); + + /* -------------------------------------------- */ + + /** @inheritDoc */ + static localize() { + super.localize(); + this._localizeSchema(this.schema.fields.effects.element, ["DND5E.ENCHANT.FIELDS.effects"]); + } + + /* -------------------------------------------- */ + /* Properties */ + /* -------------------------------------------- */ + + /** + * List of item types that are enchantable. + * @type {Set} + */ + get enchantableTypes() { + return Object.entries(CONFIG.Item.dataModels).reduce((set, [k, v]) => { + if ( v.metadata?.enchantable ) set.add(k); + return set; + }, new Set()); + } +} diff --git a/module/documents/actor/actor.mjs b/module/documents/actor/actor.mjs index 31bc691504..feab43017e 100644 --- a/module/documents/actor/actor.mjs +++ b/module/documents/actor/actor.mjs @@ -229,7 +229,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) { /** @inheritDoc */ *allApplicableEffects() { for ( const effect of super.allApplicableEffects() ) { - if ( (effect.getFlag("dnd5e", "type") !== "enchantment") && !effect.getFlag("dnd5e", "rider") ) yield effect; + if ( (effect.type !== "enchantment") && !effect.getFlag("dnd5e", "rider") ) yield effect; } } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index 372cfcfda5..289332fc41 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -1273,7 +1273,7 @@ export default class Item5e extends SystemDocumentMixin(Item) { config: CONFIG.DND5E, tokenId: token?.uuid || null, item: this, - effects: this.effects.filter(e => (e.getFlag("dnd5e", "type") !== "enchantment") && !e.getFlag("dnd5e", "rider")), + effects: this.effects.filter(e => (e.type !== "enchantment") && !e.getFlag("dnd5e", "rider")), data: await this.system.getCardData(), labels: this.labels, hasAttack: this.hasAttack, diff --git a/system.json b/system.json index 6e1853b56c..d8d4039684 100644 --- a/system.json +++ b/system.json @@ -24,6 +24,9 @@ "dnd5e.css" ], "documentTypes": { + "ActiveEffect": { + "enchantment": {} + }, "Actor": { "character": { "htmlFields": ["details.biography.value", "details.biography.public"] diff --git a/templates/activity/enchant-effect.hbs b/templates/activity/enchant-effect.hbs new file mode 100644 index 0000000000..7a6d2c782a --- /dev/null +++ b/templates/activity/enchant-effect.hbs @@ -0,0 +1,5 @@ +
+ {{> "templates/generic/tab-navigation.hbs" tabs=tabs.effect.tabs }} + {{> "systems/dnd5e/templates/activity/parts/enchant-enchantments.hbs" tab=tabs.effect.tabs.enchantments }} + {{> "systems/dnd5e/templates/activity/parts/enchant-restrictions.hbs" tab=tabs.effect.tabs.restrictions }} +
diff --git a/templates/activity/parts/activity-effects.hbs b/templates/activity/parts/activity-effects.hbs index 389a712580..d14b61488c 100644 --- a/templates/activity/parts/activity-effects.hbs +++ b/templates/activity/parts/activity-effects.hbs @@ -29,7 +29,7 @@ {{#if additionalSettings}} -
+ -
+