Skip to content

Commit

Permalink
Implementation of Summonning (#187)
Browse files Browse the repository at this point in the history
  • Loading branch information
PwQt authored Oct 17, 2024
2 parents d7c854a + d41200b commit ed520e0
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 16 deletions.
4 changes: 3 additions & 1 deletion src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
46 changes: 46 additions & 0 deletions src/scripts/magic-item-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
}
21 changes: 21 additions & 0 deletions src/scripts/magic-item-owned-entry/AbstractOwnedMagicItemEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
49 changes: 34 additions & 15 deletions src/scripts/magic-item-owned-entry/OwnedMagicItemSpell.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 });
Expand All @@ -57,26 +79,23 @@ 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()) {
spell = spell.clone({ effects: {} }, { keepId: true });
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) {
Expand All @@ -93,8 +112,8 @@ export class OwnedMagicItemSpell extends AbstractOwnedMagicItemEntry {
if (this.hasCharges(consumption)) {
await proceed();
} else {
this.showNoChargesMessage(() => {
proceed();
this.showNoChargesMessage(async () => {
await proceed();
});
}
}
Expand Down
41 changes: 41 additions & 0 deletions src/templates/magic-item-summon-dialog.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<form id="summon-form">
{{#if (ne createSummons null)}}
<div class="form-group">
<label class="checkbox">
<input type="checkbox" name="createSummons" {{ checked createSummons }}>
{{ localize "DND5E.Summoning.Action.Place" }}
</label>
{{#if profiles}}
<div class="form-fields">
<select name="summonsProfile" aria-label="{{ localize 'DND5E.Summoning.Profile.Label' }}">
{{ selectOptions profiles selected=summonsProfile }}
</select>
</div>
{{else}}
<input type="hidden" name="summonsProfile" value="{{ profile }}">
{{/if}}
</div>

{{#if creatureSizes}}
<div class="form-group">
<label>{{ localize "DND5E.Size" }}</label>
<div class="form-fields">
<select name="creatureSize">
{{ selectOptions creatureSizes }}
</select>
</div>
</div>
{{/if}}

{{#if creatureTypes}}
<div class="form-group">
<label>{{ localize "DND5E.CreatureType" }}</label>
<div class="form-fields">
<select name="creatureType">
{{ selectOptions creatureTypes }}
</select>
</div>
</div>
{{/if}}
{{/if}}
</form>

0 comments on commit ed520e0

Please sign in to comment.