2021-06-26 22:02:00 +02:00
// SPDX-FileCopyrightText: 2021 Johannes Loher
// SPDX-FileCopyrightText: 2021 Oliver Rümpelein
//
// SPDX-License-Identifier: MIT
2021-07-07 19:22:35 +02:00
import { getGame } from "../helpers" ;
2021-01-09 23:21:57 +01:00
/ * *
* Provides default values for all arguments the ` CheckFactory ` expects .
* /
class DefaultCheckOptions implements DS4CheckFactoryOptions {
2021-03-17 20:00:36 +01:00
readonly maximumCoupResult = 1 ;
readonly minimumFumbleResult = 20 ;
2021-01-25 01:25:45 +01:00
readonly useSlayingDice = false ;
2021-12-28 21:38:35 +01:00
// TODO: Use the type `keyof CONFIG.Dice.rollModes` instead as soon as https://github.com/League-of-Foundry-Developers/foundry-vtt-types/issues/1501 has been resolved.
readonly rollMode : foundry.CONST.DICE_ROLL_MODES = "publicroll" ;
2021-03-18 08:52:02 +01:00
readonly flavor : undefined ;
2021-01-09 23:14:31 +01:00
2021-01-09 23:21:57 +01:00
mergeWith ( other : Partial < DS4CheckFactoryOptions > ) : DS4CheckFactoryOptions {
2021-02-05 02:52:55 +01:00
return { . . . this , . . . other } ;
2021-01-09 23:14:31 +01:00
}
}
2021-01-13 18:56:19 +01:00
/ * *
* Singleton reference for default value extraction .
* /
2021-01-10 16:40:11 +01:00
const defaultCheckOptions = new DefaultCheckOptions ( ) ;
2021-01-09 23:21:57 +01:00
/ * *
* Most basic class responsible for generating the chat formula and passing it to the chat as roll .
* /
2021-01-09 23:14:31 +01:00
class CheckFactory {
constructor (
2021-03-18 08:52:02 +01:00
private checkTargetNumber : number ,
2021-01-09 23:14:31 +01:00
private gmModifier : number ,
2021-03-18 08:52:02 +01:00
options : Partial < DS4CheckFactoryOptions > = { } ,
2021-01-09 23:14:31 +01:00
) {
2021-03-18 08:52:02 +01:00
this . options = defaultCheckOptions . mergeWith ( options ) ;
2021-01-09 23:14:31 +01:00
}
2021-01-13 17:11:07 +01:00
2021-03-18 08:52:02 +01:00
private options : DS4CheckFactoryOptions ;
2021-01-09 23:14:31 +01:00
2021-06-30 04:32:10 +02:00
async execute ( ) : Promise < ChatMessage | undefined > {
2021-03-18 08:58:35 +01:00
const innerFormula = [ "ds" , this . createCheckTargetNumberModifier ( ) , this . createCoupFumbleModifier ( ) ] . filterJoin (
"" ,
) ;
2021-03-18 08:52:02 +01:00
const formula = this . options . useSlayingDice ? ` { ${ innerFormula } }x ` : innerFormula ;
2021-03-14 08:47:03 +01:00
const roll = Roll . create ( formula ) ;
2021-09-19 20:12:01 +02:00
const speaker = this . options . speaker ? ? ChatMessage . getSpeaker ( ) ;
2021-06-30 04:32:10 +02:00
2021-07-01 02:56:09 +02:00
return roll . toMessage (
2021-09-19 12:23:52 +02:00
{
speaker ,
flavor : this.options.flavor ,
flags : this.options.flavorData ? { ds4 : { flavorData : this.options.flavorData } } : undefined ,
} ,
2021-03-18 08:52:02 +01:00
{ rollMode : this.options.rollMode , create : true } ,
) ;
2021-01-09 23:14:31 +01:00
}
2021-06-26 12:50:55 +02:00
createCheckTargetNumberModifier ( ) : string {
2021-06-26 14:29:49 +02:00
const totalCheckTargetNumber = Math . max ( this . checkTargetNumber + this . gmModifier , 0 ) ;
return ` v ${ totalCheckTargetNumber } ` ;
2021-01-09 23:14:31 +01:00
}
2021-03-18 08:52:02 +01:00
createCoupFumbleModifier ( ) : string | null {
const isMinimumFumbleResultRequired =
this . options . minimumFumbleResult !== defaultCheckOptions . minimumFumbleResult ;
const isMaximumCoupResultRequired = this . options . maximumCoupResult !== defaultCheckOptions . maximumCoupResult ;
2021-01-09 23:14:31 +01:00
2021-03-18 08:52:02 +01:00
if ( isMinimumFumbleResultRequired || isMaximumCoupResultRequired ) {
2021-06-26 14:29:49 +02:00
return ` c ${ this . options . maximumCoupResult ? ? "" } : ${ this . options . minimumFumbleResult ? ? "" } ` ;
2021-01-09 23:14:31 +01:00
} else {
return null ;
}
}
}
2021-01-09 23:21:57 +01:00
/ * *
* Asks the user for all unknown / necessary information and passes them on to perform a roll .
2021-03-18 08:52:02 +01:00
* @param checkTargetNumber - The Check Target Number ( "CTN" )
* @param options - Options changing the behavior of the roll and message .
2021-01-09 23:21:57 +01:00
* /
2021-01-15 20:46:26 +01:00
export async function createCheckRoll (
2021-03-18 08:52:02 +01:00
checkTargetNumber : number ,
2021-01-15 20:46:26 +01:00
options : Partial < DS4CheckFactoryOptions > = { } ,
2021-01-19 03:31:40 +01:00
) : Promise < ChatMessage | unknown > {
2021-01-09 23:14:31 +01:00
// Ask for additional required data;
2021-03-18 08:52:02 +01:00
const gmModifierData = await askGmModifier ( checkTargetNumber , options ) ;
2021-01-09 23:14:31 +01:00
2021-03-18 08:52:02 +01:00
const newTargetValue = gmModifierData . checkTargetNumber ? ? checkTargetNumber ;
2021-01-24 03:19:31 +01:00
const gmModifier = gmModifierData . gmModifier ? ? 0 ;
2021-01-10 21:03:11 +01:00
const newOptions : Partial < DS4CheckFactoryOptions > = {
2021-03-18 08:52:02 +01:00
maximumCoupResult : gmModifierData.maximumCoupResult ? ? options . maximumCoupResult ,
minimumFumbleResult : gmModifierData.minimumFumbleResult ? ? options . minimumFumbleResult ,
2021-07-07 19:22:35 +02:00
useSlayingDice : getGame ( ) . settings . get ( "ds4" , "useSlayingDiceForAutomatedChecks" ) ,
2021-03-18 08:52:02 +01:00
rollMode : gmModifierData.rollMode ? ? options . rollMode ,
flavor : options.flavor ,
2021-09-19 12:23:52 +02:00
flavorData : options.flavorData ,
2021-09-19 20:12:01 +02:00
speaker : options.speaker ,
2021-01-10 21:03:11 +01:00
} ;
2021-01-09 23:14:31 +01:00
// Create Factory
2021-01-24 03:19:31 +01:00
const cf = new CheckFactory ( newTargetValue , gmModifier , newOptions ) ;
2021-01-09 23:14:31 +01:00
// Possibly additional processing
// Execute roll
2021-01-19 03:31:40 +01:00
return cf . execute ( ) ;
2021-01-09 23:14:31 +01:00
}
2021-01-09 23:21:57 +01:00
/ * *
2021-01-13 18:56:19 +01:00
* Responsible for rendering the modal interface asking for the modifier specified by GM and ( currently ) additional data .
2021-01-09 23:21:57 +01:00
*
2021-01-10 16:40:11 +01:00
* @notes
* At the moment , this asks for more data than it will do after some iterations .
*
2021-01-24 03:19:31 +01:00
* @returns The data given by the user .
2021-01-09 23:21:57 +01:00
* /
2021-01-10 16:40:11 +01:00
async function askGmModifier (
2021-03-17 20:00:36 +01:00
checkTargetNumber : number ,
2021-01-15 20:46:26 +01:00
options : Partial < DS4CheckFactoryOptions > = { } ,
2021-01-10 16:40:11 +01:00
{ template , title } : { template? : string ; title? : string } = { } ,
2021-01-24 03:19:31 +01:00
) : Promise < Partial < IntermediateGmModifierData > > {
2021-04-13 21:40:52 +02:00
const usedTemplate = template ? ? "systems/ds4/templates/dialogs/roll-options.hbs" ;
2021-07-07 19:22:35 +02:00
const usedTitle = title ? ? getGame ( ) . i18n . localize ( "DS4.DialogRollOptionsDefaultTitle" ) ;
2021-01-10 16:40:11 +01:00
const templateData = {
2021-01-13 18:02:22 +01:00
title : usedTitle ,
2021-03-17 20:00:36 +01:00
checkTargetNumber : checkTargetNumber ,
maximumCoupResult : options.maximumCoupResult ? ? defaultCheckOptions . maximumCoupResult ,
minimumFumbleResult : options.minimumFumbleResult ? ? defaultCheckOptions . minimumFumbleResult ,
2021-07-07 19:22:35 +02:00
rollMode : options.rollMode ? ? getGame ( ) . settings . get ( "core" , "rollMode" ) ,
2021-03-17 19:23:57 +01:00
rollModes : CONFIG.Dice.rollModes ,
2021-01-10 16:40:11 +01:00
} ;
const renderedHtml = await renderTemplate ( usedTemplate , templateData ) ;
2021-01-10 21:03:11 +01:00
const dialogPromise = new Promise < HTMLFormElement > ( ( resolve ) = > {
2021-05-13 22:09:38 +02:00
new Dialog ( {
title : usedTitle ,
content : renderedHtml ,
buttons : {
ok : {
icon : '<i class="fas fa-check"></i>' ,
2021-07-07 19:22:35 +02:00
label : getGame ( ) . i18n . localize ( "DS4.GenericOkButton" ) ,
2021-05-13 22:09:38 +02:00
callback : ( html ) = > {
if ( ! ( "jquery" in html ) ) {
throw new Error (
2021-07-07 19:22:35 +02:00
getGame ( ) . i18n . format ( "DS4.ErrorUnexpectedHtmlType" , {
2021-05-13 22:09:38 +02:00
exType : "JQuery" ,
realType : "HTMLElement" ,
} ) ,
) ;
} else {
2021-09-12 17:48:14 +02:00
const innerForm = html [ 0 ] ? . querySelector ( "form" ) ;
2021-05-13 22:09:38 +02:00
if ( ! innerForm ) {
2021-02-07 13:51:20 +01:00
throw new Error (
2021-07-07 19:22:35 +02:00
getGame ( ) . i18n . format ( "DS4.ErrorCouldNotFindHtmlElement" , { htmlElement : "form" } ) ,
2021-02-07 13:51:20 +01:00
) ;
}
2021-05-13 22:09:38 +02:00
resolve ( innerForm ) ;
}
2021-01-10 21:03:11 +01:00
} ,
} ,
2021-05-13 22:09:38 +02:00
cancel : {
icon : '<i class="fas fa-times"></i>' ,
2021-07-07 19:22:35 +02:00
label : getGame ( ) . i18n . localize ( "DS4.GenericCancelButton" ) ,
2021-05-13 22:09:38 +02:00
} ,
2021-01-10 16:40:11 +01:00
} ,
2021-05-13 22:09:38 +02:00
default : "ok" ,
} ) . render ( true ) ;
2021-01-10 21:03:11 +01:00
} ) ;
const dialogForm = await dialogPromise ;
2021-01-24 03:19:31 +01:00
return parseDialogFormData ( dialogForm ) ;
2021-01-10 21:03:11 +01:00
}
2021-01-10 16:40:11 +01:00
2021-01-13 18:56:19 +01:00
/ * *
* Extracts Dialog data from the returned DOM element .
2021-02-07 11:51:36 +01:00
* @param formData - The filed dialog
2021-01-13 18:56:19 +01:00
* /
2021-01-24 03:19:31 +01:00
function parseDialogFormData ( formData : HTMLFormElement ) : Partial < IntermediateGmModifierData > {
2021-06-26 14:29:49 +02:00
const chosenCheckTargetNumber = parseInt ( formData [ "check-target-number" ] ? . value ) ;
const chosenGMModifier = parseInt ( formData [ "gm-modifier" ] ? . value ) ;
const chosenMaximumCoupResult = parseInt ( formData [ "maximum-coup-result" ] ? . value ) ;
const chosenMinimumFumbleResult = parseInt ( formData [ "minimum-fumble-result" ] ? . value ) ;
const chosenRollMode = formData [ "roll-mode" ] ? . value ;
2021-01-13 18:02:22 +01:00
return {
2021-07-07 22:46:21 +02:00
checkTargetNumber : Number.isSafeInteger ( chosenCheckTargetNumber ) ? chosenCheckTargetNumber : undefined ,
gmModifier : Number.isSafeInteger ( chosenGMModifier ) ? chosenGMModifier : undefined ,
maximumCoupResult : Number.isSafeInteger ( chosenMaximumCoupResult ) ? chosenMaximumCoupResult : undefined ,
minimumFumbleResult : Number.isSafeInteger ( chosenMinimumFumbleResult ) ? chosenMinimumFumbleResult : undefined ,
2021-12-28 21:38:35 +01:00
rollMode : Object.keys ( CONFIG . Dice . rollModes ) . includes ( chosenRollMode ) ? chosenRollMode : undefined ,
2021-01-10 21:03:11 +01:00
} ;
2021-01-10 16:40:11 +01:00
}
2021-01-13 18:56:19 +01:00
/ * *
* Contains data that needs retrieval from an interactive Dialog .
* /
2021-01-10 16:40:11 +01:00
interface GmModifierData {
2021-01-13 18:56:19 +01:00
gmModifier : number ;
2021-12-28 21:38:35 +01:00
rollMode : foundry.CONST.DICE_ROLL_MODES ;
2021-01-13 18:56:19 +01:00
}
/ * *
* Contains * CURRENTLY * necessary Data for drafting a roll .
*
* @deprecated
* Quite a lot of this information is requested due to a lack of automation :
2021-03-17 20:00:36 +01:00
* - maximumCoupResult
* - minimumFumbleResult
2021-01-13 18:56:19 +01:00
* - useSlayingDice
2021-03-17 20:00:36 +01:00
* - checkTargetNumber
2021-01-13 18:56:19 +01:00
*
* They will and should be removed once effects and data retrieval is in place .
2021-03-17 19:23:57 +01:00
* If a "raw" roll dialog is necessary , create another pre - processing Dialog
2021-01-13 18:56:19 +01:00
* class asking for the required information .
* This interface should then be replaced with the ` GmModifierData ` .
* /
interface IntermediateGmModifierData extends GmModifierData {
2021-03-17 20:00:36 +01:00
checkTargetNumber : number ;
maximumCoupResult : number ;
minimumFumbleResult : number ;
2021-01-09 23:14:31 +01:00
}
2021-01-13 18:56:19 +01:00
/ * *
2021-03-17 19:23:57 +01:00
* The minimum behavioral options that need to be passed to the factory .
2021-01-13 18:56:19 +01:00
* /
2021-01-09 23:21:57 +01:00
export interface DS4CheckFactoryOptions {
2021-03-17 20:00:36 +01:00
maximumCoupResult : number ;
minimumFumbleResult : number ;
2021-01-09 23:14:31 +01:00
useSlayingDice : boolean ;
2021-12-28 21:38:35 +01:00
rollMode : foundry.CONST.DICE_ROLL_MODES ;
2021-03-18 08:52:02 +01:00
flavor? : string ;
2021-09-19 12:23:52 +02:00
flavorData? : Record < string , string | number | null > ;
2021-09-19 20:12:01 +02:00
speaker? : ReturnType < typeof ChatMessage.getSpeaker > ;
2021-01-09 23:14:31 +01:00
}