Add simulated nominal flag
This commit is contained in:
parent
8793011350
commit
9d6b389d71
|
@ -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]&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
|
||||
auth: inherit
|
||||
}
|
||||
|
@ -20,11 +20,12 @@ params:query {
|
|||
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]
|
||||
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, can pass number with it, 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]
|
||||
cc: [confirm-crit-flag, cannot be used with sn]
|
||||
rd: [roll-dist-flag]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* `-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
|
||||
* `-m` or `-max` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with `-n` or `-min`
|
||||
* `-min` - Minimize Roll - Rolls the theoretical minimum roll, cannot be used with `-m`, `-max`, or `-n`
|
||||
* `-n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with `-m`, `-max`, 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`, `-n`, or `-sn`
|
||||
* `-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
|
||||
* `-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
|
||||
* `-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
|
||||
* The results have some formatting applied on them to provide details on what happened during this roll.
|
||||
* Critical successes will be **bolded**
|
||||
|
|
|
@ -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.
|
||||
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
|
||||
simulatedNominal: 100000, // Number of loops to run for simulating a nominal
|
||||
},
|
||||
api: {
|
||||
// Setting for the built-in API
|
||||
|
|
|
@ -61,13 +61,18 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
|
|||
line2 = resultStr;
|
||||
|
||||
// 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 theoreticalTexts = ['Maximum', 'Minimum', 'Nominal'];
|
||||
const theoreticalBools = [rollRequest.modifiers.maxRoll, rollRequest.modifiers.minRoll, rollRequest.modifiers.nominalRoll];
|
||||
const theoreticalBools = [
|
||||
rollRequest.modifiers.maxRoll,
|
||||
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)];
|
||||
|
||||
line1 = ` requested the Theoretical ${theoreticalText} of:\n\`${rawCmd}\``;
|
||||
line2 = `Theoretical ${theoreticalText} ${resultStr}`;
|
||||
line1 = ` requested the ${theoreticalText.toLowerCase()} of:\n\`${rawCmd}\``;
|
||||
line2 = `${theoreticalText} ${resultStr}`;
|
||||
} else if (rollRequest.modifiers.order === 'a') {
|
||||
line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${rawCmd}\``;
|
||||
tempReturnData.sort(compareTotalRolls);
|
||||
|
@ -78,6 +83,8 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
|
|||
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
|
||||
tempReturnData.forEach((e) => {
|
||||
loopCountCheck();
|
||||
|
@ -86,6 +93,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
|
|||
let preFormat = '';
|
||||
let postFormat = '';
|
||||
|
||||
if (!rollRequest.modifiers.simulatedNominal) {
|
||||
// If the roll contained a crit success or fail, set the formatting around it
|
||||
if (e.containsCrit) {
|
||||
preFormat = `**${preFormat}`;
|
||||
|
@ -95,6 +103,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
|
|||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate line2 (the results) and line3 (the details) with their data
|
||||
if (rollRequest.modifiers.order === '') {
|
||||
|
@ -106,7 +115,7 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
|
|||
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`;
|
||||
});
|
||||
|
||||
|
|
|
@ -14,13 +14,14 @@ import { reduceCountDetails } from 'artigen/utils/counter.ts';
|
|||
import { closeInternal, internalWrapRegex, openInternal } from 'artigen/utils/escape.ts';
|
||||
import { loggingEnabled } from 'artigen/utils/logFlag.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
|
||||
export const tokenizeCmd = (
|
||||
cmd: string[],
|
||||
modifiers: RollModifiers,
|
||||
topLevel: boolean,
|
||||
previousResults: number[] = [],
|
||||
previousResults: number[] = []
|
||||
): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
|
||||
loggingEnabled && log(LT.LOG, `Tokenizing command ${JSON.stringify(cmd)}`);
|
||||
|
||||
|
@ -37,13 +38,29 @@ export const tokenizeCmd = (
|
|||
|
||||
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;
|
||||
|
||||
loggingEnabled &&
|
||||
log(
|
||||
LT.LOG,
|
||||
`Setting previous results: topLevel:${topLevel} ${
|
||||
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults
|
||||
} simulatedLoopCount:${simulatedLoopCount}`
|
||||
);
|
||||
|
||||
const simulatedData: ReturnData[] = [];
|
||||
for (let i = 0; i < simulatedLoopCount; i++) {
|
||||
loopCountCheck();
|
||||
|
||||
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)}`);
|
||||
|
||||
if (topLevel) {
|
||||
// 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('');
|
||||
|
@ -51,13 +68,9 @@ export const tokenizeCmd = (
|
|||
|
||||
// Chop off all formatting between cmds along with the processed cmd
|
||||
cmd.splice(0, closeIdx + 1);
|
||||
} else {
|
||||
// We're handling something nested, replace [[cmd]] with the cmd's result
|
||||
cmd.splice(openIdx, closeIdx - openIdx + 1, `${openInternal}${data.rollTotal}${closeInternal}`);
|
||||
}
|
||||
|
||||
// Store results
|
||||
returnData.push(data);
|
||||
modifiers.simulatedNominal ? simulatedData.push(data) : returnData.push(data);
|
||||
countDetails.push(...tempCounts);
|
||||
rollDists.push(...tempDists);
|
||||
|
||||
|
@ -73,12 +86,13 @@ export const tokenizeCmd = (
|
|||
currentCmd,
|
||||
modifiers,
|
||||
false,
|
||||
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults,
|
||||
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)}`);
|
||||
loggingEnabled &&
|
||||
log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)} | Rolled again ${JSON.stringify(ccData)} ${JSON.stringify(ccTempCounts)}`);
|
||||
|
||||
// Store CC results
|
||||
returnData.push(ccData);
|
||||
|
@ -90,6 +104,27 @@ export const tokenizeCmd = (
|
|||
}
|
||||
}
|
||||
|
||||
// Turn the simulated return data into a single usable payload
|
||||
if (modifiers.simulatedNominal) {
|
||||
loggingEnabled && log(LT.LOG, `SN on, condensing array into single item ${JSON.stringify(simulatedData)}`);
|
||||
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)}`);
|
||||
}
|
||||
|
||||
// Finally, if we are handling a nested [[cmd]], fill in the rollTotal correctly
|
||||
if (!topLevel) {
|
||||
cmd.splice(openIdx, closeIdx - openIdx + 1, `${openInternal}${Math.round(returnData[returnData.length - 1].rollTotal)}${closeInternal}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (topLevel) {
|
||||
if (cmd.length) {
|
||||
loggingEnabled && log(LT.LOG, `Adding leftover formatting to last returnData ${JSON.stringify(cmd)}`);
|
||||
|
|
|
@ -46,6 +46,7 @@ export interface RollModifiers {
|
|||
maxRoll: boolean;
|
||||
minRoll: boolean;
|
||||
nominalRoll: boolean;
|
||||
simulatedNominal: number;
|
||||
gmRoll: boolean;
|
||||
gms: string[];
|
||||
order: string;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { log, LogTypes as LT } from '@Log4Deno';
|
||||
|
||||
import { RollModifiers } from 'artigen/dice/dice.d.ts';
|
||||
import config from '~config';
|
||||
|
||||
export const Modifiers = Object.freeze({
|
||||
Count: '-c',
|
||||
|
@ -12,6 +13,7 @@ export const Modifiers = Object.freeze({
|
|||
MaxShorthand: '-m',
|
||||
Min: '-min',
|
||||
Nominal: '-n',
|
||||
SimulatedNominal: '-sn',
|
||||
GM: '-gm',
|
||||
Order: '-o',
|
||||
CommaTotals: '-ct',
|
||||
|
@ -28,6 +30,7 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
|
|||
maxRoll: false,
|
||||
minRoll: false,
|
||||
nominalRoll: false,
|
||||
simulatedNominal: 0,
|
||||
gmRoll: false,
|
||||
gms: [],
|
||||
order: '',
|
||||
|
@ -36,7 +39,7 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
|
|||
confirmCrit: false,
|
||||
rollDist: false,
|
||||
apiWarn: '',
|
||||
valid: false,
|
||||
valid: true,
|
||||
error: new Error(),
|
||||
};
|
||||
|
||||
|
@ -70,6 +73,16 @@ export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
|
|||
case Modifiers.Nominal:
|
||||
modifiers.nominalRoll = true;
|
||||
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:
|
||||
modifiers.confirmCrit = true;
|
||||
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
|
||||
if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll].filter((b) => b).length > 1) {
|
||||
// maxRoll, minRoll, nominalRoll, simulatedNominal cannot be on at same time, throw an error
|
||||
if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll, modifiers.simulatedNominal].filter((b) => b).length > 1) {
|
||||
modifiers.error.name = 'MaxAndNominal';
|
||||
modifiers.error.message = 'Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`';
|
||||
return [modifiers, args];
|
||||
modifiers.error.message = 'Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`, `simulatedNominal`';
|
||||
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];
|
||||
};
|
||||
|
|
|
@ -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
|
||||
rollCmd = rollCmd.replace(/%20/g, ' ').trim();
|
||||
|
||||
const rawSimNom = parseInt(query.get('sn') ?? '0');
|
||||
const simNom = rawSimNom || 10000;
|
||||
const modifiers: RollModifiers = {
|
||||
noDetails: query.has('nd'),
|
||||
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'),
|
||||
minRoll: query.has('min'),
|
||||
nominalRoll: query.has('n'),
|
||||
simulatedNominal: query.has('sn') ? simNom : 0,
|
||||
gmRoll: query.has('gms'),
|
||||
gms: query.has('gms') ? (query.get('gms') || '').split(',') : [],
|
||||
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(),
|
||||
};
|
||||
|
||||
// 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) => {
|
||||
sendRollRequest({
|
||||
apiRoll: true,
|
||||
|
@ -120,7 +138,7 @@ export const apiRoll = async (query: Map<string, string>, apiUserid: bigint): Pr
|
|||
} else {
|
||||
// Alert API user that they messed up
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue