Add simulated nominal flag

This commit is contained in:
Ean Milligan 2025-06-21 21:08:53 -04:00
parent 8793011350
commit 9d6b389d71
8 changed files with 165 additions and 73 deletions

View File

@ -5,7 +5,7 @@ meta {
} }
get { 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]&hr=[hide-raw-roll-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]&cc=[confirm-crit-flag]&rd=[roll-dist-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]&hr=[hide-raw-roll-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, sn, or max]&n=[nominal-roll-flag, cannot be used with sn, max or min flag]&sn=[simulated-nominal-flag, cannot be used with max, min, n. or cc]&gms=[csv-of-discord-user-ids-to-be-dmed-results]&o=[order-rolls, must be a or d]&c=[count-flag]&cc=[confirm-crit-flag, cannot be used with sn]&rd=[roll-dist-flag]
body: none body: none
auth: inherit auth: inherit
} }
@ -20,11 +20,12 @@ params:query {
hr: [hide-raw-roll-details-flag] hr: [hide-raw-roll-details-flag]
s: [spoiler-results-flag] s: [spoiler-results-flag]
m-or-max: [max-roll-flag, cannot be used with n flag] m-or-max: [max-roll-flag, cannot be used with n flag]
min: [min-roll-flag, cannot be used with n or max] min: [min-roll-flag, cannot be used with n, sn, or max]
n: [nominal-roll-flag, cannot be used with max or min flag] n: [nominal-roll-flag, cannot be used with sn, max or min flag]
sn: [simulated-nominal-flag, can pass number with it, cannot be used with max, min, n. or cc]
gms: [csv-of-discord-user-ids-to-be-dmed-results] gms: [csv-of-discord-user-ids-to-be-dmed-results]
o: [order-rolls, must be a or d] o: [order-rolls, must be a or d]
c: [count-flag] c: [count-flag]
cc: [confirm-crit-flag] cc: [confirm-crit-flag, cannot be used with sn]
rd: [roll-dist-flag] rd: [roll-dist-flag]
} }

View File

@ -126,13 +126,14 @@ The Artificer comes with a few supplemental commands to the main rolling command
* `-nd` - No Details - Suppresses all details of the requested roll * `-nd` - No Details - Suppresses all details of the requested roll
* `-snd` - Super No Details - Suppresses all details of the requested roll and hides no details message * `-snd` - Super No Details - Suppresses all details of the requested roll and hides no details message
* `-s` - Spoiler - Spoilers all details of the requested roll * `-s` - Spoiler - Spoilers all details of the requested roll
* `-m` or `-max` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with `-n` or `-min` * `-m` or `-max` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with `-n`, `-min`, or `-sn`
* `-min` - Minimize Roll - Rolls the theoretical minimum roll, cannot be used with `-m`, `-max`, or `-n` * `-min` - Minimize Roll - Rolls the theoretical minimum roll, cannot be used with `-m`, `-max`, `-n`, or `-sn`
* `-n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with `-m`, `-max`, or `-min` * `-n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with `-m`, `-max`, `-min`, or `-sn`
* `-sn` or `-sn [number]` - Simulated Nominal - Rolls the requests roll many times to approximately simulate the nominal of complex rolls, can specify the amount or accept default amount by not specify the amount, cannot be used with `-m`, `-max`, `-min`, `-n`, or `-cc`
* `-gm @user1 @user2 ... @userN` - GM Roll - Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs * `-gm @user1 @user2 ... @userN` - GM Roll - Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs
* `-o a` or `-o d` - Order Roll - Rolls the requested roll and orders the results in the requested direction * `-o a` or `-o d` - Order Roll - Rolls the requested roll and orders the results in the requested direction
* `-ct` - Comma Totals - Adds commas to totals for readability * `-ct` - Comma Totals - Adds commas to totals for readability
* `-cc` - Confirm Critical Hits - Automatically rerolls whenever a crit hits * `-cc` - Confirm Critical Hits - Automatically rerolls whenever a crit hits, cannot be used with `-sn`
- `-rd` - Roll Distribution - Shows a raw roll distribution of all dice in roll - `-rd` - Roll Distribution - Shows a raw roll distribution of all dice in roll
* The results have some formatting applied on them to provide details on what happened during this roll. * The results have some formatting applied on them to provide details on what happened during this roll.
* Critical successes will be **bolded** * Critical successes will be **bolded**

View File

