Add target success/failures option

This commit is contained in:
Ean Milligan 2025-06-28 20:26:44 -04:00
parent 38bc021455
commit 15fd57ea18
10 changed files with 247 additions and 119 deletions

View File

@ -16,6 +16,8 @@ export interface RollSet {
critFail: boolean; critFail: boolean;
isComplex: boolean; isComplex: boolean;
matchLabel: string; matchLabel: string;
success: boolean;
fail: boolean;
} }
// CountDetails is the object holding the count data for creating the Count Embed // CountDetails is the object holding the count data for creating the Count Embed
@ -119,9 +121,18 @@ export interface RollConf {
returnTotal: boolean; returnTotal: boolean;
}; };
sort: SortDisabled | SortEnabled; sort: SortDisabled | SortEnabled;
success: RangeConf;
fail: RangeConf;
} }
export interface SumOverride { export interface SumOverride {
on: boolean; on: boolean;
value: number; value: number;
} }
export interface ExecutedRoll {
rollSet: RollSet[];
countSuccessOverride: boolean;
countFailOverride: boolean;
sumOverride: SumOverride;
}

View File

@ -42,6 +42,13 @@ export const DiceOptions = Object.freeze({
Sort: 's', Sort: 's',
SortAsc: 'sa', SortAsc: 'sa',
SortDesc: 'sd', SortDesc: 'sd',
SuccessLt: '<',
SuccessGtr: '>',
SuccessEqu: '=',
Fail: 'f',
FailLt: 'f<',
FailGtr: 'f>',
FailEqu: 'f=',
}); });
// Should be ordered such that 'mt' will be encountered before 'm' // Should be ordered such that 'mt' will be encountered before 'm'

View File

