The fix has 2 components: 1. The logic for evaluating checks now supports check target numbers<= 0 by still using a single die in this case 2. The CheckFactory sets the check target number to 0 even if it would be < 0. This is necessary because negative numbers would interfer with foundry's math evaluation in rolls and would not be picked up correctly.
117 lines
3.8 KiB
TypeScript
117 lines
3.8 KiB
TypeScript
export default function evaluateCheck(
|
|
dice: number[],
|
|
checkTargetNumber: number,
|
|
{
|
|
maximumCoupResult = 1,
|
|
minimumFumbleResult = 20,
|
|
canFumble = true,
|
|
}: { maximumCoupResult?: number; minimumFumbleResult?: number; canFumble?: boolean } = {},
|
|
): SubCheckResult[] {
|
|
const diceWithSubChecks = assignSubChecksToDice(dice, checkTargetNumber, {
|
|
maximumCoupResult: maximumCoupResult,
|
|
});
|
|
return evaluateDiceWithSubChecks(diceWithSubChecks, {
|
|
maximumCoupResult: maximumCoupResult,
|
|
minimumFumbleResult: minimumFumbleResult,
|
|
canFumble: canFumble,
|
|
});
|
|
}
|
|
|
|
interface DieWithSubCheck {
|
|
result: number;
|
|
checkTargetNumber: number;
|
|
}
|
|
|
|
function assignSubChecksToDice(
|
|
dice: number[],
|
|
checkTargetNumber: number,
|
|
{
|
|
maximumCoupResult = 1,
|
|
}: {
|
|
maximumCoupResult?: number;
|
|
} = {},
|
|
): DieWithSubCheck[] {
|
|
const requiredNumberOfDice = getRequiredNumberOfDice(checkTargetNumber);
|
|
|
|
if (dice.length !== requiredNumberOfDice || requiredNumberOfDice < 1) {
|
|
throw new Error(game.i18n.localize("DS4.ErrorInvalidNumberOfDice"));
|
|
}
|
|
|
|
const checkTargetNumberForLastSubCheck = checkTargetNumber - 20 * (requiredNumberOfDice - 1);
|
|
|
|
const indexOfSmallestNonCoup = findIndexOfSmallestNonCoup(dice, maximumCoupResult);
|
|
const indexOfFirstCoup = dice.findIndex((die) => die <= maximumCoupResult);
|
|
const indexForLastSubCheck = shouldUseCoupForLastSubCheck(
|
|
indexOfSmallestNonCoup,
|
|
indexOfFirstCoup,
|
|
dice,
|
|
checkTargetNumberForLastSubCheck,
|
|
)
|
|
? indexOfFirstCoup
|
|
: indexOfSmallestNonCoup;
|
|
|
|
return dice.map((die, index) => ({
|
|
result: die,
|
|
checkTargetNumber: index === indexForLastSubCheck ? checkTargetNumberForLastSubCheck : 20,
|
|
}));
|
|
}
|
|
|
|
function findIndexOfSmallestNonCoup(dice: number[], maximumCoupResult: number): number {
|
|
return dice
|
|
.map((die, index) => [die, index])
|
|
.filter((indexedDie) => indexedDie[0] > maximumCoupResult)
|
|
.reduce(
|
|
(smallestIndexedDie, indexedDie) =>
|
|
indexedDie[0] < smallestIndexedDie[0] ? indexedDie : smallestIndexedDie,
|
|
[Infinity, -1],
|
|
)[1];
|
|
}
|
|
|
|
function shouldUseCoupForLastSubCheck(
|
|
indexOfSmallestNonCoup: number,
|
|
indexOfFirstCoup: number,
|
|
dice: number[],
|
|
checkTargetNumberForLastSubCheck: number,
|
|
) {
|
|
return (
|
|
indexOfFirstCoup !== -1 &&
|
|
(indexOfSmallestNonCoup === -1 ||
|
|
(dice[indexOfSmallestNonCoup] > checkTargetNumberForLastSubCheck &&
|
|
dice[indexOfSmallestNonCoup] + checkTargetNumberForLastSubCheck > 20))
|
|
);
|
|
}
|
|
|
|
interface SubCheckResult extends DieWithSubCheck, DiceTerm.Result {
|
|
success?: boolean;
|
|
failure?: boolean;
|
|
count?: number;
|
|
}
|
|
|
|
function evaluateDiceWithSubChecks(
|
|
results: DieWithSubCheck[],
|
|
{
|
|
maximumCoupResult,
|
|
minimumFumbleResult,
|
|
canFumble,
|
|
}: { maximumCoupResult: number; minimumFumbleResult: number; canFumble: boolean },
|
|
): SubCheckResult[] {
|
|
return results.map((dieWithSubCheck, index) => {
|
|
const result: SubCheckResult = {
|
|
...dieWithSubCheck,
|
|
active: dieWithSubCheck.result <= dieWithSubCheck.checkTargetNumber,
|
|
discarded: dieWithSubCheck.result > dieWithSubCheck.checkTargetNumber,
|
|
};
|
|
if (result.result <= maximumCoupResult) {
|
|
result.success = true;
|
|
result.count = result.checkTargetNumber;
|
|
result.active = true;
|
|
result.discarded = false;
|
|
}
|
|
if (index === 0 && canFumble && result.result >= minimumFumbleResult) result.failure = true;
|
|
return result;
|
|
});
|
|
}
|
|
|
|
export function getRequiredNumberOfDice(checkTargetNumber: number): number {
|
|
return Math.max(Math.ceil(checkTargetNumber / 20), 1);
|
|
}
|