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 c1a0b7b..00cfbad 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); @@ -149,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 0f0e804..ef2be01 100644 --- a/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js +++ b/src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js @@ -165,6 +165,27 @@ export class AbstractOwnedMagicItemEntry { x.render(true); } + async askSummonningMessage(summonOptions) { + let html = await renderTemplate( + `modules/${CONSTANTS.MODULE_ID}/templates/magic-item-summon-dialog.hbs`, + summonOptions, + ); + let dialog = await foundry.applications.api.DialogV2.prompt({ + window: { + title: game.i18n.localize("MAGICITEMS.SummoningDialogTitle"), + }, + content: html, + modal: true, + rejectClose: false, + ok: { + label: game.i18n.localize("MAGICITEMS.SummoningDialogButton"), + icon: "fas fa-wand-magic-sparkles", + 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..67dbf1e 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,28 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { let proceed = async () => { let spell = this.ownedItem; let clonedOwnedItem = this.ownedItem; - let configSpellUpcast = spell.system.level; + let itemUseConfiguration = {}; + + const sOptions = MagicItemHelpers.createSummoningOptions(spell); + if ( + MagicItemHelpers.canSummon() && + (spell.system.summons?.creatureTypes?.length > 1 || spell.system.summons?.profiles?.length > 1) + ) { + 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, + }, + }); + } else { + Logger.info(`The summoning dialog has been dismissed, not using the item.`); + return; + } + } if (spell.system.level === 0 && !MagicItemHelpers.isLevelScalingSettingOn()) { spell = spell.clone({ "system.scaling": "none" }, { keepId: true }); @@ -57,7 +79,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 +89,13 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry { spell.prepareFinalAttributes(); } - let chatData = await spell.use( - { - slotLevel: configSpellUpcast, - }, - { - configureDialog: false, - createMessage: true, - flags: { - "dnd5e.itemData": clonedOwnedItem.toJSON(), - }, + let chatData = await spell.use(itemUseConfiguration, { + configureDialog: false, + createMessage: true, + flags: { + "dnd5e.itemData": clonedOwnedItem.toJSON(), }, - ); + }); if (chatData) { await this.consume(consumption); if (!this.magicItem.isDestroyed) { @@ -93,8 +112,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/templates/magic-item-summon-dialog.hbs b/src/templates/magic-item-summon-dialog.hbs new file mode 100644 index 0000000..f4a3f1e --- /dev/null +++ b/src/templates/magic-item-summon-dialog.hbs @@ -0,0 +1,41 @@ +
\ No newline at end of file