2021-01-13 18:02:22 +01:00
|
|
|
import { DS4 } from "../config";
|
|
|
|
|
2021-01-09 23:21:57 +01:00
|
|
|
/**
|
|
|
|
* Provides default values for all arguments the `CheckFactory` expects.
|
|
|
|
*/
|
|
|
|
class DefaultCheckOptions implements DS4CheckFactoryOptions {
|
2021-01-25 01:25:45 +01:00
|
|
|
readonly maxCritSuccess = 1;
|
|
|
|
readonly minCritFailure = 20;
|
|
|
|
readonly useSlayingDice = false;
|
2021-02-20 17:30:17 +01:00
|
|
|
readonly rollMode: Const.DiceRollMode = "roll";
|
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(
|
|
|
|
private checkTargetValue: number,
|
|
|
|
private gmModifier: number,
|
2021-01-09 23:21:57 +01:00
|
|
|
passedOptions: Partial<DS4CheckFactoryOptions> = {},
|
2021-01-09 23:14:31 +01:00
|
|
|
) {
|
2021-01-24 03:19:31 +01:00
|
|
|
this.checkOptions = defaultCheckOptions.mergeWith(passedOptions);
|
2021-01-09 23:14:31 +01:00
|
|
|
}
|
2021-01-13 17:11:07 +01:00
|
|
|
|
2021-01-09 23:21:57 +01:00
|
|
|
private checkOptions: DS4CheckFactoryOptions;
|
2021-01-09 23:14:31 +01:00
|
|
|
|
2021-01-19 03:31:40 +01:00
|
|
|
async execute(): Promise<ChatMessage | unknown> {
|
2021-02-05 02:35:47 +01:00
|
|
|
const rollCls = CONFIG.Dice.rolls[0];
|
2021-01-09 23:14:31 +01:00
|
|
|
|
|
|
|
const formula = [
|
|
|
|
"ds",
|
|
|
|
this.createTargetValueTerm(),
|
|
|
|
this.createCritTerm(),
|
|
|
|
this.createSlayingDiceTerm(),
|
|
|
|
].filterJoin("");
|
|
|
|
const roll = new rollCls(formula);
|
|
|
|
|
|
|
|
const rollModeTemplate = this.checkOptions.rollMode;
|
|
|
|
return roll.toMessage({}, { rollMode: rollModeTemplate, create: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Term generators
|
2021-01-15 20:46:26 +01:00
|
|
|
createTargetValueTerm(): string | null {
|
|
|
|
if (this.checkTargetValue !== null) {
|
2021-01-10 21:03:11 +01:00
|
|
|
return "v" + (this.checkTargetValue + this.gmModifier);
|
2021-01-09 23:14:31 +01:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-15 20:46:26 +01:00
|
|
|
createCritTerm(): string | null {
|
2021-01-10 16:40:11 +01:00
|
|
|
const minCritRequired = this.checkOptions.minCritFailure !== defaultCheckOptions.minCritFailure;
|
|
|
|
const maxCritRequired = this.checkOptions.maxCritSuccess !== defaultCheckOptions.maxCritSuccess;
|
2021-01-09 23:14:31 +01:00
|
|
|
|
|
|
|
if (minCritRequired || maxCritRequired) {
|
|
|
|
return "c" + (this.checkOptions.maxCritSuccess ?? "") + "," + (this.checkOptions.minCritFailure ?? "");
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-15 20:46:26 +01:00
|
|
|
createSlayingDiceTerm(): string | null {
|
2021-01-09 23:14:31 +01:00
|
|
|
return this.checkOptions.useSlayingDice ? "x" : 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-02-07 11:51:36 +01:00
|
|
|
* @param targetValue - 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(
|
|
|
|
targetValue: number,
|
|
|
|
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-01-10 16:40:11 +01:00
|
|
|
const gmModifierData = await askGmModifier(targetValue, options);
|
2021-01-09 23:14:31 +01:00
|
|
|
|
2021-01-24 03:19:31 +01:00
|
|
|
const newTargetValue = gmModifierData.checkTargetValue ?? targetValue;
|
|
|
|
const gmModifier = gmModifierData.gmModifier ?? 0;
|
2021-01-10 21:03:11 +01:00
|
|
|
const newOptions: Partial<DS4CheckFactoryOptions> = {
|
|
|
|
maxCritSuccess: gmModifierData.maxCritSuccess ?? options.maxCritSuccess ?? undefined,
|
|
|
|
minCritFailure: gmModifierData.minCritFailure ?? options.minCritFailure ?? undefined,
|
|
|
|
useSlayingDice: gmModifierData.useSlayingDice ?? options.useSlayingDice ?? undefined,
|
|
|
|
rollMode: gmModifierData.rollMode ?? options.rollMode ?? undefined,
|
|
|
|
};
|
|
|
|
|
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(
|
|
|
|
targetValue: 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-01-09 23:14:31 +01:00
|
|
|
// Render model interface and return value
|
2021-01-10 16:40:11 +01:00
|
|
|
const usedTemplate = template ?? "systems/ds4/templates/roll/roll-options.hbs";
|
2021-01-13 18:02:22 +01:00
|
|
|
const usedTitle = title ?? game.i18n.localize("DS4.RollDialogDefaultTitle");
|
2021-01-10 16:40:11 +01:00
|
|
|
const templateData = {
|
|
|
|
cssClass: "roll-option",
|
2021-01-13 18:02:22 +01:00
|
|
|
title: usedTitle,
|
2021-01-10 16:40:11 +01:00
|
|
|
checkTargetValue: targetValue,
|
|
|
|
maxCritSuccess: options.maxCritSuccess ?? defaultCheckOptions.maxCritSuccess,
|
|
|
|
minCritFailure: options.minCritFailure ?? defaultCheckOptions.minCritFailure,
|
2021-01-24 03:19:31 +01:00
|
|
|
rollMode: options.rollMode,
|
2021-01-13 18:02:22 +01:00
|
|
|
config: DS4,
|
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-02-07 13:51:20 +01:00
|
|
|
new Dialog(
|
|
|
|
{
|
|
|
|
title: usedTitle,
|
|
|
|
content: renderedHtml,
|
|
|
|
buttons: {
|
|
|
|
ok: {
|
|
|
|
icon: '<i class="fas fa-check"></i>',
|
2021-03-04 00:14:16 +01:00
|
|
|
label: game.i18n.localize("DS4.GenericOkButton"),
|
2021-02-07 13:51:20 +01:00
|
|
|
callback: (html) => {
|
|
|
|
if (!("jquery" in html)) {
|
|
|
|
throw new Error(
|
|
|
|
game.i18n.format("DS4.ErrorUnexpectedHtmlType", {
|
|
|
|
exType: "JQuery",
|
|
|
|
realType: "HTMLElement",
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
const innerForm = html[0].querySelector("form");
|
|
|
|
if (!innerForm) {
|
2021-02-07 14:00:26 +01:00
|
|
|
throw new Error(
|
|
|
|
game.i18n.format("DS4.ErrorCouldNotFindHtmlElement", { htmlElement: "form" }),
|
|
|
|
);
|
2021-02-07 13:51:20 +01:00
|
|
|
}
|
|
|
|
resolve(innerForm);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
cancel: {
|
|
|
|
icon: '<i class="fas fa-times"></i>',
|
2021-03-04 00:14:16 +01:00
|
|
|
label: game.i18n.localize("DS4.GenericCancelButton"),
|
2021-01-10 21:03:11 +01:00
|
|
|
},
|
|
|
|
},
|
2021-02-07 13:51:20 +01:00
|
|
|
default: "ok",
|
2021-01-10 16:40:11 +01:00
|
|
|
},
|
2021-02-07 13:51:20 +01:00
|
|
|
{ jQuery: true },
|
|
|
|
).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-01-13 18:02:22 +01:00
|
|
|
return {
|
2021-01-24 03:19:31 +01:00
|
|
|
checkTargetValue: parseInt(formData["ctv"]?.value),
|
|
|
|
gmModifier: parseInt(formData["gmmod"]?.value),
|
|
|
|
maxCritSuccess: parseInt(formData["maxcoup"]?.value),
|
|
|
|
minCritFailure: parseInt(formData["minfumble"]?.value),
|
|
|
|
rollMode: formData["visibility"]?.value,
|
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-02-20 17:30:17 +01:00
|
|
|
rollMode: Const.DiceRollMode;
|
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:
|
|
|
|
* - maxCritSuccess
|
|
|
|
* - minCritFailure
|
|
|
|
* - useSlayingDice
|
|
|
|
* - checkTargetValue
|
|
|
|
*
|
|
|
|
* They will and should be removed once effects and data retrieval is in place.
|
|
|
|
* If a "raw" roll dialog is necessary, create another pre-porcessing Dialog
|
|
|
|
* class asking for the required information.
|
|
|
|
* This interface should then be replaced with the `GmModifierData`.
|
|
|
|
*/
|
|
|
|
interface IntermediateGmModifierData extends GmModifierData {
|
2021-01-10 21:03:11 +01:00
|
|
|
checkTargetValue: number;
|
2021-01-10 16:40:11 +01:00
|
|
|
gmModifier: number;
|
2021-01-10 21:03:11 +01:00
|
|
|
maxCritSuccess: number;
|
|
|
|
minCritFailure: number;
|
|
|
|
// TODO: In final version from system settings
|
|
|
|
useSlayingDice: boolean;
|
2021-02-20 17:30:17 +01:00
|
|
|
rollMode: Const.DiceRollMode;
|
2021-01-09 23:14:31 +01:00
|
|
|
}
|
|
|
|
|
2021-01-13 18:56:19 +01:00
|
|
|
/**
|
|
|
|
* The minimum behavioural options that need to be passed to the factory.
|
|
|
|
*/
|
2021-01-09 23:21:57 +01:00
|
|
|
export interface DS4CheckFactoryOptions {
|
2021-01-09 23:14:31 +01:00
|
|
|
maxCritSuccess: number;
|
|
|
|
minCritFailure: number;
|
|
|
|
useSlayingDice: boolean;
|
2021-02-20 17:30:17 +01:00
|
|
|
rollMode: Const.DiceRollMode;
|
2021-01-09 23:14:31 +01:00
|
|
|
}
|