@ -11,6 +11,7 @@ export const config = {
maxLoops: 1000000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk. maxLoops: 1000000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk.
maxWorkers: 16, // Maximum number of worker threads to spawn at once (Set this to less than the number of threads your CPU has, Artificer will eat it all if too many rolls happen at once) maxWorkers: 16, // Maximum number of worker threads to spawn at once (Set this to less than the number of threads your CPU has, Artificer will eat it all if too many rolls happen at once)
workerTimeout: 300000, // Maximum time before the bot kills a worker thread in ms workerTimeout: 300000, // Maximum time before the bot kills a worker thread in ms
simulatedNominal: 100000, // Number of loops to run for simulating a nominal
}, },
api: { api: {
// Setting for the built-in API // Setting for the built-in API

View File

@ -61,13 +61,18 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
line2 = resultStr; line2 = resultStr;
// If a theoretical roll is requested, mark the output as such, else use default formatting // If a theoretical roll is requested, mark the output as such, else use default formatting
if (rollRequest.modifiers.maxRoll || rollRequest.modifiers.minRoll || rollRequest.modifiers.nominalRoll) { const theoreticalBools = [
const theoreticalTexts = ['Maximum', 'Minimum', 'Nominal']; rollRequest.modifiers.maxRoll,
const theoreticalBools = [rollRequest.modifiers.maxRoll, rollRequest.modifiers.minRoll, rollRequest.modifiers.nominalRoll]; rollRequest.modifiers.minRoll,
rollRequest.modifiers.nominalRoll,
rollRequest.modifiers.simulatedNominal > 0,
];
if (theoreticalBools.includes(true)) {
const theoreticalTexts = ['Theoretical Maximum', 'Theoretical Minimum', 'Theoretical Nominal', 'Simulated Nominal'];
const theoreticalText = theoreticalTexts[theoreticalBools.indexOf(true)]; const theoreticalText = theoreticalTexts[theoreticalBools.indexOf(true)];
line1 = ` requested the Theoretical ${theoreticalText} of:\n\`${rawCmd}\``; line1 = ` requested the ${theoreticalText.toLowerCase()} of:\n\`${rawCmd}\``;
line2 = `Theoretical ${theoreticalText} ${resultStr}`; line2 = `${theoreticalText} ${resultStr}`;
} else if (rollRequest.modifiers.order === 'a') { } else if (rollRequest.modifiers.order === 'a') {
line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${rawCmd}\``; line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${rawCmd}\``;
tempReturnData.sort(compareTotalRolls); tempReturnData.sort(compareTotalRolls);
@ -78,6 +83,8 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
line1 = ` rolled:\n\`${rawCmd}\``; line1 = ` rolled:\n\`${rawCmd}\``;
} }
if (rollRequest.modifiers.simulatedNominal) line2 += `Iterations performed per roll: \`${rollRequest.modifiers.simulatedNominal}\`\n`;
// Fill out all of the details and results now // Fill out all of the details and results now
tempReturnData.forEach((e) => { tempReturnData.forEach((e) => {
loopCountCheck(); loopCountCheck();
@ -86,14 +93,16 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
let preFormat = ''; let preFormat = '';
let postFormat = ''; let postFormat = '';
// If the roll contained a crit success or fail, set the formatting around it if (!rollRequest.modifiers.simulatedNominal) {
if (e.containsCrit) { // If the roll contained a crit success or fail, set the formatting around it
preFormat = `**${preFormat}`; if (e.containsCrit) {
postFormat = `${postFormat}**`; preFormat = `**${preFormat}`;
} postFormat = `${postFormat}**`;
if (e.containsFail) { }
preFormat = `__${preFormat}`; if (e.containsFail) {
postFormat = `${postFormat}__`; preFormat = `__${preFormat}`;
postFormat = `${postFormat}__`;
}
} }
// Populate line2 (the results) and line3 (the details) with their data // Populate line2 (the results) and line3 (the details) with their data
@ -106,7 +115,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
line2 += `${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}, `; line2 += `${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}, `;
} }
const rollDetails = rollRequest.modifiers.noDetails ? ' = ' : ` = ${e.rollDetails} = `; const rollDetails = rollRequest.modifiers.noDetails || rollRequest.modifiers.simulatedNominal > 0 ? ' = ' : ` = ${e.rollDetails} = `;
line3 += `\`${e.initConfig}\`${rollDetails}${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}\n`; line3 += `\`${e.initConfig}\`${rollDetails}${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}\n`;
}); });

View File

