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
215 lines
8.5 KiB
JavaScript
215 lines
8.5 KiB
JavaScript
/**
|
|
* A type of Roll specific to a d20-based check, save, or attack 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 D20Roll
|
|
* @param {number} [options.advantageMode] What advantage modifier to apply to the roll (none, advantage, disadvantage)
|
|
* @param {number} [options.critical] The value of d20 result which represents a critical success
|
|
* @param {number} [options.fumble] The value of d20 result which represents a critical failure
|
|
* @param {(number)} [options.targetValue] Assign a target value against which the result of this roll should be compared
|
|
* @param {boolean} [options.elvenAccuracy=false] Allow Elven Accuracy to modify this roll?
|
|
* @param {boolean} [options.halflingLucky=false] Allow Halfling Luck to modify this roll?
|
|
* @param {boolean} [options.reliableTalent=false] Allow Reliable Talent to modify this roll?
|
|
*/
|
|
export default class D20Roll extends Roll {
|
|
constructor(formula, data, options) {
|
|
super(formula, data, options);
|
|
if ( !((this.terms[0] instanceof Die) && (this.terms[0].faces === 20)) ) {
|
|
throw new Error(`Invalid D20Roll formula provided ${this._formula}`);
|
|
}
|
|
this.configureModifiers();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Advantage mode of a 5e d20 roll
|
|
* @enum {number}
|
|
*/
|
|
static ADV_MODE = {
|
|
NORMAL: 0,
|
|
ADVANTAGE: 1,
|
|
DISADVANTAGE: -1,
|
|
}
|
|
|
|
/**
|
|
* 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 D20Roll has advantage
|
|
* @type {boolean}
|
|
*/
|
|
get hasAdvantage() {
|
|
return this.options.advantageMode === D20Roll.ADV_MODE.ADVANTAGE;
|
|
}
|
|
|
|
/**
|
|
* A convenience reference for whether this D20Roll has disadvantage
|
|
* @type {boolean}
|
|
*/
|
|
get hasDisadvantage() {
|
|
return this.options.advantageMode === D20Roll.ADV_MODE.DISADVANTAGE;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* D20 Roll Methods */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Apply optional modifiers which customize the behavior of the d20term
|
|
* @private
|
|
*/
|
|
configureModifiers() {
|
|
const d20 = this.terms[0];
|
|
d20.modifiers = [];
|
|
|
|
// Halfling Lucky
|
|
if ( this.options.halflingLucky ) d20.modifiers.push("r1=1");
|
|
|
|
// Reliable Talent
|
|
if ( this.options.reliableTalent ) d20.modifiers.push("min10");
|
|
|
|
// Handle Advantage or Disadvantage
|
|
if ( this.hasAdvantage ) {
|
|
d20.number = this.options.elvenAccuracy ? 3 : 2;
|
|
d20.modifiers.push("kh");
|
|
d20.options.advantage = true;
|
|
}
|
|
else if ( this.hasDisadvantage ) {
|
|
d20.number = 2;
|
|
d20.modifiers.push("kl");
|
|
d20.options.disadvantage = true;
|
|
}
|
|
else d20.number = 1;
|
|
|
|
// Assign critical and fumble thresholds
|
|
if ( this.options.critical ) d20.options.critical = this.options.critical;
|
|
if ( this.options.fumble ) d20.options.fumble = this.options.fumble;
|
|
if ( this.options.targetValue ) d20.options.target = this.options.targetValue;
|
|
|
|
// Re-compile the underlying formula
|
|
this._formula = this.constructor.getFormula(this.terms);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
async toMessage(messageData={}, options={}) {
|
|
|
|
// Evaluate the roll now so we have the results available to determine whether reliable talent came into play
|
|
if ( !this._evaluated ) await this.evaluate({async: true});
|
|
|
|
// Add appropriate advantage mode message flavor and sw5e roll flags
|
|
messageData.flavor = messageData.flavor || this.options.flavor;
|
|
if ( this.hasAdvantage ) messageData.flavor += ` (${game.i18n.localize("SW5E.Advantage")})`;
|
|
else if ( this.hasDisadvantage ) messageData.flavor += ` (${game.i18n.localize("SW5E.Disadvantage")})`;
|
|
|
|
// Add reliable talent to the d20-term flavor text if it applied
|
|
if ( this.options.reliableTalent ) {
|
|
const d20 = this.dice[0];
|
|
const isRT = d20.results.every(r => !r.active || (r.result < 10));
|
|
const label = `(${game.i18n.localize("SW5E.FlagsReliableTalent")})`;
|
|
if ( isRT ) d20.options.flavor = d20.options.flavor ? `${d20.options.flavor} (${label})` : label;
|
|
}
|
|
|
|
// Record the preferred rollMode
|
|
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 {number} [data.defaultAction] The button marked as default
|
|
* @param {boolean} [data.chooseModifier] Choose which ability modifier should be applied to the roll?
|
|
* @param {string} [data.defaultAbility] For tool rolls, the default ability modifier applied to the roll
|
|
* @param {string} [data.template] A custom path to an HTML template to use instead of the default
|
|
* @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, defaultAction=D20Roll.ADV_MODE.NORMAL, chooseModifier=false, defaultAbility, template}={}, options={}) {
|
|
|
|
// Render the Dialog inner HTML
|
|
const content = await renderTemplate(template ?? this.constructor.EVALUATION_TEMPLATE, {
|
|
formula: `${this.formula} + @bonus`,
|
|
defaultRollMode,
|
|
rollModes: CONFIG.Dice.rollModes,
|
|
chooseModifier,
|
|
defaultAbility,
|
|
abilities: CONFIG.SW5E.abilities
|
|
});
|
|
|
|
let defaultButton = "normal";
|
|
switch (defaultAction) {
|
|
case D20Roll.ADV_MODE.ADVANTAGE: defaultButton = "advantage"; break;
|
|
case D20Roll.ADV_MODE.DISADVANTAGE: defaultButton = "disadvantage"; break;
|
|
}
|
|
|
|
// Create the Dialog window and await submission of the form
|
|
return new Promise(resolve => {
|
|
new Dialog({
|
|
title,
|
|
content,
|
|
buttons: {
|
|
advantage: {
|
|
label: game.i18n.localize("SW5E.Advantage"),
|
|
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.ADVANTAGE))
|
|
},
|
|
normal: {
|
|
label: game.i18n.localize("SW5E.Normal"),
|
|
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.NORMAL))
|
|
},
|
|
disadvantage: {
|
|
label: game.i18n.localize("SW5E.Disadvantage"),
|
|
callback: html => resolve(this._onDialogSubmit(html, D20Roll.ADV_MODE.DISADVANTAGE))
|
|
}
|
|
},
|
|
default: defaultButton,
|
|
close: () => resolve(null)
|
|
}, options).render(true);
|
|
});
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Handle submission of the Roll evaluation configuration Dialog
|
|
* @param {jQuery} html The submitted dialog content
|
|
* @param {number} advantageMode The chosen advantage mode
|
|
* @private
|
|
*/
|
|
_onDialogSubmit(html, advantageMode) {
|
|
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);
|
|
}
|
|
|
|
// Customize the modifier
|
|
if ( form.ability?.value ) {
|
|
const abl = this.data.abilities[form.ability.value];
|
|
this.terms.findSplice(t => t.term === "@mod", new NumericTerm({number: abl.mod}));
|
|
this.options.flavor += ` (${CONFIG.SW5E.abilities[form.ability.value]})`;
|
|
}
|
|
|
|
// Apply advantage or disadvantage
|
|
this.options.advantageMode = advantageMode;
|
|
this.options.rollMode = form.rollMode.value;
|
|
this.configureModifiers();
|
|
return this;
|
|
}
|
|
}
|