ds4/src/module/rolls/check.ts

135 lines
5 KiB
TypeScript
Raw Normal View History

2021-01-08 23:18:01 +01:00
import { RollResult, RollResultStatus } from "./roll-data";
import { ds4roll } from "./roll-executor";
interface TermData {
number: number;
faces: number;
modifiers: Array<string>;
options: Record<string, unknown>;
}
export class DS4Check extends DiceTerm {
constructor(termData: Partial<TermData>) {
super({
number: termData.number,
faces: termData.faces, // should be null
modifiers: termData.modifiers ?? [],
options: termData.options ?? {},
});
console.log("This @ constructor: ", termData);
// Store and parse target value.
const targetValueModifier = this.modifiers.filter((m) => m[0] === "v")[0];
const tvRgx = new RegExp("v([0-9]+)?");
const tvMatch = targetValueModifier?.match(tvRgx);
if (tvMatch) {
const [parseTargetValue] = tvMatch.slice(1);
this.targetValue = parseTargetValue ? parseInt(parseTargetValue) : DS4Check.DEFAULT_TARGET_VALUE;
}
// Store and parse min/max crit
const critModifier = this.modifiers.filter((m) => m[0] === "c")[0];
const cmRgx = new RegExp("c([0-9]+)?,([0-9]+)?");
const cmMatch = critModifier?.match(cmRgx);
if (cmMatch) {
const [parseMaxCritSuccess, parseMinCritFailure] = cmMatch.slice(1);
this.maxCritSuccess = parseMaxCritSuccess
? parseInt(parseMaxCritSuccess)
: DS4Check.DEFAULT_MAX_CRIT_SUCCESS;
this.minCritFailure = parseMinCritFailure
? parseInt(parseMinCritFailure)
: DS4Check.DEFAULT_MIN_CRIT_FAILURE;
if (this.minCritFailure <= this.maxCritSuccess)
throw new SyntaxError("There's an overlap between Fumbles and Coups");
}
}
success = null;
failure = null;
targetValue = DS4Check.DEFAULT_TARGET_VALUE;
minCritFailure = DS4Check.DEFAULT_MIN_CRIT_FAILURE;
maxCritSuccess = DS4Check.DEFAULT_MAX_CRIT_SUCCESS;
/**
* @override
* @param param0
*
* @private_notes This gets only called once for the first roll.
*/
roll({ minimize = false, maximize = false } = {}): RollResult {
console.log(`This targetValue @ roll: ${this.targetValue}`);
const rollResult = this.rollWithDifferentBorders({ minimize, maximize });
this.results.push(rollResult);
if (rollResult.status == RollResultStatus.CRITICAL_SUCCESS) {
this.success = true;
} else if (rollResult.status == RollResultStatus.CRITICAL_FAILURE) {
this.failure = true;
}
return rollResult;
}
rollWithDifferentBorders({ minimize = false, maximize = false } = {}, slayingDiceRepetition = false): RollResult {
const targetValueToUse = this.targetValue;
if (minimize) {
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20], true);
} else if (maximize) {
const maximizedDice = Array(Math.ceil(targetValueToUse / 20)).fill(1);
return new RollResult(targetValueToUse, RollResultStatus.CRITICAL_SUCCESS, maximizedDice, true);
} else {
return ds4roll(targetValueToUse, {
maxCritSuccess: this.maxCritSuccess,
minCritFail: this.minCritFailure,
slayingDiceRepetition: slayingDiceRepetition,
useSlayingDice: slayingDiceRepetition,
});
}
}
/** Term Modifiers */
noop(): this {
return this;
}
// DS4 only allows recursive explosions
explode(modifier: string): this {
const rgx = /[xX]/;
const match = modifier.match(rgx);
if (!match) return this;
this.results = (this.results as Array<RollResult>)
.map((r) => {
const intermedResult = [r];
let checked = 0;
while (checked < intermedResult.length) {
const r = (intermedResult as Array<RollResult>)[checked];
checked++;
if (!r.active) continue;
if (r.dice[0] <= this.maxCritSuccess) {
r.exploded = true;
const newRoll = this.rollWithDifferentBorders({}, true);
intermedResult.push(newRoll);
}
if (checked > 1000) throw new Error("Maximum recursion depth for exploding dice roll exceeded");
}
return intermedResult;
})
.reduce((acc, cur) => {
return acc.concat(cur);
}, []);
}
static readonly DEFAULT_TARGET_VALUE = 10;
static readonly DEFAULT_MAX_CRIT_SUCCESS = 1;
static readonly DEFAULT_MIN_CRIT_FAILURE = 20;
// TODO: add to Type declarations
static DENOMINATION = "s";
static MODIFIERS = {
x: "explode",
c: "noop", // Modifier is consumed in constructor for target value
v: "noop", // Modifier is consumed in constructor for target value
};
}