forked from GitHub-Mirrors/foundry-sw5e
Moved most files for new system
This commit is contained in:
parent
b4bc7fc550
commit
0906bab02e
158 changed files with 2204 additions and 2160 deletions
217
src/module/dice/d20-roll.js
Normal file
217
src/module/dice/d20-roll.js
Normal file
|
@ -0,0 +1,217 @@
|
|||
/**
|
||||
* 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?
|
||||
*/
|
||||
// TODO: Check elven accuracy, halfling lucky, and reliable talent are required
|
||||
// Elven Accuracy is Supreme accuracy feat, Reliable Talent is operative's Reliable Talent Class Feat
|
||||
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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue