2021-03-13 17:43:48 +01:00
|
|
|
import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation";
|
|
|
|
|
2021-01-08 23:41:23 +01:00
|
|
|
/**
|
|
|
|
* Implements DS4 Checks as an emulated "dice throw".
|
|
|
|
*
|
|
|
|
* @example
|
2021-03-13 17:43:48 +01:00
|
|
|
* - Roll a check against a Check Target Number (CTN) of 18: `/r dsv18`
|
2021-01-08 23:41:23 +01:00
|
|
|
* - Roll a check with multiple dice against a CTN of 34: `/r dsv34`
|
2021-03-13 22:08:04 +01:00
|
|
|
* - Roll a check with a racial ability that makes `2` a coup and `19` a fumble: `/r dsv19c2:19`
|
2021-01-08 23:41:23 +01:00
|
|
|
* - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5`
|
|
|
|
*/
|
2021-01-08 23:18:01 +01:00
|
|
|
export class DS4Check extends DiceTerm {
|
2021-03-13 22:08:04 +01:00
|
|
|
constructor({ modifiers = [], options }: Partial<DiceTerm.TermData> = {}) {
|
2021-01-08 23:18:01 +01:00
|
|
|
super({
|
2021-03-13 17:43:48 +01:00
|
|
|
faces: 20,
|
|
|
|
modifiers: modifiers,
|
|
|
|
options: options,
|
2021-01-08 23:18:01 +01:00
|
|
|
});
|
|
|
|
|
2021-03-13 17:43:48 +01:00
|
|
|
// Parse and store check target number
|
2021-03-13 18:50:39 +01:00
|
|
|
const checkTargetNumberModifier = this.modifiers.filter((m) => m[0] === "v")[0];
|
|
|
|
const ctnRgx = new RegExp("v([0-9]+)?");
|
|
|
|
const ctnMatch = checkTargetNumberModifier?.match(ctnRgx);
|
|
|
|
if (ctnMatch) {
|
|
|
|
const [parseCheckTargetNumber] = ctnMatch.slice(1);
|
|
|
|
this.checkTargetNumber = parseCheckTargetNumber
|
|
|
|
? parseInt(parseCheckTargetNumber)
|
2021-03-13 17:43:48 +01:00
|
|
|
: DS4Check.DEFAULT_CHECK_TARGET_NUMBER;
|
2021-01-08 23:18:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-13 17:43:48 +01:00
|
|
|
this.number = getRequiredNumberOfDice(this.checkTargetNumber);
|
|
|
|
|
2021-03-13 17:47:47 +01:00
|
|
|
// Parse and store maximumCoupResult and minimumFumbleResult
|
2021-03-13 17:43:48 +01:00
|
|
|
const coupFumbleModifier = this.modifiers.filter((m) => m[0] === "c")[0];
|
2021-03-13 22:08:04 +01:00
|
|
|
const cfmRgx = new RegExp("c([0-9]+)?(:([0-9]+))?");
|
2021-03-13 17:43:48 +01:00
|
|
|
const cfmMatch = coupFumbleModifier?.match(cfmRgx);
|
|
|
|
if (cfmMatch) {
|
2021-03-13 22:08:04 +01:00
|
|
|
const parseMaximumCoupResult = cfmMatch[1];
|
|
|
|
const parseMinimumFumbleResult = cfmMatch[3];
|
2021-03-13 17:43:48 +01:00
|
|
|
this.maximumCoupResult = parseMaximumCoupResult
|
|
|
|
? parseInt(parseMaximumCoupResult)
|
|
|
|
: DS4Check.DEFAULT_MAXIMUM_COUP_RESULT;
|
|
|
|
this.minimumFumbleResult = parseMinimumFumbleResult
|
|
|
|
? parseInt(parseMinimumFumbleResult)
|
|
|
|
: DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
|
|
|
|
if (this.minimumFumbleResult <= this.maximumCoupResult)
|
2021-03-13 22:22:28 +01:00
|
|
|
throw new SyntaxError(game.i18n.localize("DS4.ErrorDiceCoupFumbleOverlap"));
|
2021-01-08 23:18:01 +01:00
|
|
|
}
|
2021-03-13 22:08:04 +01:00
|
|
|
|
|
|
|
// Parse and store no fumble
|
|
|
|
const noFumbleModifier = this.modifiers.filter((m) => m[0] === "n")[0];
|
|
|
|
if (noFumbleModifier) {
|
|
|
|
this.canFumble = false;
|
|
|
|
}
|
2021-01-08 23:18:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-13 17:43:48 +01:00
|
|
|
coup: boolean | null = null;
|
|
|
|
fumble: boolean | null = null;
|
2021-03-13 22:08:04 +01:00
|
|
|
canFumble = true;
|
2021-03-13 17:43:48 +01:00
|
|
|
checkTargetNumber = DS4Check.DEFAULT_CHECK_TARGET_NUMBER;
|
|
|
|
minimumFumbleResult = DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
|
|
|
|
maximumCoupResult = DS4Check.DEFAULT_MAXIMUM_COUP_RESULT;
|
2021-01-08 23:18:01 +01:00
|
|
|
|
2021-03-14 14:52:50 +01:00
|
|
|
/** @override */
|
2021-03-13 17:43:48 +01:00
|
|
|
get expression(): string {
|
|
|
|
return `ds${this.modifiers.join("")}`;
|
2021-01-08 23:18:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-14 14:52:50 +01:00
|
|
|
/** @override */
|
2021-03-13 17:43:48 +01:00
|
|
|
get total(): number | null {
|
|
|
|
if (this.fumble) return 0;
|
|
|
|
return super.total;
|
|
|
|
}
|
|
|
|
|
2021-03-14 14:52:50 +01:00
|
|
|
/** @override */
|
2021-03-13 19:34:05 +01:00
|
|
|
evaluate({ minimize = false, maximize = false } = {}): this {
|
|
|
|
super.evaluate({ minimize, maximize });
|
|
|
|
this.evaluateResults();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-03-14 14:52:50 +01:00
|
|
|
/** @override */
|
2021-03-13 17:43:48 +01:00
|
|
|
roll({ minimize = false, maximize = false } = {}): DiceTerm.Result {
|
|
|
|
// Swap minimize / maximize because in DS4, the best possible roll is a 1 and the worst possible roll is a 20
|
|
|
|
return super.roll({ minimize: maximize, maximize: minimize });
|
2021-01-08 23:18:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-13 17:43:48 +01:00
|
|
|
evaluateResults(): void {
|
|
|
|
const dice = this.results.map((die) => die.result);
|
|
|
|
const results = evaluateCheck(dice, this.checkTargetNumber, {
|
|
|
|
maximumCoupResult: this.maximumCoupResult,
|
|
|
|
minimumFumbleResult: this.minimumFumbleResult,
|
|
|
|
canFumble: this.canFumble,
|
|
|
|
});
|
|
|
|
this.results = results;
|
|
|
|
this.coup = results[0].success ?? false;
|
|
|
|
this.fumble = results[0].failure ?? false;
|
2021-01-08 23:18:01 +01:00
|
|
|
}
|
|
|
|
|
2021-03-14 14:52:50 +01:00
|
|
|
/** @override */
|
|
|
|
static fromResults<T extends DS4Check>(
|
|
|
|
this: ConstructorOf<T>,
|
|
|
|
options: Partial<DiceTerm.TermData>,
|
|
|
|
results: DiceTerm.Result[],
|
|
|
|
): T {
|
2021-03-13 19:34:05 +01:00
|
|
|
const term = new this(options);
|
|
|
|
term.results = results;
|
|
|
|
term.evaluateResults();
|
|
|
|
term._evaluated = true;
|
|
|
|
return term;
|
|
|
|
}
|
|
|
|
|
2021-03-13 17:43:48 +01:00
|
|
|
static readonly DEFAULT_CHECK_TARGET_NUMBER = 10;
|
|
|
|
static readonly DEFAULT_MAXIMUM_COUP_RESULT = 1;
|
|
|
|
static readonly DEFAULT_MINIMUM_FUMBLE_RESULT = 20;
|
2021-01-08 23:18:01 +01:00
|
|
|
static DENOMINATION = "s";
|
|
|
|
static MODIFIERS = {
|
2021-03-13 19:34:05 +01:00
|
|
|
c: (): void => undefined, // Modifier is consumed in constructor for maximumCoupResult / minimumFumbleResult
|
|
|
|
v: (): void => undefined, // Modifier is consumed in constructor for checkTargetNumber
|
2021-03-13 22:08:04 +01:00
|
|
|
n: (): void => undefined, // Modifier is consumed in constructor for canFumble
|
2021-01-08 23:18:01 +01:00
|
|
|
};
|
|
|
|
}
|