diff --git a/lang/en.json b/lang/en.json index 644ea5fa76..503d178ce3 100644 --- a/lang/en.json +++ b/lang/en.json @@ -156,8 +156,10 @@ }, "DND5E.ACTIVITY": { - "Label": "Activity", - "LabelPl": "Activities", + "Title": { + "one": "Activity", + "other": "Activities" + }, "FIELDS": { "activation": { "label": "Activation", @@ -207,6 +209,9 @@ "hint": "Value of the duration in the specified units, if applicable." } }, + "effects": { + "label": "Applied Effects" + }, "img": { "label": "Icon" }, @@ -740,7 +745,7 @@ "DND5E.ConsumableWithoutCharges": "available units to use", "DND5E.Consumption": { "Action": { - "Add": "Add Consumption Target", + "Create": "Create Consumption Target", "Delete": "Delete Consumption Target" }, "Scaling": { @@ -943,6 +948,12 @@ "DND5E.Dusk": "Dusk", "DND5E.Effect": "Effect", "DND5E.Effects": "Effects", +"DND5E.EFFECT": { + "Action": { + "Create": "Create Effect", + "Delete": "Delete Effect" + } +}, "DND5E.EffectsApplyTokens": "Apply to selected tokens", "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.", @@ -2137,7 +2148,7 @@ }, "Recovery": { "Action": { - "Add": "Add Recovery Profile", + "Create": "Create Recovery Profile", "Delete": "Delete Recovery Profile" }, "Recharge": { diff --git a/module/applications/activity/activity-sheet.mjs b/module/applications/activity/activity-sheet.mjs index a163bb20f3..4f353f25d7 100644 --- a/module/applications/activity/activity-sheet.mjs +++ b/module/applications/activity/activity-sheet.mjs @@ -24,8 +24,10 @@ export default class ActivitySheet extends Application5e { }, actions: { addConsumption: ActivitySheet.#addConsumption, + addEffect: ActivitySheet.#addEffect, addRecovery: ActivitySheet.#addRecovery, deleteConsumption: ActivitySheet.#deleteConsumption, + deleteEffect: ActivitySheet.#deleteEffect, deleteRecovery: ActivitySheet.#deleteRecovery }, form: { @@ -59,7 +61,10 @@ export default class ActivitySheet extends Application5e { ] }, effect: { - template: "systems/dnd5e/templates/activity/effect.hbs" + template: "systems/dnd5e/templates/activity/effect.hbs", + templates: [ + "systems/dnd5e/templates/activity/parts/activity-effects.hbs" + ] } }; @@ -277,6 +282,12 @@ export default class ActivitySheet extends Application5e { */ async _prepareEffectContext(context) { context.tab = context.tabs.effect; + + 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) + })); + return context; } @@ -358,7 +369,7 @@ export default class ActivitySheet extends Application5e { /** @override */ _canRender(options) { if ( !this.isVisible ) throw new Error(game.i18n.format("SHEETS.DocumentSheetPrivate", { - type: game.i18n.localize("DND5E.ACTIVITY.Label") + type: game.i18n.localize("DND5E.ACTIVITY.Title.one") })); } @@ -377,6 +388,15 @@ export default class ActivitySheet extends Application5e { this.activity.constructor._unregisterApp(this.activity, this); } + /* -------------------------------------------- */ + + /** @inheritDoc */ + async _renderFrame(options) { + const frame = await super._renderFrame(options); + frame.autocomplete = "off"; + return frame; + } + /* -------------------------------------------- */ /* Event Listeners and Handlers */ /* -------------------------------------------- */ @@ -401,6 +421,25 @@ export default class ActivitySheet extends Application5e { /* -------------------------------------------- */ + /** + * Handle creating a new active effect and adding it to the applied effects list. + * @this {ActivityConfig} + * @param {Event} event Triggering click event. + * @param {HTMLElement} target Button that was clicked. + */ + static async #addEffect(event, target) { + const effectData = { + 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 }] }); + } + + /* -------------------------------------------- */ + /** * Handle adding a new entry to the uses recovery list. * @this {ActivityConfig} @@ -437,6 +476,23 @@ export default class ActivitySheet extends Application5e { /* -------------------------------------------- */ + /** + * Handle deleting an active effect and removing it from the applied effects list. + * @this {ActivityConfig} + * @param {Event} event Triggering click event. + * @param {HTMLElement} target Button that was clicked. + */ + static async #deleteEffect(event, target) { + const effectId = target.closest("[data-effect-id]")?.dataset.effectId; + const result = await this.item.effects.get(effectId)?.deleteDialog(); + if ( result instanceof ActiveEffect ) { + const effects = this.activity.toObject().effects.filter(e => e.id !== effectId); + this.activity.update({ effects }); + } + } + + /* -------------------------------------------- */ + /** * Handle removing an entry from the uses recovery list. * @this {ActivityConfig} @@ -474,6 +530,14 @@ export default class ActivitySheet extends Application5e { */ _prepareSubmitData(event, formData) { const submitData = foundry.utils.expandObject(formData.object); + if ( foundry.utils.hasProperty(submitData, "appliedEffects") ) { + const effects = submitData.effects ?? this.activity.toObject().effects; + submitData.effects = effects.filter(e => submitData.appliedEffects.includes(e.id)); + for ( const id of submitData.appliedEffects ) { + if ( submitData.effects.find(e => e.id === id) ) continue; + submitData.effects.push({ id }); + } + } if ( foundry.utils.hasProperty(submitData, "consumption.targets") ) { submitData.consumption.targets = Object.values(submitData.consumption.targets); } diff --git a/module/data/activity/base-activity.mjs b/module/data/activity/base-activity.mjs index cf41016b1e..195d695a58 100644 --- a/module/data/activity/base-activity.mjs +++ b/module/data/activity/base-activity.mjs @@ -19,6 +19,13 @@ const { * @property {string} scaling.formula Specific scaling formula if not automatically calculated from target's value. */ +/** + * Data for effects that can be applied. + * + * @typedef {object} EffectApplicationData + * @property {string} effect ID of the effect to apply. + */ + /** * Data for a recovery profile for an activity's uses. * @@ -48,6 +55,7 @@ const { * @property {string} duration.value Scalar value for the activity's duration. * @property {string} duration.units Units that are used for the duration. * @property {string} duration.special Description of any special duration details. + * @property {EffectApplicationData[]} effects Linked effects that can be applied. * @property {object} range * @property {string} range.value Scalar value for the activity's range. * @property {string} range.units Units that are used for the range. @@ -120,6 +128,9 @@ export default class BaseActivityData extends foundry.abstract.DataModel { units: new StringField({ initial: "inst" }), special: new StringField() }), + effects: new ArrayField(new SchemaField({ + id: new DocumentIdField() + })), range: new SchemaField({ value: new FormulaField({ deterministic: true }), units: new StringField(), @@ -156,6 +167,11 @@ export default class BaseActivityData extends foundry.abstract.DataModel { prepareData() { this.name = this.name || game.i18n.localize(this.metadata?.title); this.img = this.img || this.metadata?.img; + const item = this.item; + this.effects.forEach(e => Object.defineProperty(e, "effect", { + get() { return item.effects.get(e.id); }, + configurable: true + })); UsesField.prepareData.call(this, this.getRollData({ deterministic: true })); } diff --git a/module/documents/mixins/pseudo-document.mjs b/module/documents/mixins/pseudo-document.mjs index 0dbef4877b..0155f71aef 100644 --- a/module/documents/mixins/pseudo-document.mjs +++ b/module/documents/mixins/pseudo-document.mjs @@ -159,7 +159,9 @@ export default Base => class extends Base { * @param {RenderOptions} [options] Rendering options. */ render(options) { - for ( const app of this.constructor._apps.get(this.uuid) ?? [] ) app.render(options); + for ( const app of this.constructor._apps.get(this.uuid) ?? [] ) { + app.render({ window: { title: app.title }, ...options }); + } } /* -------------------------------------------- */ diff --git a/templates/activity/effect.hbs b/templates/activity/effect.hbs index 66f7d81821..f50c8b0240 100644 --- a/templates/activity/effect.hbs +++ b/templates/activity/effect.hbs @@ -1,3 +1,3 @@
- TODO: Active effects to apply & effects from specific activity types + {{> "systems/dnd5e/templates/activity/parts/activity-effects.hbs" }}
diff --git a/templates/activity/parts/activity-consumption.hbs b/templates/activity/parts/activity-consumption.hbs index 9b67690189..65a8c13a37 100644 --- a/templates/activity/parts/activity-consumption.hbs +++ b/templates/activity/parts/activity-consumption.hbs @@ -25,7 +25,7 @@ {{/each}} diff --git a/templates/activity/parts/activity-effects.hbs b/templates/activity/parts/activity-effects.hbs new file mode 100644 index 0000000000..6ff9a9d86b --- /dev/null +++ b/templates/activity/parts/activity-effects.hbs @@ -0,0 +1,20 @@ +
+ {{ localize "DND5E.ACTIVITY.FIELDS.effects.label" }} + + {{ selectOptions allEffects }} + + + +
diff --git a/templates/shared/uses-recovery.hbs b/templates/shared/uses-recovery.hbs index 55e0fd74d9..d8f3127144 100644 --- a/templates/shared/uses-recovery.hbs +++ b/templates/shared/uses-recovery.hbs @@ -17,6 +17,6 @@ {{/each}}