Formatting updates, slight reorg on loop control, drop loop count to 1million, use new Log4Deno to get closeLog func, fix logging in workers

This commit is contained in:
Ean Milligan 2025-04-26 22:34:07 -04:00
parent 8f24a3bfae
commit 09e97eabc1
9 changed files with 291 additions and 232 deletions

View File

@ -7,7 +7,7 @@ export const config = {
postfix: ']]', // Postfix for rolling command
limits: {
// Limits for the bot functions
maxLoops: 5000000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk, originally was set to 5 Million before rollWorkers were added, increased to 10 Million since multiple rolls can be handled concurrently
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
},

View File

@ -6,6 +6,7 @@
"jsr:@std/fmt@^1.0.7": "1.0.7",
"jsr:@std/html@^1.0.3": "1.0.3",
"jsr:@std/http@1.0.15": "1.0.15",
"jsr:@std/io@0.225.2": "0.225.2",
"jsr:@std/media-types@^1.1.0": "1.1.0",
"jsr:@std/net@^1.0.4": "1.0.4",
"jsr:@std/path@^1.0.9": "1.0.9",
@ -37,6 +38,9 @@
"jsr:@std/streams"
]
},
"@std/io@0.225.2": {
"integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7"
},
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
},
@ -823,6 +827,12 @@
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/deps.ts": "9a1b2d559fc8c33ae1aeed899aa821f53f9d094e9df40bd4b51b099c58961cd7",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/src/logger.ts": "a1924f1f02b35a7501161349de90b60a3aa329e12f1033fdb212b598542897c4",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.0/deps.ts": "3ab026026d146ca5e7160b16146d5665e45487a62749a7970f8e00c0c934874d",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.0/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.0/src/logger.ts": "78072b8257a25b4e6adc03d5b92d64ef68b215159a732832fe6020bdebce2ec7",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/deps.ts": "3ab026026d146ca5e7160b16146d5665e45487a62749a7970f8e00c0c934874d",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/src/logger.ts": "b3a39724d58102dfbcdcd640a829cbfe1f083065060f68003f9c8fd49fdd658a",
"https://unpkg.com/@evan/wasm@0.0.65/target/zlib/deno.js": "36cd3f1edd2f3a6d6fd4c2376f701c2748338c132703810d4866cfa52b5e7bf9"
}
}

View File

