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 { + const finalRollValue = testValue; + if (finalRollValue <= 20) { + return rollCheckSingleDie(finalRollValue, rollOptions); + } else { + return rollCheckMultipleDice(finalRollValue, rollOptions); + } +} + +async function rollCheckSingleDie(testValue: number, rollOptions: RollOptions): Promise { + 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 { + const finalCheck = testValue % 20; + const numberOfDice = Math.ceil(testValue / 20); + const rolls = new Array(); + 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 { + 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, +}