2021-05-18 09:11:03 -04:00
export { default as D20Roll } from "./dice/d20-roll.js" ;
export { default as DamageRoll } from "./dice/damage-roll.js" ;
2021-01-19 20:54:45 -05:00
/ * *
* A standardized helper function for simplifying the constant parts of a multipart roll formula
*
* @ param { string } formula The original Roll formula
* @ param { Object } data Actor or item data against which to parse the roll
* @ param { Object } options Formatting options
* @ param { boolean } options . constantFirst Puts the constants before the dice terms in the resulting formula
*
* @ return { string } The resulting simplified formula
* /
export function simplifyRollFormula ( formula , data , { constantFirst = false } = { } ) {
const roll = new Roll ( formula , data ) ; // Parses the formula and replaces any @properties
const terms = roll . terms ;
// Some terms are "too complicated" for this algorithm to simplify
// In this case, the original formula is returned.
if ( terms . some ( _isUnsupportedTerm ) ) return roll . formula ;
const rollableTerms = [ ] ; // Terms that are non-constant, and their associated operators
const constantTerms = [ ] ; // Terms that are constant, and their associated operators
let operators = [ ] ; // Temporary storage for operators before they are moved to one of the above
2021-05-18 09:11:03 -04:00
for ( let term of terms ) { // For each term
if ( term instanceof OperatorTerm ) operators . push ( term ) ; // If the term is an addition/subtraction operator, push the term into the operators array
else { // Otherwise the term is not an operator
if ( term instanceof DiceTerm ) { // If the term is something rollable
rollableTerms . push ( ... operators ) ; // Place all the operators into the rollableTerms array
rollableTerms . push ( term ) ; // Then place this rollable term into it as well
} //
else { // Otherwise, this must be a constant
constantTerms . push ( ... operators ) ; // Place the operators into the constantTerms array
constantTerms . push ( term ) ; // Then also add this constant term to that array.
} //
operators = [ ] ; // Finally, the operators have now all been assigend to one of the arrays, so empty this before the next iteration.
2021-01-19 20:54:45 -05:00
}
}
2021-05-18 09:11:03 -04:00
const constantFormula = Roll . getFormula ( constantTerms ) ; // Cleans up the constant terms and produces a new formula string
const rollableFormula = Roll . getFormula ( rollableTerms ) ; // Cleans up the non-constant terms and produces a new formula string
2021-01-19 20:54:45 -05:00
2021-05-18 09:11:03 -04:00
// Mathematically evaluate the constant formula to produce a single constant term
let constantPart = undefined ;
if ( constantFormula ) {
try {
constantPart = Roll . safeEval ( constantFormula )
} catch ( err ) {
console . warn ( ` Unable to evaluate constant term ${ constantFormula } in simplifyRollFormula ` ) ;
}
}
2021-01-19 20:54:45 -05:00
2021-05-18 09:11:03 -04:00
// Order the rollable and constant terms, either constant first or second depending on the optional argument
const parts = constantFirst ? [ constantPart , rollableFormula ] : [ rollableFormula , constantPart ] ;
2021-01-19 21:28:45 -05:00
2021-01-19 20:54:45 -05:00
// Join the parts with a + sign, pass them to `Roll` once again to clean up the formula
return new Roll ( parts . filterJoin ( " + " ) ) . formula ;
}
/* -------------------------------------------- */
/ * *
* Only some terms are supported by simplifyRollFormula , this method returns true when the term is not supported .
* @ param { * } term - A single Dice term to check support on
2021-01-19 21:28:45 -05:00
* @ return { Boolean } True when unsupported , false if supported
2021-01-19 20:54:45 -05:00
* /
function _isUnsupportedTerm ( term ) {
const diceTerm = term instanceof DiceTerm ;
2021-05-18 09:11:03 -04:00
const operator = term instanceof OperatorTerm && [ "+" , "-" ] . includes ( term . operator ) ;
const number = term instanceof NumericTerm ;
2021-01-19 20:54:45 -05:00
return ! ( diceTerm || operator || number ) ;
}
2021-05-18 09:11:03 -04:00
/* -------------------------------------------- */
/* D20 Roll */
2021-01-19 20:54:45 -05:00
/* -------------------------------------------- */
2021-01-19 20:47:48 -05:00
/ * *
2021-05-18 09:11:03 -04:00
* A standardized helper function for managing core 5 e d20 rolls .
2021-01-19 20:47:48 -05:00
* Holding SHIFT , ALT , or CTRL when the attack is rolled will "fast-forward" .
* This chooses the default options of a normal attack with no bonus , Advantage , or Disadvantage respectively
*
2021-05-18 09:11:03 -04:00
* @ param { string [ ] } parts The dice roll component parts , excluding the initial d20
* @ param { object } data Actor or item data against which to parse the roll
2020-09-11 17:11:11 -04:00
*
2021-05-18 09:11:03 -04:00
* @ param { boolean } [ advantage ] Apply advantage to the roll ( unless otherwise specified )
* @ param { boolean } [ disadvantage ] Apply disadvantage to the roll ( unless otherwise specified )
* @ param { number } [ critical ] The value of d20 result which represents a critical success
* @ param { number } [ fumble ] The value of d20 result which represents a critical failure
* @ param { number } [ targetValue ] Assign a target value against which the result of this roll should be compared
* @ param { boolean } [ elvenAccuracy ] Allow Elven Accuracy to modify this roll ?
* @ param { boolean } [ halflingLucky ] Allow Halfling Luck to modify this roll ?
* @ param { boolean } [ reliableTalent ] Allow Reliable Talent to modify this roll ?
* @ param { boolean } [ chooseModifier = false ] Choose the ability modifier that should be used when the roll is made
* @ param { boolean } [ fastForward = false ] Allow fast - forward advantage selection
* @ param { Event } [ event ] The triggering event which initiated the roll
* @ param { string } [ rollMode ] A specific roll mode to apply as the default for the resulting roll
* @ param { string } [ template ] The HTML template used to render the roll dialog
* @ param { string } [ title ] The dialog window title
* @ param { Object } [ dialogOptions ] Modal dialog options
*
* @ param { boolean } [ chatMessage = true ] Automatically create a Chat Message for the result of this roll
* @ param { object } [ messageData = { } ] Additional data which is applied to the created Chat Message , if any
* @ param { string } [ rollMode ] A specific roll mode to apply as the default for the resulting roll
* @ param { object } [ speaker ] The ChatMessage speaker to pass when creating the chat
* @ param { string } [ flavor ] Flavor text to use in the posted chat message
*
* @ return { Promise < D20Roll | null > } The evaluated D20Roll , or null if the workflow was cancelled
2021-01-19 20:47:48 -05:00
* /
2021-05-18 09:11:03 -04:00
export async function d20Roll ( {
parts = [ ] , data = { } , // Roll creation
advantage , disadvantage , fumble = 1 , critical = 20 , targetValue , elvenAccuracy , halflingLucky , reliableTalent , // Roll customization
chooseModifier = false , fastForward = false , event , template , title , dialogOptions , // Dialog configuration
chatMessage = true , messageData = { } , rollMode , speaker , flavor // Chat Message customization
} = { } ) {
// Handle input arguments
const formula = [ "1d20" ] . concat ( parts ) . join ( " + " ) ;
const { advantageMode , isFF } = _determineAdvantageMode ( { advantage , disadvantage , fastForward , event } ) ;
const defaultRollMode = rollMode || game . settings . get ( "core" , "rollMode" ) ;
if ( chooseModifier && ! isFF ) data [ "mod" ] = "@mod" ;
// Construct the D20Roll instance
const roll = new CONFIG . Dice . D20Roll ( formula , data , {
flavor : flavor || title ,
advantageMode ,
defaultRollMode ,
critical ,
fumble ,
targetValue ,
elvenAccuracy ,
halflingLucky ,
reliableTalent
} ) ;
2021-01-19 20:47:48 -05:00
2021-05-18 09:11:03 -04:00
// Prompt a Dialog to further configure the D20Roll
if ( ! isFF ) {
const configured = await roll . configureDialog ( {
title ,
chooseModifier ,
defaultRollMode : defaultRollMode ,
defaultAction : advantageMode ,
defaultAbility : data ? . item ? . ability ,
template
} , dialogOptions ) ;
if ( configured === null ) return null ;
2021-01-19 20:47:48 -05:00
}
2021-05-18 09:11:03 -04:00
// Evaluate the configured roll
await roll . evaluate ( { async : true } ) ;
2020-09-11 17:11:11 -04:00
// Create a Chat Message
2021-05-18 09:11:03 -04:00
if ( speaker ) {
console . warn ( ` You are passing the speaker argument to the d20Roll function directly which should instead be passed as an internal key of messageData ` ) ;
messageData . speaker = speaker ;
}
if ( roll && chatMessage ) await roll . toMessage ( messageData ) ;
2020-09-11 17:11:11 -04:00
return roll ;
}
/* -------------------------------------------- */
/ * *
2021-05-18 09:11:03 -04:00
* Determines whether this d20 roll should be fast - forwarded , and whether advantage or disadvantage should be applied
* @ returns { { isFF : boolean , advantageMode : number } } Whether the roll is fast - forward , and its advantage mode
2020-09-11 17:11:11 -04:00
* /
2021-05-18 09:11:03 -04:00
function _determineAdvantageMode ( { event , advantage = false , disadvantage = false , fastForward = false } = { } ) {
const isFF = fastForward || ( event && ( event . shiftKey || event . altKey || event . ctrlKey || event . metaKey ) ) ;
let advantageMode = CONFIG . Dice . D20Roll . ADV _MODE . NORMAL ;
if ( advantage || event ? . altKey ) advantageMode = CONFIG . Dice . D20Roll . ADV _MODE . ADVANTAGE ;
else if ( disadvantage || event ? . ctrlKey || event ? . metaKey ) advantageMode = CONFIG . Dice . D20Roll . ADV _MODE . DISADVANTAGE ;
return { isFF , advantageMode } ;
2021-01-19 20:47:48 -05:00
}
2020-09-04 10:03:19 -04:00
2021-05-18 09:11:03 -04:00
/* -------------------------------------------- */
/* Damage Roll */
2021-01-19 20:47:48 -05:00
/* -------------------------------------------- */
/ * *
2021-05-18 09:11:03 -04:00
* A standardized helper function for managing core 5 e damage rolls .
2021-01-19 20:47:48 -05:00
* Holding SHIFT , ALT , or CTRL when the attack is rolled will "fast-forward" .
* This chooses the default options of a normal attack with no bonus , Critical , or no bonus respectively
*
2021-05-18 09:11:03 -04:00
* @ param { string [ ] } parts The dice roll component parts , excluding the initial d20
* @ param { object } [ data ] Actor or item data against which to parse the roll
*
* @ param { boolean } [ critical = false ] Flag this roll as a critical hit for the purposes of fast - forward or default dialog action
* @ param { number } [ criticalBonusDice = 0 ] A number of bonus damage dice that are added for critical hits
* @ param { number } [ criticalMultiplier = 2 ] A critical hit multiplier which is applied to critical hits
* @ param { boolean } [ multiplyNumeric = false ] Multiply numeric terms by the critical multiplier
* @ param { boolean } [ powerfulCritical = false ] Apply the "powerful criticals" house rule to critical hits
* @ param { boolean } [ fastForward = false ] Allow fast - forward advantage selection
* @ param { Event } [ event ] The triggering event which initiated the roll
* @ param { boolean } [ allowCritical = true ] Allow the opportunity for a critical hit to be rolled
* @ param { string } [ template ] The HTML template used to render the roll dialog
* @ param { string } [ title ] The dice roll UI window title
* @ param { object } [ dialogOptions ] Configuration dialog options
2021-01-19 20:47:48 -05:00
*
2021-05-18 09:11:03 -04:00
* @ param { boolean } [ chatMessage = true ] Automatically create a Chat Message for the result of this roll
* @ param { object } [ messageData = { } ] Additional data which is applied to the created Chat Message , if any
* @ param { string } [ rollMode ] A specific roll mode to apply as the default for the resulting roll
* @ param { object } [ speaker ] The ChatMessage speaker to pass when creating the chat
* @ param { string } [ flavor ] Flavor text to use in the posted chat message
*
* @ return { Promise < DamageRoll | null > } The evaluated DamageRoll , or null if the workflow was canceled
2021-01-19 20:47:48 -05:00
* /
2021-05-18 09:11:03 -04:00
export async function damageRoll ( {
parts = [ ] , data , // Roll creation
critical = false , criticalBonusDice , criticalMultiplier , multiplyNumeric , powerfulCritical , // Damage customization
fastForward = false , event , allowCritical = true , template , title , dialogOptions , // Dialog configuration
chatMessage = true , messageData = { } , rollMode , speaker , flavor , // Chat Message customization
} = { } ) {
// Handle input arguments
const defaultRollMode = rollMode || game . settings . get ( "core" , "rollMode" ) ;
// Construct the DamageRoll instance
const formula = parts . join ( " + " ) ;
const { isCritical , isFF } = _determineCriticalMode ( { critical , fastForward , event } ) ;
const roll = new CONFIG . Dice . DamageRoll ( formula , data , {
flavor : flavor || title ,
critical : isCritical ,
criticalBonusDice ,
criticalMultiplier ,
multiplyNumeric ,
powerfulCritical
} ) ;
2020-09-04 10:03:19 -04:00
2021-05-18 09:11:03 -04:00
// Prompt a Dialog to further configure the DamageRoll
if ( ! isFF ) {
const configured = await roll . configureDialog ( {
title ,
defaultRollMode : defaultRollMode ,
defaultCritical : isCritical ,
template ,
allowCritical
} , dialogOptions ) ;
if ( configured === null ) return null ;
}
2020-09-04 10:03:19 -04:00
2021-05-18 09:11:03 -04:00
// Evaluate the configured roll
await roll . evaluate ( { async : true } ) ;
2020-09-04 10:03:19 -04:00
2020-09-11 17:11:11 -04:00
// Create a Chat Message
2021-05-18 09:11:03 -04:00
if ( speaker ) {
console . warn ( ` You are passing the speaker argument to the damageRoll function directly which should instead be passed as an internal key of messageData ` ) ;
messageData . speaker = speaker ;
}
if ( roll && chatMessage ) await roll . toMessage ( messageData ) ;
2020-09-11 17:11:11 -04:00
return roll ;
}
/* -------------------------------------------- */
/ * *
2021-05-18 09:11:03 -04:00
* Determines whether this d20 roll should be fast - forwarded , and whether advantage or disadvantage should be applied
* @ returns { { isFF : boolean , isCritical : boolean } } Whether the roll is fast - forward , and whether it is a critical hit
2020-09-11 17:11:11 -04:00
* /
2021-05-18 09:11:03 -04:00
function _determineCriticalMode ( { event , critical = false , fastForward = false } = { } ) {
const isFF = fastForward || ( event && ( event . shiftKey || event . altKey || event . ctrlKey || event . metaKey ) ) ;
if ( event ? . altKey ) critical = true ;
return { isFF , isCritical : critical } ;
2020-09-04 10:03:19 -04:00
}