mirror of
https://github.com/Burn-E99/TheArtificer.git
synced 2026-06-04 00:53:50 -04:00
Add simulated nominal flag
This commit is contained in:
@@ -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,14 +93,16 @@ export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
|
||||
let preFormat = '';
|
||||
let postFormat = '';
|
||||
|
||||
// If the roll contained a crit success or fail, set the formatting around it
|
||||
if (e.containsCrit) {
|
||||
preFormat = `**${preFormat}`;
|
||||
postFormat = `${postFormat}**`;
|
||||
}
|
||||
if (e.containsFail) {
|
||||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
if (!rollRequest.modifiers.simulatedNominal) {
|
||||
// If the roll contained a crit success or fail, set the formatting around it
|
||||
if (e.containsCrit) {
|
||||
preFormat = `**${preFormat}`;
|
||||
postFormat = `${postFormat}**`;
|
||||
}
|
||||
if (e.containsFail) {
|
||||
preFormat = `__${preFormat}`;
|
||||
postFormat = `${postFormat}__`;
|
||||
}
|
||||
}
|
||||
|
||||
// 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}, `;
|
||||
}
|
||||
|
||||
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,56 +38,90 @@ 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;
|
||||
|
||||
// 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,
|
||||
`Setting previous results: topLevel:${topLevel} ${
|
||||
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults
|
||||
} simulatedLoopCount:${simulatedLoopCount}`
|
||||
);
|
||||
|
||||
if (topLevel) {
|
||||
// Handle saving any formatting between dice
|
||||
if (openIdx !== 0) {
|
||||
data.rollPreFormat = cmd.slice(0, openIdx).join('');
|
||||
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)}`);
|
||||
|
||||
// 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
|
||||
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}`);
|
||||
// Handle ConfirmCrit if its on
|
||||
if (topLevel && modifiers.confirmCrit && reduceCountDetails(tempCounts).successful) {
|
||||
loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store results
|
||||
returnData.push(data);
|
||||
countDetails.push(...tempCounts);
|
||||
rollDists.push(...tempDists);
|
||||
// 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)}`);
|
||||
}
|
||||
|
||||
// Handle ConfirmCrit if its on
|
||||
if (topLevel && modifiers.confirmCrit && reduceCountDetails(tempCounts).successful) {
|
||||
loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`);
|
||||
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;
|
||||
}
|
||||
// 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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
src/artigen/dice/dice.d.ts
vendored
1
src/artigen/dice/dice.d.ts
vendored
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user