diff --git a/.bruno/Authenticated/Roll Requests/Roll Dice.bru b/.bruno/Authenticated/Roll Requests/Roll Dice.bru index 61a70b8..389133b 100644 --- a/.bruno/Authenticated/Roll Requests/Roll Dice.bru +++ b/.bruno/Authenticated/Roll Requests/Roll Dice.bru @@ -5,7 +5,7 @@ meta { } get { - url: http://localhost:8166/api/roll?user=[discord-user-id]&channel=[discord-channel-id]&rollstr=[artificer-roll-cmd]&documentation=All items below are optional. Flags do not need values.&nd=[no-details-flag]&snd=[super-no-details-flag]&s=[spoiler-results-flag]&m=[max-roll-flag, cannot be used with n flag]&n=[nominal-roll-flag, cannot be used with m flag]&gms=[csv-of-discord-user-ids-to-be-dmed-results]&o=[order-rolls, must be a or d]&c=[count-flag] + url: http://localhost:8166/api/roll?user=[discord-user-id]&channel=[discord-channel-id]&rollstr=[artificer-roll-cmd]&documentation=All items below are optional. Flags do not need values.&nd=[no-details-flag]&snd=[super-no-details-flag]&s=[spoiler-results-flag]&m or max=[max-roll-flag, cannot be used with n flag]&min=[[min-roll-flag, cannot be used with n or max&n=[nominal-roll-flag, cannot be used with max or min flag]&gms=[csv-of-discord-user-ids-to-be-dmed-results]&o=[order-rolls, must be a or d]&c=[count-flag] body: none auth: inherit } @@ -18,8 +18,9 @@ params:query { nd: [no-details-flag] snd: [super-no-details-flag] s: [spoiler-results-flag] - m: [max-roll-flag, cannot be used with n flag] - n: [nominal-roll-flag, cannot be used with m flag] + m or max: [max-roll-flag, cannot be used with n flag] + min: [[min-roll-flag, cannot be used with n or max + n: [nominal-roll-flag, cannot be used with max or min flag] gms: [csv-of-discord-user-ids-to-be-dmed-results] o: [order-rolls, must be a or d] c: [count-flag] diff --git a/src/commands/roll/getModifiers.ts b/src/commands/roll/getModifiers.ts index 20b515d..c87abf9 100644 --- a/src/commands/roll/getModifiers.ts +++ b/src/commands/roll/getModifiers.ts @@ -20,6 +20,7 @@ export const getModifiers = (m: DiscordenoMessage, args: string[], command: stri superNoDetails: false, spoiler: '', maxRoll: false, + minRoll: false, nominalRoll: false, gmRoll: false, gms: [], @@ -46,9 +47,13 @@ export const getModifiers = (m: DiscordenoMessage, args: string[], command: stri case '-s': modifiers.spoiler = '||'; break; + case '-max': case '-m': modifiers.maxRoll = true; break; + case '-min': + modifiers.minRoll = true; + break; case '-n': modifiers.nominalRoll = true; break; @@ -106,9 +111,11 @@ export const getModifiers = (m: DiscordenoMessage, args: string[], command: stri } } - // maxRoll and nominalRoll cannot both be on, throw an error - if (modifiers.maxRoll && modifiers.nominalRoll) { - m.edit(generateRollError(errorType, 'Cannot maximize and nominize the roll at the same time')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:106', m, e)); + // maxRoll, minRoll, and nominalRoll cannot be on at same time, throw an error + if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll].filter((b) => b).length > 1) { + m.edit(generateRollError(errorType, 'Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`')).catch((e) => + utils.commonLoggers.messageEditError('getModifiers.ts:106', m, e) + ); if (DEVMODE && config.logRolls) { // If enabled, log rolls so we can verify the bots math diff --git a/src/endpoints/gets/apiRoll.ts b/src/endpoints/gets/apiRoll.ts index bf46667..8b45343 100644 --- a/src/endpoints/gets/apiRoll.ts +++ b/src/endpoints/gets/apiRoll.ts @@ -84,7 +84,8 @@ export const apiRoll = async (query: Map, apiUserid: bigint): Pr noDetails: query.has('nd'), superNoDetails: query.has('snd'), spoiler: query.has('s') ? '||' : '', - maxRoll: query.has('m'), + maxRoll: query.has('m') || query.has('max'), + minRoll: query.has('min'), nominalRoll: query.has('n'), gmRoll: query.has('gms'), gms: query.has('gms') ? (query.get('gms') || '').split(',') : [], diff --git a/src/mod.d.ts b/src/mod.d.ts index 8f40288..476a743 100644 --- a/src/mod.d.ts +++ b/src/mod.d.ts @@ -16,6 +16,7 @@ export type RollModifiers = { superNoDetails: boolean; spoiler: string; maxRoll: boolean; + minRoll: boolean; nominalRoll: boolean; gmRoll: boolean; gms: string[]; diff --git a/src/solver/parser.ts b/src/solver/parser.ts index d46e2ce..be6f4e7 100644 --- a/src/solver/parser.ts +++ b/src/solver/parser.ts @@ -8,7 +8,7 @@ import config from '../../config.ts'; import { RollModifiers } from '../mod.d.ts'; import { CountDetails, ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts'; -import { compareTotalRolls, escapeCharacters, loggingEnabled } from './rollUtils.ts'; +import { compareTotalRolls, compareTotalRollsReverse, escapeCharacters, loggingEnabled } from './rollUtils.ts'; import { formatRoll } from './rollFormatter.ts'; import { fullSolver } from './solver.ts'; @@ -151,7 +151,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll i += 2; } else if (!operators.includes(mathConf[i].toString())) { // If nothing else has handled it by now, try it as a roll - const formattedRoll = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll); + const formattedRoll = formatRoll(mathConf[i].toString(), modifiers); mathConf[i] = formattedRoll.solvedStep; tempCountDetails.push(formattedRoll.countDetails); } @@ -197,13 +197,14 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll let line2 = ''; let line3 = ''; - // If maximizeRoll or nominalRoll are on, mark the output as such, else use default formatting - if (modifiers.maxRoll) { - line1 = ` requested the theoretical maximum of:\n\`${config.prefix}${fullCmd}\``; - line2 = 'Theoretical Maximum Results: '; - } else if (modifiers.nominalRoll) { - line1 = ` requested the theoretical nominal of:\n\`${config.prefix}${fullCmd}\``; - line2 = 'Theoretical Nominal Results: '; + // If a theoretical roll is requested, mark the output as such, else use default formatting + if (modifiers.maxRoll || modifiers.minRoll || modifiers.nominalRoll) { + const theoreticalTexts = ['Maximum', 'Minimum', 'Nominal']; + const theoreticalBools = [modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll]; + const theoreticalText = theoreticalTexts[theoreticalBools.indexOf(true)]; + + line1 = ` requested the Theoretical ${theoreticalText} of:\n\`${config.prefix}${fullCmd}\``; + line2 = `Theoretical ${theoreticalText} Results: `; } else if (modifiers.order === 'a') { line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${config.prefix}${fullCmd}\``; line2 = 'Results: '; @@ -211,8 +212,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll } else if (modifiers.order === 'd') { line1 = ` requested the following rolls to be ordered from greatest to least:\n\`${config.prefix}${fullCmd}\``; line2 = 'Results: '; - tempReturnData.sort(compareTotalRolls); - tempReturnData.reverse(); + tempReturnData.sort(compareTotalRollsReverse); } else { line1 = ` rolled:\n\`${config.prefix}${fullCmd}\``; line2 = 'Results: '; diff --git a/src/solver/rollFormatter.ts b/src/solver/rollFormatter.ts index a36619d..18f8510 100644 --- a/src/solver/rollFormatter.ts +++ b/src/solver/rollFormatter.ts @@ -8,17 +8,18 @@ import { roll } from './roller.ts'; import { rollCounter } from './counter.ts'; import { RollFormat } from './solver.d.ts'; import { loggingEnabled } from './rollUtils.ts'; +import { RollModifiers } from '../mod.d.ts'; -// formatRoll(rollConf, maximizeRoll, nominalRoll) returns one SolvedStep +// formatRoll(rollConf, modifiers) returns one SolvedStep // formatRoll handles creating and formatting the completed rolls into the SolvedStep format -export const formatRoll = (rollConf: string, maximizeRoll: boolean, nominalRoll: boolean): RollFormat => { +export const formatRoll = (rollConf: string, modifiers: RollModifiers): RollFormat => { let tempTotal = 0; let tempDetails = '['; let tempCrit = false; let tempFail = false; // Generate the roll, passing flags thru - const tempRollSet = roll(rollConf, maximizeRoll, nominalRoll); + const tempRollSet = roll(rollConf, modifiers); // Loop thru all parts of the roll to document everything that was done to create the total roll tempRollSet.forEach((e) => { diff --git a/src/solver/rollUtils.ts b/src/solver/rollUtils.ts index 56737b1..6b5d537 100644 --- a/src/solver/rollUtils.ts +++ b/src/solver/rollUtils.ts @@ -3,6 +3,7 @@ import { // Log4Deno deps LT, } from '../../deps.ts'; +import { RollModifiers } from '../mod.d.ts'; import { ReturnData, RollSet } from './solver.d.ts'; @@ -10,23 +11,25 @@ export const loggingEnabled = false; // genRoll(size) returns number // genRoll rolls a die of size size and returns the result -export const genRoll = (size: number, maximizeRoll: boolean, nominalRoll: boolean): number => { - if (maximizeRoll) { +export const genRoll = (size: number, modifiers: RollModifiers): number => { + if (modifiers.maxRoll) { return size; + } else if (modifiers.minRoll) { + return 1; } else { // Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result - return nominalRoll ? size / 2 + 0.5 : Math.floor(Math.random() * size + 1); + return modifiers.nominalRoll ? size / 2 + 0.5 : Math.floor(Math.random() * size + 1); } }; // genFateRoll returns -1|0|1 // genFateRoll turns a d6 into a fate die, with sides: -1, -1, 0, 0, 1, 1 -export const genFateRoll = (maximizeRoll: boolean, nominalRoll: boolean): number => { - if (nominalRoll) { +export const genFateRoll = (modifiers: RollModifiers): number => { + if (modifiers.nominalRoll) { return 0; } else { const sides = [-1, -1, 0, 0, 1, 1]; - return sides[genRoll(6, maximizeRoll, nominalRoll) - 1]; + return sides[genRoll(6, modifiers) - 1]; } }; @@ -42,18 +45,24 @@ export const compareRolls = (a: RollSet, b: RollSet): number => { return 0; }; -// compareTotalRolls(a, b) returns -1|0|1 -// compareTotalRolls is used to order an array of RollSets by RollSet.roll -export const compareTotalRolls = (a: ReturnData, b: ReturnData): number => { +const internalCompareTotalRolls = (a: ReturnData, b: ReturnData, dir: 1 | -1): number => { if (a.rollTotal < b.rollTotal) { - return -1; + return -1 * dir; } if (a.rollTotal > b.rollTotal) { - return 1; + return 1 * dir; } return 0; }; +// compareTotalRolls(a, b) returns -1|0|1 +// compareTotalRolls is used to order an array of RollSets by RollSet.roll +export const compareTotalRolls = (a: ReturnData, b: ReturnData): number => internalCompareTotalRolls(a, b, 1); + +// compareTotalRollsReverse(a, b) returns 1|0|-1 +// compareTotalRollsReverse is used to order an array of RollSets by RollSet.roll reversed +export const compareTotalRollsReverse = (a: ReturnData, b: ReturnData): number => internalCompareTotalRolls(a, b, -1); + // compareRolls(a, b) returns -1|0|1 // compareRolls is used to order an array of RollSets by RollSet.origIdx export const compareOrigIdx = (a: RollSet, b: RollSet): number => { diff --git a/src/solver/roller.ts b/src/solver/roller.ts index 5f4dd0f..9972011 100644 --- a/src/solver/roller.ts +++ b/src/solver/roller.ts @@ -7,6 +7,7 @@ import { import { RollConf, RollSet, RollType } from './solver.d.ts'; import { compareOrigIdx, compareRolls, genFateRoll, genRoll, loggingEnabled } from './rollUtils.ts'; +import { RollModifiers } from '../mod.d.ts'; // Call with loopCountCheck(++loopCount); // Will ensure if maxLoops is 10, 10 loops will be allowed, 11 will not. @@ -20,9 +21,9 @@ const throwDoubleSepError = (sep: string): void => { throw new Error(`DoubleSeparator_${sep}`); }; -// roll(rollStr, maximizeRoll, nominalRoll) returns RollSet -// roll parses and executes the rollStr, if needed it will also make the roll the maximum or average -export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolean): RollSet[] => { +// roll(rollStr, modifiers) returns RollSet +// roll parses and executes the rollStr +export const roll = (rollStr: string, modifiers: RollModifiers): RollSet[] => { /* Roll Capabilities * Deciphers and rolls a single dice roll set * @@ -494,7 +495,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea // Copy the template to fill out for this iteration const rolling = getTemplateRoll(); // If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll - rolling.roll = rollType === 'fate' ? genFateRoll(maximizeRoll, nominalRoll) : genRoll(rollConf.dieSize, maximizeRoll, nominalRoll); + rolling.roll = rollType === 'fate' ? genFateRoll(modifiers) : genRoll(rollConf.dieSize, modifiers); // Set origIdx of roll rolling.origIdx = i; @@ -534,22 +535,33 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea // Copy the template to fill out for this iteration const newReroll = getTemplateRoll(); - if (maximizeRoll) { + if (modifiers.maxRoll && !minMaxOverride) { // If maximizeRoll is on and we've entered the reroll code, dieSize is not allowed, determine the next best option and always return that - if (!minMaxOverride) { - mmLoop: for (let m = rollConf.dieSize - 1; m > 0; m--) { - loopCountCheck(++loopCount); + mmMaxLoop: for (let m = rollConf.dieSize - 1; m > 0; m--) { + loopCountCheck(++loopCount); - if (!rollConf.reroll.nums.includes(m)) { - minMaxOverride = m; - break mmLoop; - } + if (!rollConf.reroll.nums.includes(m)) { + minMaxOverride = m; + break mmMaxLoop; } } + } else if (modifiers.minRoll && !minMaxOverride) { + // If minimizeRoll is on and we've entered the reroll code, 1 is not allowed, determine the next best option and always return that + mmMinLoop: for (let m = 2; m <= rollConf.dieSize; m++) { + loopCountCheck(++loopCount); + + if (!rollConf.reroll.nums.includes(m)) { + minMaxOverride = m; + break mmMinLoop; + } + } + } + + if (modifiers.maxRoll || modifiers.minRoll) { newReroll.roll = minMaxOverride; } else { // If nominalRoll is on, set the roll to the average roll of dieSize, otherwise generate a new random roll - newReroll.roll = genRoll(rollConf.dieSize, maximizeRoll, nominalRoll); + newReroll.roll = genRoll(rollConf.dieSize, modifiers); } // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size @@ -579,7 +591,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea // Copy the template to fill out for this iteration const newExplodingRoll = getTemplateRoll(); // If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll - newExplodingRoll.roll = genRoll(rollConf.dieSize, maximizeRoll, nominalRoll); + newExplodingRoll.roll = genRoll(rollConf.dieSize, modifiers); // Always mark this roll as exploding newExplodingRoll.exploding = true;