@ -14,13 +14,14 @@ import { reduceCountDetails } from 'artigen/utils/counter.ts';
import { closeInternal, internalWrapRegex, openInternal } from 'artigen/utils/escape.ts'; import { closeInternal, internalWrapRegex, openInternal } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts'; import { getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
// tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls // tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls
export const tokenizeCmd = ( export const tokenizeCmd = (
cmd: string[], cmd: string[],
modifiers: RollModifiers, modifiers: RollModifiers,
topLevel: boolean, topLevel: boolean,
previousResults: number[] = [], previousResults: number[] = []
): [ReturnData[], CountDetails[], RollDistributionMap[]] => { ): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
loggingEnabled && log(LT.LOG, `Tokenizing command ${JSON.stringify(cmd)}`); loggingEnabled && log(LT.LOG, `Tokenizing command ${JSON.stringify(cmd)}`);
@ -37,56 +38,90 @@ export const tokenizeCmd = (
const currentCmd = cmd.slice(openIdx + 1, closeIdx); const currentCmd = cmd.slice(openIdx + 1, closeIdx);
loggingEnabled && log(LT.LOG, `Setting previous results: topLevel:${topLevel} ${topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults}`); const simulatedLoopCount = modifiers.simulatedNominal || 1;
// Handle any nested commands loggingEnabled &&
const [tempData, tempCounts, tempDists] = tokenizeCmd(currentCmd, modifiers, false, topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults); log(
const data = tempData[0]; LT.LOG,
`Setting previous results: topLevel:${topLevel} ${
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults
} simulatedLoopCount:${simulatedLoopCount}`
);
if (topLevel) { const simulatedData: ReturnData[] = [];
// Handle saving any formatting between dice for (let i = 0; i < simulatedLoopCount; i++) {
if (openIdx !== 0) { loopCountCheck();
data.rollPreFormat = cmd.slice(0, openIdx).join('');
loggingEnabled && log(LT.LOG, `In simLoop:${i} "${currentCmd}" of ${JSON.stringify(cmd)}`);
// Handle any nested commands
const [tempData, tempCounts, tempDists] = tokenizeCmd(currentCmd, modifiers, false, topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults);
const data = tempData[0];
loggingEnabled && log(LT.LOG, `Data back from tokenizeCmd, "${currentCmd}" of "${JSON.stringify(cmd)}" ${JSON.stringify(data)}`);
// Only run this on first loop
if (topLevel && i === 0) {
// Handle saving any formatting between dice
if (openIdx !== 0) {
data.rollPreFormat = cmd.slice(0, openIdx).join('');
}
// Chop off all formatting between cmds along with the processed cmd
cmd.splice(0, closeIdx + 1);
} }
// Store results
modifiers.simulatedNominal ? simulatedData.push(data) : returnData.push(data);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Chop off all formatting between cmds along with the processed cmd // Handle ConfirmCrit if its on
cmd.splice(0, closeIdx + 1); if (topLevel && modifiers.confirmCrit && reduceCountDetails(tempCounts).successful) {
} else { loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`);
// We're handling something nested, replace [[cmd]] with the cmd's result let done = false;
cmd.splice(openIdx, closeIdx - openIdx + 1, `${openInternal}${data.rollTotal}${closeInternal}`); while (!done) {
loopCountCheck();
// Keep running the same roll again until its not successful
const [ccTempData, ccTempCounts, ccTempDists] = tokenizeCmd(
currentCmd,
modifiers,
false,
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults
);
const ccData = ccTempData[0];
ccData.rollPreFormat = '\nAuto-Confirming Crit: ';
loggingEnabled &&
log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)} | Rolled again ${JSON.stringify(ccData)} ${JSON.stringify(ccTempCounts)}`);
// Store CC results
returnData.push(ccData);
countDetails.push(...ccTempCounts);
rollDists.push(...ccTempDists);
done = reduceCountDetails(ccTempCounts).successful === 0;
}
}
} }
// Store results // Turn the simulated return data into a single usable payload
returnData.push(data); if (modifiers.simulatedNominal) {
countDetails.push(...tempCounts); loggingEnabled && log(LT.LOG, `SN on, condensing array into single item ${JSON.stringify(simulatedData)}`);
rollDists.push(...tempDists); returnData.push({
rollTotal: simulatedData.map((data) => data.rollTotal).reduce(basicReducer) / simulatedData.length,
rollPreFormat: simulatedData[0].rollPreFormat,
rollPostFormat: simulatedData[0].rollPostFormat,
rollDetails: simulatedData[0].rollDetails,
containsCrit: simulatedData.some((data) => data.containsCrit),
containsFail: simulatedData.some((data) => data.containsFail),
initConfig: simulatedData[0].initConfig,
});
loggingEnabled && log(LT.LOG, `SN on, returnData updated ${JSON.stringify(returnData)}`);
}
// Handle ConfirmCrit if its on // Finally, if we are handling a nested [[cmd]], fill in the rollTotal correctly
if (topLevel && modifiers.confirmCrit && reduceCountDetails(tempCounts).successful) { if (!topLevel) {
loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`); cmd.splice(openIdx, closeIdx - openIdx + 1, `${openInternal}${Math.round(returnData[returnData.length - 1].rollTotal)}${closeInternal}`);
let done = false;
while (!done) {
loopCountCheck();
// Keep running the same roll again until its not successful
const [ccTempData, ccTempCounts, ccTempDists] = tokenizeCmd(
currentCmd,
modifiers,
false,
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults,
);
const ccData = ccTempData[0];
ccData.rollPreFormat = '\nAuto-Confirming Crit: ';
loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)} | Rolled again ${JSON.stringify(ccData)} ${JSON.stringify(ccTempCounts)}`);
// Store CC results
returnData.push(ccData);
countDetails.push(...ccTempCounts);
rollDists.push(...ccTempDists);
done = reduceCountDetails(ccTempCounts).successful === 0;
}
} }
} }