@ -6,6 +6,7 @@ export {
DiscordActivityTypes,
editBotNickname,
editBotStatus,
getMessage,
hasGuildPermissions,
Intents,
sendDirectMessage,
@ -23,6 +24,6 @@ export type { StatusCode } from 'jsr:@std/http@1.0.15';
export { nanoid } from 'https://deno.land/x/nanoid@v3.0.0/mod.ts';
export { initLog, log, LogTypes as LT } from 'https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/mod.ts';
export { initLog, closeLog, log, LogTypes as LT } from 'https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/mod.ts';
export * as is from 'https://deno.land/x/imagescript@1.3.0/mod.ts';

View File

@ -1,6 +1,8 @@
import config from '../config.ts';
import { CountDetails, SolvedRoll } from './solver/solver.d.ts';
import { RollModifiers } from './mod.d.ts';
import { loggingEnabled } from './solver/rollUtils.ts';
import { log, LT } from '../deps.ts';
export const failColor = 0xe71212;
export const warnColor = 0xe38f28;
@ -9,114 +11,137 @@ export const infoColor1 = 0x313bf9;
export const infoColor2 = 0x6805e9;
export const rollingEmbed = {
embeds: [{
color: infoColor1,
title: 'Rolling . . .',
}],
embeds: [
{
color: infoColor1,
title: 'Rolling . . .',
},
],
};
export const generatePing = (time: number) => ({
embeds: [{
color: infoColor1,
title: time === -1 ? 'Ping?' : `Pong! Latency is ${time}ms.`,
}],
embeds: [
{
color: infoColor1,
title: time === -1 ? 'Ping?' : `Pong! Latency is ${time}ms.`,
},
],
});
export const generateReport = (msg: string) => ({
embeds: [{
color: infoColor2,
title: 'USER REPORT:',
description: msg || 'No message',
}],
embeds: [
{
color: infoColor2,
title: 'USER REPORT:',
description: msg || 'No message',
},
],
});
export const generateStats = (guildCount: number, channelCount: number, memberCount: number, rollCount: bigint, utilityCount: bigint, rollRate: number, utilityRate: number) => ({
embeds: [{
color: infoColor2,
title: "The Artificer's Statistics:",
timestamp: new Date().toISOString(),
fields: [
{
name: 'Guilds:',
value: `${guildCount}`,
inline: true,
},
{
name: 'Channels:',
value: `${channelCount}`,
inline: true,
},
{
name: 'Active Members:',
value: `${memberCount}`,
inline: true,
},
{
name: 'Roll Commands:',
value: `${rollCount}
(${rollRate.toFixed(2)} per hour)`,
inline: true,
},
{
name: 'Utility Commands:',
value: `${utilityCount}
(${utilityRate.toFixed(2)} per hour)`,
inline: true,
},
],
}],
export const generateStats = (
guildCount: number,
channelCount: number,
memberCount: number,
rollCount: bigint,
utilityCount: bigint,
rollRate: number,
utilityRate: number
) => ({
embeds: [
{
color: infoColor2,
title: "The Artificer's Statistics:",
timestamp: new Date().toISOString(),
fields: [
{
name: 'Guilds:',
value: `${guildCount}`,
inline: true,
},
{
name: 'Channels:',
value: `${channelCount}`,
inline: true,
},
{
name: 'Active Members:',
value: `${memberCount}`,
inline: true,
},
{
name: 'Roll Commands:',
value: `${rollCount}\n(${rollRate.toFixed(2)} per hour)`,
inline: true,
},
{
name: 'Utility Commands:',
value: `${utilityCount}\n(${utilityRate.toFixed(2)} per hour)`,
inline: true,
},
],
},
],
});
export const generateApiFailed = (args: string) => ({
embeds: [{
color: failColor,
title: `Failed to ${args} API rolls for this guild.`,
description: 'If this issue persists, please report this to the developers.',
}],
embeds: [
{
color: failColor,
title: `Failed to ${args} API rolls for this guild.`,
description: 'If this issue persists, please report this to the developers.',
},
],
});
export const generateApiStatus = (banned: boolean, active: boolean) => {
const apiStatus = active ? 'allowed' : 'blocked from being used';
return {
embeds: [{
color: infoColor1,
title: `The Artificer's API is ${config.api.enable ? 'currently enabled' : 'currently disabled'}.`,
description: banned ? 'API rolls are banned from being used in this guild.\n\nThis will not be reversed.' : `API rolls are ${apiStatus} in this guild.`,
}],
embeds: [
{
color: infoColor1,
title: `The Artificer's API is ${config.api.enable ? 'currently enabled' : 'currently disabled'}.`,
description: banned ? 'API rolls are banned from being used in this guild.\n\nThis will not be reversed.' : `API rolls are ${apiStatus} in this guild.`,
},
],
};
};
export const generateApiSuccess = (args: string) => ({
embeds: [{
color: successColor,
title: `API rolls have successfully been ${args} for this guild.`,
}],
embeds: [
{
color: successColor,
title: `API rolls have successfully been ${args} for this guild.`,
},
],
});
export const generateDMFailed = (user: string) => ({
embeds: [{
color: failColor,
title: `WARNING: ${user} could not be messaged.`,
description: 'If this issue persists, make sure direct messages are allowed from this server.',
}],
embeds: [
{
color: failColor,
title: `WARNING: ${user} could not be messaged.`,
description: 'If this issue persists, make sure direct messages are allowed from this server.',
},
],
});
export const generateApiKeyEmail = (email: string, key: string) => ({
content: `<@${config.api.admin}> A USER HAS REQUESTED AN API KEY`,
embeds: [{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Key',
},
{
name: 'Body:',
value: `Hello Artificer API User,
embeds: [
{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Key',
},
{
name: 'Body:',
value: `Hello Artificer API User,
Welcome aboard The Artificer's API. You can find full details about the API on the GitHub: https://github.com/Burn-E99/TheArtificer
@ -126,27 +151,29 @@ Guard this well, as there is zero tolerance for API abuse.
Welcome aboard,
The Artificer Developer - Ean Milligan`,
},
],
}],
},
],
},
],
});
export const generateApiDeleteEmail = (email: string, deleteCode: string) => ({
content: `<@${config.api.admin}> A USER HAS REQUESTED A DELETE CODE`,
embeds: [{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Delete Code',
},
{
name: 'Body:',
value: `Hello Artificer API User,
embeds: [
{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Delete Code',
},
{
name: 'Body:',
value: `Hello Artificer API User,
I am sorry to see you go. If you would like, please respond to this email detailing what I could have done better.
@ -154,20 +181,25 @@ As requested, here is your delete code: ${deleteCode}
Sorry to see you go,
The Artificer Developer - Ean Milligan`,
},
],
}],
},
],
},
],
});
export const generateRollError = (errorType: string, errorMsg: string) => ({
embeds: [{
color: failColor,
title: 'Roll command encountered the following error:',
fields: [{
name: errorType,
value: `${errorMsg}\n\nPlease try again. If the error is repeated, please report the issue using the \`${config.prefix}report\` command.`,
}],
}],
embeds: [
{
color: failColor,
title: 'Roll command encountered the following error:',
fields: [
{
name: errorType,
value: `${errorMsg}\n\nPlease try again. If the error is repeated, please report the issue using the \`${config.prefix}report\` command.`,
},
],
},
],
});
export const generateCountDetailsEmbed = (counts: CountDetails) => ({
@ -221,8 +253,8 @@ export const generateRollEmbed = async (authorId: bigint, returnDetails: SolvedR
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
blob: await new Blob(['' as BlobPart], { type: 'text' }),
name: 'rollDetails.txt',
},
};
} else {
@ -231,14 +263,12 @@ export const generateRollEmbed = async (authorId: bigint, returnDetails: SolvedR
return {
embed: {
color: infoColor2,
description: `<@${authorId}>${returnDetails.line1}
Results have been messaged to the following GMs: ${modifiers.gms.join(' ')}`,
description: `<@${authorId}>${returnDetails.line1}\n\nResults have been messaged to the following GMs: ${modifiers.gms.join(' ')}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
blob: await new Blob(['' as BlobPart], { type: 'text' }),
name: 'rollDetails.txt',
},
};
} else {
@ -248,65 +278,57 @@ Results have been messaged to the following GMs: ${modifiers.gms.join(' ')}`,
if (!modifiers.superNoDetails) {
if (modifiers.noDetails) {
details = `**Details:**
Suppressed by -nd flag`;
details = `**Details:**\nSuppressed by -nd flag`;
} else {
details = `**Details:**
${modifiers.spoiler}${returnDetails.line3}${modifiers.spoiler}`;
details = `**Details:**\n${modifiers.spoiler}${returnDetails.line3}${modifiers.spoiler}`;
loggingEnabled && log(LT.LOG, `${returnDetails.line3} |&| ${details}`);
}
}
const baseDesc = `<@${authorId}>${returnDetails.line1}
**${line2Details.shift()}:**
${line2Details.join(': ')}`;
const baseDesc = `<@${authorId}>${returnDetails.line1}\n**${line2Details.shift()}:**\n${line2Details.join(': ')}`;
// Embed desc limit is 4096
if (baseDesc.length + details.length < 4090) {
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
description: `${baseDesc}\n\n${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
blob: await new Blob(['' as BlobPart], { type: 'text' }),
name: 'rollDetails.txt',
},
};
} else {
// If its too big, collapse it into a .txt file and send that instead.
const b = await new Blob([`${baseDesc}\n\n${details}` as BlobPart], { 'type': 'text' });
details = 'Details have been ommitted from this message for being over 2000 characters.';
const b = await new Blob([`${baseDesc}\n\n${details}` as BlobPart], { type: 'text' });
details = 'Details have been omitted from this message for being over 4000 characters.';
if (b.size > 8388290) {
details +=
'\n\nFull details could not be attached to this messaged as a \`.txt\` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.';
'\n\nFull details could not be attached to this messaged as a `.txt` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages instead of bundled into one.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
description: `${baseDesc}\n\n${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
blob: await new Blob(['' as BlobPart], { type: 'text' }),
name: 'rollDetails.txt',
},
};
} else {
details += '\n\nFull details have been attached to this messaged as a \`.txt\` file for verification purposes.';
details += '\n\nFull details have been attached to this messaged as a `.txt` file for verification purposes.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
description: `${baseDesc}\n\n${details}`,
},
hasAttachment: true,
attachment: {
'blob': b,
'name': 'rollDetails.txt',
blob: b,
name: 'rollDetails.txt',
},
};
}

View File

@ -16,7 +16,7 @@ import { fullSolver } from './solver.ts';
// 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 operators = ['^', '*', '/', '%', '+', '-', '(', ')'];
const returnmsg = <SolvedRoll> {
const returnmsg = <SolvedRoll>{
error: false,
errorCode: '',
errorMsg: '',
@ -39,23 +39,25 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
const sepRolls = fullCmd.split(config.prefix);
const tempReturnData: ReturnData[] = [];
const tempCountDetails: CountDetails[] = [{
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
}];
const tempCountDetails: CountDetails[] = [
{
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
},
];
// Loop thru all roll/math ops
for (const sepRoll of sepRolls) {
loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Working ${sepRoll}`);
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the opertaion
// Split the current iteration on the command postfix to separate the operation to be parsed and the text formatting after the operation
const [tempConf, tempFormat] = sepRoll.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);
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;
@ -98,7 +100,11 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
containsCrit: false,
containsFail: false,
};
} else if (mathConf[i].toString().toLowerCase() === 'inf' || mathConf[i].toString().toLowerCase() === 'infinity' || mathConf[i].toString().toLowerCase() === '∞') {
} else if (
mathConf[i].toString().toLowerCase() === 'inf' ||
mathConf[i].toString().toLowerCase() === 'infinity' ||
mathConf[i].toString().toLowerCase() === '∞'
) {
// If the operand is the constant Infinity, create a SolvedStep for it
mathConf[i] = {
total: Infinity,
@ -122,12 +128,19 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
containsCrit: false,
containsFail: false,
};
mathConf.splice(i + 1, 0, ...['*', {
total: Math.E,
details: '*e*',
containsCrit: false,
containsFail: false,
}]);
mathConf.splice(
i + 1,
0,
...[
'*',
{
total: Math.E,
details: '*e*',
containsCrit: false,
containsFail: false,
},
]
);
} else if (!operators.includes(mathConf[i].toString())) {
// If nothing else has handled it by now, try it as a roll
const formattedRoll = formatRoll(mathConf[i].toString(), modifiers.maxRoll, modifiers.nominalRoll);
@ -137,10 +150,10 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || mathConf[i - 2] === '(')) {
if (typeof mathConf[i] === 'number') {
mathConf[i] = <number> mathConf[i] * -1;
mathConf[i] = <number>mathConf[i] * -1;
} else {
(<SolvedStep> mathConf[i]).total = (<SolvedStep> mathConf[i]).total * -1;
(<SolvedStep> mathConf[i]).details = `-${(<SolvedStep> mathConf[i]).details}`;
(<SolvedStep>mathConf[i]).total = (<SolvedStep>mathConf[i]).total * -1;
(<SolvedStep>mathConf[i]).details = `-${(<SolvedStep>mathConf[i]).details}`;
}
mathConf.splice(i - 1, 1);
i--;
@ -221,7 +234,10 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
line2 += `${preFormat}${e.rollTotal}${postFormat}, `;
}
line2 = line2.replace(/\*\*\*\*/g, '** **').replace(/____/g, '__ __').replace(/~~~~/g, '~~ ~~');
line2 = line2
.replace(/\*\*\*\*/g, '** **')
.replace(/____/g, '__ __')
.replace(/~~~~/g, '~~ ~~');
line3 += `\`${e.initConfig}\` = ${e.rollDetails} = ${preFormat}${e.rollTotal}${postFormat}\n`;
});
@ -245,7 +261,8 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll
dropped: acc.dropped + cnt.dropped,
exploded: acc.exploded + cnt.exploded,
}));
} catch (solverError) {
} catch (e) {
const solverError = e as Error;
// Welp, the unthinkable happened, we hit an error
// Split on _ for the error messages that have more info than just their name

View File

@ -17,6 +17,7 @@ import { ApiQueuedRoll, DDQueuedRoll, RollModifiers } from '../mod.d.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollEmbed, infoColor2, rollingEmbed } from '../commandUtils.ts';
import stdResp from '../endpoints/stdResponses.ts';
import utils from '../utils.ts';
import { loggingEnabled } from './rollUtils.ts';
let currentWorkers = 0;
const rollQueue: Array<ApiQueuedRoll | DDQueuedRoll> = [];
@ -59,6 +60,7 @@ const handleRollWorker = (rq: ApiQueuedRoll | DDQueuedRoll) => {
rollWorker.addEventListener('message', async (workerMessage) => {
if (workerMessage.data === 'ready') {
loggingEnabled && log(LT.LOG, `Sending roll to worker: ${rq.rollCmd}, ${JSON.stringify(rq.modifiers)}`);
rollWorker.postMessage({
rollCmd: rq.rollCmd,
modifiers: rq.modifiers,
@ -70,9 +72,12 @@ const handleRollWorker = (rq: ApiQueuedRoll | DDQueuedRoll) => {
currentWorkers--;
clearTimeout(workerTimeout);
const returnmsg = workerMessage.data;
loggingEnabled && log(LT.LOG, `Roll came back from worker: ${returnmsg.line1.length} |&| ${returnmsg.line2.length} |&| ${returnmsg.line3.length} `);
loggingEnabled && log(LT.LOG, `Roll came back from worker: ${returnmsg.line1} |&| ${returnmsg.line2} |&| ${returnmsg.line3} `);
const pubEmbedDetails = await generateRollEmbed(rq.apiRoll ? rq.api.userId : rq.dd.message.authorId, returnmsg, rq.modifiers);
const gmEmbedDetails = await generateRollEmbed(rq.apiRoll ? rq.api.userId : rq.dd.message.authorId, returnmsg, gmModifiers);
const countEmbed = generateCountDetailsEmbed(returnmsg.counts);
loggingEnabled && log(LT.LOG, `Embeds are generated: ${JSON.stringify(pubEmbedDetails)} |&| ${JSON.stringify(gmEmbedDetails)}`);
// If there was an error, report it to the user in hopes that they can determine what they did wrong
if (returnmsg.error) {

View File

@ -6,7 +6,7 @@ import {
import { ReturnData, RollSet } from './solver.d.ts';
export const loggingEnabled = false;
export const loggingEnabled = true;
// genRoll(size) returns number
// genRoll rolls a die of size size and returns the result
@ -15,7 +15,7 @@ export const genRoll = (size: number, maximiseRoll: boolean, nominalRoll: boolea
return size;
} else {
// Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result
return nominalRoll ? ((size / 2) + 0.5) : Math.floor((Math.random() * size) + 1);
return nominalRoll ? size / 2 + 0.5 : Math.floor(Math.random() * size + 1);
}
};

View File

@ -1,10 +1,15 @@
import { initLog, closeLog } from '../../deps.ts';
import { DEBUG } from '../../flags.ts';
import { parseRoll } from './parser.ts';
import { loggingEnabled } from './rollUtils.ts';
loggingEnabled && initLog('logs/worker', DEBUG);
// Alert rollQueue that this worker is ready
self.postMessage('ready');
// Handle the roll
self.onmessage = async (e: any) => {
self.onmessage = async (e) => {
const payload = e.data;
const returnmsg = parseRoll(payload.rollCmd, payload.modifiers) || {
error: true,
@ -23,5 +28,6 @@ self.onmessage = async (e: any) => {
},
};
self.postMessage(returnmsg);
loggingEnabled && (await closeLog());
self.close();
};

View File

@ -12,10 +12,10 @@ import { compareOrigidx, compareRolls, genFateRoll, genRoll, loggingEnabled } fr
// roll parses and executes the rollStr, if needed it will also make the roll the maximum or average
export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolean): RollSet[] => {
/* Roll Capabilities
* Deciphers and rolls a single dice roll set
*
* Check the README.md of this project for details on the roll options. I gave up trying to keep three places updated at once.
*/
* Deciphers and rolls a single dice roll set
*
* Check the README.md of this project for details on the roll options. I gave up trying to keep three places updated at once.
*/
// Make entire roll lowercase for ease of parsing
rollStr = rollStr.toLowerCase();
@ -47,22 +47,22 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
reroll: {
on: false,
once: false,
nums: <number[]> [],
nums: <number[]>[],
},
critScore: {
on: false,
range: <number[]> [],
range: <number[]>[],
},
critFail: {
on: false,
range: <number[]> [],
range: <number[]>[],
},
exploding: {
on: false,
once: false,
compounding: false,
penetrating: false,
nums: <number[]> [],
nums: <number[]>[],
},
};
@ -146,7 +146,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Loop until all remaining args are parsed
while (remains.length > 0) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing remains ${remains}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing remains ${remains}`);
// Find the next number in the remains to be able to cut out the rule name
let afterSepIdx = remains.search(/\d/);
if (afterSepIdx < 0) {
@ -190,7 +190,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
case 'ro':
case 'ro=':
rollConf.reroll.once = true;
// falls through as ro/ro= functions the same as r/r= in this context
// falls through as ro/ro= functions the same as r/r= in this context
case 'r':
case 'r=':
// Configure Reroll (this can happen multiple times)
@ -199,23 +199,23 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
break;
case 'ro>':
rollConf.reroll.once = true;
// falls through as ro> functions the same as r> in this context
// falls through as ro> functions the same as r> in this context
case 'r>':
// Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing r> ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing r> ${i}`);
rollConf.reroll.nums.push(i);
}
break;
case 'ro<':
rollConf.reroll.once = true;
// falls through as ro< functions the same as r< in this context
// falls through as ro< functions the same as r< in this context
case 'r<':
// Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true;
for (let i = 1; i <= tNum; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing r< ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing r< ${i}`);
rollConf.reroll.nums.push(i);
}
break;
@ -229,7 +229,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing cs> ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing cs> ${i}`);
rollConf.critScore.range.push(i);
}
break;
@ -237,7 +237,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
for (let i = 0; i <= tNum; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing cs< ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing cs< ${i}`);
rollConf.critScore.range.push(i);
}
break;
@ -251,7 +251,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing cf> ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing cf> ${i}`);
rollConf.critFail.range.push(i);
}
break;
@ -259,7 +259,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
for (let i = 0; i <= tNum; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing cf< ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing cf< ${i}`);
rollConf.critFail.range.push(i);
}
break;
@ -292,7 +292,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing !> ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing !> ${i}`);
rollConf.exploding.nums.push(i);
}
break;
@ -303,7 +303,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true;
for (let i = 1; i <= tNum; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Parsing !< ${i}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsing !< ${i}`);
rollConf.exploding.nums.push(i);
}
break;
@ -349,7 +349,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Since only one drop or keep option can be active, count how many are active to throw the right error
let dkdkCnt = 0;
[rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach((e) => {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Checking if drop/keep is on ${e}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Checking if drop/keep is on ${e}`);
if (e) {
dkdkCnt++;
}
@ -376,27 +376,27 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Roll the roll
const rollSet = [];
/* Roll will contain objects of the following format:
* {
* origidx: 0,
* roll: 0,
* dropped: false,
* rerolled: false,
* exploding: false,
* critHit: false,
* critFail: false
* }
*
* Each of these is defined as following:
* {
* origidx: The original index of the roll
* roll: The resulting roll on this die in the set
* dropped: This die is to be dropped as it was one of the dy lowest dice
* rerolled: This die has been rerolled as it matched rz, it is replaced by the very next die in the set
* exploding: This die was rolled as the previous die exploded (was a crit hit)
* critHit: This die matched csq[-u], max die value used if cs not used
* critFail: This die rolled a nat 1, a critical failure
* }
*/
* {
* origidx: 0,
* roll: 0,
* dropped: false,
* rerolled: false,
* exploding: false,
* critHit: false,
* critFail: false
* }
*
* Each of these is defined as following:
* {
* origidx: The original index of the roll
* roll: The resulting roll on this die in the set
* dropped: This die is to be dropped as it was one of the dy lowest dice
* rerolled: This die has been rerolled as it matched rz, it is replaced by the very next die in the set
* exploding: This die was rolled as the previous die exploded (was a crit hit)
* critHit: This die matched csq[-u], max die value used if cs not used
* critFail: This die rolled a nat 1, a critical failure
* }
*/
// Initialize a templet rollSet to copy multiple times
const templateRoll: RollSet = {
@ -415,8 +415,9 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Initial rolling, not handling reroll or exploding here
for (let i = 0; i < rollConf.dieCount; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`);
loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`);
// If loopCount gets too high, stop trying to calculate infinity
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
@ -447,21 +448,21 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Push the newly created roll and loop again
rollSet.push(rolling);
loopCount++;
}
// If needed, handle rerolling and exploding dice now
if (rollConf.reroll.on || rollConf.exploding.on) {
for (let i = 0; i < rollSet.length; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`);
loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`);
// If loopCount gets too high, stop trying to calculate infinity
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
// If we need to reroll this roll, flag its been replaced and...
// This big boolean statement first checks if reroll is on, if the roll is within the reroll range, and finally if ro is ON, make sure we haven't already rerolled the roll
if (rollConf.reroll.on && rollConf.reroll.nums.indexOf(rollSet[i].roll) >= 0 && (!rollConf.reroll.once || !rollSet[i ? (i - 1) : i].rerolled)) {
if (rollConf.reroll.on && rollConf.reroll.nums.indexOf(rollSet[i].roll) >= 0 && (!rollConf.reroll.once || !rollSet[i ? i - 1 : i].rerolled)) {
rollSet[i].rerolled = true;
// Copy the template to fill out for this iteration
@ -485,7 +486,9 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Slot this new roll in after the current iteration so it can be processed in the next loop
rollSet.splice(i + 1, 0, newReroll);
} else if (
rollConf.exploding.on && !rollSet[i].rerolled && (rollConf.exploding.nums.length ? rollConf.exploding.nums.indexOf(rollSet[i].roll) >= 0 : rollSet[i].critHit) &&
rollConf.exploding.on &&
!rollSet[i].rerolled &&
(rollConf.exploding.nums.length ? rollConf.exploding.nums.indexOf(rollSet[i].roll) >= 0 : rollSet[i].critHit) &&
(!rollConf.exploding.once || !rollSet[i].exploding)
) {
// If we have exploding.nums set, use those to determine the exploding range, and make sure if !o is on, make sure we don't repeatedly explode
@ -514,16 +517,15 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Slot this new roll in after the current iteration so it can be processed in the next loop
rollSet.splice(i + 1, 0, newExplodingRoll);
}
loopCount++;
}
}
// If penetrating is on, do the decrements
if (rollConf.exploding.penetrating) {
for (const penRoll of rollSet) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Handling penetrating explosions ${JSON.stringify(penRoll)}`);
loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Handling penetrating explosions ${JSON.stringify(penRoll)}`);
// If loopCount gets too high, stop trying to calculate infinity
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
@ -532,16 +534,15 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
if (penRoll.exploding) {
penRoll.roll--;
}
loopCount++;
}
}
// Handle compounding explosions
if (rollConf.exploding.compounding) {
for (let i = 0; i < rollSet.length; i++) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Handling compounding explosions ${JSON.stringify(rollSet[i])}`);
loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Handling compounding explosions ${JSON.stringify(rollSet[i])}`);
// If loopCount gets too high, stop trying to calculate infinity
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
@ -555,8 +556,6 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
rollSet.splice(i, 1);
i--;
}
loopCount++;
}
}
@ -567,18 +566,17 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
if (rollConf.reroll.on) {
for (let j = 0; j < rollSet.length; j++) {
// If loopCount gets too high, stop trying to calculate infinity
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`);
loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`);
rollSet[j].origidx = j;
if (rollSet[j].rerolled) {
rerollCount++;
}
loopCount++;
}
}
@ -621,18 +619,18 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
let i = 0;
while (dropCount > 0 && i < rollSet.length) {
// If loopCount gets too high, stop trying to calculate infinity
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
// Skip all rolls that were rerolled
if (!rollSet[i].rerolled) {
rollSet[i].dropped = true;
dropCount--;
}
i++;
loopCount++;
}
// Finally, return the rollSet to its original order
@ -646,7 +644,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Sum up all rolls
for (const ovaRoll of rollSet) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | incrementing rollVals for ${ovaRoll}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | incrementing rollVals for ${ovaRoll}`);
rollVals[ovaRoll.roll - 1] += ovaRoll.roll;
}
@ -655,7 +653,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Drop all dice that are not a part of the max
for (const ovaRoll of rollSet) {
loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | checking if this roll should be dropped ${ovaRoll.roll} | to keep: ${maxRoll}`);
loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | checking if this roll should be dropped ${ovaRoll.roll} | to keep: ${maxRoll}`);
if (ovaRoll.roll !== maxRoll) {
ovaRoll.dropped = true;
ovaRoll.critFail = false;