forked from GitHub-Mirrors/foundry-sw5e

Things unfinished: - Migration - The update adds new sections to the class sheet to allow some light customisation, this hasn't been included, but could be extended for the sake of dynamic classes with automatic class features and more - The French - The packs have not yet been updated, meaning due to the addition of a progression field to the class item, classes now don't set force or tech points - I updated the function calls in starships, but I didn't update it very thoroughly, it'll need checking - I only did a little testing - There has since been updates to DND5e that hasn't made it to release that patch bugs, those should be implemented Things changed from base 5e: - Short rests and long rests were merged into one function, this needed some rewrites to account for force and tech points, and for printing the correct message Extra Comments: - Unfinished code exists for automatic spell scrolls, this could be extended for single use force or tech powers - Weapon proficiencies probably need revising - Elven accuracy, halfling lucky, and reliable talent are present in the roll logic, this probably needs revising for sw5e - SW5e has a variant rule that permits force powers of any alignment to use either charisma or wisdom, that could be implemented - SW5e's version of gritty realism, [Longer Rests](https://sw5e.com/rules/variantRules/Longer%20Rests) differs from base dnd, this could be implemented - Extra ideas I've had while looking through the code can be found in Todos next to the ideas relevant context
217 lines
9.4 KiB
JavaScript
217 lines
9.4 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?
|
|
*/
|
|
// 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;
|
|
}
|
|
}
|