View File

@ -46,6 +46,7 @@ export interface RollModifiers {
maxRoll: boolean; maxRoll: boolean;
minRoll: boolean; minRoll: boolean;
nominalRoll: boolean; nominalRoll: boolean;
simulatedNominal: number;
gmRoll: boolean; gmRoll: boolean;
gms: string[]; gms: string[];
order: string; order: string;

View File

@ -1,6 +1,7 @@
import { log, LogTypes as LT } from '@Log4Deno'; import { log, LogTypes as LT } from '@Log4Deno';
import { RollModifiers } from 'artigen/dice/dice.d.ts'; import { RollModifiers } from 'artigen/dice/dice.d.ts';
import config from '~config';
export const Modifiers = Object.freeze({ export const Modifiers = Object.freeze({
Count: '-c', Count: '-c',
@ -12,6 +13,7 @@ export const Modifiers = Object.freeze({
MaxShorthand: '-m', MaxShorthand: '-m',
Min: '-min', Min: '-min',
Nominal: '-n', Nominal: '-n',
SimulatedNominal: '-sn',
GM: '-gm', GM: '-gm',
Order: '-o', Order: '-o',
CommaTotals: '-ct', CommaTotals: '-ct',
@ -28,6 +30,7 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
maxRoll: false, maxRoll: false,
minRoll: false, minRoll: false,
nominalRoll: false, nominalRoll: false,
simulatedNominal: 0,
gmRoll: false, gmRoll: false,
gms: [], gms: [],
order: '', order: '',
@ -36,7 +39,7 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
confirmCrit: false, confirmCrit: false,
rollDist: false, rollDist: false,
apiWarn: '', apiWarn: '',
valid: false, valid: true,
error: new Error(), error: new Error(),
}; };
@ -70,6 +73,16 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
case Modifiers.Nominal: case Modifiers.Nominal:
modifiers.nominalRoll = true; modifiers.nominalRoll = true;
break; break;
case Modifiers.SimulatedNominal:
if (args[i + 1] && parseInt(args[i + 1]).toString() === args[i + 1]) {
// Shift the -sn out so the next item is the amount
args.splice(i, 1);
modifiers.simulatedNominal = parseInt(args[i]);
} else {
modifiers.simulatedNominal = 10000;
}
break;
case Modifiers.ConfirmCrit: case Modifiers.ConfirmCrit:
modifiers.confirmCrit = true; modifiers.confirmCrit = true;
break; break;
@ -121,13 +134,26 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
} }
} }
// maxRoll, minRoll, and nominalRoll cannot be on at same time, throw an error // maxRoll, minRoll, nominalRoll, simulatedNominal cannot be on at same time, throw an error
if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll].filter((b) => b).length > 1) { if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll, modifiers.simulatedNominal].filter((b) => b).length > 1) {
modifiers.error.name = 'MaxAndNominal'; modifiers.error.name = 'MaxAndNominal';
modifiers.error.message = 'Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`'; modifiers.error.message = 'Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`, `simulatedNominal`';
return [modifiers, args]; modifiers.valid = false;
}
// simulatedNominal and confirmCrit cannot be used at same time, throw an error
if ([modifiers.confirmCrit, modifiers.simulatedNominal].filter((b) => b).length > 1) {
modifiers.error.name = 'SimNominalAndCC';
modifiers.error.message = 'Cannot use the following at the same time:\n`confirmCrit`, `simulatedNominal`';
modifiers.valid = false;
}
// simulatedNominal cannot be greater than config.limits.simulatedNominal
if (modifiers.simulatedNominal > config.limits.simulatedNominal) {
modifiers.error.name = 'SimNominalTooBig';
modifiers.error.message = `Number of iterations for \`simulatedNominal\` cannot be greater than \`${config.limits.simulatedNominal}\``;
modifiers.valid = false;
} }
modifiers.valid = true;
return [modifiers, args]; return [modifiers, args];
}; };

