From 7f973e7de82f14df92cc9e865a37ab95a6e8b293 Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Sat, 13 Mar 2021 22:08:04 +0100 Subject: [PATCH] Add slying dice modifier to DicePool --- src/module/ds4.ts | 17 +++++++------ src/module/rolls/check.ts | 26 ++++++++++---------- src/module/rolls/slaying-dice-modifier.ts | 29 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 src/module/rolls/slaying-dice-modifier.ts diff --git a/src/module/ds4.ts b/src/module/ds4.ts index 67d03724..f64e05c0 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -1,15 +1,16 @@ 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 { DS4CreatureActorSheet } from "./actor/sheets/creature-sheet"; -import { createCheckRoll } from "./rolls/check-factory"; -import { registerSystemSettings } from "./settings"; -import { migration } from "./migrations"; +import { DS4 } from "./config"; import registerHandlebarsHelpers from "./handlebars/handlebars-helpers"; 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 () => { 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.terms.s = DS4Check; + registerSlayingDiceModifier(); + registerSystemSettings(); Actors.unregisterSheet("core", ActorSheet); diff --git a/src/module/rolls/check.ts b/src/module/rolls/check.ts index e8c518c9..ea24c4e5 100644 --- a/src/module/rolls/check.ts +++ b/src/module/rolls/check.ts @@ -1,28 +1,22 @@ import evaluateCheck, { getRequiredNumberOfDice } from "./check-evaluation"; -interface DS4CheckTermData extends DiceTerm.TermData { - canFumble: boolean; -} - /** * Implements DS4 Checks as an emulated "dice throw". * * @example * - 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 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` */ export class DS4Check extends DiceTerm { - constructor({ modifiers = [], options = {}, canFumble = true }: Partial = {}) { + constructor({ modifiers = [], options }: Partial = {}) { super({ faces: 20, modifiers: modifiers, options: options, }); - this.canFumble = canFumble; - // Parse and store check target number const checkTargetNumberModifier = this.modifiers.filter((m) => m[0] === "v")[0]; const ctnRgx = new RegExp("v([0-9]+)?"); @@ -38,10 +32,11 @@ export class DS4Check extends DiceTerm { // Parse and store maximumCoupResult and minimumFumbleResult 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); if (cfmMatch) { - const [parseMaximumCoupResult, parseMinimumFumbleResult] = cfmMatch.slice(1); + const parseMaximumCoupResult = cfmMatch[1]; + const parseMinimumFumbleResult = cfmMatch[3]; this.maximumCoupResult = parseMaximumCoupResult ? parseInt(parseMaximumCoupResult) : DS4Check.DEFAULT_MAXIMUM_COUP_RESULT; @@ -51,11 +46,17 @@ export class DS4Check extends DiceTerm { if (this.minimumFumbleResult <= this.maximumCoupResult) 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; fumble: boolean | null = null; - canFumble: boolean; + canFumble = true; checkTargetNumber = DS4Check.DEFAULT_CHECK_TARGET_NUMBER; minimumFumbleResult = DS4Check.DEFAULT_MINIMUM_FUMBLE_RESULT; maximumCoupResult = DS4Check.DEFAULT_MAXIMUM_COUP_RESULT; @@ -107,7 +108,7 @@ export class DS4Check extends DiceTerm { /** * @override */ - static fromResults(options: Partial, results: DiceTerm.Result[]): DS4Check { + static fromResults(options: Partial, results: DiceTerm.Result[]): DS4Check { const term = new this(options); term.results = results; term.evaluateResults(); @@ -122,5 +123,6 @@ export class DS4Check extends DiceTerm { static MODIFIERS = { c: (): void => undefined, // Modifier is consumed in constructor for maximumCoupResult / minimumFumbleResult v: (): void => undefined, // Modifier is consumed in constructor for checkTargetNumber + n: (): void => undefined, // Modifier is consumed in constructor for canFumble }; } diff --git a/src/module/rolls/slaying-dice-modifier.ts b/src/module/rolls/slaying-dice-modifier.ts new file mode 100644 index 00000000..dbb4176a --- /dev/null +++ b/src/module/rolls/slaying-dice-modifier.ts @@ -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")); + } +}