From 9feaa5216db3d2faf93f898f196af0a091a70f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Wed, 6 Jan 2021 18:27:22 +0100 Subject: [PATCH 01/10] Make roll executor fir for modifier-approach. --- spec/support/ds4rolls/executor.spec.ts | 201 +++++++------------------ src/module/rolls/roll-data.ts | 7 +- src/module/rolls/roll-executor.ts | 50 +++--- 3 files changed, 89 insertions(+), 169 deletions(-) diff --git a/spec/support/ds4rolls/executor.spec.ts b/spec/support/ds4rolls/executor.spec.ts index 3417e6e6..eff0bce8 100644 --- a/spec/support/ds4rolls/executor.spec.ts +++ b/spec/support/ds4rolls/executor.spec.ts @@ -1,76 +1,43 @@ import { rollCheckMultipleDice, rollCheckSingleDie } from "../../../src/module/rolls/roll-executor"; -import { RollProvider } from "../../../src/module/rolls/roll-provider"; import "jasmine"; import { RollResult, RollResultStatus } from "../../../src/module/rolls/roll-data"; -function mockSingleThrow(value: number): RollProvider { - const rollProvider = jasmine.createSpyObj("rollProvider", ["getNextRoll"]); - rollProvider.getNextRoll = jasmine.createSpy("getNextRoll").and.returnValue(value); - return rollProvider; -} - -function mockMultipleThrows(values: Array): RollProvider { - const rollProvider = jasmine.createSpyObj("rollProvider", ["getNextRolls"]); - rollProvider.getNextRolls = jasmine.createSpy("getNextRolls").and.returnValue(values); - return rollProvider; -} - describe("DS4 Rolls with one die and no modifications.", () => { it("Should do a regular success roll.", () => { - const rollProvider = mockSingleThrow(4); - - expect(rollCheckSingleDie(12, {}, rollProvider)).toEqual(new RollResult(4, RollResultStatus.SUCCESS, [4])); + expect(rollCheckSingleDie(12, {}, [4])).toEqual(new RollResult(4, RollResultStatus.SUCCESS, [4], true)); }); it("Should do a single success roll on success upper edge case.", () => { - const rollProvider = mockSingleThrow(4); - - expect(rollCheckSingleDie(4, {}, rollProvider)).toEqual(new RollResult(4, RollResultStatus.SUCCESS, [4])); + expect(rollCheckSingleDie(4, {}, [4])).toEqual(new RollResult(4, RollResultStatus.SUCCESS, [4], true)); }); it("Should do a single failure roll on lower edge case.", () => { - const rollProvider = mockSingleThrow(5); - - expect(rollCheckSingleDie(4, {}, rollProvider)).toEqual(new RollResult(0, RollResultStatus.FAILURE, [5])); + expect(rollCheckSingleDie(4, {}, [5])).toEqual(new RollResult(0, RollResultStatus.FAILURE, [5], true)); }); it("Should do a single failure roll on upper edge case '19'.", () => { - const rollProvider = mockSingleThrow(19); - - expect(rollCheckSingleDie(4, {}, rollProvider)).toEqual(new RollResult(0, RollResultStatus.FAILURE, [19])); + expect(rollCheckSingleDie(4, {}, [19])).toEqual(new RollResult(0, RollResultStatus.FAILURE, [19])); }); it("Should do a single crit success roll on '1'.", () => { - const rollProvider = mockSingleThrow(1); - - expect(rollCheckSingleDie(4, {}, rollProvider)).toEqual( - new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), - ); + expect(rollCheckSingleDie(4, {}, [1])).toEqual(new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1], true)); }); it("Should do a single crit failure roll on '20'.", () => { - const rollProvider = mockSingleThrow(20); - - expect(rollCheckSingleDie(4, {}, rollProvider)).toEqual( - new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20]), - ); + expect(rollCheckSingleDie(4, {}, [20])).toEqual(new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20])); }); }); describe("DS4 Rolls with one die and slaying dice, first throw.", () => { it("Should do a crit success on `1`", () => { - const rollProvider = mockSingleThrow(1); - - expect(rollCheckSingleDie(4, { useSlayingDice: true }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { useSlayingDice: true }, [1])).toEqual( new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), ); }); it("Should do a crit fail on `20`", () => { - const rollProvider = mockSingleThrow(20); - - expect(rollCheckSingleDie(4, { useSlayingDice: true }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { useSlayingDice: true }, [20])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20]), ); }); @@ -78,25 +45,19 @@ describe("DS4 Rolls with one die and slaying dice, first throw.", () => { describe("DS4 Rolls with one die and slaying dice, followup throw.", () => { it("Should do a crit success on `1`", () => { - const rollProvider = mockSingleThrow(1); - - expect(rollCheckSingleDie(4, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { useSlayingDice: true, slayingDiceRepetition: true }, [1])).toEqual( new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), ); }); it("Should do a regular fail on `20`", () => { - const rollProvider = mockSingleThrow(20); - - expect(rollCheckSingleDie(4, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { useSlayingDice: true, slayingDiceRepetition: true }, [20])).toEqual( new RollResult(0, RollResultStatus.FAILURE, [20]), ); }); it("Should do a regular success on `20` with a CTN of 20", () => { - const rollProvider = mockSingleThrow(20); - - expect(rollCheckSingleDie(20, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual( + expect(rollCheckSingleDie(20, { useSlayingDice: true, slayingDiceRepetition: true }, [20])).toEqual( new RollResult(20, RollResultStatus.SUCCESS, [20]), ); }); @@ -104,49 +65,37 @@ describe("DS4 Rolls with one die and slaying dice, followup throw.", () => { describe("DS4 Rolls with one die and crit roll modifications.", () => { it("Should do a crit success on `1`.", () => { - const rollProvider = mockSingleThrow(1); - - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [1])).toEqual( new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), ); }); it("Should do a crit success on `maxCritSucc`.", () => { - const rollProvider = mockSingleThrow(2); - - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [2])).toEqual( new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [2]), ); }); it("Should do a success on lower edge case `3`.", () => { - const rollProvider = mockSingleThrow(3); - - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [3])).toEqual( new RollResult(3, RollResultStatus.SUCCESS, [3]), ); }); it("Should do a success on upper edge case `18`.", () => { - const rollProvider = mockSingleThrow(18); - - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [18])).toEqual( new RollResult(0, RollResultStatus.FAILURE, [18]), ); }); it("Should do a crit fail on `minCritFail`.", () => { - const rollProvider = mockSingleThrow(19); - - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [19])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19]), ); }); it("Should do a crit fail on `20`", () => { - const rollProvider = mockSingleThrow(20); - - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [20])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20]), ); }); @@ -154,149 +103,109 @@ describe("DS4 Rolls with one die and crit roll modifications.", () => { describe("DS4 Rolls with multiple dice and no modifiers.", () => { it("Should do a crit fail on `20` for first roll.", () => { - const rollProvider = mockMultipleThrows([20, 15, 6]); - - expect(rollCheckMultipleDice(48, {}, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, {}, [20, 15, 6])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20, 15, 6]), ); }); it("Should succeed normally with all rolls crit successes.", () => { - const rollProvider = mockMultipleThrows([1, 1, 1]); - - expect(rollCheckMultipleDice(48, {}, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, {}, [1, 1, 1])).toEqual( new RollResult(48, RollResultStatus.SUCCESS, [1, 1, 1]), ); }); it("Should succeed with the last roll not being suficient.", () => { - const rollProvider = mockMultipleThrows([15, 15, 15]); - - expect(rollCheckMultipleDice(48, {}, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, {}, [15, 15, 15])).toEqual( new RollResult(30, RollResultStatus.SUCCESS, [15, 15, 15]), ); }); it("Should succeed with the last roll a crit success.", () => { - const rollProvider = mockMultipleThrows([15, 15, 1]); - - expect(rollCheckMultipleDice(48, {}, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, {}, [15, 15, 1])).toEqual( new RollResult(38, RollResultStatus.SUCCESS, [15, 15, 1]), ); }); it("Should succeed with the last roll being 20 and one crit success.", () => { - const rollProvider = mockMultipleThrows([15, 1, 20]); - - expect(rollCheckMultipleDice(48, {}, rollProvider)).toEqual( - new RollResult(43, RollResultStatus.SUCCESS, [20, 15, 1]), + expect(rollCheckMultipleDice(48, {}, [15, 1, 20])).toEqual( + new RollResult(43, RollResultStatus.SUCCESS, [15, 1, 20]), ); }); it("Should properly maximize throw result with all dice success.", () => { - const rollProvider = mockMultipleThrows([15, 4, 12]); - - expect(rollCheckMultipleDice(46, {}, rollProvider)).toEqual( - new RollResult(31, RollResultStatus.SUCCESS, [15, 12, 4]), + expect(rollCheckMultipleDice(46, {}, [15, 4, 12])).toEqual( + new RollResult(31, RollResultStatus.SUCCESS, [15, 4, 12]), ); }); it("Should properly maximize throw result with one dice a failure.", () => { - const rollProvider = mockMultipleThrows([15, 8, 20]); - - expect(rollCheckMultipleDice(46, {}, rollProvider)).toEqual( - new RollResult(35, RollResultStatus.SUCCESS, [20, 15, 8]), + expect(rollCheckMultipleDice(46, {}, [15, 8, 20])).toEqual( + new RollResult(35, RollResultStatus.SUCCESS, [15, 8, 20]), ); }); it("Should maximize on 'lowest dice higher than last CTN and crit success thrown'-Edge case, no change required.", () => { - const rollProvider = mockMultipleThrows([15, 1, 8]); - - expect(rollCheckMultipleDice(46, {}, rollProvider)).toEqual( - new RollResult(35, RollResultStatus.SUCCESS, [1, 15, 8]), + expect(rollCheckMultipleDice(46, {}, [15, 1, 8])).toEqual( + new RollResult(35, RollResultStatus.SUCCESS, [15, 1, 8]), ); }); it("Should maximize on 2-dice 'lowest dice higher than last CTN and crit success thrown'-Edge case, no change required.", () => { - const rollProvider = mockMultipleThrows([1, 8]); - - expect(rollCheckMultipleDice(24, {}, rollProvider)).toEqual( - new RollResult(20, RollResultStatus.SUCCESS, [1, 8]), - ); + expect(rollCheckMultipleDice(24, {}, [1, 8])).toEqual(new RollResult(20, RollResultStatus.SUCCESS, [1, 8])); }); it("Should maximize on 2-dice 'lowest dice higher than last CTN and crit success thrown'-Edge case, change required.", () => { - const rollProvider = mockMultipleThrows([1, 19]); - - expect(rollCheckMultipleDice(38, {}, rollProvider)).toEqual( - new RollResult(37, RollResultStatus.SUCCESS, [19, 1]), - ); + expect(rollCheckMultipleDice(38, {}, [1, 19])).toEqual(new RollResult(37, RollResultStatus.SUCCESS, [1, 19])); }); it("Should maximize correctly when swapping with more than one crit success", () => { - const rollProvider = mockMultipleThrows([1, 1, 15]); - - expect(rollCheckMultipleDice(48, {}, rollProvider)).toEqual( - new RollResult(43, RollResultStatus.SUCCESS, [1, 15, 1]), + expect(rollCheckMultipleDice(48, {}, [1, 1, 15])).toEqual( + new RollResult(43, RollResultStatus.SUCCESS, [1, 1, 15]), ); }); }); describe("DS4 Rolls with multiple dice and min/max modifiers.", () => { it("Should do a crit fail on `19` for first roll.", () => { - const rollProvider = mockMultipleThrows([19, 15, 6]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [19, 15, 6])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19, 15, 6]), ); }); it("Should succeed with all rolls crit successes (1 and 2).", () => { - const rollProvider = mockMultipleThrows([2, 1, 2]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( - new RollResult(48, RollResultStatus.SUCCESS, [2, 2, 1]), + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [2, 1, 2])).toEqual( + new RollResult(48, RollResultStatus.SUCCESS, [2, 1, 2]), ); }); it("Should succeed with the last roll not being sufficient.", () => { - const rollProvider = mockMultipleThrows([15, 15, 15]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 15, 15])).toEqual( new RollResult(30, RollResultStatus.SUCCESS, [15, 15, 15]), ); }); it("Should succeed with the last roll a crit success `2`.", () => { - const rollProvider = mockMultipleThrows([15, 15, 2]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 15, 2])).toEqual( new RollResult(38, RollResultStatus.SUCCESS, [15, 15, 2]), ); }); it("Should succeed with the last roll being `20` and one crit success '2'.", () => { - const rollProvider = mockMultipleThrows([15, 2, 20]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( - new RollResult(43, RollResultStatus.SUCCESS, [20, 15, 2]), + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 2, 20])).toEqual( + new RollResult(43, RollResultStatus.SUCCESS, [15, 2, 20]), ); }); it("Should succeed with the last roll being `19` and one crit success '2'.", () => { - const rollProvider = mockMultipleThrows([15, 2, 19]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, rollProvider)).toEqual( - new RollResult(42, RollResultStatus.SUCCESS, [19, 15, 2]), + expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 2, 19])).toEqual( + new RollResult(42, RollResultStatus.SUCCESS, [15, 2, 19]), ); }); }); describe("DS4 Rolls with multiple dice and fail modifiers.", () => { it("Should do a crit fail on `19` for first roll.", () => { - const rollProvider = mockMultipleThrows([19, 15, 6]); - - expect(rollCheckMultipleDice(48, { minCritFail: 19 }, rollProvider)).toEqual( + expect(rollCheckMultipleDice(48, { minCritFail: 19 }, [19, 15, 6])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19, 15, 6]), ); }); @@ -304,38 +213,30 @@ describe("DS4 Rolls with multiple dice and fail modifiers.", () => { describe("DS4 Rolls with multiple dice and success modifiers.", () => { it("Should succeed with all rolls crit successes (1 and 2).", () => { - const rollProvider = mockMultipleThrows([2, 1, 2]); - - expect(rollCheckMultipleDice(48, { maxCritSucc: 2 }, rollProvider)).toEqual( - new RollResult(48, RollResultStatus.SUCCESS, [2, 2, 1]), + expect(rollCheckMultipleDice(48, { maxCritSucc: 2 }, [2, 1, 2])).toEqual( + new RollResult(48, RollResultStatus.SUCCESS, [2, 1, 2]), ); }); }); describe("DS4 Rolls with multiple and slaying dice, first throw", () => { it("Should fail with the first roll being a `20`", () => { - const rollProvider = mockMultipleThrows([20, 2, 19]); - - expect(rollCheckMultipleDice(48, { useSlayingDice: true }, rollProvider)).toEqual( - new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20, 2, 19]), + expect(rollCheckMultipleDice(48, { useSlayingDice: true }, [20, 2, 19])).toEqual( + new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20, 2, 19], true), ); }); it("Should issue a critical success, even with resorting dice", () => { - const rollProvider = mockMultipleThrows([2, 19, 15]); - - expect(rollCheckMultipleDice(48, { useSlayingDice: true, maxCritSucc: 2 }, rollProvider)).toEqual( - new RollResult(42, RollResultStatus.CRITICAL_SUCCESS, [19, 15, 2]), + expect(rollCheckMultipleDice(48, { useSlayingDice: true, maxCritSucc: 2 }, [2, 19, 15])).toEqual( + new RollResult(42, RollResultStatus.CRITICAL_SUCCESS, [2, 19, 15]), ); }); }); describe("DS4 Rolls with multiple and slaying dice, recurrent throw", () => { it("Should regularly succeed with the first roll being a `20`", () => { - const rollProvider = mockMultipleThrows([20, 2, 19]); - - expect(rollCheckMultipleDice(48, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual( - new RollResult(41, RollResultStatus.SUCCESS, [20, 19, 2]), + expect(rollCheckMultipleDice(48, { useSlayingDice: true, slayingDiceRepetition: true }, [20, 2, 19])).toEqual( + new RollResult(41, RollResultStatus.SUCCESS, [20, 2, 19]), ); }); }); diff --git a/src/module/rolls/roll-data.ts b/src/module/rolls/roll-data.ts index e36fd6cd..f5b2b13e 100644 --- a/src/module/rolls/roll-data.ts +++ b/src/module/rolls/roll-data.ts @@ -17,7 +17,12 @@ export class DefaultRollOptions implements RollOptions { } export class RollResult { - constructor(public value: number, public status: RollResultStatus, public dice: Array) {} + constructor( + public value: number, + public status: RollResultStatus, + public dice: Array, + public active: boolean = true, + ) {} } export enum RollResultStatus { diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts index 0a7f1694..66d07690 100644 --- a/src/module/rolls/roll-executor.ts +++ b/src/module/rolls/roll-executor.ts @@ -1,5 +1,5 @@ import { DefaultRollOptions, RollOptions, RollResult, RollResultStatus } from "./roll-data"; -import { DS4RollProvider, RollProvider } from "./roll-provider"; +import { DS4RollProvider } from "./roll-provider"; import { calculateRollResult, isDiceSwapNecessary, isSlayingDiceRepetition, separateCriticalHits } from "./roll-utils"; /** @@ -7,11 +7,15 @@ import { calculateRollResult, isDiceSwapNecessary, isSlayingDiceRepetition, sepa * @param {number} checkTargetValue the final CTN, including all static modifiers. * @param {Partial} rollOptions optional, final option override that affect the checks outcome, e.g. different values for crits or whether slaying dice are used. */ -export function ds4roll(checkTargetValue: number, rollOptions: Partial = {}): RollResult { +export function ds4roll( + checkTargetValue: number, + rollOptions: Partial = {}, + dice: Array = null, +): RollResult { if (checkTargetValue <= 20) { - return rollCheckSingleDie(checkTargetValue, rollOptions); + return rollCheckSingleDie(checkTargetValue, rollOptions, dice); } else { - return rollCheckMultipleDice(checkTargetValue, rollOptions); + return rollCheckMultipleDice(checkTargetValue, rollOptions, dice); } } @@ -34,21 +38,27 @@ export function ds4roll(checkTargetValue: number, rollOptions: Partial, - provider: RollProvider = new DS4RollProvider(), + dice: Array = null, ): RollResult { const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); - const roll = provider.getNextRoll(); - const dice = [roll]; + + if (dice == null || dice.length != 1) { + console.error("Rolled a dice!"); + // TODO: Make "twist" from foundry.js available! + dice = [new DS4RollProvider().getNextRoll()]; + } + const usedDice = dice; + const roll = usedDice[0]; if (roll <= usedOptions.maxCritSucc) { - return new RollResult(checkTargetValue, RollResultStatus.CRITICAL_SUCCESS, dice); + return new RollResult(checkTargetValue, RollResultStatus.CRITICAL_SUCCESS, usedDice, true); } else if (roll >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) { - return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice); + return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, usedDice, true); } else { if (roll <= checkTargetValue) { - return new RollResult(roll, RollResultStatus.SUCCESS, dice); + return new RollResult(roll, RollResultStatus.SUCCESS, usedDice, true); } else { - return new RollResult(0, RollResultStatus.FAILURE, dice); + return new RollResult(0, RollResultStatus.FAILURE, usedDice, true); } } } @@ -66,29 +76,33 @@ export function rollCheckSingleDie( * @param {number} checkTargetValue- - The target value to check against. * @param {RollOptions} rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used. * @param {RollProvider} provider - Service providing the various, real dice throws. + * @param {Array} dice - Optional array of dice values to consider. * * @returns {RollResult} An object containing detailed information on the roll result. */ export function rollCheckMultipleDice( targetValue: number, rollOptions: Partial, - provider: RollProvider = new DS4RollProvider(), + dice: Array = null, ): RollResult { const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); const remainderTargetValue = targetValue % 20; const numberOfDice = Math.ceil(targetValue / 20); - const dice = provider.getNextRolls(numberOfDice); + if (!dice || dice.length != numberOfDice) { + dice = new DS4RollProvider().getNextRolls(numberOfDice); + } + const usedDice = dice; - const firstResult = dice[0]; + const firstResult = usedDice[0]; const slayingDiceRepetition = isSlayingDiceRepetition(usedOptions); // Slaying Dice require a different handling. if (firstResult >= usedOptions.minCritFail && !slayingDiceRepetition) { - return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice); + return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, usedDice, true); } - const [critSuccesses, otherRolls] = separateCriticalHits(dice, usedOptions); + const [critSuccesses, otherRolls] = separateCriticalHits(usedDice, usedOptions); const swapLastWithCrit: boolean = isDiceSwapNecessary([critSuccesses, otherRolls], remainderTargetValue); @@ -105,8 +119,8 @@ export function rollCheckMultipleDice( const evaluationResult = calculateRollResult(sortedRollResults, remainderTargetValue, usedOptions); if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) { - return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, sortedRollResults); + return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, usedDice, true); } else { - return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults); + return new RollResult(evaluationResult, RollResultStatus.SUCCESS, usedDice, true); } } From c885d2d40586cf94a1ae2e9c3d0e106ca6fe0015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Wed, 6 Jan 2021 19:15:56 +0100 Subject: [PATCH 02/10] Implement a custom DS4 "dice" class. Includes exploding and crit dice modifier, but not tested yet, as some required types are still missing. --- src/module/ds4.ts | 9 +++ src/module/rolls/ds4roll.ts | 95 +++++++++++++++++++++++++++++++ src/module/rolls/roll-data.ts | 1 + src/module/rolls/roll-executor.ts | 10 ++-- src/module/rolls/roll-provider.ts | 3 +- 5 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 src/module/rolls/ds4roll.ts diff --git a/src/module/ds4.ts b/src/module/ds4.ts index 358074b0..ddc135ae 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -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 }); diff --git a/src/module/rolls/ds4roll.ts b/src/module/rolls/ds4roll.ts new file mode 100644 index 00000000..336fb354 --- /dev/null +++ b/src/module/rolls/ds4roll.ts @@ -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 { + return (this.results as Array) + .filter((r) => r.active) + .map((r) => r.dice) + .reduce((acc: Array, cur: Array) => { + 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 = (this.results as Array).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)[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"); + } + } +} diff --git a/src/module/rolls/roll-data.ts b/src/module/rolls/roll-data.ts index f5b2b13e..516f8c61 100644 --- a/src/module/rolls/roll-data.ts +++ b/src/module/rolls/roll-data.ts @@ -22,6 +22,7 @@ export class RollResult { public status: RollResultStatus, public dice: Array, public active: boolean = true, + public exploded: boolean = false, ) {} } diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts index 66d07690..b28ca4a3 100644 --- a/src/module/rolls/roll-executor.ts +++ b/src/module/rolls/roll-executor.ts @@ -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); } diff --git a/src/module/rolls/roll-provider.ts b/src/module/rolls/roll-provider.ts index 0781e5b0..86c55606 100644 --- a/src/module/rolls/roll-provider.ts +++ b/src/module/rolls/roll-provider.ts @@ -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 { From ab450808f5f789e59ec1a5059e7fbd9168184d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Wed, 6 Jan 2021 23:42:05 +0100 Subject: [PATCH 03/10] Update TS-Types. --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index a5d6c7a6..ff2912b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2702,7 +2702,7 @@ } }, "foundry-pc-types": { - "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#4e20e5c9cb1b3cd2e44555d7acfa89a3cf63f6ce", + "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f84074f63d1aeeb9229e441e8c3ccaa9cba64142", "from": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes", "dev": true, "requires": { From 1422f28760f358f3817575c1dee703ca3b3470df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Thu, 7 Jan 2021 02:26:09 +0100 Subject: [PATCH 04/10] Get basic features to work. --- src/module/rolls/ds4roll.ts | 56 +++++++++++++++++++++++++++---- src/module/rolls/roll-data.ts | 13 +++++-- src/module/rolls/roll-executor.ts | 6 ++-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/module/rolls/ds4roll.ts b/src/module/rolls/ds4roll.ts index 336fb354..162b6667 100644 --- a/src/module/rolls/ds4roll.ts +++ b/src/module/rolls/ds4roll.ts @@ -1,17 +1,44 @@ import { RollResult, RollResultStatus } from "./roll-data"; import { ds4roll } from "./roll-executor"; +interface TermData { + number: number; + faces: number; + modifiers: Array; + options: Record; +} + export class DS4Roll extends DiceTerm { - constructor({ faces = 20, modifiers = [], options = {} } = {}) { - super({ number: 1, faces: faces, modifiers: modifiers, options: options }); + constructor(termData: Partial) { + super({ + number: 1, + faces: termData.number ?? -1, + modifiers: termData.modifiers ?? [], + options: termData.options ?? {}, + }); + console.log("This @ constructor: ", termData); } + success = null; + failure = null; + number = 1; + /** * @override * @param param0 + * + * @private_notes This gets only called once for the first roll. */ roll({ minimize = false, maximize = false } = {}): RollResult { - return this.rollWithDifferentBorders(1, 20, { minimize, maximize }); + console.log(`This faces @ roll: ${this.faces}`); + const rollResult = this.rollWithDifferentBorders(1, 20, { 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( @@ -45,9 +72,17 @@ export class DS4Roll extends DiceTerm { }, []); } + get expression(): string { + return `${this.faces}ds${this.modifiers.join("")}`; + } + + get formula(): string { + return this.expression; + } + /** Term Modifiers */ crits(modifier: string): this { - const rgx: RegExp = /[c([0-9]+)?,([0-9]+)?]/; + const rgx = new RegExp("c([0-9]+)?,([0-9]+)?"); const match = modifier.match(rgx); if (!match) return this; const [parseCritSucc, parsedCritFail] = match.slice(1); @@ -66,8 +101,7 @@ export class DS4Roll extends DiceTerm { 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}`); + console.error(`Skipped explode for term ${this.expression}`); return this; } @@ -86,10 +120,18 @@ export class DS4Roll extends DiceTerm { if (r.dice[0] <= maxCritSucc) { r.exploded = true; - this.rollWithDifferentBorders(maxCritSucc, 21); + const newRoll = this.rollWithDifferentBorders(maxCritSucc, 21); + this.results.push(newRoll); } if (checked > 1000) throw new Error("Maximum recursion depth for explodign dice roll exceeded"); } } + + // TODO: To Types + static DENOMINATION = "s"; + static MODIFIERS = { + x: "explode", + c: "crits", + }; } diff --git a/src/module/rolls/roll-data.ts b/src/module/rolls/roll-data.ts index 516f8c61..0f298f7e 100644 --- a/src/module/rolls/roll-data.ts +++ b/src/module/rolls/roll-data.ts @@ -18,12 +18,21 @@ export class DefaultRollOptions implements RollOptions { export class RollResult { constructor( - public value: number, + public result: number, public status: RollResultStatus, public dice: Array, public active: boolean = true, public exploded: boolean = false, - ) {} + ) { + if (this.status == RollResultStatus.CRITICAL_FAILURE) { + this.failure = true; + } else if (this.status == RollResultStatus.CRITICAL_SUCCESS) { + this.success = true; + } + } + + public failure: boolean | void = undefined; + public success: boolean | void = undefined; } export enum RollResultStatus { diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts index b28ca4a3..f5464ce3 100644 --- a/src/module/rolls/roll-executor.ts +++ b/src/module/rolls/roll-executor.ts @@ -43,9 +43,9 @@ export function rollCheckSingleDie( const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); if (dice == null || dice.length != 1) { - console.error("Rolled a dice!"); - // TODO: Make "twist" from foundry.js available! + console.error(`Rolled a dice. Target: ${checkTargetValue}`); dice = [new DS4RollProvider().getNextRoll()]; + console.log(dice); } const usedDice = dice; const rolledDie = usedDice[0]; @@ -118,7 +118,7 @@ export function rollCheckMultipleDice( const evaluationResult = calculateRollResult(sortedRollResults, remainderTargetValue, usedOptions); - if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) { + if (firstResult <= usedOptions.maxCritSucc) { return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, usedDice, true); } else { return new RollResult(evaluationResult, RollResultStatus.SUCCESS, usedDice, true); From 14ffff1985dacc83aab2f4f491ec00038817b6bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Thu, 7 Jan 2021 02:33:22 +0100 Subject: [PATCH 05/10] Fix tests. --- spec/support/ds4rolls/executor.spec.ts | 16 ++++++++++------ src/module/rolls/roll-data.ts | 8 ++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/spec/support/ds4rolls/executor.spec.ts b/spec/support/ds4rolls/executor.spec.ts index eff0bce8..98978b20 100644 --- a/spec/support/ds4rolls/executor.spec.ts +++ b/spec/support/ds4rolls/executor.spec.ts @@ -110,7 +110,7 @@ describe("DS4 Rolls with multiple dice and no modifiers.", () => { it("Should succeed normally with all rolls crit successes.", () => { expect(rollCheckMultipleDice(48, {}, [1, 1, 1])).toEqual( - new RollResult(48, RollResultStatus.SUCCESS, [1, 1, 1]), + new RollResult(48, RollResultStatus.CRITICAL_SUCCESS, [1, 1, 1]), ); }); @@ -151,16 +151,20 @@ describe("DS4 Rolls with multiple dice and no modifiers.", () => { }); it("Should maximize on 2-dice 'lowest dice higher than last CTN and crit success thrown'-Edge case, no change required.", () => { - expect(rollCheckMultipleDice(24, {}, [1, 8])).toEqual(new RollResult(20, RollResultStatus.SUCCESS, [1, 8])); + expect(rollCheckMultipleDice(24, {}, [1, 8])).toEqual( + new RollResult(20, RollResultStatus.CRITICAL_SUCCESS, [1, 8]), + ); }); it("Should maximize on 2-dice 'lowest dice higher than last CTN and crit success thrown'-Edge case, change required.", () => { - expect(rollCheckMultipleDice(38, {}, [1, 19])).toEqual(new RollResult(37, RollResultStatus.SUCCESS, [1, 19])); + expect(rollCheckMultipleDice(38, {}, [1, 19])).toEqual( + new RollResult(37, RollResultStatus.CRITICAL_SUCCESS, [1, 19]), + ); }); it("Should maximize correctly when swapping with more than one crit success", () => { expect(rollCheckMultipleDice(48, {}, [1, 1, 15])).toEqual( - new RollResult(43, RollResultStatus.SUCCESS, [1, 1, 15]), + new RollResult(43, RollResultStatus.CRITICAL_SUCCESS, [1, 1, 15]), ); }); }); @@ -174,7 +178,7 @@ describe("DS4 Rolls with multiple dice and min/max modifiers.", () => { it("Should succeed with all rolls crit successes (1 and 2).", () => { expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [2, 1, 2])).toEqual( - new RollResult(48, RollResultStatus.SUCCESS, [2, 1, 2]), + new RollResult(48, RollResultStatus.CRITICAL_SUCCESS, [2, 1, 2]), ); }); @@ -214,7 +218,7 @@ describe("DS4 Rolls with multiple dice and fail modifiers.", () => { describe("DS4 Rolls with multiple dice and success modifiers.", () => { it("Should succeed with all rolls crit successes (1 and 2).", () => { expect(rollCheckMultipleDice(48, { maxCritSucc: 2 }, [2, 1, 2])).toEqual( - new RollResult(48, RollResultStatus.SUCCESS, [2, 1, 2]), + new RollResult(48, RollResultStatus.CRITICAL_SUCCESS, [2, 1, 2]), ); }); }); diff --git a/src/module/rolls/roll-data.ts b/src/module/rolls/roll-data.ts index 0f298f7e..ef98b436 100644 --- a/src/module/rolls/roll-data.ts +++ b/src/module/rolls/roll-data.ts @@ -36,8 +36,8 @@ export class RollResult { } export enum RollResultStatus { - FAILURE, - SUCCESS, - CRITICAL_FAILURE, - CRITICAL_SUCCESS, + FAILURE = "FAILURE", + SUCCESS = "SUCCESS", + CRITICAL_FAILURE = "CRITICAL_FAILURE", + CRITICAL_SUCCESS = "CRITICAL_SUCCESS", } From 3dca6a5ae41928da1caee2cdc49f84bc00b36018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Fri, 8 Jan 2021 21:07:15 +0100 Subject: [PATCH 06/10] Implement CTN as modifier. --- src/module/rolls/ds4roll.ts | 45 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/module/rolls/ds4roll.ts b/src/module/rolls/ds4roll.ts index 162b6667..2d588b74 100644 --- a/src/module/rolls/ds4roll.ts +++ b/src/module/rolls/ds4roll.ts @@ -12,16 +12,25 @@ export class DS4Roll extends DiceTerm { constructor(termData: Partial) { super({ number: 1, - faces: termData.number ?? -1, + faces: termData.faces, modifiers: termData.modifiers ?? [], options: termData.options ?? {}, }); console.log("This @ constructor: ", termData); + + const targetValueModifier = this.modifiers.filter((m) => m[0] === "v")[0] ?? "v" + DS4Roll.DEFAULT_TARGET_VALUE; + const rgx = new RegExp("v([0-9]+)?"); + const match = targetValueModifier.match(rgx); + if (match) { + const [parseTargetValue] = match.slice(1); + this.targetValue = parseTargetValue ? parseInt(parseTargetValue) : DS4Roll.DEFAULT_TARGET_VALUE; + } } success = null; failure = null; number = 1; + targetValue = DS4Roll.DEFAULT_TARGET_VALUE; /** * @override @@ -30,7 +39,7 @@ export class DS4Roll extends DiceTerm { * @private_notes This gets only called once for the first roll. */ roll({ minimize = false, maximize = false } = {}): RollResult { - console.log(`This faces @ roll: ${this.faces}`); + console.log(`This targetValue @ roll: ${this.targetValue}`); const rollResult = this.rollWithDifferentBorders(1, 20, { minimize, maximize }); this.results.push(rollResult); if (rollResult.status == RollResultStatus.CRITICAL_SUCCESS) { @@ -46,17 +55,14 @@ export class DS4Roll extends DiceTerm { minCritFail: number, { minimize = false, maximize = false } = {}, ): RollResult { + const targetValueToUse = this.targetValue; 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, - ); + const numberOfDice = Array(Math.ceil(targetValueToUse / 20)).fill(1); + return new RollResult(targetValueToUse, RollResultStatus.CRITICAL_SUCCESS, numberOfDice, true); } else { - return ds4roll(this.faces, { maxCritSucc: maxCritSuccess, minCritFail: minCritFail }); + return ds4roll(targetValueToUse, { maxCritSucc: maxCritSuccess, minCritFail: minCritFail }); } } @@ -72,15 +78,11 @@ export class DS4Roll extends DiceTerm { }, []); } - get expression(): string { - return `${this.faces}ds${this.modifiers.join("")}`; - } - - get formula(): string { - return this.expression; - } - /** Term Modifiers */ + noop(): this { + return this; + } + crits(modifier: string): this { const rgx = new RegExp("c([0-9]+)?,([0-9]+)?"); const match = modifier.match(rgx); @@ -90,8 +92,9 @@ export class DS4Roll extends DiceTerm { const maxCritSuccess = parseCritSucc ? parseInt(parseCritSucc) : 1; const minCritFail = parsedCritFail ? parseInt(parsedCritFail) : 20; + // noinspection UnnecessaryLocalVariableJS const newResults: Array = (this.results as Array).map((r) => { - return ds4roll(this.faces, { minCritFail: minCritFail, maxCritSucc: maxCritSuccess }, r.dice); + return ds4roll(this.targetValue, { minCritFail: minCritFail, maxCritSucc: maxCritSuccess }, r.dice); }); this.results = newResults; @@ -124,14 +127,16 @@ export class DS4Roll extends DiceTerm { this.results.push(newRoll); } - if (checked > 1000) throw new Error("Maximum recursion depth for explodign dice roll exceeded"); + if (checked > 1000) throw new Error("Maximum recursion depth for exploding dice roll exceeded"); } } - // TODO: To Types + static DEFAULT_TARGET_VALUE = 10; + // TODO: add to Type declarations static DENOMINATION = "s"; static MODIFIERS = { x: "explode", c: "crits", + v: "noop", // Modifier is consumed in constructor for target value }; } From 22f53e542049664607619893dd1aac4700d4874d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Fri, 8 Jan 2021 23:18:01 +0100 Subject: [PATCH 07/10] Allow multiple dice and error check. --- spec/support/ds4rolls/executor.spec.ts | 28 ++--- src/module/ds4.ts | 6 +- src/module/rolls/check.ts | 134 +++++++++++++++++++++++ src/module/rolls/ds4roll.ts | 142 ------------------------- src/module/rolls/roll-data.ts | 4 +- src/module/rolls/roll-executor.ts | 18 ++-- src/module/rolls/roll-utils.ts | 4 +- 7 files changed, 161 insertions(+), 175 deletions(-) create mode 100644 src/module/rolls/check.ts delete mode 100644 src/module/rolls/ds4roll.ts diff --git a/spec/support/ds4rolls/executor.spec.ts b/spec/support/ds4rolls/executor.spec.ts index 98978b20..705241a9 100644 --- a/spec/support/ds4rolls/executor.spec.ts +++ b/spec/support/ds4rolls/executor.spec.ts @@ -65,37 +65,37 @@ describe("DS4 Rolls with one die and slaying dice, followup throw.", () => { describe("DS4 Rolls with one die and crit roll modifications.", () => { it("Should do a crit success on `1`.", () => { - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [1])).toEqual( + expect(rollCheckSingleDie(4, { maxCritSuccess: 2, minCritFail: 19 }, [1])).toEqual( new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [1]), ); }); it("Should do a crit success on `maxCritSucc`.", () => { - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [2])).toEqual( + expect(rollCheckSingleDie(4, { maxCritSuccess: 2, minCritFail: 19 }, [2])).toEqual( new RollResult(4, RollResultStatus.CRITICAL_SUCCESS, [2]), ); }); it("Should do a success on lower edge case `3`.", () => { - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [3])).toEqual( + expect(rollCheckSingleDie(4, { maxCritSuccess: 2, minCritFail: 19 }, [3])).toEqual( new RollResult(3, RollResultStatus.SUCCESS, [3]), ); }); it("Should do a success on upper edge case `18`.", () => { - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [18])).toEqual( + expect(rollCheckSingleDie(4, { maxCritSuccess: 2, minCritFail: 19 }, [18])).toEqual( new RollResult(0, RollResultStatus.FAILURE, [18]), ); }); it("Should do a crit fail on `minCritFail`.", () => { - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [19])).toEqual( + expect(rollCheckSingleDie(4, { maxCritSuccess: 2, minCritFail: 19 }, [19])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19]), ); }); it("Should do a crit fail on `20`", () => { - expect(rollCheckSingleDie(4, { maxCritSucc: 2, minCritFail: 19 }, [20])).toEqual( + expect(rollCheckSingleDie(4, { maxCritSuccess: 2, minCritFail: 19 }, [20])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20]), ); }); @@ -171,37 +171,37 @@ describe("DS4 Rolls with multiple dice and no modifiers.", () => { describe("DS4 Rolls with multiple dice and min/max modifiers.", () => { it("Should do a crit fail on `19` for first roll.", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [19, 15, 6])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2, minCritFail: 19 }, [19, 15, 6])).toEqual( new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [19, 15, 6]), ); }); it("Should succeed with all rolls crit successes (1 and 2).", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [2, 1, 2])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2, minCritFail: 19 }, [2, 1, 2])).toEqual( new RollResult(48, RollResultStatus.CRITICAL_SUCCESS, [2, 1, 2]), ); }); it("Should succeed with the last roll not being sufficient.", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 15, 15])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2, minCritFail: 19 }, [15, 15, 15])).toEqual( new RollResult(30, RollResultStatus.SUCCESS, [15, 15, 15]), ); }); it("Should succeed with the last roll a crit success `2`.", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 15, 2])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2, minCritFail: 19 }, [15, 15, 2])).toEqual( new RollResult(38, RollResultStatus.SUCCESS, [15, 15, 2]), ); }); it("Should succeed with the last roll being `20` and one crit success '2'.", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 2, 20])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2, minCritFail: 19 }, [15, 2, 20])).toEqual( new RollResult(43, RollResultStatus.SUCCESS, [15, 2, 20]), ); }); it("Should succeed with the last roll being `19` and one crit success '2'.", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2, minCritFail: 19 }, [15, 2, 19])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2, minCritFail: 19 }, [15, 2, 19])).toEqual( new RollResult(42, RollResultStatus.SUCCESS, [15, 2, 19]), ); }); @@ -217,7 +217,7 @@ describe("DS4 Rolls with multiple dice and fail modifiers.", () => { describe("DS4 Rolls with multiple dice and success modifiers.", () => { it("Should succeed with all rolls crit successes (1 and 2).", () => { - expect(rollCheckMultipleDice(48, { maxCritSucc: 2 }, [2, 1, 2])).toEqual( + expect(rollCheckMultipleDice(48, { maxCritSuccess: 2 }, [2, 1, 2])).toEqual( new RollResult(48, RollResultStatus.CRITICAL_SUCCESS, [2, 1, 2]), ); }); @@ -231,7 +231,7 @@ describe("DS4 Rolls with multiple and slaying dice, first throw", () => { }); it("Should issue a critical success, even with resorting dice", () => { - expect(rollCheckMultipleDice(48, { useSlayingDice: true, maxCritSucc: 2 }, [2, 19, 15])).toEqual( + expect(rollCheckMultipleDice(48, { useSlayingDice: true, maxCritSuccess: 2 }, [2, 19, 15])).toEqual( new RollResult(42, RollResultStatus.CRITICAL_SUCCESS, [2, 19, 15]), ); }); diff --git a/src/module/ds4.ts b/src/module/ds4.ts index 1c593ff6..cd5dd1a8 100644 --- a/src/module/ds4.ts +++ b/src/module/ds4.ts @@ -4,7 +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"; +import { DS4Check } from "./rolls/check"; Hooks.once("init", async function () { console.log(`DS4 | Initializing the DS4 Game System\n${DS4.ASCII}`); @@ -23,11 +23,11 @@ Hooks.once("init", async function () { CONFIG.Item.entityClass = DS4Item as typeof Item; // Configure Dice - CONFIG.Dice.types = [Die, DS4Roll]; + CONFIG.Dice.types = [Die, DS4Check]; CONFIG.Dice.terms = { c: Coin, d: Die, - s: DS4Roll, + s: DS4Check, }; // Register sheet application classes diff --git a/src/module/rolls/check.ts b/src/module/rolls/check.ts new file mode 100644 index 00000000..4dffa96c --- /dev/null +++ b/src/module/rolls/check.ts @@ -0,0 +1,134 @@ +import { RollResult, RollResultStatus } from "./roll-data"; +import { ds4roll } from "./roll-executor"; + +interface TermData { + number: number; + faces: number; + modifiers: Array; + options: Record; +} + +export class DS4Check extends DiceTerm { + constructor(termData: Partial) { + 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) + .map((r) => { + const intermedResult = [r]; + + let checked = 0; + while (checked < intermedResult.length) { + const r = (intermedResult as Array)[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 + }; +} diff --git a/src/module/rolls/ds4roll.ts b/src/module/rolls/ds4roll.ts deleted file mode 100644 index 2d588b74..00000000 --- a/src/module/rolls/ds4roll.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { RollResult, RollResultStatus } from "./roll-data"; -import { ds4roll } from "./roll-executor"; - -interface TermData { - number: number; - faces: number; - modifiers: Array; - options: Record; -} - -export class DS4Roll extends DiceTerm { - constructor(termData: Partial) { - super({ - number: 1, - faces: termData.faces, - modifiers: termData.modifiers ?? [], - options: termData.options ?? {}, - }); - console.log("This @ constructor: ", termData); - - const targetValueModifier = this.modifiers.filter((m) => m[0] === "v")[0] ?? "v" + DS4Roll.DEFAULT_TARGET_VALUE; - const rgx = new RegExp("v([0-9]+)?"); - const match = targetValueModifier.match(rgx); - if (match) { - const [parseTargetValue] = match.slice(1); - this.targetValue = parseTargetValue ? parseInt(parseTargetValue) : DS4Roll.DEFAULT_TARGET_VALUE; - } - } - - success = null; - failure = null; - number = 1; - targetValue = DS4Roll.DEFAULT_TARGET_VALUE; - - /** - * @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(1, 20, { 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( - maxCritSuccess: number, - minCritFail: number, - { minimize = false, maximize = false } = {}, - ): RollResult { - const targetValueToUse = this.targetValue; - if (minimize) { - return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, [20], true); - } else if (maximize) { - const numberOfDice = Array(Math.ceil(targetValueToUse / 20)).fill(1); - return new RollResult(targetValueToUse, RollResultStatus.CRITICAL_SUCCESS, numberOfDice, true); - } else { - return ds4roll(targetValueToUse, { maxCritSucc: maxCritSuccess, minCritFail: minCritFail }); - } - } - - /** - * @override - */ - get values(): Array { - return (this.results as Array) - .filter((r) => r.active) - .map((r) => r.dice) - .reduce((acc: Array, cur: Array) => { - return acc.concat(cur); - }, []); - } - - /** Term Modifiers */ - noop(): this { - return this; - } - - crits(modifier: string): this { - const rgx = new 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; - - // noinspection UnnecessaryLocalVariableJS - const newResults: Array = (this.results as Array).map((r) => { - return ds4roll(this.targetValue, { 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) { - 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)[checked]; - checked++; - if (!r.active) continue; - - if (r.dice[0] <= maxCritSucc) { - r.exploded = true; - const newRoll = this.rollWithDifferentBorders(maxCritSucc, 21); - this.results.push(newRoll); - } - - if (checked > 1000) throw new Error("Maximum recursion depth for exploding dice roll exceeded"); - } - } - - static DEFAULT_TARGET_VALUE = 10; - // TODO: add to Type declarations - static DENOMINATION = "s"; - static MODIFIERS = { - x: "explode", - c: "crits", - v: "noop", // Modifier is consumed in constructor for target value - }; -} diff --git a/src/module/rolls/roll-data.ts b/src/module/rolls/roll-data.ts index ef98b436..964034c9 100644 --- a/src/module/rolls/roll-data.ts +++ b/src/module/rolls/roll-data.ts @@ -1,12 +1,12 @@ export interface RollOptions { - maxCritSucc: number; + maxCritSuccess: number; minCritFail: number; useSlayingDice: boolean; slayingDiceRepetition: boolean; } export class DefaultRollOptions implements RollOptions { - public maxCritSucc = 1; + public maxCritSuccess = 1; public minCritFail = 20; public useSlayingDice = false; public slayingDiceRepetition = false; diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts index f5464ce3..3efe386f 100644 --- a/src/module/rolls/roll-executor.ts +++ b/src/module/rolls/roll-executor.ts @@ -6,6 +6,7 @@ import { calculateRollResult, isDiceSwapNecessary, isSlayingDiceRepetition, sepa * Performs a roll against a check target number, e.g. for usage in battle, but not for herbs. * @param {number} checkTargetValue the final CTN, including all static modifiers. * @param {Partial} rollOptions optional, final option override that affect the checks outcome, e.g. different values for crits or whether slaying dice are used. + * @param {Array} dice optional, pass already thrown dice that are used instead of rolling new ones. */ export function ds4roll( checkTargetValue: number, @@ -26,12 +27,9 @@ export function ds4roll( * This is not intended for direct usage. Use * {@link ds4roll | the function that is not bound to an amount of Dice} instead. * - * @remarks - * The `provider` is only exposed for testing. - * * @param {number} checkTargetValue - The target value to check against. * @param {RollOptions} rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used. - * @param {RollProvider} provider - Service providing the various, real dice throws. + * @param {Array} dice optional, pass already thrown dice that are used instead of rolling new ones. * * @returns {RollResult} An object containing detailed information on the roll result. */ @@ -50,7 +48,7 @@ export function rollCheckSingleDie( const usedDice = dice; const rolledDie = usedDice[0]; - if (rolledDie <= usedOptions.maxCritSucc) { + if (rolledDie <= usedOptions.maxCritSuccess) { return new RollResult(checkTargetValue, RollResultStatus.CRITICAL_SUCCESS, usedDice, true); } else if (rolledDie >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) { return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, usedDice, true); @@ -70,13 +68,9 @@ export function rollCheckSingleDie( * This is not intended for direct usage. Use * {@link ds4roll | the function that is not bound to an amount of Dice} instead. * - * @remarks - * The `provider` is only exposed for testing. - * - * @param {number} checkTargetValue- - The target value to check against. + * @param {number} targetValue- - The target value to check against. * @param {RollOptions} rollOptions - Options that affect the checks outcome, e.g. different values for crits or whether slaying dice are used. - * @param {RollProvider} provider - Service providing the various, real dice throws. - * @param {Array} dice - Optional array of dice values to consider. + * @param {Array} dice - Optional array of dice values to consider instead of rolling new ones. * * @returns {RollResult} An object containing detailed information on the roll result. */ @@ -118,7 +112,7 @@ export function rollCheckMultipleDice( const evaluationResult = calculateRollResult(sortedRollResults, remainderTargetValue, usedOptions); - if (firstResult <= usedOptions.maxCritSucc) { + if (firstResult <= usedOptions.maxCritSuccess) { return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, usedDice, true); } else { return new RollResult(evaluationResult, RollResultStatus.SUCCESS, usedDice, true); diff --git a/src/module/rolls/roll-utils.ts b/src/module/rolls/roll-utils.ts index 09d618c3..a880a66d 100644 --- a/src/module/rolls/roll-utils.ts +++ b/src/module/rolls/roll-utils.ts @@ -14,7 +14,7 @@ import { RollOptions } from "./roll-data"; */ export function separateCriticalHits(dice: Array, usedOptions: RollOptions): CritsAndNonCrits { const [critSuccesses, otherRolls] = partition(dice, (v: number) => { - return v <= usedOptions.maxCritSucc; + return v <= usedOptions.maxCritSuccess; }).map((a) => a.sort((r1, r2) => r2 - r1)); return [critSuccesses, otherRolls]; @@ -112,7 +112,7 @@ export function calculateRollResult( return rollsAndMaxValues .map(([v, m]) => { - return v <= rollOptions.maxCritSucc ? [m, m] : [v, m]; + return v <= rollOptions.maxCritSuccess ? [m, m] : [v, m]; }) .filter(([v, m]) => v <= m) .map(([v]) => v) From a5e240ab75c90ed4b909c5efa2a475d3bee15231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Fri, 8 Jan 2021 23:31:42 +0100 Subject: [PATCH 08/10] Localize Error Messages. --- src/lang/en.json | 4 +++- src/module/rolls/check.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index 267e0b2c..a768c9f4 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -110,5 +110,7 @@ "DS4.ProfileWeight": "Weight", "DS4.ProfileEyeColor": "Eye Color", "DS4.ProfileSpecialCharacteristics": "Special Characteristics", - "DS4.WarningManageActiveEffectOnOwnedItem": "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update." + "DS4.WarningManageActiveEffectOnOwnedItem": "Managing Active Effects within an Owned Item is not currently supported and will be added in a subsequent update.", + "DS4.ErrorDiceCritOverlap": "There's an overlap between Fumbles and Coups", + "DS4.ErrorExplodingRecursionLimitExceeded": "Maximum recursion depth for exploding dice roll exceeded" } diff --git a/src/module/rolls/check.ts b/src/module/rolls/check.ts index 4dffa96c..e931f680 100644 --- a/src/module/rolls/check.ts +++ b/src/module/rolls/check.ts @@ -40,7 +40,7 @@ export class DS4Check extends DiceTerm { ? parseInt(parseMinCritFailure) : DS4Check.DEFAULT_MIN_CRIT_FAILURE; if (this.minCritFailure <= this.maxCritSuccess) - throw new SyntaxError("There's an overlap between Fumbles and Coups"); + throw new SyntaxError(game.i18n.localize("DS4.ErrorDiceCritOverlap")); } } @@ -98,23 +98,23 @@ export class DS4Check extends DiceTerm { this.results = (this.results as Array) .map((r) => { - const intermedResult = [r]; + const intermediateResults = [r]; let checked = 0; - while (checked < intermedResult.length) { - const r = (intermedResult as Array)[checked]; + while (checked < intermediateResults.length) { + const r = (intermediateResults as Array)[checked]; checked++; if (!r.active) continue; if (r.dice[0] <= this.maxCritSuccess) { r.exploded = true; const newRoll = this.rollWithDifferentBorders({}, true); - intermedResult.push(newRoll); + intermediateResults.push(newRoll); } - if (checked > 1000) throw new Error("Maximum recursion depth for exploding dice roll exceeded"); + if (checked > 1000) throw new Error(game.i18n.localize("DS4.ErrorExplodingRecursionLimitExceeded")); } - return intermedResult; + return intermediateResults; }) .reduce((acc, cur) => { return acc.concat(cur); From 25119fd9e3af9bcc9ffa96963be9ab4c87078f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Fri, 8 Jan 2021 23:41:23 +0100 Subject: [PATCH 09/10] Add very simple docs. --- src/module/rolls/check.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/module/rolls/check.ts b/src/module/rolls/check.ts index e931f680..58cba71d 100644 --- a/src/module/rolls/check.ts +++ b/src/module/rolls/check.ts @@ -8,6 +8,19 @@ interface TermData { options: Record; } +/** + * Implements DS4 Checks as an emulated "dice throw". + * + * @notes + * Be aware that, even though this behaves like one roll, it actually throws several iones internally + * + * @example + * - Roll a check against a Check Target Number (CTV) 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 `5` a coup and default fumble: `/r dsv19c5` + * - Roll a check with exploding dice: `/r dsv34x` + */ export class DS4Check extends DiceTerm { constructor(termData: Partial) { super({ From 42868e4a836a0ef9dbbcea29761c3d3666cbeeee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= Date: Fri, 8 Jan 2021 23:46:28 +0100 Subject: [PATCH 10/10] Fix review comments. --- src/module/rolls/check.ts | 7 +------ src/module/rolls/roll-executor.ts | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/module/rolls/check.ts b/src/module/rolls/check.ts index 58cba71d..5886813c 100644 --- a/src/module/rolls/check.ts +++ b/src/module/rolls/check.ts @@ -12,7 +12,7 @@ interface TermData { * Implements DS4 Checks as an emulated "dice throw". * * @notes - * Be aware that, even though this behaves like one roll, it actually throws several iones internally + * Be aware that, even though this behaves like one roll, it actually throws several ones internally * * @example * - Roll a check against a Check Target Number (CTV) of 18: `/r dsv18` @@ -29,7 +29,6 @@ export class DS4Check extends DiceTerm { 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]; @@ -65,12 +64,8 @@ export class DS4Check extends DiceTerm { /** * @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) { diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts index 3efe386f..72e6b2c4 100644 --- a/src/module/rolls/roll-executor.ts +++ b/src/module/rolls/roll-executor.ts @@ -40,10 +40,8 @@ export function rollCheckSingleDie( ): RollResult { const usedOptions = new DefaultRollOptions().mergeWith(rollOptions); - if (dice == null || dice.length != 1) { - console.error(`Rolled a dice. Target: ${checkTargetValue}`); + if (dice?.length != 1) { dice = [new DS4RollProvider().getNextRoll()]; - console.log(dice); } const usedDice = dice; const rolledDie = usedDice[0];