From 1d7574afa7b42b0ec1f8eab2653a972d955a8162 Mon Sep 17 00:00:00 2001 From: PwQt Date: Mon, 14 Oct 2024 19:43:26 +0200 Subject: [PATCH 1/2] #185 implement summoning --- src/scripts/magic-item-helpers.js | 4 ++ .../AbstractOwnedMagicItemEntry.js | 19 ++++++ .../OwnedMagicItemSpell.js | 66 ++++++++++++++----- .../magic-item-summon-dialog-helper.js | 9 +++ src/templates/magic-item-summon-dialog.hbs | 25 +++++++ 5 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 src/scripts/magic-item-summon-dialog-helper.js create mode 100644 src/templates/magic-item-summon-dialog.hbs diff --git a/src/scripts/magic-item-helpers.js b/src/scripts/magic-item-helpers.js index 8816f26..f634aae 100644 --- a/src/scripts/magic-item-helpers.js +++ b/src/scripts/magic-item-helpers.js @@ -17,6 +17,10 @@ export class MagicItemHelpers { return game.settings.get(CONSTANTS.MODULE_ID, "scaleSpellDamage"); } + static canSummon() { + return game.user.can("TOKEN_CREATE") && (game.user.isGM || game.settings.get("dnd5e", "allowSummoning")); + } + static numeric = function (value, fallback) { // if ($.isNumeric(value)) { // return parseInt(value); diff --git a/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js b/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js index 0f0e804..10ccc1a 100644 --- a/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js +++ b/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js @@ -1,6 +1,7 @@ import CONSTANTS from "../constants/constants"; import Logger from "../lib/Logger"; import { RetrieveHelpers } from "../lib/retrieve-helpers"; +import { MagicItemSummonDialogHelper } from "../magic-item-summon-dialog-helper"; export class AbstractOwnedMagicItemEntry { constructor(magicItem, item) { @@ -165,6 +166,24 @@ export class AbstractOwnedMagicItemEntry { x.render(true); } + async askSummonningMessage(summonList, creatureTypes) { + const title = game.i18n.localize("MAGICITEMS.ToggleActiveEffectDialogTitle"); + let html = await renderTemplate( + `modules/${CONSTANTS.MODULE_ID}/templates/magic-item-summon-dialog.hbs`, + new MagicItemSummonDialogHelper(true, summonList, creatureTypes), + ); + let dialog = await foundry.applications.api.DialogV2.prompt({ + window: { title: title }, + content: html, + modal: true, + ok: { + label: "Confirm Summon", + callback: (event, button, dialog) => button.form.elements, + }, + }); + return dialog; + } + computeSaveDC(item) { const data = this.magicItem.actor.system; data.attributes.spelldc = data.attributes.spellcasting ? data.abilities[data.attributes.spellcasting].dc : 10; diff --git a/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js b/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js index 4b96ca3..ec80201 100644 --- a/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js +++ b/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js @@ -2,6 +2,7 @@ import Logger from "../lib/Logger"; import { MagicItemUpcastDialog } from "../magicitemupcastdialog"; import { AbstractOwnedMagicItemEntry } from "./AbstractOwnedMagicItemEntry"; import { MagicItemHelpers } from "../magic-item-helpers"; +import { RetrieveHelpers } from "../lib/retrieve-helpers"; export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { async roll() { @@ -48,7 +49,45 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { let proceed = async () => { let spell = this.ownedItem; let clonedOwnedItem = this.ownedItem; - let configSpellUpcast = spell.system.level; + let itemUseConfiguration = {}; + + if ( + MagicItemHelpers.canSummon() && + (spell.system.summons?.creatureTypes?.length > 0 || spell.system.summons?.profiles?.length > 0) + ) { + const summonProfilesSync = (profiles) => { + const mapProfiles = profiles.map((profile) => { + const name = profile.name?.length ? profile.name : RetrieveHelpers.getActorSync(profile.uuid).name; + const obj = { + key: profile._id, + value: name, + }; + return obj; + }); + return mapProfiles; + }; + const summonProfiles = summonProfilesSync(spell.system.summons?.profiles); + + const creatureTypes = new Array(); + for (const type of spell.system.summons?.creatureTypes) { + const name = CONFIG.DND5E.creatureTypes[type]; + const obj = { + key: type, + value: name.label, + }; + creatureTypes.push(obj); + } + + let summonningMessageResult = await this.askSummonningMessage(summonProfiles.flat(), creatureTypes.flat()); + + foundry.utils.mergeObject(itemUseConfiguration, { + createSummons: summonningMessageResult.createSummons.value === "on", + summonsProfile: summonningMessageResult.summonsProfile.value, + summonsOptions: { + creatureType: summonningMessageResult.creatureType.value, + }, + }); + } if (spell.system.level === 0 && !MagicItemHelpers.isLevelScalingSettingOn()) { spell = spell.clone({ "system.scaling": "none" }, { keepId: true }); @@ -57,7 +96,9 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { } if (upcastLevel !== spell.system.level) { - configSpellUpcast = upcastLevel; + foundry.utils.mergeObject(itemUseConfiguration, { + slotLevel: upcastLevel, + }); } if (spell.effects?.size > 0 && !MagicItemHelpers.isMidiItemEffectWorkflowOn()) { @@ -65,18 +106,13 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { spell.prepareFinalAttributes(); } - let chatData = await spell.use( - { - slotLevel: configSpellUpcast, + let chatData = await spell.use(itemUseConfiguration, { + configureDialog: false, + createMessage: true, + flags: { + "dnd5e.itemData": clonedOwnedItem.toJSON(), }, - { - configureDialog: false, - createMessage: true, - flags: { - "dnd5e.itemData": clonedOwnedItem.toJSON(), - }, - }, - ); + }); if (chatData) { await this.consume(consumption); if (!this.magicItem.isDestroyed) { @@ -93,8 +129,8 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { if (this.hasCharges(consumption)) { await proceed(); } else { - this.showNoChargesMessage(() => { - proceed(); + this.showNoChargesMessage(async () => { + await proceed(); }); } } diff --git a/src/scripts/magic-item-summon-dialog-helper.js b/src/scripts/magic-item-summon-dialog-helper.js new file mode 100644 index 0000000..7fdbf52 --- /dev/null +++ b/src/scripts/magic-item-summon-dialog-helper.js @@ -0,0 +1,9 @@ +import CONSTANTS from "./constants/constants"; + +export class MagicItemSummonDialogHelper { + constructor(createSummons, summonProfiles, creatureTypes) { + this.createSummons = createSummons; + this.summonProfiles = summonProfiles; + this.creatureTypes = creatureTypes; + } +} diff --git a/src/templates/magic-item-summon-dialog.hbs b/src/templates/magic-item-summon-dialog.hbs new file mode 100644 index 0000000..0cf43b0 --- /dev/null +++ b/src/templates/magic-item-summon-dialog.hbs @@ -0,0 +1,25 @@ +
+
+ +
+ +
+
+
+ +
+ +
+
+
\ No newline at end of file From d41200b2c0e9a3e2ebc85f0df1345f509a6b7a13 Mon Sep 17 00:00:00 2001 From: PwQt Date: Thu, 17 Oct 2024 18:58:43 +0200 Subject: [PATCH 2/2] #185 Add localization files + fixes --- src/languages/en.json | 4 +- src/scripts/magic-item-helpers.js | 42 +++++++++++++++++ .../AbstractOwnedMagicItemEntry.js | 14 +++--- .../OwnedMagicItemSpell.js | 45 ++++++------------- .../magic-item-summon-dialog-helper.js | 9 ---- src/templates/magic-item-summon-dialog.hbs | 36 ++++++++++----- 6 files changed, 93 insertions(+), 57 deletions(-) delete mode 100644 src/scripts/magic-item-summon-dialog-helper.js diff --git a/src/languages/en.json b/src/languages/en.json index 0717956..be65670 100644 --- a/src/languages/en.json +++ b/src/languages/en.json @@ -113,5 +113,7 @@ "MAGICITEMS.ToggleActiveEffectDialogYes": "Yes", "MAGICITEMS.ToggleActiveEffectDialogNo": "No", "MAGICITEMS.ToggleActiveEffectError": "An error occured while adding active effect - please check console.", - "MAGICITEMS.ShowChargesMessage": "The magic item \"{name}\" has {chargesLeft} charges out of {chargesMax} left." + "MAGICITEMS.ShowChargesMessage": "The magic item \"{name}\" has {chargesLeft} charges out of {chargesMax} left.", + "MAGICITEMS.SummoningDialogTitle": "Summoning Options", + "MAGICITEMS.SummoningDialogButton": "Confirm summon" } diff --git a/src/scripts/magic-item-helpers.js b/src/scripts/magic-item-helpers.js index f634aae..b36a1ba 100644 --- a/src/scripts/magic-item-helpers.js +++ b/src/scripts/magic-item-helpers.js @@ -153,4 +153,46 @@ export class MagicItemHelpers { } } } + + /** + * Create details on the summoning profiles and other related options. + * Method fetched from D&D5e ability-use-dialog.mjs + * @param {Item5e} item The item. + * @returns {{ profiles: object, creatureTypes: object }|null} + */ + static createSummoningOptions(item) { + const summons = item.system.summons; + if (!summons?.profiles.length) return null; + const options = { mode: summons.mode, createSummons: true }; + const rollData = item.getRollData(); + const level = summons.relevantLevel; + options.profiles = Object.fromEntries( + summons.profiles + .map((profile) => { + if (!summons.mode && !fromUuidSync(profile.uuid)) return null; + const withinRange = (profile.level.min ?? -Infinity) <= level && level <= (profile.level.max ?? Infinity); + if (!withinRange) return null; + return [profile._id, summons.getProfileLabel(profile, rollData)]; + }) + .filter((f) => f), + ); + if (Object.values(options.profiles).every((p) => p.startsWith("1 × "))) { + Object.entries(options.profiles).forEach(([k, v]) => (options.profiles[k] = v.replace("1 × ", ""))); + } + if (Object.values(options.profiles).length <= 1) { + options.profile = Object.keys(options.profiles)[0]; + options.profiles = null; + } + if (summons.creatureSizes.size > 1) + options.creatureSizes = summons.creatureSizes.reduce((obj, k) => { + obj[k] = CONFIG.DND5E.actorSizes[k]?.label; + return obj; + }, {}); + if (summons.creatureTypes.size > 1) + options.creatureTypes = summons.creatureTypes.reduce((obj, k) => { + obj[k] = CONFIG.DND5E.creatureTypes[k]?.label; + return obj; + }, {}); + return options; + } } diff --git a/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js b/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js index 10ccc1a..ef2be01 100644 --- a/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js +++ b/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js @@ -1,7 +1,6 @@ import CONSTANTS from "../constants/constants"; import Logger from "../lib/Logger"; import { RetrieveHelpers } from "../lib/retrieve-helpers"; -import { MagicItemSummonDialogHelper } from "../magic-item-summon-dialog-helper"; export class AbstractOwnedMagicItemEntry { constructor(magicItem, item) { @@ -166,18 +165,21 @@ export class AbstractOwnedMagicItemEntry { x.render(true); } - async askSummonningMessage(summonList, creatureTypes) { - const title = game.i18n.localize("MAGICITEMS.ToggleActiveEffectDialogTitle"); + async askSummonningMessage(summonOptions) { let html = await renderTemplate( `modules/${CONSTANTS.MODULE_ID}/templates/magic-item-summon-dialog.hbs`, - new MagicItemSummonDialogHelper(true, summonList, creatureTypes), + summonOptions, ); let dialog = await foundry.applications.api.DialogV2.prompt({ - window: { title: title }, + window: { + title: game.i18n.localize("MAGICITEMS.SummoningDialogTitle"), + }, content: html, modal: true, + rejectClose: false, ok: { - label: "Confirm Summon", + label: game.i18n.localize("MAGICITEMS.SummoningDialogButton"), + icon: "fas fa-wand-magic-sparkles", callback: (event, button, dialog) => button.form.elements, }, }); diff --git a/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js b/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js index ec80201..67dbf1e 100644 --- a/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js +++ b/src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js @@ -51,42 +51,25 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { let clonedOwnedItem = this.ownedItem; let itemUseConfiguration = {}; + const sOptions = MagicItemHelpers.createSummoningOptions(spell); if ( MagicItemHelpers.canSummon() && - (spell.system.summons?.creatureTypes?.length > 0 || spell.system.summons?.profiles?.length > 0) + (spell.system.summons?.creatureTypes?.length > 1 || spell.system.summons?.profiles?.length > 1) ) { - const summonProfilesSync = (profiles) => { - const mapProfiles = profiles.map((profile) => { - const name = profile.name?.length ? profile.name : RetrieveHelpers.getActorSync(profile.uuid).name; - const obj = { - key: profile._id, - value: name, - }; - return obj; + const summoningDialogResult = await this.askSummonningMessage(sOptions); + if (summoningDialogResult) { + foundry.utils.mergeObject(itemUseConfiguration, { + createSummons: summoningDialogResult.createSummons?.value === "on", + summonsProfile: summoningDialogResult.summonsProfile?.value, + summonsOptions: { + creatureType: summoningDialogResult.creatureType?.value, + creatureSize: summoningDialogResult.creatureSize?.value, + }, }); - return mapProfiles; - }; - const summonProfiles = summonProfilesSync(spell.system.summons?.profiles); - - const creatureTypes = new Array(); - for (const type of spell.system.summons?.creatureTypes) { - const name = CONFIG.DND5E.creatureTypes[type]; - const obj = { - key: type, - value: name.label, - }; - creatureTypes.push(obj); + } else { + Logger.info(`The summoning dialog has been dismissed, not using the item.`); + return; } - - let summonningMessageResult = await this.askSummonningMessage(summonProfiles.flat(), creatureTypes.flat()); - - foundry.utils.mergeObject(itemUseConfiguration, { - createSummons: summonningMessageResult.createSummons.value === "on", - summonsProfile: summonningMessageResult.summonsProfile.value, - summonsOptions: { - creatureType: summonningMessageResult.creatureType.value, - }, - }); } if (spell.system.level === 0 && !MagicItemHelpers.isLevelScalingSettingOn()) { diff --git a/src/scripts/magic-item-summon-dialog-helper.js b/src/scripts/magic-item-summon-dialog-helper.js deleted file mode 100644 index 7fdbf52..0000000 --- a/src/scripts/magic-item-summon-dialog-helper.js +++ /dev/null @@ -1,9 +0,0 @@ -import CONSTANTS from "./constants/constants"; - -export class MagicItemSummonDialogHelper { - constructor(createSummons, summonProfiles, creatureTypes) { - this.createSummons = createSummons; - this.summonProfiles = summonProfiles; - this.creatureTypes = creatureTypes; - } -} diff --git a/src/templates/magic-item-summon-dialog.hbs b/src/templates/magic-item-summon-dialog.hbs index 0cf43b0..f4a3f1e 100644 --- a/src/templates/magic-item-summon-dialog.hbs +++ b/src/templates/magic-item-summon-dialog.hbs @@ -1,25 +1,41 @@
+ {{#if (ne createSummons null)}}
+ {{#if profiles}}
- + {{ selectOptions profiles selected=summonsProfile }}
+ {{else}} + + {{/if}}
+ + {{#if creatureSizes}}
- + +
+ +
+
+ {{/if}} + + {{#if creatureTypes}} +
+
+ {{/if}} + {{/if}}
\ No newline at end of file