diff --git a/README.md b/README.md index e516718..dfbe779 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,10 @@ The Artificer comes with a few supplemental commands to the main rolling command | !!=u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u, but adds the resulting explosion to the die that caused this explosion | | !!>u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or greater, but adds the resulting explosion to the die that caused this explosion | | !! { +export const executeRoll = (rollStr: string, modifiers: RollModifiers): [RollSet[], SumOverride] => { /* Roll Capabilities * Deciphers and rolls a single dice roll set * @@ -67,6 +68,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[ rollConf.critScore.on || rollConf.critFail.on || rollConf.exploding.on, + matchLabel: '', }); // Initial rolling, not handling reroll or exploding here @@ -307,18 +309,7 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[ // Handle OVA dropping/keeping if (rollConf.type === 'ova') { - // Make "empty" vals array to easily sum up which die value is the greatest - const rollVals: Array = new Array(rollConf.dieSize).fill(0); - - // Sum up all rolls - for (const ovaRoll of rollSet) { - loopCountCheck(); - - loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`); - if (!ovaRoll.dropped && !ovaRoll.rerolled) { - rollVals[ovaRoll.roll - 1] += ovaRoll.roll; - } - } + const rollVals: Array = generateRollVals(rollConf, rollSet, rollStr, false); // Find max value, using lastIndexOf to use the greatest die size max in case of duplicate maximums const maxRoll = rollVals.lastIndexOf(Math.max(...rollVals)) + 1; @@ -337,5 +328,47 @@ export const executeRoll = (rollStr: string, modifiers: RollModifiers): RollSet[ } } - return rollSet; + const sumOverride: SumOverride = { + on: rollConf.match.returnTotal, + value: 0, + }; + if (rollConf.match.on) { + const rollVals: Array = generateRollVals(rollConf, rollSet, rollStr, true).map((count) => (count >= rollConf.match.minCount ? count : 0)); + const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let labelIdx = 0; + const rollLabels: Array = rollVals.map((count) => { + loopCountCheck(); + + if (labelIdx >= labels.length) { + throw new Error(`TooManyLabels_${labels.length}`); + } + + if (count) { + return labels[labelIdx++]; + } + return ''; + }); + + loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | current match state: ${rollVals} | ${rollLabels}`); + + // Apply labels + for (const roll of rollSet) { + loopCountCheck(); + + loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | trying to add a label to ${JSON.stringify(roll)}`); + if (rollLabels[roll.roll - 1]) { + roll.matchLabel = rollLabels[roll.roll - 1]; + } else if (rollConf.match.returnTotal) { + roll.dropped = true; + } + } + + loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | labels added: ${JSON.stringify(rollSet)}`); + + if (rollConf.match.returnTotal) { + sumOverride.value = rollVals.filter((count) => count !== 0).length; + } + } + + return [rollSet, sumOverride]; }; diff --git a/src/artigen/dice/generateFormattedRoll.ts b/src/artigen/dice/generateFormattedRoll.ts index f902e50..7af14ec 100644 --- a/src/artigen/dice/generateFormattedRoll.ts +++ b/src/artigen/dice/generateFormattedRoll.ts @@ -19,7 +19,7 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers let tempComplex = false; // Generate the roll, passing flags thru - const tempRollSet = executeRoll(rollConf, modifiers); + const [tempRollSet, sumOverride] = executeRoll(rollConf, modifiers); // Loop thru all parts of the roll to document everything that was done to create the total roll tempRollSet.forEach((e) => { @@ -72,8 +72,13 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers postFormat = `!${postFormat}`; } + let rollLabel = ''; + if (e.matchLabel) { + rollLabel = `${e.matchLabel}:`; + } + // Finally add this to the roll's details - tempDetails += `${preFormat}${e.roll}${postFormat} + `; + tempDetails += `${preFormat}${rollLabel}${e.roll}${postFormat} + `; }); // After the looping is done, remove the extra " + " from the details and cap it with the closing ] tempDetails = tempDetails.substring(0, tempDetails.length - 3); @@ -86,7 +91,7 @@ export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers return { solvedStep: { - total: tempTotal, + total: sumOverride.on ? sumOverride.value : tempTotal, details: tempDetails, containsCrit: tempCrit, containsFail: tempFail, diff --git a/src/artigen/dice/getRollConf.ts b/src/artigen/dice/getRollConf.ts index 7a20dd9..1acce41 100644 --- a/src/artigen/dice/getRollConf.ts +++ b/src/artigen/dice/getRollConf.ts @@ -61,6 +61,11 @@ export const getRollConf = (rollStr: string): RollConf => { penetrating: false, nums: [], }, + match: { + on: false, + minCount: 2, + returnTotal: false, + }, }; // If the dPts is not long enough, throw error @@ -380,12 +385,23 @@ export const getRollConf = (rollStr: string): RollConf => { !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); } break; + case 'm': + case 'mt': + rollConf.match.on = true; + if (afterNumIdx > 0) { + // User gave a number to explode on, save it + rollConf.match.minCount = tNum; + } else { + // User did not give number, use cs + afterNumIdx = 1; + } + break; default: // Throw error immediately if unknown op is encountered throw new Error(`UnknownOperation_${tSep}`); } - // Exploding flags get set in their own switch statement to avoid weird duplicated code + // Followup switch to avoid weird duplicated code switch (tSep) { case '!o': case '!o=': @@ -405,6 +421,9 @@ export const getRollConf = (rollStr: string): RollConf => { case '!!<': rollConf.exploding.compounding = true; break; + case 'mt': + rollConf.match.returnTotal = true; + break; } // Finally slice off everything else parsed this loop diff --git a/src/artigen/utils/rollValCounter.ts b/src/artigen/utils/rollValCounter.ts new file mode 100644 index 0000000..11f6673 --- /dev/null +++ b/src/artigen/utils/rollValCounter.ts @@ -0,0 +1,25 @@ +import { log, LogTypes as LT } from '@Log4Deno'; + +import { RollConf, RollSet } from 'artigen/dice/dice.d.ts'; + +import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts'; + +import { loggingEnabled } from 'artigen/utils/logFlag.ts'; + +// Can either count or sum each die +export const generateRollVals = (rollConf: RollConf, rollSet: RollSet[], rollStr: string, count: boolean): Array => { + const rollVals = new Array(rollConf.dieSize).fill(0); + + // Count up all rolls + for (const ovaRoll of rollSet) { + loopCountCheck(); + + loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`); + if (!ovaRoll.dropped && !ovaRoll.rerolled) { + rollVals[ovaRoll.roll - 1] += count ? 1 : ovaRoll.roll; + } + } + + loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | rollVals ${rollVals}`); + return rollVals; +}; diff --git a/src/artigen/utils/translateError.ts b/src/artigen/utils/translateError.ts index 1401d5a..09cec2a 100644 --- a/src/artigen/utils/translateError.ts +++ b/src/artigen/utils/translateError.ts @@ -112,6 +112,9 @@ export const translateError = (solverError: Error): [string, string] => { case 'IllegalVariable': errorMsg = `Error: \`${errorDetails}\` is not a valid variable`; break; + case 'TooManyLabels': + errorMsg = `Error: ${config.name} can only support a maximum of \`${errorDetails}\` labels when using the dice matching options (\`m\` or \`mt\`)`; + break; default: log(LT.ERROR, `Unhandled Parser Error: ${errorName}, ${errorDetails}`); errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`; diff --git a/src/commands/helpLibrary/diceOptions.ts b/src/commands/helpLibrary/diceOptions.ts index c3dc340..adf3bda 100644 --- a/src/commands/helpLibrary/diceOptions.ts +++ b/src/commands/helpLibrary/diceOptions.ts @@ -230,6 +230,22 @@ Any time a Compounding Explosion happens, the formatting on the die will still s ], }, ], + [ + 'dice-matching', + { + name: 'Dice Matching', + 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.`, + 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', + '`[[10d6mt]]` => [**C:6** + B:2 + ~~4~~ + __C:1__ + __C:1__ + B:2 + **C:6** + B:2 + **C:6** + **C:6**] = 3', + '`[[10d6mt4]]` => [**A:6** + ~~2~~ + ~~4~~ + ~~__1__~~ + ~~__1__~~ + ~~2~~ + **A:6** + ~~2~~ + **A:6** + **A:6**] = 1', + ], + }, + ], ]); export const DiceOptionsHelpPages: HelpPage = {