111 lines
4 KiB
TypeScript
111 lines
4 KiB
TypeScript
import { DefaultRollOptions, RollOptions, RollResult, RollResultStatus } from "./roll-data";
|
|
import { DS4RollProvider, RollProvider } from "./roll-provider";
|
|
import { isDiceSwapNecessary, isSlayingDiceRepetition } from "./roll-utils";
|
|
|
|
export function ds4test(testValue: number, rollOptions: Partial<RollOptions> = {}): RollResult {
|
|
const finalRollValue = testValue;
|
|
if (finalRollValue <= 20) {
|
|
return rollCheckSingleDie(finalRollValue, rollOptions);
|
|
} else {
|
|
return rollCheckMultipleDice(finalRollValue, rollOptions);
|
|
}
|
|
}
|
|
|
|
export function rollCheckSingleDie(
|
|
testValue: number,
|
|
rollOptions: Partial<RollOptions>,
|
|
provider: RollProvider = new DS4RollProvider(),
|
|
): RollResult {
|
|
const usedOptions = new DefaultRollOptions().mergeWith(rollOptions);
|
|
const roll = provider.getNextRoll();
|
|
const dice = [roll];
|
|
|
|
if (roll <= usedOptions.maxCritSucc) {
|
|
return new RollResult(testValue, RollResultStatus.CRITICAL_SUCCESS, dice);
|
|
} else if (roll >= usedOptions.minCritFail && !isSlayingDiceRepetition(usedOptions)) {
|
|
return new RollResult(0, RollResultStatus.CRITICAL_FAILURE, dice);
|
|
} else {
|
|
if (roll <= testValue) {
|
|
return new RollResult(roll, RollResultStatus.SUCCESS, dice);
|
|
} else {
|
|
return new RollResult(0, RollResultStatus.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);
|
|
} else {
|
|
prev[1].push(cur);
|
|
}
|
|
return prev;
|
|
};
|
|
|
|
const [critSuccesses, otherRolls] = dice
|
|
.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>;
|
|
|
|
if (swapLastWithCrit) {
|
|
const diceToMove = critSuccesses[0];
|
|
const remainingSuccesses = critSuccesses.slice(1);
|
|
sortedRollResults = remainingSuccesses.concat(otherRolls).concat([diceToMove]);
|
|
} else {
|
|
sortedRollResults = critSuccesses.concat(otherRolls);
|
|
}
|
|
|
|
const evaluationResult = sortedRollResults
|
|
.map((value, index) => {
|
|
if (index == numberOfDice - 1) {
|
|
if (value <= usedOptions.maxCritSucc) {
|
|
return finalCheck;
|
|
} else if (value <= finalCheck) {
|
|
return value;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (value <= usedOptions.maxCritSucc) {
|
|
return 20;
|
|
} else {
|
|
return value;
|
|
}
|
|
}
|
|
})
|
|
.reduce((a, b) => a + b);
|
|
|
|
if (usedOptions.useSlayingDice && firstResult <= usedOptions.maxCritSucc) {
|
|
return new RollResult(evaluationResult, RollResultStatus.CRITICAL_SUCCESS, sortedRollResults);
|
|
} else {
|
|
return new RollResult(evaluationResult, RollResultStatus.SUCCESS, sortedRollResults);
|
|
}
|
|
}
|