From aaa10d7469c0cb03ea85619292455f1420fd311c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= <oli_r@fg4f.de>
Date: Sat, 2 Jan 2021 16:40:30 +0100
Subject: [PATCH] Add slaying dice behaviour.

---
 spec/support/ds4rolls/executor.spec.ts |  7 ++--
 src/module/rolls/roll-executor.ts      | 49 ++++++++++++++++----------
 2 files changed, 33 insertions(+), 23 deletions(-)

diff --git a/spec/support/ds4rolls/executor.spec.ts b/spec/support/ds4rolls/executor.spec.ts
index e28447c9..1ab1bda4 100644
--- a/spec/support/ds4rolls/executor.spec.ts
+++ b/spec/support/ds4rolls/executor.spec.ts
@@ -314,13 +314,12 @@ describe("DS4 Rolls with multiple and slaying dice, first throw", () => {
     });
 });
 
-// TODO: Implement & reactivate
-xdescribe("DS4 Rolls with multiple and slaying dice, recurrent throw", () => {
+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, {}, rollProvider)).toEqual(
-            new RollResult(40, RollResultStatus.CRITICAL_FAILURE, [20, 2, 19]),
+        expect(rollCheckMultipleDice(48, { useSlayingDice: true, slayingDiceRepetition: true }, rollProvider)).toEqual(
+            new RollResult(41, RollResultStatus.SUCCESS, [20, 19, 2]),
         );
     });
 });
diff --git a/src/module/rolls/roll-executor.ts b/src/module/rolls/roll-executor.ts
index e9df9a9b..233950ea 100644
--- a/src/module/rolls/roll-executor.ts
+++ b/src/module/rolls/roll-executor.ts
@@ -33,24 +33,7 @@ export function rollCheckSingleDie(
     }
 }
 
-export function rollCheckMultipleDice(
-    testValue: number,
-    rollOptions: Partial<RollOptions>,
-    provider: RollProvider = new DS4RollProvider(),
-): RollResult {
-    const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
-    const finalCheck = testValue % 20;
-    const numberOfDice = Math.ceil(testValue / 20);
-
-    const dice = provider.getNextRolls(numberOfDice);
-
-    const firstResult = dice[0];
-
-    // Slaying Dice require a different handling.
-    if (firstResult >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) {
-        return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
-    }
-
+function separateCriticalHits(dice: Array<number>, usedOptions: RollOptions): [Array<number>, Array<number>] {
     const partitionCallback = (prev: [Array<number>, Array<number>], cur: number) => {
         if (cur <= usedOptions.maxCritSucc) {
             prev[0].push(cur);
@@ -64,6 +47,30 @@ export function rollCheckMultipleDice(
         .reduce(partitionCallback, [[], []])
         .map((a) => a.sort((r1, r2) => r2 - r1));
 
+    return [critSuccesses, otherRolls];
+}
+
+export function rollCheckMultipleDice(
+    testValue: number,
+    rollOptions: Partial<RollOptions>,
+    provider: RollProvider = new DS4RollProvider(),
+): RollResult {
+    const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
+    const finalCheck = testValue % 20;
+    const numberOfDice = Math.ceil(testValue / 20);
+
+    const dice = provider.getNextRolls(numberOfDice);
+
+    const firstResult = dice[0];
+    const slayingDiceRepetition = isSlayingDiceRepetition(usedOptions);
+
+    // Slaying Dice require a different handling.
+    if (firstResult >= usedOptions.minCritFail && !slayingDiceRepetition) {
+        return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
+    }
+
+    const [critSuccesses, otherRolls] = separateCriticalHits(dice, usedOptions);
+
     const swapLastWithCrit: boolean = isDiceSwapNecessary(critSuccesses, otherRolls, finalCheck);
 
     let sortedRollResults: Array<number>;
@@ -96,5 +103,9 @@ export function rollCheckMultipleDice(
         })
         .reduce((a, b) => a + b);
 
-    return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults);
+    if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) {
+        return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, sortedRollResults);
+    } else {
+        return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults);
+    }
 }