@ -1,18 +1,19 @@
import { log, LogTypes as LT } from '@Log4Deno'; import { log, LogTypes as LT } from '@Log4Deno';
import { RollModifiers, RollSet, SumOverride } from 'artigen/dice/dice.d.ts'; import { ExecutedRoll, RollModifiers, RollSet, SumOverride } from 'artigen/dice/dice.d.ts';
import { genFateRoll, genRoll } from 'artigen/dice/randomRoll.ts'; import { genFateRoll, genRoll } from 'artigen/dice/randomRoll.ts';
import { getRollConf } from 'artigen/dice/getRollConf.ts'; import { getRollConf } from 'artigen/dice/getRollConf.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { compareOrigIdx, compareRolls, compareRollsReverse } from 'artigen/utils/sortFuncs.ts'; import { compareOrigIdx, compareRolls, compareRollsReverse } from 'artigen/utils/sortFuncs.ts';
import { flagRoll } from 'artigen/utils/diceFlagger.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts'; import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { generateRollVals } from 'artigen/utils/rollValCounter.ts'; import { generateRollVals } from 'artigen/utils/rollValCounter.ts';
// roll(rollStr, modifiers) returns RollSet // roll(rollStr, modifiers) returns RollSet
// roll parses and executes the rollStr // roll parses and executes the rollStr
export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet[], SumOverride] => { export const executeRoll = (rollStr: string, modifiers: RollModifiers): ExecutedRoll => {
/* Roll Capabilities /* Roll Capabilities
* Deciphers and rolls a single dice roll set * Deciphers and rolls a single dice roll set
* *
@ -67,8 +68,12 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet
rollConf.keepLow.on || rollConf.keepLow.on ||
rollConf.critScore.on || rollConf.critScore.on ||
rollConf.critFail.on || rollConf.critFail.on ||
rollConf.exploding.on, rollConf.exploding.on ||
rollConf.success.on ||
rollConf.fail.on,
matchLabel: '', matchLabel: '',
success: false,
fail: false,
}); });
// Initial rolling, not handling reroll or exploding here // Initial rolling, not handling reroll or exploding here
@ -85,22 +90,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet
// Set origIdx of roll // Set origIdx of roll
rolling.origIdx = i; rolling.origIdx = i;
// If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size flagRoll(rollConf, rolling);
if (rollConf.critScore.on && rollConf.critScore.range.includes(rolling.roll)) {
rolling.critHit = true;
} else if (!rollConf.critScore.on) {
rolling.critHit = rolling.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
}
// If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
if (rollConf.critFail.on && rollConf.critFail.range.includes(rolling.roll)) {
rolling.critFail = true;
} else if (!rollConf.critFail.on) {
if (rollConf.type === 'fate') {
rolling.critFail = rolling.roll === -1;
} else {
rolling.critFail = rolling.roll === (rollConf.dPercent.on ? 0 : 1);
}
}
// Push the newly created roll and loop again // Push the newly created roll and loop again
rollSet.push(rolling); rollSet.push(rolling);
@ -151,18 +141,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet
newReroll.roll = genRoll(rollConf.dieSize, modifiers, rollConf.dPercent); newReroll.roll = genRoll(rollConf.dieSize, modifiers, rollConf.dPercent);
} }
// If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size flagRoll(rollConf, newReroll);
if (rollConf.critScore.on && rollConf.critScore.range.includes(newReroll.roll)) {
newReroll.critHit = true;
} else if (!rollConf.critScore.on) {
newReroll.critHit = newReroll.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
}
// If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
if (rollConf.critFail.on && rollConf.critFail.range.includes(newReroll.roll)) {
newReroll.critFail = true;
} else if (!rollConf.critFail.on) {
newReroll.critFail = newReroll.roll === (rollConf.dPercent.on ? 0 : 1);
}
// Slot this new roll in after the current iteration so it can be processed in the next loop // Slot this new roll in after the current iteration so it can be processed in the next loop
rollSet.splice(i + 1, 0, newReroll); rollSet.splice(i + 1, 0, newReroll);
@ -183,18 +162,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet
// Always mark this roll as exploding // Always mark this roll as exploding
newExplodingRoll.exploding = true; newExplodingRoll.exploding = true;
// If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size flagRoll(rollConf, newExplodingRoll);
if (rollConf.critScore.on && rollConf.critScore.range.includes(newExplodingRoll.roll)) {
newExplodingRoll.critHit = true;
} else if (!rollConf.critScore.on) {
newExplodingRoll.critHit = newExplodingRoll.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
}
// If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
if (rollConf.critFail.on && rollConf.critFail.range.includes(newExplodingRoll.roll)) {
newExplodingRoll.critFail = true;
} else if (!rollConf.critFail.on) {
newExplodingRoll.critFail = newExplodingRoll.roll === (rollConf.dPercent.on ? 0 : 1);
}
// Slot this new roll in after the current iteration so it can be processed in the next loop // Slot this new roll in after the current iteration so it can be processed in the next loop
rollSet.splice(i + 1, 0, newExplodingRoll); rollSet.splice(i + 1, 0, newExplodingRoll);
@ -374,5 +342,10 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet
rollSet.sort(rollConf.sort.direction === 'a' ? compareRolls : compareRollsReverse); rollSet.sort(rollConf.sort.direction === 'a' ? compareRolls : compareRollsReverse);
} }
return [rollSet, sumOverride]; return {
rollSet,
sumOverride,
countSuccessOverride: rollConf.success.on,
countFailOverride: rollConf.fail.on,
};
}; };

View File

@ -19,10 +19,10 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers
let tempComplex = false; let tempComplex = false;
// Generate the roll, passing flags thru // Generate the roll, passing flags thru
const [tempRollSet, sumOverride] = executeRoll(rollConf, modifiers); const executedRoll = executeRoll(rollConf, modifiers);
// Loop thru all parts of the roll to document everything that was done to create the total roll // Loop thru all parts of the roll to document everything that was done to create the total roll
tempRollSet.forEach((e) => { executedRoll.rollSet.forEach((e) => {
loopCountCheck(); loopCountCheck();
loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
@ -38,7 +38,7 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers
tempTotal += e.roll; tempTotal += e.roll;
break; break;
case 'cwod': case 'cwod':
tempTotal += e.critHit ? 1 : 0; tempTotal += e.success ? 1 : 0;
break; break;
} }
if (e.critHit) { if (e.critHit) {
@ -82,22 +82,33 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers
}); });
// After the looping is done, remove the extra " + " from the details and cap it with the closing ] // After the looping is done, remove the extra " + " from the details and cap it with the closing ]
tempDetails = tempDetails.substring(0, tempDetails.length - 3); tempDetails = tempDetails.substring(0, tempDetails.length - 3);
if (tempRollSet[0]?.type === 'cwod') { if (executedRoll.countSuccessOverride) {
const successCnt = tempRollSet.filter((e) => !e.dropped && !e.rerolled && e.critHit).length; const successCnt = executedRoll.rollSet.filter((e) => !e.dropped && !e.rerolled && e.success).length;
const failCnt = tempRollSet.filter((e) => !e.dropped && !e.rerolled && e.critFail).length; tempDetails += `, ${successCnt} Success${successCnt !== 1 ? 'es' : ''}`;
tempDetails += `, ${successCnt} Success${successCnt !== 1 ? 'es' : ''}, ${failCnt} Fail${failCnt !== 1 ? 's' : ''}`;
executedRoll.sumOverride.on = true;
executedRoll.sumOverride.value += successCnt;
}
if (executedRoll.countFailOverride) {
const failCnt = executedRoll.rollSet.filter((e) => !e.dropped && !e.rerolled && e.fail).length;
tempDetails += `, ${failCnt} Fail${failCnt !== 1 ? 's' : ''}`;
executedRoll.sumOverride.on = true;
if (executedRoll.rollSet[0]?.type !== 'cwod') {
executedRoll.sumOverride.value -= failCnt;
}
} }
tempDetails += ']'; tempDetails += ']';
return { return {
solvedStep: { solvedStep: {
total: sumOverride.on ? sumOverride.value : tempTotal, total: executedRoll.sumOverride.on ? executedRoll.sumOverride.value : tempTotal,
details: tempDetails, details: tempDetails,
containsCrit: tempCrit, containsCrit: tempCrit,
containsFail: tempFail, containsFail: tempFail,
isComplex: tempComplex, isComplex: tempComplex,
}, },
countDetails: modifiers.count || modifiers.confirmCrit ? rollCounter(tempRollSet) : rollCounter([]), countDetails: modifiers.count || modifiers.confirmCrit ? rollCounter(executedRoll.rollSet) : rollCounter([]),
rollDistributions: modifiers.rollDist ? createRollDistMap(tempRollSet) : new Map<string, number[]>(), rollDistributions: modifiers.rollDist ? createRollDistMap(executedRoll.rollSet) : new Map<string, number[]>(),
}; };
}; };

View File

@ -4,9 +4,11 @@ import { RollConf } from 'artigen/dice/dice.d.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts'; import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { DiceOptions, NumberlessDiceOptions } from 'artigen/dice/diceOptions.ts'; import { DiceOptions, NumberlessDiceOptions } from 'artigen/dice/diceOptions.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { addToRange, gtrAddToRange, ltAddToRange } from 'artigen/utils/rangeAdder.ts';
const throwDoubleSepError = (sep: string): void => { const throwDoubleSepError = (sep: string): void => {
throw new Error(`DoubleSeparator_${sep}`); throw new Error(`DoubleSeparator_${sep}`);
}; };
@ -71,6 +73,14 @@ export const getRollConf = (rollStr: string): RollConf => {
on: false, on: false,
direction: '', direction: '',
}, },
success: {
on: false,
range: [],
},
fail: {
on: false,
range: [],
},
}; };
// If the dPts is not long enough, throw error // If the dPts is not long enough, throw error
@ -101,8 +111,10 @@ export const getRollConf = (rollStr: string): RollConf => {
rollConf.dieCount = parseInt(cwodParts[0] || '1'); rollConf.dieCount = parseInt(cwodParts[0] || '1');
rollConf.dieSize = 10; rollConf.dieSize = 10;
// Use critScore to set the difficulty // Use success to set the difficulty
rollConf.critScore.on = true; rollConf.success.on = true;
rollConf.fail.on = true;
addToRange('cwod', rollConf.fail.range, 1);
const tempDifficulty = (cwodParts[1] ?? '').search(/\d/) === 0 ? cwodParts[1] : ''; const tempDifficulty = (cwodParts[1] ?? '').search(/\d/) === 0 ? cwodParts[1] : '';
let afterDifficultyIdx = tempDifficulty.search(/[^\d]/); let afterDifficultyIdx = tempDifficulty.search(/[^\d]/);
if (afterDifficultyIdx === -1) { if (afterDifficultyIdx === -1) {
@ -114,7 +126,7 @@ export const getRollConf = (rollStr: string): RollConf => {
loopCountCheck(); loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling cwod ${rollStr} | Parsing difficulty ${i}`); loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling cwod ${rollStr} | Parsing difficulty ${i}`);
rollConf.critScore.range.push(i); rollConf.success.range.push(i);
} }
// Remove any garbage from the remains // Remove any garbage from the remains
@ -275,7 +287,7 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.RerollEqu: case DiceOptions.RerollEqu:
// Configure Reroll (this can happen multiple times) // Configure Reroll (this can happen multiple times)
rollConf.reroll.on = true; rollConf.reroll.on = true;
!rollConf.reroll.nums.includes(tNum) && rollConf.reroll.nums.push(tNum); addToRange(tSep, rollConf.reroll.nums, tNum);
break; break;
case DiceOptions.RerollOnceGtr: case DiceOptions.RerollOnceGtr:
rollConf.reroll.once = true; rollConf.reroll.once = true;
@ -283,12 +295,7 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.RerollGtr: case DiceOptions.RerollGtr:
// Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why) // Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true; rollConf.reroll.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { gtrAddToRange(tSep, rollConf.reroll.nums, tNum, rollConf.dieSize);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing r> ${i}`);
!rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i);
}
break; break;
case DiceOptions.RerollOnceLt: case DiceOptions.RerollOnceLt:
rollConf.reroll.once = true; rollConf.reroll.once = true;
@ -296,64 +303,39 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.RerollLt: case DiceOptions.RerollLt:
// Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why) // Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true; rollConf.reroll.on = true;
for (let i = 1; i <= tNum; i++) { ltAddToRange(tSep, rollConf.reroll.nums, tNum);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing r< ${i}`);
!rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i);
}
break; break;
case DiceOptions.CritSuccess: case DiceOptions.CritSuccess:
case DiceOptions.CritSuccessEqu: case DiceOptions.CritSuccessEqu:
// Configure CritScore for one number (this can happen multiple times) // Configure CritScore for one number (this can happen multiple times)
rollConf.critScore.on = true; rollConf.critScore.on = true;
!rollConf.critScore.range.includes(tNum) && rollConf.critScore.range.push(tNum); addToRange(tSep, rollConf.critScore.range, tNum);
break; break;
case DiceOptions.CritSuccessGtr: case DiceOptions.CritSuccessGtr:
// Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why) // Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true; rollConf.critScore.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { gtrAddToRange(tSep, rollConf.critScore.range, tNum, rollConf.dieSize);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cs> ${i}`);
!rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i);
}
break; break;
case DiceOptions.CritSuccessLt: case DiceOptions.CritSuccessLt:
// Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why) // Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true; rollConf.critScore.on = true;
for (let i = 0; i <= tNum; i++) { ltAddToRange(tSep, rollConf.critScore.range, tNum);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cs< ${i}`);
!rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i);
}
break; break;
case DiceOptions.CritFail: case DiceOptions.CritFail:
case DiceOptions.CritFailEqu: case DiceOptions.CritFailEqu:
// Configure CritFail for one number (this can happen multiple times) // Configure CritFail for one number (this can happen multiple times)
rollConf.critFail.on = true; rollConf.critFail.on = true;
!rollConf.critFail.range.includes(tNum) && rollConf.critFail.range.push(tNum); addToRange(tSep, rollConf.critFail.range, tNum);
break; break;
case DiceOptions.CritFailGtr: case DiceOptions.CritFailGtr:
// Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why) // Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true; rollConf.critFail.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { gtrAddToRange(tSep, rollConf.critFail.range, tNum, rollConf.dieSize);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cf> ${i}`);
!rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i);
}
break; break;
case DiceOptions.CritFailLt: case DiceOptions.CritFailLt:
// Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why) // Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true; rollConf.critFail.on = true;
for (let i = 0; i <= tNum; i++) { ltAddToRange(tSep, rollConf.critFail.range, tNum);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cf< ${i}`);
!rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i);
}
break; break;
case DiceOptions.Exploding: case DiceOptions.Exploding:
case DiceOptions.ExplodeOnce: case DiceOptions.ExplodeOnce:
@ -363,7 +345,7 @@ export const getRollConf = (rollStr: string): RollConf => {
rollConf.exploding.on = true; rollConf.exploding.on = true;
if (afterNumIdx > 0) { if (afterNumIdx > 0) {
// User gave a number to explode on, save it // User gave a number to explode on, save it
!rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum); addToRange(tSep, rollConf.exploding.nums, tNum);
} }
break; break;
case DiceOptions.ExplodingEqu: case DiceOptions.ExplodingEqu:
@ -372,7 +354,7 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.CompoundingExplosionEqu: case DiceOptions.CompoundingExplosionEqu:
// Configure Exploding (this can happen multiple times) // Configure Exploding (this can happen multiple times)
rollConf.exploding.on = true; rollConf.exploding.on = true;
!rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum); addToRange(tSep, rollConf.exploding.nums, tNum);
break; break;
case DiceOptions.ExplodingGtr: case DiceOptions.ExplodingGtr:
case DiceOptions.ExplodeOnceGtr: case DiceOptions.ExplodeOnceGtr:
@ -380,12 +362,7 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.CompoundingExplosionGtr: case DiceOptions.CompoundingExplosionGtr:
// Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why) // Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true; rollConf.exploding.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { gtrAddToRange(tSep, rollConf.exploding.nums, tNum, rollConf.dieSize);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing !> ${i}`);
!rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i);
}
break; break;
case DiceOptions.ExplodingLt: case DiceOptions.ExplodingLt:
case DiceOptions.ExplodeOnceLt: case DiceOptions.ExplodeOnceLt:
@ -393,15 +370,16 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.CompoundingExplosionLt: case DiceOptions.CompoundingExplosionLt:
// Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why) // Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true; rollConf.exploding.on = true;
for (let i = 1; i <= tNum; i++) { ltAddToRange(tSep, rollConf.exploding.nums, tNum);
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing !< ${i}`);
!rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i);
}
break; break;
case DiceOptions.Matching:
case DiceOptions.MatchingTotal: case DiceOptions.MatchingTotal:
rollConf.match.returnTotal = true;
// falls through as mt functions the same as m in this context
case DiceOptions.Matching:
if (rollConf.match.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
rollConf.match.on = true; rollConf.match.on = true;
if (afterNumIdx > 0) { if (afterNumIdx > 0) {
// User gave a number to work with, save it // User gave a number to work with, save it
@ -410,13 +388,52 @@ export const getRollConf = (rollStr: string): RollConf => {
break; break;
case DiceOptions.Sort: case DiceOptions.Sort:
case DiceOptions.SortAsc: case DiceOptions.SortAsc:
if (rollConf.sort.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
rollConf.sort.on = true; rollConf.sort.on = true;
rollConf.sort.direction = 'a'; rollConf.sort.direction = 'a';
break; break;
case DiceOptions.SortDesc: case DiceOptions.SortDesc:
if (rollConf.sort.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
rollConf.sort.on = true; rollConf.sort.on = true;
rollConf.sort.direction = 'd'; rollConf.sort.direction = 'd';
break; break;
case DiceOptions.SuccessEqu:
// Configure success (this can happen multiple times)
rollConf.success.on = true;
addToRange(tSep, rollConf.success.range, tNum);
break;
case DiceOptions.SuccessGtr:
// Configure success for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.success.on = true;
gtrAddToRange(tSep, rollConf.success.range, tNum, rollConf.dieSize);
break;
case DiceOptions.SuccessLt:
// Configure success for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.success.on = true;
ltAddToRange(tSep, rollConf.success.range, tNum);
break;
case DiceOptions.Fail:
case DiceOptions.FailEqu:
// Configure fail (this can happen multiple times)
rollConf.fail.on = true;
addToRange(tSep, rollConf.fail.range, tNum);
break;
case DiceOptions.FailGtr:
// Configure fail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.fail.on = true;
gtrAddToRange(tSep, rollConf.fail.range, tNum, rollConf.dieSize);
break;
case DiceOptions.FailLt:
// Configure fail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.fail.on = true;
ltAddToRange(tSep, rollConf.fail.range, tNum);
break;
default: default:
// Throw error immediately if unknown op is encountered // Throw error immediately if unknown op is encountered
throw new Error(`UnknownOperation_${tSep}`); throw new Error(`UnknownOperation_${tSep}`);
@ -442,9 +459,6 @@ export const getRollConf = (rollStr: string): RollConf => {
case DiceOptions.CompoundingExplosionEqu: case DiceOptions.CompoundingExplosionEqu:
rollConf.exploding.compounding = true; rollConf.exploding.compounding = true;
break; break;
case DiceOptions.MatchingTotal:
rollConf.match.returnTotal = true;
break;
} }
// Finally slice off everything else parsed this loop // Finally slice off everything else parsed this loop
@ -472,6 +486,10 @@ export const getRollConf = (rollStr: string): RollConf => {
throw new Error('FormattingError_dk'); throw new Error('FormattingError_dk');
} }
if (rollConf.match.on && (rollConf.success.on || rollConf.fail.on)) {
throw new Error('FormattingError_mtsf');
}
if (rollConf.drop.on && rollConf.drop.count === 0) { if (rollConf.drop.on && rollConf.drop.count === 0) {
throw new Error('NoZerosAllowed_drop'); throw new Error('NoZerosAllowed_drop');
} }

View File

@ -0,0 +1,33 @@
import { RollConf, RollSet } from 'artigen/dice/dice.d.ts';
export const flagRoll = (rollConf: RollConf, rollSet: RollSet) => {
// If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size
if (rollConf.critScore.on && rollConf.critScore.range.includes(rollSet.roll)) {
rollSet.critHit = true;
} else if (!rollConf.critScore.on) {
rollSet.critHit = rollSet.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
}
// If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
if (rollConf.critFail.on && rollConf.critFail.range.includes(rollSet.roll)) {
rollSet.critFail = true;
} else if (!rollConf.critFail.on) {
if (rollConf.type === 'fate') {
rollSet.critFail = rollSet.roll === -1;
} else {
rollSet.critFail = rollSet.roll === (rollConf.dPercent.on ? 0 : 1);
}
}
// If success arg is on, check if roll should be successful
if (rollConf.success.on && rollConf.success.range.includes(rollSet.roll)) {
rollSet.success = true;
rollSet.matchLabel = 'S';
}
// If fail arg is on, check if roll should be failed
if (rollConf.fail.on && rollConf.fail.range.includes(rollSet.roll)) {
rollSet.fail = true;
rollSet.matchLabel = 'F';
}
};

View File

@ -0,0 +1,24 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
// Add tNum to range
export const addToRange = (tSep: string, range: Array<number>, tNum: number) => {
loggingEnabled && log(LT.LOG, `${getLoopCount()} addToRange on ${tSep} attempting to add: ${tNum}`);
!range.includes(tNum) && range.push(tNum);
};
const internalAddMultipleToRange = (tSep: string, range: Array<number>, start: number, end: number) => {
for (let i = start; i <= end; i++) {
loopCountCheck();
addToRange(tSep, range, i);
}
};
// Add numbers less than or equal to tNum to range
export const ltAddToRange = (tSep: string, range: Array<number>, tNum: number) => internalAddMultipleToRange(tSep, range, 0, tNum);
// Add numbers greater than or equal to tNum to range
export const gtrAddToRange = (tSep: string, range: Array<number>, tNum: number, dieSize: number) => internalAddMultipleToRange(tSep, range, tNum, dieSize);

View File

@ -32,7 +32,18 @@ export const translateError = (solverError: Error): [string, string] => {
errorMsg = `Formatting Error: \`${errorDetails}\` should only be specified once per roll, remove all but one and repeat roll`; errorMsg = `Formatting Error: \`${errorDetails}\` should only be specified once per roll, remove all but one and repeat roll`;
break; break;
case 'FormattingError': case 'FormattingError':
errorMsg = 'Formatting Error: Cannot use Keep and Drop at the same time, remove all but one and repeat roll'; errorMsg = 'Formatting Error: ';
switch (errorDetails) {
case 'dk':
errorMsg += 'Cannot use Keep and Drop at the same time, remove all but one and repeat roll';
break;
case 'mtsf':
errorMsg += 'Cannot use Match with CWOD Dice, or the Success or Fail options, remove all but one and repeat roll';
break;
default:
errorMsg += `Unhandled - ${errorDetails}`;
break;
}
break; break;
case 'NoMaxWithDash': case 'NoMaxWithDash':
errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct'; errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct';

View File

@ -237,7 +237,10 @@ Any time a Compounding Explosion happens, the formatting on the die will still s
description: `**Usage:** \`xdym\`, \`xdymz\`, \`xdymt\`, or \`xdymtz\` description: `**Usage:** \`xdym\`, \`xdymz\`, \`xdymt\`, or \`xdymtz\`
\`z\` - Minimum count of matches for a label to be added \`z\` - Minimum count of matches for a label to be added
The basic \`m\` option will only add labels without modifying the results, whereas the \`mt\` options will add labels and will change the result of the roll to be equal to the number of labels that have been added.`, \`m\` will only add labels without modifying the results.
\`mt\` will add labels and will change the result of the roll to be equal to the number of labels that have been added.
**Notice:** Cannot be combined with Target Number/Successes or Target Failures`,
example: [ example: [
'`[[10d6m]]` => [**C:6** + B:2 + 4 + __C:1__ + __C:1__ + B:2 + **C:6** + B:2 + **C:6** + **C:6**] = 36', '`[[10d6m]]` => [**C:6** + B:2 + 4 + __C:1__ + __C:1__ + B:2 + **C:6** + B:2 + **C:6** + **C:6**] = 36',
'`[[10d6m4]]` => [**A:6** + 2 + 4 + __1__ + __1__ + 2 + **A:6** + 2 + **A:6** + **A:6**] = 36', '`[[10d6m4]]` => [**A:6** + 2 + 4 + __1__ + __1__ + 2 + **A:6** + 2 + **A:6** + **A:6**] = 36',
@ -260,6 +263,43 @@ The basic \`m\` option will only add labels without modifying the results, where
], ],
}, },
], ],
[
'target-success',
{
name: 'Target Number/Successes',
description: `**Usage:** \`xdy=z\`, \`xdy<z\`, or \`xdy>z\`
\`z\` - Number to compare to for configuring the success target
Will set the result to be the number of successes. When combined with the Target Failures option, the failures will be subtracted from the result.
**Notice:** Cannot be combined with the Dice Matching option`,
example: [
'`[[10d6=5]]` => [2 + 3 + 3 + __1__ + 2 + S:5 + S:5 + 2 + **6** + __1__, 2 Successes] = **__2__**',
'`[[10d6>5]]` => [__1__ + S:5 + 4 + 3 + **S:6** + 2 + 3 + S:5 + S:5 + 3, 4 Successes] = 4',
'`[[10d6<5]]` => [S:5 + S:2 + S:2 + **6** + S:2 + **6** + S:4 + S:4 + **6** + S:3, 7 Successes] = **7**',
'`[[10d6>5f<2]]` => [__F:1__ + **S:6** + __F:1__ + S:5 + S:5 + S:5 + 4 + **S:6** + 4 + __F:1__, 5 Successes, 3 Fails] = **__2__**',
],
},
],
[
'target-fail',
{
name: 'Target Failures',
description: `**Usage:** \`xdyfz\`, \`xdyf=z\`, \`xdyf<z\`, or \`xdyf>z\`
\`z\` - Number to compare to for configuring the fail target
Will set the result to be the number of fails (as a negative number). When combined with the Target Number/Successes option, the successes will be added to the result.
**Notice:** Cannot be combined with the Dice Matching option`,
example: [
'`[[10d6f5]]` => [**6** + 4 + **6** + 3 + F:5 + __1__ + **6** + F:5 + 4 + 2, 2 Fails] = **__-2__**',
'`[[10d6f=5]]` => [**6** + 4 + **6** + 3 + F:5 + __1__ + **6** + F:5 + 4 + 2, 2 Fails] = **__-2__**',
'`[[10d6f>5]]` => [2 + 3 + 3 + **F:6** + F:5 + 2 + __1__ + **F:6** + 2 + **F:6**, 4 Fails] = **__-4__**',
'`[[10d6f<5]]` => [F:4 + F:5 + **6** + F:4 + F:3 + F:5 + F:4 + F:2 + F:4 + **6**, 8 Fails] = **-8**',
'`[[10d6>5f<2]]` => [F:2 + 4 + __F:1__ + __F:1__ + __F:1__ + **S:6** + __F:1__ + **S:6** + __F:1__ + 3, 2 Successes, 6 Fails] = **__-4__**',
],
},
],
]); ]);
export const DiceOptionsHelpPages: HelpPage = { export const DiceOptionsHelpPages: HelpPage = {

View File

@ -37,8 +37,8 @@ Rolls a Fate die, has 6 sides with values \`[-1, -1, 0, 0, 1, 1]\`.`,
Rolls a specified number of 10 sided dice and counts successful and failed rolls.`, Rolls a specified number of 10 sided dice and counts successful and failed rolls.`,
example: [ example: [
'`[[cwod]]` => [3, 0 Successes, 0 Fails] = 0', '`[[cwod]]` => [3, 0 Successes, 0 Fails] = 0',
'`[[4cwod]]` => [**10** + __1__ + 9 + __1__, 1 Success, 2 Fails] = **__1__**', '`[[4cwod]]` => [8 + __F:1__ + **S:10** + **S:10**, 2 Successes, 1 Fail] = **__2__**',
'`[[5cwod8]]` => [**10** + 2 + 5 + **8** + 4, 2 Successes, 0 Fails] = **2**', '`[[5cwod8]]` => [__F:1__ + S:8 + 6 + 7 + S:9, 2 Successes, 1 Fail] = __2__',
], ],
}, },
], ],