Implement a custom DS4 "dice" class.

Includes exploding and crit dice modifier, but not tested yet,
as some required types are still missing.
This commit is contained in:
Oliver Rümpelein 2021-01-06 19:15:56 +01:00
parent 9feaa5216d
commit c885d2d405
5 changed files with 112 additions and 6 deletions

View file

@ -4,6 +4,7 @@ import { DS4ActorSheet } from "./actor/actor-sheet";
import { DS4Item } from "./item/item";
import { DS4ItemSheet } from "./item/item-sheet";
import { DS4 } from "./config";
import { DS4Roll } from "./rolls/ds4roll";
Hooks.once("init", async function () {
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
@ -21,6 +22,14 @@ Hooks.once("init", async function () {
CONFIG.Actor.entityClass = DS4Actor as typeof Actor;
CONFIG.Item.entityClass = DS4Item as typeof Item;
// Configure Dice
CONFIG.Dice.types = [Die, DS4Roll];
CONFIG.Dice.terms = {
c: Coin,
d: Die,
s: DS4Roll,
};
// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("ds4", DS4ActorSheet, { makeDefault: true });

View file

@ -0,0 +1,95 @@
import { RollResult, RollResultStatus } from "./roll-data";
import { ds4roll } from "./roll-executor";
export class DS4Roll extends DiceTerm {
constructor({ faces = 20, modifiers = [], options = {} } = {}) {
super({ number: 1, faces: faces, modifiers: modifiers, options: options });
}
/**
* @override
* @param param0
*/
roll({ minimize = false, maximize = false } = {}): RollResult {
return this.rollWithDifferentBorders(1, 20, { minimize, maximize });
}
rollWithDifferentBorders(
maxCritSuccess: number,
minCritFail: number,
{ minimize = false, maximize = false } = {},
): RollResult {
if (minimize) {
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20], true);
} else if (maximize) {
return new RollResult(
this.faces,
RollResultStatus.CRITICAL_SUCCESS,
Array(Math.ceil(this.faces / 20)).fill(1),
true,
);
} else {
return ds4roll(this.faces, { maxCritSucc: maxCritSuccess, minCritFail: minCritFail });
}
}
/**
* @override
*/
get values(): Array<number> {
return (this.results as Array<RollResult>)
.filter((r) => r.active)
.map((r) => r.dice)
.reduce((acc: Array<number>, cur: Array<number>) => {
return acc.concat(cur);
}, []);
}
/** Term Modifiers */
crits(modifier: string): this {
const rgx: RegExp = /[c([0-9]+)?,([0-9]+)?]/;
const match = modifier.match(rgx);
if (!match) return this;
const [parseCritSucc, parsedCritFail] = match.slice(1);
const maxCritSuccess = parseCritSucc ? parseInt(parseCritSucc) : 1;
const minCritFail = parsedCritFail ? parseInt(parsedCritFail) : 20;
const newResults: Array<RollResult> = (this.results as Array<RollResult>).map((r) => {
return ds4roll(this.faces, { minCritFail: minCritFail, maxCritSucc: maxCritSuccess }, r.dice);
});
this.results = newResults;
}
// DS4 only allows recursive explosions
explode(modifier: string): this {
// There should only ever be a single dice in the results-array at this point!
if (this.results.length != 1) {
// TODO: Add 'expression' to types!
// console.error(`Skipped explode for term ${this.expression}`);
return this;
}
const rgx = /[xX]([0-9]+)?/;
const match = modifier.match(rgx);
if (!match) return this;
const [parsedCritSucc] = match.slice(1);
const maxCritSucc = parsedCritSucc ? parseInt(parsedCritSucc) : 1;
let checked = 0;
while (checked < this.results.length) {
const r = (this.results as Array<RollResult>)[checked];
checked++;
if (!r.active) continue;
if (r.dice[0] <= maxCritSucc) {
r.exploded = true;
this.rollWithDifferentBorders(maxCritSucc, 21);
}
if (checked > 1000) throw new Error("Maximum recursion depth for explodign dice roll exceeded");
}
}
}

View file

@ -22,6 +22,7 @@ export class RollResult {
public status: RollResultStatus,
public dice: Array<number>,
public active: boolean = true,
public exploded: boolean = false,
) {}
}

View file

@ -48,15 +48,15 @@ export function rollCheckSingleDie(
dice = [new DS4RollProvider().getNextRoll()];
}
const usedDice = dice;
const roll = usedDice[0];
const rolledDie = usedDice[0];
if (roll <= usedOptions.maxCritSucc) {
if (rolledDie <= usedOptions.maxCritSucc) {
return new RollResult(checkTargetValue, RollResultStatus.CRITICAL_SUCCESS, usedDice, true);
} else if (roll >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) {
} else if (rolledDie >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) {
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, usedDice, true);
} else {
if (roll <= checkTargetValue) {
return new RollResult(roll, RollResultStatus.SUCCESS, usedDice, true);
if (rolledDie <= checkTargetValue) {
return new RollResult(rolledDie, RollResultStatus.SUCCESS, usedDice, true);
} else {
return new RollResult(0, RollResultStatus.FAILURE, usedDice, true);
}

View file

@ -6,7 +6,8 @@
*/
export class DS4RollProvider implements RollProvider {
getNextRoll(): number {
return new Roll("1d20").roll().total;
const rand = CONFIG.Dice.randomUniform();
return Math.ceil(rand * 20);
}
getNextRolls(amount: number): Array<number> {