- Add SpellCastDialog with delivery/duration/damage configuration - Fix mana cost calculation to match rulebook formula: - Effect-only or 1d6 damage-only = 0 mana - Both damage AND effect = 1 mana base - +1 per extra damage die beyond first - +delivery cost (Touch/Remote/Imbue=0, Cube=1, Area=2) - Duration has no initial cost (Focus requires maintenance) - Add "Include Effect" toggle for damage vs effect choice - Create spell cast chat card template - Add 20+ i18n strings for spell casting UI - Create comprehensive Quench tests for mana calculation - Add Cast Spell macro for testing - Update CLAUDE.md with NoteDiscovery access instructions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
648 lines
20 KiB
JavaScript
648 lines
20 KiB
JavaScript
/**
|
|
* Spell Cast Dialog for Vagabond RPG
|
|
*
|
|
* Extends VagabondRollDialog to handle spell casting configuration:
|
|
* - Spell selection from known spells
|
|
* - Damage dice selection (0 to casting max)
|
|
* - Delivery type selection (filtered to valid types)
|
|
* - Duration type selection (filtered to valid types)
|
|
* - Live mana cost calculation
|
|
* - Focus tracking for Focus duration spells
|
|
*
|
|
* @extends VagabondRollDialog
|
|
*/
|
|
|
|
import VagabondRollDialog from "./base-roll-dialog.mjs";
|
|
import { skillCheck, damageRoll } from "../dice/rolls.mjs";
|
|
|
|
export default class SpellCastDialog extends VagabondRollDialog {
|
|
/**
|
|
* @param {VagabondActor} actor - The actor casting the spell
|
|
* @param {Object} options - Dialog options
|
|
* @param {string} [options.spellId] - Pre-selected spell ID
|
|
*/
|
|
constructor(actor, options = {}) {
|
|
super(actor, options);
|
|
|
|
this.spellId = options.spellId || null;
|
|
|
|
// Casting configuration
|
|
this.castConfig = {
|
|
damageDice: 0,
|
|
delivery: null,
|
|
duration: null,
|
|
includeEffect: true, // Whether to include the spell's effect (beyond damage)
|
|
};
|
|
|
|
// Auto-select first known spell if none specified
|
|
if (!this.spellId) {
|
|
const knownSpells = this._getKnownSpells();
|
|
if (knownSpells.length > 0) {
|
|
this.spellId = knownSpells[0].id;
|
|
}
|
|
}
|
|
|
|
// Initialize cast config from selected spell
|
|
this._initializeCastConfig();
|
|
|
|
// Load automatic favor/hinder for spell casting
|
|
const castingSkill = this._getCastingSkill();
|
|
this.rollConfig.autoFavorHinder = actor.getNetFavorHinder({ skillId: castingSkill });
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Static Properties */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
static DEFAULT_OPTIONS = foundry.utils.mergeObject(
|
|
super.DEFAULT_OPTIONS,
|
|
{
|
|
id: "vagabond-spell-cast-dialog",
|
|
window: {
|
|
title: "VAGABOND.CastSpell",
|
|
icon: "fa-solid fa-wand-sparkles",
|
|
},
|
|
position: {
|
|
width: 400,
|
|
},
|
|
},
|
|
{ inplace: false }
|
|
);
|
|
|
|
/** @override */
|
|
static PARTS = {
|
|
form: {
|
|
template: "systems/vagabond/templates/dialog/spell-cast.hbs",
|
|
},
|
|
};
|
|
|
|
/* -------------------------------------------- */
|
|
/* Getters */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
get title() {
|
|
if (this.spell) {
|
|
return `${game.i18n.localize("VAGABOND.Cast")}: ${this.spell.name}`;
|
|
}
|
|
return game.i18n.localize("VAGABOND.CastSpell");
|
|
}
|
|
|
|
/**
|
|
* Get the currently selected spell.
|
|
* @returns {VagabondItem|null}
|
|
*/
|
|
get spell() {
|
|
if (!this.spellId) return null;
|
|
return this.actor.items.get(this.spellId) || null;
|
|
}
|
|
|
|
/**
|
|
* Get the actor's current mana.
|
|
* @returns {number}
|
|
*/
|
|
get currentMana() {
|
|
return this.actor.system.resources?.mana?.value || 0;
|
|
}
|
|
|
|
/**
|
|
* Get the actor's max mana.
|
|
* @returns {number}
|
|
*/
|
|
get maxMana() {
|
|
return this.actor.system.resources?.mana?.max || 0;
|
|
}
|
|
|
|
/**
|
|
* Get the actor's casting max (max dice in one spell).
|
|
* @returns {number}
|
|
*/
|
|
get castingMax() {
|
|
return this.actor.system.resources?.mana?.castingMax || 3;
|
|
}
|
|
|
|
/**
|
|
* Calculate the current mana cost based on cast config.
|
|
* @returns {number}
|
|
*/
|
|
get manaCost() {
|
|
const spell = this.spell;
|
|
if (!spell) return 0;
|
|
|
|
return spell.system.calculateManaCost({
|
|
damageDice: this.castConfig.damageDice,
|
|
delivery: this.castConfig.delivery,
|
|
duration: this.castConfig.duration,
|
|
includeEffect: this.castConfig.includeEffect,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if the actor can afford to cast the spell.
|
|
* @returns {boolean}
|
|
*/
|
|
get canAfford() {
|
|
return this.currentMana >= this.manaCost;
|
|
}
|
|
|
|
/**
|
|
* Get the casting skill for this spell.
|
|
* @returns {string}
|
|
*/
|
|
_getCastingSkill() {
|
|
const spell = this.spell;
|
|
if (spell?.system.castingSkill) {
|
|
return spell.system.castingSkill;
|
|
}
|
|
// Default to arcana, but could be overridden by class
|
|
return "arcana";
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Helper Methods */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Get all known spells for this actor.
|
|
* @returns {Array<VagabondItem>}
|
|
* @private
|
|
*/
|
|
_getKnownSpells() {
|
|
return this.actor.items.filter((item) => item.type === "spell");
|
|
}
|
|
|
|
/**
|
|
* Initialize cast config from the selected spell's defaults.
|
|
* @private
|
|
*/
|
|
_initializeCastConfig() {
|
|
const spell = this.spell;
|
|
if (!spell) return;
|
|
|
|
// Default to 1 damage die if spell is damaging, 0 otherwise
|
|
this.castConfig.damageDice = spell.system.isDamaging() ? 1 : 0;
|
|
|
|
// Default to first valid delivery type
|
|
const validDelivery = spell.system.getValidDeliveryTypes();
|
|
this.castConfig.delivery = validDelivery[0] || "touch";
|
|
|
|
// Default to first valid duration type
|
|
const validDuration = spell.system.getValidDurationTypes();
|
|
this.castConfig.duration = validDuration[0] || "instant";
|
|
}
|
|
|
|
/**
|
|
* Get the maximum damage dice this spell can use.
|
|
* @returns {number}
|
|
* @private
|
|
*/
|
|
_getMaxDamageDice() {
|
|
const spell = this.spell;
|
|
if (!spell) return 0;
|
|
|
|
// Spell-specific max or actor's casting max
|
|
const spellMax = spell.system.maxDice || 0;
|
|
const castingMax = this.castingMax;
|
|
|
|
// If spell has a specific max, use the lower of spell max and casting max
|
|
if (spellMax > 0) {
|
|
return Math.min(spellMax, castingMax);
|
|
}
|
|
|
|
return castingMax;
|
|
}
|
|
|
|
/**
|
|
* Get the damage formula for the current config.
|
|
* @returns {string}
|
|
* @private
|
|
*/
|
|
_getDamageFormula() {
|
|
const spell = this.spell;
|
|
if (!spell || !spell.system.isDamaging() || this.castConfig.damageDice <= 0) {
|
|
return "";
|
|
}
|
|
|
|
const diceBase = spell.system.damageBase || "d6";
|
|
return `${this.castConfig.damageDice}${diceBase}`;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Data Preparation */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
async _prepareRollContext(_options) {
|
|
const context = {};
|
|
|
|
// Get all known spells for selection
|
|
const knownSpells = this._getKnownSpells();
|
|
context.spells = knownSpells.map((s) => ({
|
|
id: s.id,
|
|
name: s.name,
|
|
img: s.img,
|
|
damageType: s.system.damageType,
|
|
isDamaging: s.system.isDamaging(),
|
|
selected: s.id === this.spellId,
|
|
}));
|
|
|
|
context.hasSpells = knownSpells.length > 0;
|
|
context.selectedSpellId = this.spellId;
|
|
context.spell = this.spell;
|
|
|
|
// Mana info
|
|
context.currentMana = this.currentMana;
|
|
context.maxMana = this.maxMana;
|
|
context.castingMax = this.castingMax;
|
|
context.manaCost = this.manaCost;
|
|
context.canAfford = this.canAfford;
|
|
|
|
// Spell-specific data when a spell is selected
|
|
const spell = this.spell;
|
|
if (spell) {
|
|
// Casting skill
|
|
const castingSkill = this._getCastingSkill();
|
|
const skillConfig = CONFIG.VAGABOND?.skills?.[castingSkill];
|
|
const skillData = this.actor.system.skills?.[castingSkill];
|
|
const statKey = skillConfig?.stat || "reason";
|
|
const statValue = this.actor.system.stats?.[statKey]?.value || 0;
|
|
const trained = skillData?.trained || false;
|
|
|
|
context.castingSkill = castingSkill;
|
|
context.castingSkillLabel = game.i18n.localize(skillConfig?.label || castingSkill);
|
|
context.statLabel = game.i18n.localize(CONFIG.VAGABOND?.stats?.[statKey] || statKey);
|
|
context.statValue = statValue;
|
|
context.trained = trained;
|
|
context.difficulty = trained ? 20 - statValue * 2 : 20 - statValue;
|
|
context.critThreshold = skillData?.critThreshold || 20;
|
|
|
|
// Damage configuration
|
|
context.isDamaging = spell.system.isDamaging();
|
|
context.damageDice = this.castConfig.damageDice;
|
|
context.maxDamageDice = this._getMaxDamageDice();
|
|
context.damageBase = spell.system.damageBase || "d6";
|
|
context.damageType = spell.system.damageType;
|
|
context.damageTypeLabel = game.i18n.localize(
|
|
CONFIG.VAGABOND?.damageTypes?.[spell.system.damageType] || spell.system.damageType
|
|
);
|
|
context.damageFormula = this._getDamageFormula();
|
|
|
|
// Delivery options (filtered to valid types)
|
|
const validDelivery = spell.system.getValidDeliveryTypes();
|
|
context.deliveryOptions = validDelivery.map((type) => {
|
|
const config = CONFIG.VAGABOND?.spellDelivery?.[type] || {};
|
|
return {
|
|
value: type,
|
|
label: game.i18n.localize(config.label || type),
|
|
cost: config.cost || 0,
|
|
selected: type === this.castConfig.delivery,
|
|
};
|
|
});
|
|
|
|
// Duration options (filtered to valid types)
|
|
const validDuration = spell.system.getValidDurationTypes();
|
|
context.durationOptions = validDuration.map((type) => {
|
|
const config = CONFIG.VAGABOND?.spellDuration?.[type] || {};
|
|
return {
|
|
value: type,
|
|
label: game.i18n.localize(config.label || type),
|
|
isFocus: config.focus || false,
|
|
selected: type === this.castConfig.duration,
|
|
};
|
|
});
|
|
|
|
// Current cast config
|
|
context.delivery = this.castConfig.delivery;
|
|
context.duration = this.castConfig.duration;
|
|
|
|
// Effect description
|
|
context.effect = spell.system.effect;
|
|
context.critEffect = spell.system.critEffect;
|
|
context.hasEffect = Boolean(spell.system.effect && spell.system.effect.trim());
|
|
context.includeEffect = this.castConfig.includeEffect;
|
|
|
|
// Focus warning if actor is already focusing
|
|
const currentFocus = this.actor.system.focus?.active || [];
|
|
context.isCurrentlyFocusing = currentFocus.length > 0;
|
|
context.focusedSpells = currentFocus.map((f) => f.spellName);
|
|
context.maxConcurrentFocus = this.actor.system.focus?.maxConcurrent || 1;
|
|
context.canAddFocus = currentFocus.length < context.maxConcurrentFocus;
|
|
context.willRequireFocus = this.castConfig.duration === "focus";
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Event Handlers */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @override */
|
|
_onRender(context, options) {
|
|
super._onRender(context, options);
|
|
|
|
// Spell selection dropdown
|
|
const spellSelect = this.element.querySelector('[name="spellId"]');
|
|
spellSelect?.addEventListener("change", (event) => {
|
|
this.spellId = event.target.value;
|
|
this._initializeCastConfig();
|
|
this.render();
|
|
});
|
|
|
|
// Damage dice input/slider
|
|
const damageDiceInput = this.element.querySelector('[name="damageDice"]');
|
|
damageDiceInput?.addEventListener("input", (event) => {
|
|
this.castConfig.damageDice = parseInt(event.target.value, 10) || 0;
|
|
this.render();
|
|
});
|
|
|
|
// Delivery type dropdown
|
|
const deliverySelect = this.element.querySelector('[name="delivery"]');
|
|
deliverySelect?.addEventListener("change", (event) => {
|
|
this.castConfig.delivery = event.target.value;
|
|
this.render();
|
|
});
|
|
|
|
// Duration type dropdown
|
|
const durationSelect = this.element.querySelector('[name="duration"]');
|
|
durationSelect?.addEventListener("change", (event) => {
|
|
this.castConfig.duration = event.target.value;
|
|
this.render();
|
|
});
|
|
|
|
// Include effect toggle
|
|
const includeEffectToggle = this.element.querySelector('[name="includeEffect"]');
|
|
includeEffectToggle?.addEventListener("change", (event) => {
|
|
this.castConfig.includeEffect = event.target.checked;
|
|
this.render();
|
|
});
|
|
|
|
// Favor/hinder toggles (from parent)
|
|
const favorBtn = this.element.querySelector('[data-action="toggle-favor"]');
|
|
const hinderBtn = this.element.querySelector('[data-action="toggle-hinder"]');
|
|
favorBtn?.addEventListener("click", () => this._onToggleFavor());
|
|
hinderBtn?.addEventListener("click", () => this._onToggleHinder());
|
|
|
|
// Modifier presets
|
|
const presetBtns = this.element.querySelectorAll("[data-modifier-preset]");
|
|
for (const btn of presetBtns) {
|
|
btn.addEventListener("click", (event) => {
|
|
const value = parseInt(event.currentTarget.dataset.modifierPreset, 10);
|
|
this._onModifierPreset(value);
|
|
});
|
|
}
|
|
}
|
|
|
|
/** @override */
|
|
async _executeRoll() {
|
|
const spell = this.spell;
|
|
if (!spell) {
|
|
ui.notifications.warn(game.i18n.localize("VAGABOND.SelectSpellFirst"));
|
|
return;
|
|
}
|
|
|
|
// Check mana cost
|
|
const manaCost = this.manaCost;
|
|
if (!this.canAfford) {
|
|
ui.notifications.warn(
|
|
game.i18n.format("VAGABOND.InsufficientMana", {
|
|
cost: manaCost,
|
|
current: this.currentMana,
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Perform the casting skill check
|
|
const castingSkill = this._getCastingSkill();
|
|
const skillData = this.actor.system.skills?.[castingSkill];
|
|
const skillConfig = CONFIG.VAGABOND?.skills?.[castingSkill];
|
|
const statKey = skillConfig?.stat || "reason";
|
|
const statValue = this.actor.system.stats?.[statKey]?.value || 0;
|
|
const trained = skillData?.trained || false;
|
|
const difficulty = trained ? 20 - statValue * 2 : 20 - statValue;
|
|
const critThreshold = skillData?.critThreshold || 20;
|
|
|
|
const result = await skillCheck(this.actor, castingSkill, {
|
|
difficulty,
|
|
critThreshold,
|
|
favorHinder: this.netFavorHinder,
|
|
modifier: this.rollConfig.modifier,
|
|
});
|
|
|
|
// Roll damage if the cast succeeded and spell deals damage
|
|
let damageResult = null;
|
|
if (result.success && spell.system.isDamaging() && this.castConfig.damageDice > 0) {
|
|
const damageFormula = this._getDamageFormula();
|
|
damageResult = await damageRoll(damageFormula, {
|
|
isCrit: result.isCrit,
|
|
rollData: this.actor.getRollData(),
|
|
});
|
|
}
|
|
|
|
// Spend mana (regardless of success - mana is spent on attempt)
|
|
await this.actor.update({
|
|
"system.resources.mana.value": Math.max(0, this.currentMana - manaCost),
|
|
});
|
|
|
|
// Handle focus duration spells
|
|
if (result.success && this.castConfig.duration === "focus") {
|
|
const currentFocus = this.actor.system.focus?.active || [];
|
|
const maxFocus = this.actor.system.focus?.maxConcurrent || 1;
|
|
|
|
if (currentFocus.length < maxFocus) {
|
|
// Add to focus list
|
|
await this.actor.update({
|
|
"system.focus.active": [
|
|
...currentFocus,
|
|
{
|
|
spellId: spell.id,
|
|
spellName: spell.name,
|
|
target: "", // Could be set via target selection
|
|
manaCostPerRound: 0, // Could be defined per-spell
|
|
requiresSaveCheck: false,
|
|
canBeBroken: true,
|
|
},
|
|
],
|
|
});
|
|
ui.notifications.info(game.i18n.format("VAGABOND.NowFocusing", { spell: spell.name }));
|
|
} else {
|
|
ui.notifications.warn(game.i18n.localize("VAGABOND.FocusLimitReached"));
|
|
}
|
|
}
|
|
|
|
// Send to chat
|
|
await this._sendToChat(result, damageResult);
|
|
}
|
|
|
|
/**
|
|
* Send the spell cast result to chat.
|
|
*
|
|
* @param {VagabondRollResult} result - The casting skill check result
|
|
* @param {Roll|null} damageResult - The damage roll (if applicable)
|
|
* @returns {Promise<ChatMessage>}
|
|
* @private
|
|
*/
|
|
async _sendToChat(result, damageResult) {
|
|
const spell = this.spell;
|
|
const castingSkill = this._getCastingSkill();
|
|
const skillConfig = CONFIG.VAGABOND?.skills?.[castingSkill];
|
|
|
|
// Prepare template data
|
|
const templateData = {
|
|
actor: this.actor,
|
|
spell: {
|
|
id: spell.id,
|
|
name: spell.name,
|
|
img: spell.img,
|
|
effect: spell.system.effect,
|
|
critEffect: spell.system.critEffect,
|
|
damageType: spell.system.damageType,
|
|
damageTypeLabel: game.i18n.localize(
|
|
CONFIG.VAGABOND?.damageTypes?.[spell.system.damageType] || spell.system.damageType
|
|
),
|
|
isDamaging: spell.system.isDamaging(),
|
|
},
|
|
castingSkillLabel: game.i18n.localize(skillConfig?.label || castingSkill),
|
|
delivery: this.castConfig.delivery,
|
|
deliveryLabel: game.i18n.localize(
|
|
CONFIG.VAGABOND?.spellDelivery?.[this.castConfig.delivery]?.label ||
|
|
this.castConfig.delivery
|
|
),
|
|
duration: this.castConfig.duration,
|
|
durationLabel: game.i18n.localize(
|
|
CONFIG.VAGABOND?.spellDuration?.[this.castConfig.duration]?.label ||
|
|
this.castConfig.duration
|
|
),
|
|
isFocus: this.castConfig.duration === "focus",
|
|
manaCost: this.manaCost,
|
|
difficulty: result.difficulty,
|
|
critThreshold: result.critThreshold,
|
|
total: result.total,
|
|
d20Result: result.d20Result,
|
|
favorDie: result.favorDie,
|
|
modifier: this.rollConfig.modifier,
|
|
success: result.success,
|
|
isCrit: result.isCrit,
|
|
isFumble: result.isFumble,
|
|
formula: result.roll.formula,
|
|
netFavorHinder: this.netFavorHinder,
|
|
favorSources: this.rollConfig.autoFavorHinder.favorSources,
|
|
hinderSources: this.rollConfig.autoFavorHinder.hinderSources,
|
|
// Damage info
|
|
hasDamage: !!damageResult,
|
|
damageTotal: damageResult?.total,
|
|
damageFormula: damageResult?.formula,
|
|
damageDice: this.castConfig.damageDice,
|
|
// Effect info
|
|
includeEffect: this.castConfig.includeEffect,
|
|
hasEffect: Boolean(spell.system.effect && spell.system.effect.trim()),
|
|
};
|
|
|
|
// Render the chat card template
|
|
const content = await renderTemplate(
|
|
"systems/vagabond/templates/chat/spell-cast.hbs",
|
|
templateData
|
|
);
|
|
|
|
// Collect all rolls
|
|
const rolls = [result.roll];
|
|
if (damageResult) rolls.push(damageResult);
|
|
|
|
// Create the chat message
|
|
const chatData = {
|
|
user: game.user.id,
|
|
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
|
content,
|
|
rolls,
|
|
sound: CONFIG.sounds.dice,
|
|
};
|
|
|
|
return ChatMessage.create(chatData);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Static Methods */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Create and render a spell cast dialog.
|
|
*
|
|
* @param {VagabondActor} actor - The actor casting the spell
|
|
* @param {string} [spellId] - Optional pre-selected spell ID
|
|
* @param {Object} [options] - Additional options
|
|
* @returns {Promise<SpellCastDialog>}
|
|
*/
|
|
static async prompt(actor, spellId = null, options = {}) {
|
|
return this.create(actor, { ...options, spellId });
|
|
}
|
|
|
|
/**
|
|
* Perform a quick spell cast without showing the dialog.
|
|
* Uses default options for delivery and duration.
|
|
*
|
|
* @param {VagabondActor} actor - The actor casting the spell
|
|
* @param {VagabondItem} spell - The spell to cast
|
|
* @param {Object} [options] - Cast options
|
|
* @returns {Promise<Object>} Cast and damage results
|
|
*/
|
|
static async quickCast(actor, spell, options = {}) {
|
|
// Create temporary dialog for calculations
|
|
const tempDialog = new this(actor, { spellId: spell.id });
|
|
|
|
// Apply any option overrides
|
|
if (options.damageDice !== undefined) {
|
|
tempDialog.castConfig.damageDice = options.damageDice;
|
|
}
|
|
if (options.delivery) {
|
|
tempDialog.castConfig.delivery = options.delivery;
|
|
}
|
|
if (options.duration) {
|
|
tempDialog.castConfig.duration = options.duration;
|
|
}
|
|
|
|
// Check mana
|
|
if (!tempDialog.canAfford) {
|
|
ui.notifications.warn(
|
|
game.i18n.format("VAGABOND.InsufficientMana", {
|
|
cost: tempDialog.manaCost,
|
|
current: tempDialog.currentMana,
|
|
})
|
|
);
|
|
return null;
|
|
}
|
|
|
|
// Get automatic favor/hinder
|
|
const castingSkill = tempDialog._getCastingSkill();
|
|
const autoFavorHinder = actor.getNetFavorHinder({ skillId: castingSkill });
|
|
|
|
// Perform the skill check
|
|
const result = await skillCheck(actor, castingSkill, {
|
|
favorHinder: options.favorHinder ?? autoFavorHinder.net,
|
|
modifier: options.modifier || 0,
|
|
});
|
|
|
|
// Roll damage if applicable
|
|
let damageResult = null;
|
|
if (result.success && spell.system.isDamaging() && tempDialog.castConfig.damageDice > 0) {
|
|
const damageFormula = tempDialog._getDamageFormula();
|
|
damageResult = await damageRoll(damageFormula, {
|
|
isCrit: result.isCrit,
|
|
rollData: actor.getRollData(),
|
|
});
|
|
}
|
|
|
|
// Spend mana
|
|
await actor.update({
|
|
"system.resources.mana.value": Math.max(0, tempDialog.currentMana - tempDialog.manaCost),
|
|
});
|
|
|
|
// Send to chat
|
|
tempDialog.rollConfig.autoFavorHinder = autoFavorHinder;
|
|
await tempDialog._sendToChat(result, damageResult);
|
|
|
|
return { cast: result, damage: damageResult };
|
|
}
|
|
}
|