From 15fd57ea18a2db0da841e3ea217ca36387df5b84 Mon Sep 17 00:00:00 2001 From: Ean Milligan Date: Sat, 28 Jun 2025 20:26:44 -0400 Subject: [PATCH] Add target success/failures option --- src/artigen/dice/dice.d.ts | 11 ++ src/artigen/dice/diceOptions.ts | 7 ++ src/artigen/dice/executeRoll.ts | 61 +++------- src/artigen/dice/generateFormattedRoll.ts | 31 +++-- src/artigen/dice/getRollConf.ts | 140 ++++++++++++---------- src/artigen/utils/diceFlagger.ts | 33 +++++ src/artigen/utils/rangeAdder.ts | 24 ++++ src/artigen/utils/translateError.ts | 13 +- src/commands/helpLibrary/diceOptions.ts | 42 ++++++- src/commands/helpLibrary/diceTypes.ts | 4 +- 10 files changed, 247 insertions(+), 119 deletions(-) create mode 100644 src/artigen/utils/diceFlagger.ts create mode 100644 src/artigen/utils/rangeAdder.ts diff --git a/src/artigen/dice/dice.d.ts b/src/artigen/dice/dice.d.ts index 8647b01..db86539 100644 --- a/src/artigen/dice/dice.d.ts +++ b/src/artigen/dice/dice.d.ts @@ -16,6 +16,8 @@ export interface RollSet { critFail: boolean; isComplex: boolean; matchLabel: string; + success: boolean; + fail: boolean; } // CountDetails is the object holding the count data for creating the Count Embed @@ -119,9 +121,18 @@ export interface RollConf { returnTotal: boolean; }; sort: SortDisabled | SortEnabled; + success: RangeConf; + fail: RangeConf; } export interface SumOverride { on: boolean; value: number; } + +export interface ExecutedRoll { + rollSet: RollSet[]; + countSuccessOverride: boolean; + countFailOverride: boolean; + sumOverride: SumOverride; +} diff --git a/src/artigen/dice/diceOptions.ts b/src/artigen/dice/diceOptions.ts index 32de61d..d242750 100644 --- a/src/artigen/dice/diceOptions.ts +++ b/src/artigen/dice/diceOptions.ts @@ -42,6 +42,13 @@ export const DiceOptions = Object.freeze({ Sort: 's', SortAsc: 'sa', SortDesc: 'sd', + SuccessLt: '<', + SuccessGtr: '>', + SuccessEqu: '=', + Fail: 'f', + FailLt: 'f<', + FailGtr: 'f>', + FailEqu: 'f=', }); // Should be ordered such that 'mt' will be encountered before 'm' diff --git a/src/artigen/dice/executeRoll.ts b/src/artigen/dice/executeRoll.ts index 9d74dbd..aa49291 100644 --- a/src/artigen/dice/executeRoll.ts +++ b/src/artigen/dice/executeRoll.ts @@ -1,18 +1,19 @@ 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 { getRollConf } from 'artigen/dice/getRollConf.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.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 { generateRollVals } from 'artigen/utils/rollValCounter.ts'; // roll(rollStr, modifiers) returns RollSet // 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 * Deciphers and rolls a single dice roll set * @@ -67,8 +68,12 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet rollConf.keepLow.on || rollConf.critScore.on || rollConf.critFail.on || - rollConf.exploding.on, + rollConf.exploding.on || + rollConf.success.on || + rollConf.fail.on, matchLabel: '', + success: false, + fail: false, }); // Initial rolling, not handling reroll or exploding here @@ -85,22 +90,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet // Set origIdx of roll 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 - 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); - } - } + flagRoll(rollConf, rolling); // Push the newly created roll and loop again rollSet.push(rolling); @@ -151,18 +141,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet 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 - 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); - } + flagRoll(rollConf, newReroll); // Slot this new roll in after the current iteration so it can be processed in the next loop rollSet.splice(i + 1, 0, newReroll); @@ -183,18 +162,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet // Always mark this roll as exploding 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 - 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); - } + flagRoll(rollConf, newExplodingRoll); // Slot this new roll in after the current iteration so it can be processed in the next loop 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); } - return [rollSet, sumOverride]; + return { + rollSet, + sumOverride, + countSuccessOverride: rollConf.success.on, + countFailOverride: rollConf.fail.on, + }; }; diff --git a/src/artigen/dice/generateFormattedRoll.ts b/src/artigen/dice/generateFormattedRoll.ts index 7af14ec..22ec698 100644 --- a/src/artigen/dice/generateFormattedRoll.ts +++ b/src/artigen/dice/generateFormattedRoll.ts @@ -19,10 +19,10 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers let tempComplex = false; // 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 - tempRollSet.forEach((e) => { + executedRoll.rollSet.forEach((e) => { loopCountCheck(); loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); @@ -38,7 +38,7 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers tempTotal += e.roll; break; case 'cwod': - tempTotal += e.critHit ? 1 : 0; + tempTotal += e.success ? 1 : 0; break; } 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 ] tempDetails = tempDetails.substring(0, tempDetails.length - 3); - if (tempRollSet[0]?.type === 'cwod') { - const successCnt = tempRollSet.filter((e) => !e.dropped && !e.rerolled && e.critHit).length; - const failCnt = tempRollSet.filter((e) => !e.dropped && !e.rerolled && e.critFail).length; - tempDetails += `, ${successCnt} Success${successCnt !== 1 ? 'es' : ''}, ${failCnt} Fail${failCnt !== 1 ? 's' : ''}`; + if (executedRoll.countSuccessOverride) { + const successCnt = executedRoll.rollSet.filter((e) => !e.dropped && !e.rerolled && e.success).length; + tempDetails += `, ${successCnt} Success${successCnt !== 1 ? 'es' : ''}`; + + 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 += ']'; return { solvedStep: { - total: sumOverride.on ? sumOverride.value : tempTotal, + total: executedRoll.sumOverride.on ? executedRoll.sumOverride.value : tempTotal, details: tempDetails, containsCrit: tempCrit, containsFail: tempFail, isComplex: tempComplex, }, - countDetails: modifiers.count || modifiers.confirmCrit ? rollCounter(tempRollSet) : rollCounter([]), - rollDistributions: modifiers.rollDist ? createRollDistMap(tempRollSet) : new Map(), + countDetails: modifiers.count || modifiers.confirmCrit ? rollCounter(executedRoll.rollSet) : rollCounter([]), + rollDistributions: modifiers.rollDist ? createRollDistMap(executedRoll.rollSet) : new Map(), }; }; diff --git a/src/artigen/dice/getRollConf.ts b/src/artigen/dice/getRollConf.ts index 54fa8ba..330afc8 100644 --- a/src/artigen/dice/getRollConf.ts +++ b/src/artigen/dice/getRollConf.ts @@ -4,9 +4,11 @@ import { RollConf } from 'artigen/dice/dice.d.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 { loggingEnabled } from 'artigen/utils/logFlag.ts'; +import { addToRange, gtrAddToRange, ltAddToRange } from 'artigen/utils/rangeAdder.ts'; + const throwDoubleSepError = (sep: string): void => { throw new Error(`DoubleSeparator_${sep}`); }; @@ -71,6 +73,14 @@ export const getRollConf = (rollStr: string): RollConf => { on: false, direction: '', }, + success: { + on: false, + range: [], + }, + fail: { + on: false, + range: [], + }, }; // 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.dieSize = 10; - // Use critScore to set the difficulty - rollConf.critScore.on = true; + // Use success to set the difficulty + rollConf.success.on = true; + rollConf.fail.on = true; + addToRange('cwod', rollConf.fail.range, 1); const tempDifficulty = (cwodParts[1] ?? '').search(/\d/) === 0 ? cwodParts[1] : ''; let afterDifficultyIdx = tempDifficulty.search(/[^\d]/); if (afterDifficultyIdx === -1) { @@ -114,7 +126,7 @@ export const getRollConf = (rollStr: string): RollConf => { loopCountCheck(); 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 @@ -275,7 +287,7 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.RerollEqu: // Configure Reroll (this can happen multiple times) rollConf.reroll.on = true; - !rollConf.reroll.nums.includes(tNum) && rollConf.reroll.nums.push(tNum); + addToRange(tSep, rollConf.reroll.nums, tNum); break; case DiceOptions.RerollOnceGtr: rollConf.reroll.once = true; @@ -283,12 +295,7 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.RerollGtr: // Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.reroll.on = true; - for (let i = tNum; i <= rollConf.dieSize; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing r> ${i}`); - !rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i); - } + gtrAddToRange(tSep, rollConf.reroll.nums, tNum, rollConf.dieSize); break; case DiceOptions.RerollOnceLt: rollConf.reroll.once = true; @@ -296,64 +303,39 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.RerollLt: // Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.reroll.on = true; - for (let i = 1; i <= tNum; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing r< ${i}`); - !rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i); - } + ltAddToRange(tSep, rollConf.reroll.nums, tNum); break; case DiceOptions.CritSuccess: case DiceOptions.CritSuccessEqu: // Configure CritScore for one number (this can happen multiple times) rollConf.critScore.on = true; - !rollConf.critScore.range.includes(tNum) && rollConf.critScore.range.push(tNum); + addToRange(tSep, rollConf.critScore.range, tNum); break; case DiceOptions.CritSuccessGtr: // Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.critScore.on = true; - for (let i = tNum; i <= rollConf.dieSize; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cs> ${i}`); - !rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i); - } + gtrAddToRange(tSep, rollConf.critScore.range, tNum, rollConf.dieSize); break; case DiceOptions.CritSuccessLt: // Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.critScore.on = true; - for (let i = 0; i <= tNum; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cs< ${i}`); - !rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i); - } + ltAddToRange(tSep, rollConf.critScore.range, tNum); break; case DiceOptions.CritFail: case DiceOptions.CritFailEqu: // Configure CritFail for one number (this can happen multiple times) rollConf.critFail.on = true; - !rollConf.critFail.range.includes(tNum) && rollConf.critFail.range.push(tNum); + addToRange(tSep, rollConf.critFail.range, tNum); break; case DiceOptions.CritFailGtr: // Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.critFail.on = true; - for (let i = tNum; i <= rollConf.dieSize; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cf> ${i}`); - !rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i); - } + gtrAddToRange(tSep, rollConf.critFail.range, tNum, rollConf.dieSize); break; case DiceOptions.CritFailLt: // Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.critFail.on = true; - for (let i = 0; i <= tNum; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing cf< ${i}`); - !rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i); - } + ltAddToRange(tSep, rollConf.critFail.range, tNum); break; case DiceOptions.Exploding: case DiceOptions.ExplodeOnce: @@ -363,7 +345,7 @@ export const getRollConf = (rollStr: string): RollConf => { rollConf.exploding.on = true; if (afterNumIdx > 0) { // 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; case DiceOptions.ExplodingEqu: @@ -372,7 +354,7 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.CompoundingExplosionEqu: // Configure Exploding (this can happen multiple times) rollConf.exploding.on = true; - !rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum); + addToRange(tSep, rollConf.exploding.nums, tNum); break; case DiceOptions.ExplodingGtr: case DiceOptions.ExplodeOnceGtr: @@ -380,12 +362,7 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.CompoundingExplosionGtr: // Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.exploding.on = true; - for (let i = tNum; i <= rollConf.dieSize; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing !> ${i}`); - !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); - } + gtrAddToRange(tSep, rollConf.exploding.nums, tNum, rollConf.dieSize); break; case DiceOptions.ExplodingLt: case DiceOptions.ExplodeOnceLt: @@ -393,15 +370,16 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.CompoundingExplosionLt: // Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.exploding.on = true; - for (let i = 1; i <= tNum; i++) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsing !< ${i}`); - !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); - } + ltAddToRange(tSep, rollConf.exploding.nums, tNum); break; - case DiceOptions.Matching: 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; if (afterNumIdx > 0) { // User gave a number to work with, save it @@ -410,13 +388,52 @@ export const getRollConf = (rollStr: string): RollConf => { break; case DiceOptions.Sort: case DiceOptions.SortAsc: + if (rollConf.sort.on) { + // Ensure we do not override existing settings + throwDoubleSepError(tSep); + } rollConf.sort.on = true; rollConf.sort.direction = 'a'; break; case DiceOptions.SortDesc: + if (rollConf.sort.on) { + // Ensure we do not override existing settings + throwDoubleSepError(tSep); + } rollConf.sort.on = true; rollConf.sort.direction = 'd'; 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: // Throw error immediately if unknown op is encountered throw new Error(`UnknownOperation_${tSep}`); @@ -442,9 +459,6 @@ export const getRollConf = (rollStr: string): RollConf => { case DiceOptions.CompoundingExplosionEqu: rollConf.exploding.compounding = true; break; - case DiceOptions.MatchingTotal: - rollConf.match.returnTotal = true; - break; } // Finally slice off everything else parsed this loop @@ -472,6 +486,10 @@ export const getRollConf = (rollStr: string): RollConf => { 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) { throw new Error('NoZerosAllowed_drop'); } diff --git a/src/artigen/utils/diceFlagger.ts b/src/artigen/utils/diceFlagger.ts new file mode 100644 index 0000000..b20fb6e --- /dev/null +++ b/src/artigen/utils/diceFlagger.ts @@ -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'; + } +}; diff --git a/src/artigen/utils/rangeAdder.ts b/src/artigen/utils/rangeAdder.ts new file mode 100644 index 0000000..ed5ac59 --- /dev/null +++ b/src/artigen/utils/rangeAdder.ts @@ -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, 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, 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, tNum: number) => internalAddMultipleToRange(tSep, range, 0, tNum); + +// Add numbers greater than or equal to tNum to range +export const gtrAddToRange = (tSep: string, range: Array, tNum: number, dieSize: number) => internalAddMultipleToRange(tSep, range, tNum, dieSize); diff --git a/src/artigen/utils/translateError.ts b/src/artigen/utils/translateError.ts index 09cec2a..afc46b3 100644 --- a/src/artigen/utils/translateError.ts +++ b/src/artigen/utils/translateError.ts @@ -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`; break; 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; case 'NoMaxWithDash': errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct'; diff --git a/src/commands/helpLibrary/diceOptions.ts b/src/commands/helpLibrary/diceOptions.ts index 0b030ca..e6fd23e 100644 --- a/src/commands/helpLibrary/diceOptions.ts +++ b/src/commands/helpLibrary/diceOptions.ts @@ -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\` \`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: [ '`[[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', @@ -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\`, \`xdyz\` +\`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\`, \`xdyfz\` +\`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 = { diff --git a/src/commands/helpLibrary/diceTypes.ts b/src/commands/helpLibrary/diceTypes.ts index 4003e0c..d0c0a45 100644 --- a/src/commands/helpLibrary/diceTypes.ts +++ b/src/commands/helpLibrary/diceTypes.ts @@ -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.`, example: [ '`[[cwod]]` => [3, 0 Successes, 0 Fails] = 0', - '`[[4cwod]]` => [**10** + __1__ + 9 + __1__, 1 Success, 2 Fails] = **__1__**', - '`[[5cwod8]]` => [**10** + 2 + 5 + **8** + 4, 2 Successes, 0 Fails] = **2**', + '`[[4cwod]]` => [8 + __F:1__ + **S:10** + **S:10**, 2 Successes, 1 Fail] = **__2__**', + '`[[5cwod8]]` => [__F:1__ + S:8 + 6 + 7 + S:9, 2 Successes, 1 Fail] = __2__', ], }, ],