Add slying dice modifier to DicePool

This commit is contained in:
Johannes Loher 2021-03-13 22:08:04 +01:00
parent eeb1aa61f4
commit 7f973e7de8
3 changed files with 53 additions and 19 deletions

View file

@ -1,15 +1,16 @@
import { DS4Actor } from "./actor/actor"; import { DS4Actor } from "./actor/actor";
import { DS4Item } from "./item/item";
import { DS4ItemSheet } from "./item/item-sheet";
import { DS4 } from "./config";
import { DS4Check } from "./rolls/check";
import { DS4CharacterActorSheet } from "./actor/sheets/character-sheet"; import { DS4CharacterActorSheet } from "./actor/sheets/character-sheet";
import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet"; import { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet";
import { createCheckRoll } from "./rolls/check-factory"; import { DS4 } from "./config";
import { registerSystemSettings } from "./settings";
import { migration } from "./migrations";
import registerHandlebarsHelpers from "./handlebars/handlebars-helpers"; import registerHandlebarsHelpers from "./handlebars/handlebars-helpers";
import registerHandlebarsPartials from "./handlebars/handlebars-partials"; import registerHandlebarsPartials from "./handlebars/handlebars-partials";
import { DS4Item } from "./item/item";
import { DS4ItemSheet } from "./item/item-sheet";
import { migration } from "./migrations";
import { DS4Check } from "./rolls/check";
import { createCheckRoll } from "./rolls/check-factory";
import registerSlayingDiceModifier from "./rolls/slaying-dice-modifier";
import { registerSystemSettings } from "./settings";
Hooks.once("init", async () => { Hooks.once("init", async () => {
console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`); console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`);
@ -33,6 +34,8 @@ Hooks.once("init", async () => {
CONFIG.Dice.types.push(DS4Check); CONFIG.Dice.types.push(DS4Check);
CONFIG.Dice.terms.s = DS4Check; CONFIG.Dice.terms.s = DS4Check;
registerSlayingDiceModifier();
registerSystemSettings(); registerSystemSettings();
Actors.unregisterSheet("core", ActorSheet); Actors.unregisterSheet("core", ActorSheet);

View file

@ -1,28 +1,22 @@
import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation"; import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation";
interface DS4CheckTermData extends DiceTerm.TermData {
canFumble: boolean;
}
/** /**
* Implements DS4 Checks as an emulated "dice throw". * Implements DS4 Checks as an emulated "dice throw".
* *
* @example * @example
* - Roll a check against a Check Target Number (CTN) of 18: `/r dsv18` * - Roll a check against a Check Target Number (CTN) of 18: `/r dsv18`
* - Roll a check with multiple dice against a CTN of 34: `/r dsv34` * - Roll a check with multiple dice against a CTN of 34: `/r dsv34`
* - Roll a check with a racial ability that makes `2` a coup and `19` a fumble: `/r dsv19c2,19` * - Roll a check with a racial ability that makes `2` a coup and `19` a fumble: `/r dsv19c2:19`
* - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5` * - Roll a check with a racial ability that makes `5` a coup and default fumble: `/r dsv19c5`
*/ */
export class DS4Check extends DiceTerm { export class DS4Check extends DiceTerm {
constructor({ modifiers = [], options = {}, canFumble = true }: Partial<DS4CheckTermData> = {}) { constructor({ modifiers = [], options }: Partial<DiceTerm.TermData> = {}) {
super({ super({
faces: 20, faces: 20,
modifiers: modifiers, modifiers: modifiers,
options: options, options: options,
}); });
this.canFumble = canFumble;
// Parse and store check target number // Parse and store check target number
const checkTargetNumberModifier = this.modifiers.filter((m) => m[0] === "v")[0]; const checkTargetNumberModifier = this.modifiers.filter((m) => m[0] === "v")[0];
const ctnRgx = new RegExp("v([0-9]+)?"); const ctnRgx = new RegExp("v([0-9]+)?");
@ -38,10 +32,11 @@ export class DS4Check extends DiceTerm {
// Parse and store maximumCoupResult and minimumFumbleResult // Parse and store maximumCoupResult and minimumFumbleResult
const coupFumbleModifier = this.modifiers.filter((m) => m[0] === "c")[0]; const coupFumbleModifier = this.modifiers.filter((m) => m[0] === "c")[0];
const cfmRgx = new RegExp("c([0-9]+)?,([0-9]+)?"); const cfmRgx = new RegExp("c([0-9]+)?(:([0-9]+))?");
const cfmMatch = coupFumbleModifier?.match(cfmRgx); const cfmMatch = coupFumbleModifier?.match(cfmRgx);
if (cfmMatch) { if (cfmMatch) {
const [parseMaximumCoupResult, parseMinimumFumbleResult] = cfmMatch.slice(1); const parseMaximumCoupResult = cfmMatch[1];
const parseMinimumFumbleResult = cfmMatch[3];
this.maximumCoupResult = parseMaximumCoupResult this.maximumCoupResult = parseMaximumCoupResult
? parseInt(parseMaximumCoupResult) ? parseInt(parseMaximumCoupResult)
: DS4Check.DEFAULT_MAXIMUM_COUP_RESULT; : DS4Check.DEFAULT_MAXIMUM_COUP_RESULT;
@ -51,11 +46,17 @@ export class DS4Check extends DiceTerm {
if (this.minimumFumbleResult <= this.maximumCoupResult) if (this.minimumFumbleResult <= this.maximumCoupResult)
throw new SyntaxError(game.i18n.localize("DS4.ErrorDiceCritOverlap")); throw new SyntaxError(game.i18n.localize("DS4.ErrorDiceCritOverlap"));
} }
// Parse and store no fumble
const noFumbleModifier = this.modifiers.filter((m) => m[0] === "n")[0];
if (noFumbleModifier) {
this.canFumble = false;
}
} }
coup: boolean | null = null; coup: boolean | null = null;
fumble: boolean | null = null; fumble: boolean | null = null;
canFumble: boolean; canFumble = true;
checkTargetNumber = DS4Check.DEFAULT_CHECK_TARGET_NUMBER; checkTargetNumber = DS4Check.DEFAULT_CHECK_TARGET_NUMBER;
minimumFumbleResult = DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT; minimumFumbleResult = DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT;
maximumCoupResult = DS4Check.DEFAULT_MAXIMUM_COUP_RESULT; maximumCoupResult = DS4Check.DEFAULT_MAXIMUM_COUP_RESULT;
@ -107,7 +108,7 @@ export class DS4Check extends DiceTerm {
/** /**
* @override * @override
*/ */
static fromResults(options: Partial<DS4CheckTermData>, results: DiceTerm.Result[]): DS4Check { static fromResults(options: Partial<DiceTerm.TermData>, results: DiceTerm.Result[]): DS4Check {
const term = new this(options); const term = new this(options);
term.results = results; term.results = results;
term.evaluateResults(); term.evaluateResults();
@ -122,5 +123,6 @@ export class DS4Check extends DiceTerm {
static MODIFIERS = { static MODIFIERS = {
c: (): void => undefined, // Modifier is consumed in constructor for maximumCoupResult / minimumFumbleResult c: (): void => undefined, // Modifier is consumed in constructor for maximumCoupResult / minimumFumbleResult
v: (): void => undefined, // Modifier is consumed in constructor for checkTargetNumber v: (): void => undefined, // Modifier is consumed in constructor for checkTargetNumber
n: (): void => undefined, // Modifier is consumed in constructor for canFumble
}; };
} }

View file

@ -0,0 +1,29 @@
import { DS4Check } from "./check";
export default function registerSlayingDiceModifier(): void {
// TODO(types): Adjust types to allow extension of DiceTerm.MODIFIERS
// eslint-disable-next-line
// @ts-ignore
DicePool.MODIFIERS.x = slay;
DicePool.POOL_REGEX = /^{([^}]+)}([A-z]([A-z0-9<=>]+)?)?$/;
}
function slay(this: DicePool, modifier: string): void {
const rgx = /[xX]/;
const match = modifier.match(rgx);
if (!match || !this.rolls) return;
let checked = 0;
while (checked < (this.dice.length ?? 0)) {
const diceTerm = this.dice[checked];
checked++;
if (diceTerm instanceof DS4Check && diceTerm.coup) {
const formula = `dsv${diceTerm.checkTargetNumber}c${diceTerm.maximumCoupResult}:${diceTerm.minimumFumbleResult}n`;
const additionalRoll = Roll.create(formula).evaluate();
this.rolls.push(additionalRoll);
this.results.push({ result: additionalRoll.total ?? 0, active: true });
}
if (checked > 1000) throw new Error(game.i18n.localize("DS4.ErrorExplodingRecursionLimitExceeded"));
}
}