From 7800f0af8f8bc056dfe57e4d0bad4545cac173f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Oliver=20R=C3=BCmpelein?= <oli_r@fg4f.de>
Date: Wed, 30 Dec 2020 17:50:01 +0100
Subject: [PATCH] Add recommendet plugins, fix lint stuff.

---
 package-lock.json            |  2 +-
 src/module/rolls/ds4rolls.ts | 96 ++++++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+), 1 deletion(-)
 create mode 100644 src/module/rolls/ds4rolls.ts

diff --git a/package-lock.json b/package-lock.json
index 58ddfec0..d1ee44ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2678,7 +2678,7 @@
       }
     },
     "foundry-pc-types": {
-      "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#4ae5653e74e79bb6b4bd23b094dee066a0c7723a",
+      "version": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#4e20e5c9cb1b3cd2e44555d7acfa89a3cf63f6ce",
       "from": "git+https://git.f3l.de/dungeonslayers/foundry-pc-types.git#f3l-fixes",
       "dev": true,
       "requires": {
diff --git a/src/module/rolls/ds4rolls.ts b/src/module/rolls/ds4rolls.ts
new file mode 100644
index 00000000..50c6fed6
--- /dev/null
+++ b/src/module/rolls/ds4rolls.ts
@@ -0,0 +1,96 @@
+export function roll(testValue: number, rollOptions: RollOptions = new RollOptions()): Promise<RollResult> {
+    const finalRollValue = testValue;
+    if (finalRollValue <= 20) {
+        return rollCheckSingleDie(finalRollValue, rollOptions);
+    } else {
+        return rollCheckMultipleDice(finalRollValue, rollOptions);
+    }
+}
+
+async function rollCheckSingleDie(testValue: number, rollOptions: RollOptions): Promise<RollResult> {
+    const roll = new Roll("1d20");
+    roll.roll();
+
+    const pool = new DicePool({ rolls: [roll] });
+
+    if (roll.total <= rollOptions.maxCritSucc) {
+        return createRollResultPromise(testValue, RollResultStatus.CRITICAL_SUCCESS, pool);
+    } else if (roll.total >= rollOptions.minCritFail) {
+        return createRollResultPromise(0, RollResultStatus.CRITICAL_FAILURE, pool);
+    } else {
+        if (roll.total <= testValue) {
+            return createRollResultPromise(roll.total, RollResultStatus.SUCCESS, pool);
+        } else {
+            return createRollResultPromise(0, RollResultStatus.FAILURE, pool);
+        }
+    }
+}
+
+async function rollCheckMultipleDice(testValue: number, rollOptions: RollOptions): Promise<RollResult> {
+    const finalCheck = testValue % 20;
+    const numberOfDice = Math.ceil(testValue / 20);
+    const rolls = new Array<Roll>();
+    for (let i = 0; i < numberOfDice; i++) {
+        const roll = new Roll("1d20");
+        roll.roll();
+        rolls.concat(roll);
+    }
+    const pool = new DicePool({ rolls: rolls });
+
+    const firstResult = rolls[1].total;
+
+    // TODO: Special stuff (Gnomes!)
+    if (firstResult == 20) {
+        createRollResultPromise(0, RollResultStatus.CRITICAL_FAILURE, pool);
+    }
+
+    const [otherRolls, critSuccesses] = pool.rolls
+        .partition((r) => r.total <= rollOptions.maxCritSucc)
+        .map((a) => a.sort((r1, r2) => r2.total - r1.total));
+
+    const sortedRolls: DicePool = new DicePool({ rolls: critSuccesses.concat(otherRolls) });
+
+    const evaluationResult = sortedRolls.rolls
+        .map((r) => r.total)
+        .map((value, index) => {
+            if (index == numberOfDice - 1) {
+                if (value == 1) {
+                    return finalCheck;
+                } else {
+                    return value >= finalCheck ? value : 0;
+                }
+            } else {
+                if (value <= rollOptions.maxCritSucc) {
+                    return 20;
+                } else {
+                    return value;
+                }
+            }
+        })
+        .reduce((a, b) => a + b);
+
+    return createRollResultPromise(evaluationResult, RollResultStatus.SUCCESS, sortedRolls);
+}
+
+function createRollResultPromise(
+    totalValue: number,
+    rollResult: RollResultStatus,
+    dicePool: DicePool,
+): Promise<RollResult> {
+    return new Promise((resolve) => resolve(new RollResult(totalValue, RollResultStatus.SUCCESS, dicePool)));
+}
+
+export class RollOptions {
+    constructor(public minCritFail: number = 20, public maxCritSucc: number = 1) {}
+}
+
+export class RollResult {
+    constructor(public value: number, public status: RollResultStatus, public dice: DicePool) {}
+}
+
+enum RollResultStatus {
+    FAILURE,
+    SUCCESS,
+    CRITICAL_FAILURE,
+    CRITICAL_SUCCESS,
+}