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:
parent
9feaa5216d
commit
c885d2d405
5 changed files with 112 additions and 6 deletions
|
@ -4,6 +4,7 @@ import { DS4ActorSheet } from "./actor/actor-sheet";
|
||||||
import { DS4Item } from "./item/item";
|
import { DS4Item } from "./item/item";
|
||||||
import { DS4ItemSheet } from "./item/item-sheet";
|
import { DS4ItemSheet } from "./item/item-sheet";
|
||||||
import { DS4 } from "./config";
|
import { DS4 } from "./config";
|
||||||
|
import { DS4Roll } from "./rolls/ds4roll";
|
||||||
|
|
||||||
Hooks.once("init", async function () {
|
Hooks.once("init", async function () {
|
||||||
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
|
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.Actor.entityClass = DS4Actor as typeof Actor;
|
||||||
CONFIG.Item.entityClass = DS4Item as typeof Item;
|
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
|
// Register sheet application classes
|
||||||
Actors.unregisterSheet("core", ActorSheet);
|
Actors.unregisterSheet("core", ActorSheet);
|
||||||
Actors.registerSheet("ds4", DS4ActorSheet, { makeDefault: true });
|
Actors.registerSheet("ds4", DS4ActorSheet, { makeDefault: true });
|
||||||
|
|
95
src/module/rolls/ds4roll.ts
Normal file
95
src/module/rolls/ds4roll.ts
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ export class RollResult {
|
||||||
public status: RollResultStatus,
|
public status: RollResultStatus,
|
||||||
public dice: Array<number>,
|
public dice: Array<number>,
|
||||||
public active: boolean = true,
|
public active: boolean = true,
|
||||||
|
public exploded: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,15 +48,15 @@ export function rollCheckSingleDie(
|
||||||
dice = [new DS4RollProvider().getNextRoll()];
|
dice = [new DS4RollProvider().getNextRoll()];
|
||||||
}
|
}
|
||||||
const usedDice = dice;
|
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);
|
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);
|
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, usedDice, true);
|
||||||
} else {
|
} else {
|
||||||
if (roll <= checkTargetValue) {
|
if (rolledDie <= checkTargetValue) {
|
||||||
return new RollResult(roll, RollResultStatus.SUCCESS, usedDice, true);
|
return new RollResult(rolledDie, RollResultStatus.SUCCESS, usedDice, true);
|
||||||
} else {
|
} else {
|
||||||
return new RollResult(0, RollResultStatus.FAILURE, usedDice, true);
|
return new RollResult(0, RollResultStatus.FAILURE, usedDice, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
*/
|
*/
|
||||||
export class DS4RollProvider implements RollProvider {
|
export class DS4RollProvider implements RollProvider {
|
||||||
getNextRoll(): number {
|
getNextRoll(): number {
|
||||||
return new Roll("1d20").roll().total;
|
const rand = CONFIG.Dice.randomUniform();
|
||||||
|
return Math.ceil(rand * 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextRolls(amount: number): Array<number> {
|
getNextRolls(amount: number): Array<number> {
|
||||||
|
|
Loading…
Reference in a new issue