View File

@ -81,6 +81,8 @@ export const apiRoll = async (query: Map<string, string>, apiUserid: bigint): Pr
// Clip off the leading prefix. API calls must be formatted with a prefix at the start to match how commands are sent in Discord // Clip off the leading prefix. API calls must be formatted with a prefix at the start to match how commands are sent in Discord
rollCmd = rollCmd.replace(/%20/g, ' ').trim(); rollCmd = rollCmd.replace(/%20/g, ' ').trim();
const rawSimNom = parseInt(query.get('sn') ?? '0');
const simNom = rawSimNom || 10000;
const modifiers: RollModifiers = { const modifiers: RollModifiers = {
noDetails: query.has('nd'), noDetails: query.has('nd'),
superNoDetails: query.has('snd'), superNoDetails: query.has('snd'),
@ -89,6 +91,7 @@ export const apiRoll = async (query: Map<string, string>, apiUserid: bigint): Pr
maxRoll: query.has('m') || query.has('max'), maxRoll: query.has('m') || query.has('max'),
minRoll: query.has('min'), minRoll: query.has('min'),
nominalRoll: query.has('n'), nominalRoll: query.has('n'),
simulatedNominal: query.has('sn') ? simNom : 0,
gmRoll: query.has('gms'), gmRoll: query.has('gms'),
gms: query.has('gms') ? (query.get('gms') || '').split(',') : [], gms: query.has('gms') ? (query.get('gms') || '').split(',') : [],
order: query.has('o') ? query.get('o')?.toLowerCase() || '' : '', order: query.has('o') ? query.get('o')?.toLowerCase() || '' : '',
@ -101,6 +104,21 @@ export const apiRoll = async (query: Map<string, string>, apiUserid: bigint): Pr
error: new Error(), error: new Error(),
}; };
// maxRoll, minRoll, and nominalRoll cannot be on at same time, throw an error
if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll, modifiers.simulatedNominal].filter((b) => b).length > 1) {
return stdResp.BadRequest('Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`, `simulatedNominal`');
}
// simulatedNominal and confirmCrit cannot be used at same time, throw an error
if ([modifiers.confirmCrit, modifiers.simulatedNominal].filter((b) => b).length > 1) {
return stdResp.BadRequest('Cannot use the following at the same time:\n`confirmCrit`, `simulatedNominal`');
}
// simulatedNominal cannot be greater than config.limits.simulatedNominal
if (modifiers.simulatedNominal > config.limits.simulatedNominal) {
return stdResp.BadRequest(`Number of iterations for \`simulatedNominal\` cannot be greater than \`${config.limits.simulatedNominal}\``);
}
return new Promise<Response>((resolve) => { return new Promise<Response>((resolve) => {
sendRollRequest({ sendRollRequest({
apiRoll: true, apiRoll: true,
@ -120,7 +138,7 @@ export const apiRoll = async (query: Map<string, string>, apiUserid: bigint): Pr
} else { } else {
// Alert API user that they messed up // Alert API user that they messed up
return stdResp.Forbidden( return stdResp.Forbidden(
`Verify you are a member of the guild you are sending this roll to. If you are, the ${config.name} may not have that registered, please send a message in the guild so ${config.name} can register this. This registration is temporary, so if you see this error again, just poke your server again.`, `Verify you are a member of the guild you are sending this roll to. If you are, the ${config.name} may not have that registered, please send a message in the guild so ${config.name} can register this. This registration is temporary, so if you see this error again, just poke your server again.`
); );
} }
} else { } else {