forked from GitHub-Mirrors/foundry-sw5e

DND5e Core 1.3.5 modded to SW5e System Combining with DND5e Core 1.3.2 to see one big commit since last core update DND5e Core 1.3.2 modded to SW5e System
181 lines
7 KiB
JavaScript
181 lines
7 KiB
JavaScript
/**
|
|
* A type of Roll specific to a damage (or healing) roll in the 5e system.
|
|
* @param {string} formula The string formula to parse
|
|
* @param {object} data The data object against which to parse attributes within the formula
|
|
* @param {object} [options={}] Extra optional arguments which describe or modify the DamageRoll
|
|
* @param {number} [options.criticalBonusDice=0] A number of bonus damage dice that are added for critical hits
|
|
* @param {number} [options.criticalMultiplier=2] A critical hit multiplier which is applied to critical hits
|
|
* @param {boolean} [options.multiplyNumeric=false] Multiply numeric terms by the critical multiplier
|
|
* @param {boolean} [options.powerfulCritical=false] Apply the "powerful criticals" house rule to critical hits
|
|
*
|
|
*/
|
|
export default class DamageRoll extends Roll {
|
|
constructor(formula, data, options) {
|
|
super(formula, data, options);
|
|
// For backwards compatibility, skip rolls which do not have the "critical" option defined
|
|
if ( this.options.critical !== undefined ) this.configureDamage();
|
|
}
|
|
|
|
/**
|
|
* The HTML template path used to configure evaluation of this Roll
|
|
* @type {string}
|
|
*/
|
|
static EVALUATION_TEMPLATE = "systems/sw5e/templates/chat/roll-dialog.html";
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* A convenience reference for whether this DamageRoll is a critical hit
|
|
* @type {boolean}
|
|
*/
|
|
get isCritical() {
|
|
return this.options.critical;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Damage Roll Methods */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Apply optional modifiers which customize the behavior of the d20term
|
|
* @private
|
|
*/
|
|
configureDamage() {
|
|
let flatBonus = 0;
|
|
for ( let [i, term] of this.terms.entries() ) {
|
|
|
|
// Multiply dice terms
|
|
if ( term instanceof DiceTerm ) {
|
|
term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back
|
|
term.number = term.options.baseNumber;
|
|
if ( this.isCritical ) {
|
|
let cm = this.options.criticalMultiplier ?? 2;
|
|
|
|
// Powerful critical - maximize damage and reduce the multiplier by 1
|
|
if ( this.options.powerfulCritical ) {
|
|
flatBonus += (term.number * term.faces);
|
|
cm = Math.max(1, cm-1);
|
|
}
|
|
|
|
// Alter the damage term
|
|
let cb = (this.options.criticalBonusDice && (i === 0)) ? this.options.criticalBonusDice : 0;
|
|
term.alter(cm, cb);
|
|
term.options.critical = true;
|
|
}
|
|
|
|
}
|
|
|
|
// Multiply numeric terms
|
|
else if ( this.options.multiplyNumeric && (term instanceof NumericTerm) ) {
|
|
term.options.baseNumber = term.options.baseNumber ?? term.number; // Reset back
|
|
term.number = term.options.baseNumber;
|
|
if ( this.isCritical ) {
|
|
term.number *= (this.options.criticalMultiplier ?? 2);
|
|
term.options.critical = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add powerful critical bonus
|
|
if ( this.options.powerfulCritical && (flatBonus > 0) ) {
|
|
this.terms.push(new OperatorTerm({operator: "+"}));
|
|
this.terms.push(new NumericTerm({number: flatBonus}, {flavor: game.i18n.localize("SW5E.PowerfulCritical")}));
|
|
}
|
|
|
|
// Re-compile the underlying formula
|
|
this._formula = this.constructor.getFormula(this.terms);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
toMessage(messageData={}, options={}) {
|
|
messageData.flavor = messageData.flavor || this.options.flavor;
|
|
if ( this.isCritical ) {
|
|
const label = game.i18n.localize("SW5E.CriticalHit");
|
|
messageData.flavor = messageData.flavor ? `${messageData.flavor} (${label})` : label;
|
|
}
|
|
options.rollMode = options.rollMode ?? this.options.rollMode;
|
|
return super.toMessage(messageData, options);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Configuration Dialog */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Create a Dialog prompt used to configure evaluation of an existing D20Roll instance.
|
|
* @param {object} data Dialog configuration data
|
|
* @param {string} [data.title] The title of the shown dialog window
|
|
* @param {number} [data.defaultRollMode] The roll mode that the roll mode select element should default to
|
|
* @param {string} [data.defaultCritical] Should critical be selected as default
|
|
* @param {string} [data.template] A custom path to an HTML template to use instead of the default
|
|
* @param {boolean} [data.allowCritical=true] Allow critical hit to be chosen as a possible damage mode
|
|
* @param {object} options Additional Dialog customization options
|
|
* @returns {Promise<D20Roll|null>} A resulting D20Roll object constructed with the dialog, or null if the dialog was closed
|
|
*/
|
|
async configureDialog({title, defaultRollMode, defaultCritical=false, template, allowCritical=true}={}, options={}) {
|
|
|
|
// Render the Dialog inner HTML
|
|
const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, {
|
|
formula: `${this.formula} + @bonus`,
|
|
defaultRollMode,
|
|
rollModes: CONFIG.Dice.rollModes,
|
|
});
|
|
|
|
// Create the Dialog window and await submission of the form
|
|
return new Promise(resolve => {
|
|
new Dialog({
|
|
title,
|
|
content,
|
|
buttons: {
|
|
critical: {
|
|
condition: allowCritical,
|
|
label: game.i18n.localize("SW5E.CriticalHit"),
|
|
callback: html => resolve(this._onDialogSubmit(html, true))
|
|
},
|
|
normal: {
|
|
label: game.i18n.localize(allowCritical ? "SW5E.Normal" : "SW5E.Roll"),
|
|
callback: html => resolve(this._onDialogSubmit(html, false))
|
|
}
|
|
},
|
|
default: defaultCritical ? "critical" : "normal",
|
|
close: () => resolve(null)
|
|
}, options).render(true);
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle submission of the Roll evaluation configuration Dialog
|
|
* @param {jQuery} html The submitted dialog content
|
|
* @param {boolean} isCritical Is the damage a critical hit?
|
|
* @private
|
|
*/
|
|
_onDialogSubmit(html, isCritical) {
|
|
const form = html[0].querySelector("form");
|
|
|
|
// Append a situational bonus term
|
|
if ( form.bonus.value ) {
|
|
const bonus = new Roll(form.bonus.value, this.data);
|
|
if ( !(bonus.terms[0] instanceof OperatorTerm) ) this.terms.push(new OperatorTerm({operator: "+"}));
|
|
this.terms = this.terms.concat(bonus.terms);
|
|
}
|
|
|
|
// Apply advantage or disadvantage
|
|
this.options.critical = isCritical;
|
|
this.options.rollMode = form.rollMode.value;
|
|
this.configureDamage();
|
|
return this;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
static fromData(data) {
|
|
const roll = super.fromData(data);
|
|
roll._formula = this.getFormula(roll.terms);
|
|
return roll;
|
|
}
|
|
}
|