288 lines
10 KiB
TypeScript
288 lines
10 KiB
TypeScript
import {
|
|
log,
|
|
// Log4Deno deps
|
|
LT,
|
|
} from '../../deps.ts';
|
|
|
|
import config from '../../config.ts';
|
|
|
|
import { RollModifiers } from '../mod.d.ts';
|
|
import { ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts';
|
|
import { compareTotalRolls, escapeCharacters } from './rollUtils.ts';
|
|
import { formatRoll } from './rollFormatter.ts';
|
|
import { fullSolver } from './solver.ts';
|
|
|
|
// parseRoll(fullCmd, modifiers)
|
|
// parseRoll handles converting fullCmd into a computer readable format for processing, and finally executes the solving
|
|
export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll => {
|
|
const returnmsg = {
|
|
error: false,
|
|
errorMsg: '',
|
|
errorCode: '',
|
|
line1: '',
|
|
line2: '',
|
|
line3: '',
|
|
};
|
|
|
|
// Whole function lives in a try-catch to allow safe throwing of errors on purpose
|
|
try {
|
|
// Split the fullCmd by the command prefix to allow every roll/math op to be handled individually
|
|
const sepRolls = fullCmd.split(config.prefix);
|
|
|
|
const tempReturnData: ReturnData[] = [];
|
|
|
|
// Loop thru all roll/math ops
|
|
for (let i = 0; i < sepRolls.length; i++) {
|
|
log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRolls[i]}`);
|
|
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the opertaion
|
|
const [tempConf, tempFormat] = sepRolls[i].split(config.postfix);
|
|
|
|
// Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
|
|
const mathConf: (string | number | SolvedStep)[] = <(string | number | SolvedStep)[]> tempConf.replace(/ /g, '').split(/([-+()*/%^])/g);
|
|
|
|
// Verify there are equal numbers of opening and closing parenthesis by adding 1 for opening parens and subtracting 1 for closing parens
|
|
let parenCnt = 0;
|
|
mathConf.forEach((e) => {
|
|
log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`);
|
|
if (e === '(') {
|
|
parenCnt++;
|
|
} else if (e === ')') {
|
|
parenCnt--;
|
|
}
|
|
});
|
|
|
|
// If the parenCnt is not 0, then we do not have balanced parens and need to error out now
|
|
if (parenCnt !== 0) {
|
|
throw new Error('UnbalancedParens');
|
|
}
|
|
|
|
// Evaluate all rolls into stepSolve format and all numbers into floats
|
|
for (let i = 0; i < mathConf.length; i++) {
|
|
log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into mathable items ${JSON.stringify(mathConf[i])}`);
|
|
if (mathConf[i].toString().length === 0) {
|
|
// If its an empty string, get it out of here
|
|
mathConf.splice(i, 1);
|
|
i--;
|
|
} else if (mathConf[i] == parseFloat(mathConf[i].toString())) {
|
|
// If its a number, parse the number out
|
|
mathConf[i] = parseFloat(mathConf[i].toString());
|
|
} else if (/([0123456789])/g.test(mathConf[i].toString())) {
|
|
// If there is a number somewhere in mathconf[i] but there are also other characters preventing it from parsing correctly as a number, it should be a dice roll, parse it as such (if it for some reason is not a dice roll, formatRoll/roll will handle it)
|
|
mathConf[i] = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll);
|
|
} else if (mathConf[i].toString().toLowerCase() === 'e') {
|
|
// If the operand is the constant e, create a SolvedStep for it
|
|
mathConf[i] = {
|
|
total: Math.E,
|
|
details: '*e*',
|
|
containsCrit: false,
|
|
containsFail: false,
|
|
};
|
|
} else if (mathConf[i].toString().toLowerCase() === 'pi' || mathConf[i].toString().toLowerCase() == '𝜋') {
|
|
// If the operand is the constant pi, create a SolvedStep for it
|
|
mathConf[i] = {
|
|
total: Math.PI,
|
|
details: '𝜋',
|
|
containsCrit: false,
|
|
containsFail: false,
|
|
};
|
|
} else if (mathConf[i].toString().toLowerCase() === 'pie') {
|
|
// If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them)
|
|
mathConf[i] = {
|
|
total: Math.PI,
|
|
details: '𝜋',
|
|
containsCrit: false,
|
|
containsFail: false,
|
|
};
|
|
mathConf.splice(i + 1, 0, ...['*', {
|
|
total: Math.E,
|
|
details: '*e*',
|
|
containsCrit: false,
|
|
containsFail: false,
|
|
}]);
|
|
}
|
|
}
|
|
|
|
// Now that mathConf is parsed, send it into the solver
|
|
const tempSolved = fullSolver(mathConf, false);
|
|
|
|
// Push all of this step's solved data into the temp array
|
|
tempReturnData.push({
|
|
rollTotal: tempSolved.total,
|
|
rollPostFormat: tempFormat,
|
|
rollDetails: tempSolved.details,
|
|
containsCrit: tempSolved.containsCrit,
|
|
containsFail: tempSolved.containsFail,
|
|
initConfig: tempConf,
|
|
});
|
|
}
|
|
|
|
// Parsing/Solving done, time to format the output for Discord
|
|
|
|
// Remove any floating spaces from fullCmd
|
|
if (fullCmd[fullCmd.length - 1] === ' ') {
|
|
fullCmd = fullCmd.substring(0, fullCmd.length - 1);
|
|
}
|
|
|
|
// Escape any | and ` chars in fullCmd to prevent spoilers and code blocks from acting up
|
|
fullCmd = escapeCharacters(fullCmd, '|');
|
|
fullCmd = fullCmd.replace(/`/g, '');
|
|
|
|
let line1 = '';
|
|
let line2 = '';
|
|
let line3 = '';
|
|
|
|
// If maximiseRoll or nominalRoll are on, mark the output as such, else use default formatting
|
|
if (modifiers.maxRoll) {
|
|
line1 = ` requested the theoretical maximum of: \`${config.prefix}${fullCmd}\``;
|
|
line2 = 'Theoretical Maximum Results: ';
|
|
} else if (modifiers.nominalRoll) {
|
|
line1 = ` requested the theoretical nominal of: \`${config.prefix}${fullCmd}\``;
|
|
line2 = 'Theoretical Nominal Results: ';
|
|
} else if (modifiers.order === 'a') {
|
|
line1 = ` requested the following rolls to be ordered from least to greatest: \`${config.prefix}${fullCmd}\``;
|
|
line2 = 'Results: ';
|
|
tempReturnData.sort(compareTotalRolls);
|
|
} else if (modifiers.order === 'd') {
|
|
line1 = ` requested the following rolls to be ordered from greatest to least: \`${config.prefix}${fullCmd}\``;
|
|
line2 = 'Results: ';
|
|
tempReturnData.sort(compareTotalRolls);
|
|
tempReturnData.reverse();
|
|
} else {
|
|
line1 = ` rolled: \`${config.prefix}${fullCmd}\``;
|
|
line2 = 'Results: ';
|
|
}
|
|
|
|
// Fill out all of the details and results now
|
|
tempReturnData.forEach((e) => {
|
|
log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`);
|
|
let preFormat = '';
|
|
let postFormat = '';
|
|
|
|
// If the roll containted 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
|
|
if (modifiers.order === '') {
|
|
line2 += `${preFormat}${e.rollTotal}${postFormat}${escapeCharacters(e.rollPostFormat, '|*_~`')}`;
|
|
} else {
|
|
// If order is on, turn rolls into csv without formatting
|
|
line2 += `${preFormat}${e.rollTotal}${postFormat}, `;
|
|
}
|
|
|
|
line2 = line2.replace(/\*\*\*\*/g, '** **').replace(/____/g, '__ __').replace(/~~~~/g, '~~ ~~');
|
|
|
|
line3 += `\`${e.initConfig}\` = ${e.rollDetails} = ${preFormat}${e.rollTotal}${postFormat}\n`;
|
|
});
|
|
|
|
// If order is on, remove trailing ", "
|
|
if (modifiers.order !== '') {
|
|
line2 = line2.substring(0, line2.length - 2);
|
|
}
|
|
|
|
// Fill in the return block
|
|
returnmsg.line1 = line1;
|
|
returnmsg.line2 = line2;
|
|
returnmsg.line3 = line3;
|
|
} catch (solverError) {
|
|
// Welp, the unthinkable happened, we hit an error
|
|
|
|
// Split on _ for the error messages that have more info than just their name
|
|
const [errorName, errorDetails] = solverError.message.split('_');
|
|
|
|
let errorMsg = '';
|
|
|
|
// Translate the errorName to a specific errorMsg
|
|
switch (errorName) {
|
|
case 'YouNeedAD':
|
|
errorMsg = 'Formatting Error: Missing die size and count config';
|
|
break;
|
|
case 'FormattingError':
|
|
errorMsg = 'Formatting Error: Cannot use Keep and Drop at the same time, remove all but one and repeat roll';
|
|
break;
|
|
case 'NoMaxWithDash':
|
|
errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct';
|
|
break;
|
|
case 'UnknownOperation':
|
|
errorMsg = `Error: Unknown Operation ${errorDetails}`;
|
|
if (errorDetails === '-') {
|
|
errorMsg += '\nNote: Negative numbers are not supported';
|
|
} else if (errorDetails === ' ') {
|
|
errorMsg += `\nNote: Every roll must be closed by ${config.postfix}`;
|
|
}
|
|
break;
|
|
case 'NoZerosAllowed':
|
|
errorMsg = 'Formatting Error: ';
|
|
switch (errorDetails) {
|
|
case 'base':
|
|
errorMsg += 'Die Size and Die Count';
|
|
break;
|
|
case 'drop':
|
|
errorMsg += 'Drop (d or dl)';
|
|
break;
|
|
case 'keep':
|
|
errorMsg += 'Keep (k or kh)';
|
|
break;
|
|
case 'dropHigh':
|
|
errorMsg += 'Drop Highest (dh)';
|
|
break;
|
|
case 'keepLow':
|
|
errorMsg += 'Keep Lowest (kl)';
|
|
break;
|
|
case 'reroll':
|
|
errorMsg += 'Reroll (r)';
|
|
break;
|
|
case 'critScore':
|
|
errorMsg += 'Crit Score (cs)';
|
|
break;
|
|
default:
|
|
errorMsg += `Unhandled - ${errorDetails}`;
|
|
break;
|
|
}
|
|
errorMsg += ' cannot be zero';
|
|
break;
|
|
case 'CritScoreMinGtrMax':
|
|
errorMsg = 'Formatting Error: CritScore maximum cannot be greater than minimum, check formatting and flip min/max';
|
|
break;
|
|
case 'MaxLoopsExceeded':
|
|
errorMsg = 'Error: Roll is too complex or reaches infinity';
|
|
break;
|
|
case 'UnbalancedParens':
|
|
errorMsg = 'Formatting Error: At least one of the equations contains unbalanced parenthesis';
|
|
break;
|
|
case 'EMDASNotNumber':
|
|
errorMsg = 'Error: One or more operands is not a number';
|
|
break;
|
|
case 'ConfWhat':
|
|
errorMsg = 'Error: Not all values got processed, please report the command used';
|
|
break;
|
|
case 'OperatorWhat':
|
|
errorMsg = 'Error: Something really broke with the Operator, try again';
|
|
break;
|
|
case 'OperandNaN':
|
|
errorMsg = 'Error: One or more operands reached NaN, check input';
|
|
break;
|
|
case 'UndefinedStep':
|
|
errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input';
|
|
break;
|
|
default:
|
|
log(LT.ERROR, `Undangled Error: ${errorName}, ${errorDetails}`);
|
|
errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`;
|
|
break;
|
|
}
|
|
|
|
// Fill in the return block
|
|
returnmsg.error = true;
|
|
returnmsg.errorCode = solverError.message;
|
|
returnmsg.errorMsg = errorMsg;
|
|
}
|
|
|
|
return returnmsg;
|
|
};
|