1
1
mirror of https://github.com/Burn-E99/TheArtificer.git synced 2026-06-04 09:03:50 -04:00

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

@@ -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`;
});

View File

@@ -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}`);
}
}

View File

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

View File

@@ -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];
};