From c7c974c395e625a88135e3cb8736b251fcf1e52c Mon Sep 17 00:00:00 2001 From: Ean Milligan Date: Sat, 26 Apr 2025 13:23:37 -0400 Subject: [PATCH] deno fmt --- config.example.ts | 128 +-- mod.ts | 10 +- src/commandUtils.ts | 470 +++++----- src/commands/_index.ts | 36 +- src/commands/apiCmd.ts | 3 +- src/commands/apiCmd/_index.ts | 10 +- src/commands/apiCmd/apiHelp.ts | 106 +-- src/commands/auditCmd/_index.ts | 6 +- src/commands/auditCmd/auditGuilds.ts | 150 ++-- src/commands/auditCmd/auditHelp.ts | 50 +- src/commands/help.ts | 3 +- src/commands/optIn.ts | 2 +- src/commands/optOut.ts | 2 +- src/commands/privacy.ts | 3 +- src/commands/roll.ts | 16 +- src/commands/roll/_index.ts | 2 +- src/commands/roll/getModifiers.ts | 12 +- src/commands/rollDecorators.ts | 3 +- src/commands/rollHelp.ts | 30 +- src/commands/stats.ts | 4 +- src/commonEmbeds.ts | 8 +- src/endpoints/_index.ts | 36 +- src/endpoints/deletes/apiKeyDelete.ts | 2 +- src/endpoints/gets/apiRoll.ts | 20 +- src/endpoints/gets/heatmapPng.ts | 2 +- src/endpoints/stdResponses.ts | 7 +- src/intervals.ts | 2 +- src/mod.d.ts | 62 +- src/solver/_index.ts | 2 +- src/solver/counter.ts | 34 +- src/solver/parser.ts | 598 ++++++------- src/solver/rollFormatter.ts | 142 +-- src/solver/rollQueue.ts | 20 +- src/solver/rollUtils.ts | 88 +- src/solver/rollWorker.ts | 38 +- src/solver/roller.ts | 1150 ++++++++++++------------- src/solver/solver.d.ts | 142 +-- src/solver/solver.ts | 340 ++++---- src/utils.ts | 142 +-- 39 files changed, 1938 insertions(+), 1943 deletions(-) diff --git a/config.example.ts b/config.example.ts index 09e0e2f..c301030 100644 --- a/config.example.ts +++ b/config.example.ts @@ -1,63 +1,73 @@ export const config = { - 'name': 'The Artificer', // Name of the bot - 'version': '2.1.3', // Version of the bot - 'token': 'the_bot_token', // Discord API Token for this bot - 'localtoken': 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" - 'prefix': '[[', // Prefix for all commands - '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 - '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 - }, - 'api': { // Setting for the built-in API - 'enable': false, // Leave this off if you have no intention of using this/supporting it - 'publicDomain': 'http://example.com/', // Public domain that the API is behind, should end with a / - 'port': 8080, // Port for the API to listen on - 'supportURL': 'your_support_url_for_api_abuse', // Fill this in with the way you wish to be contacted when somebody needs to report API key abuse - 'rateLimitTime': 10000, // Time range for how often the API rate limits will be lifted (time in ms) - 'rateLimitCnt': 10, // Amount of requests that can be made (successful or not) during above time range before getting rate limited - 'admin': 0n, // Discord user ID of the bot admin, this user will be the user that can ban/unban user/channel combos and API keys - 'adminKey': 'your_25char_api_token', // API Key generated by nanoid that is 25 char long, this gets pre-populated into all_keys - 'email': 0n, // Temporary set up for email, this will be adjusted to an actual email using deno-smtp in the future. - }, - 'db': { // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot - 'host': '', // IP address for the db, usually localhost - 'localhost': '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment - 'port': 3306, // Port for the db - 'username': '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privalages - 'password': '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box - 'name': '', // Name of the database Schema to use for the bot - }, - 'logRolls': false, // Enables logging of roll commands, this should be left disabled for privacy, but exists to allow verification of rolls before deployment, all API rolls will always be logged no matter what this is set to - 'logChannel': 0n, // Discord channel ID where the bot should put startup messages and other error messages needed - 'reportChannel': 0n, // Discord channel ID where reports will be sent when using the built-in report command - 'devServer': 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts - 'emojis': [ // Array of objects containing all emojis that the bot can send on your behalf, empty this array if you don't want any of them - { // Emoji object, duplicate for each emoji - 'name': 'emoji_name', // Name of emoji in discord - 'aliases': ['alias_1', 'alias_2', 'alias_n'], // Commands that will activate this emoji - 'id': 'the_emoji_id', // Discord emoji ID for this emoji - 'animated': false, // Tells the bot this emoji is animated so it sends correctly - 'deleteSender': false, // Tells the bot to attempt to delete the sender's message after sending the emoji - }, - ], - 'botLists': [ // Array of objects containing all bot lists that stats should be posted to - { // Bot List object, duplicate for each bot list - 'name': 'Bot List Name', // Name of bot list, not used - 'enabled': true, // Should statistics be posted to this list? - 'apiUrl': 'https://example.com/api/bots/?{bot_id}/stats', // API URL, use ?{bot_id} in place of the bot id so that it can be dynamically replaced - 'headers': [ // Array of headers that need to be added to the request - { // Header Object, duplicate for every header needed - 'header': 'header_name', // Name of header needed, usually Authorization is needed - 'value': 'header_value', // Value for the header - }, - ], - 'body': { // Data payload to send to the bot list, will be turned into a string and any ?{} will be replaced with the required value, currently only has ?{server_count} - 'param_name': '?{param_value}', // Add more params as needed - }, - }, - ], + name: 'The Artificer', // Name of the bot + version: '2.1.3', // Version of the bot + token: 'the_bot_token', // Discord API Token for this bot + localtoken: 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" + prefix: '[[', // Prefix for all commands + 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 + 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 + }, + api: { + // Setting for the built-in API + enable: false, // Leave this off if you have no intention of using this/supporting it + publicDomain: 'http://example.com/', // Public domain that the API is behind, should end with a / + port: 8080, // Port for the API to listen on + supportURL: 'your_support_url_for_api_abuse', // Fill this in with the way you wish to be contacted when somebody needs to report API key abuse + rateLimitTime: 10000, // Time range for how often the API rate limits will be lifted (time in ms) + rateLimitCnt: 10, // Amount of requests that can be made (successful or not) during above time range before getting rate limited + admin: 0n, // Discord user ID of the bot admin, this user will be the user that can ban/unban user/channel combos and API keys + adminKey: 'your_25char_api_token', // API Key generated by nanoid that is 25 char long, this gets pre-populated into all_keys + email: 0n, // Temporary set up for email, this will be adjusted to an actual email using deno-smtp in the future. + }, + db: { + // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot + host: '', // IP address for the db, usually localhost + localhost: '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment + port: 3306, // Port for the db + username: '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privalages + password: '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box + name: '', // Name of the database Schema to use for the bot + }, + logRolls: false, // Enables logging of roll commands, this should be left disabled for privacy, but exists to allow verification of rolls before deployment, all API rolls will always be logged no matter what this is set to + logChannel: 0n, // Discord channel ID where the bot should put startup messages and other error messages needed + reportChannel: 0n, // Discord channel ID where reports will be sent when using the built-in report command + devServer: 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts + emojis: [ + // Array of objects containing all emojis that the bot can send on your behalf, empty this array if you don't want any of them + { + // Emoji object, duplicate for each emoji + name: 'emoji_name', // Name of emoji in discord + aliases: ['alias_1', 'alias_2', 'alias_n'], // Commands that will activate this emoji + id: 'the_emoji_id', // Discord emoji ID for this emoji + animated: false, // Tells the bot this emoji is animated so it sends correctly + deleteSender: false, // Tells the bot to attempt to delete the sender's message after sending the emoji + }, + ], + botLists: [ + // Array of objects containing all bot lists that stats should be posted to + { + // Bot List object, duplicate for each bot list + name: 'Bot List Name', // Name of bot list, not used + enabled: true, // Should statistics be posted to this list? + apiUrl: 'https://example.com/api/bots/?{bot_id}/stats', // API URL, use ?{bot_id} in place of the bot id so that it can be dynamically replaced + headers: [ + // Array of headers that need to be added to the request + { + // Header Object, duplicate for every header needed + header: 'header_name', // Name of header needed, usually Authorization is needed + value: 'header_value', // Value for the header + }, + ], + body: { + // Data payload to send to the bot list, will be turned into a string and any ?{} will be replaced with the required value, currently only has ?{server_count} + param_name: '?{param_value}', // Add more params as needed + }, + }, + ], }; export default config; diff --git a/mod.ts b/mod.ts index 3bf89c9..02e0b34 100644 --- a/mod.ts +++ b/mod.ts @@ -74,12 +74,10 @@ startBot({ }, 30000); // Interval to update bot list stats every 24 hours - LOCALMODE - ? log(LT.INFO, 'updateListStatistics not running') - : setInterval(() => { - log(LT.LOG, 'Updating all bot lists statistics'); - intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size); - }, 86400000); + LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : setInterval(() => { + log(LT.LOG, 'Updating all bot lists statistics'); + intervals.updateListStatistics(botId, cache.guilds.size + cache.dispatchedGuildIds.size); + }, 86400000); // Interval to update hourlyRates every hour setInterval(() => { diff --git a/src/commandUtils.ts b/src/commandUtils.ts index cbc62e6..a0459c8 100644 --- a/src/commandUtils.ts +++ b/src/commandUtils.ts @@ -9,114 +9,114 @@ 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} + 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} + inline: true, + }, + { + name: 'Utility Commands:', + value: `${utilityCount} (${utilityRate.toFixed(2)} per hour)`, - inline: true, - }, - ], - }], + 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.`, - }], - }; + 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.`, + }], + }; }; 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, + 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, 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 +126,27 @@ 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, + 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, I am sorry to see you go. If you would like, please respond to this email detailing what I could have done better. @@ -154,163 +154,163 @@ 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) => ({ - color: infoColor1, - title: 'Roll Count Details:', - fields: [ - { - name: 'Total Rolls:', - value: `${counts.total}`, - inline: true, - }, - { - name: 'Successful Rolls:', - value: `${counts.successful}`, - inline: true, - }, - { - name: 'Failed Rolls:', - value: `${counts.failed}`, - inline: true, - }, - { - name: 'Rerolled Dice:', - value: `${counts.rerolled}`, - inline: true, - }, - { - name: 'Dropped Dice:', - value: `${counts.dropped}`, - inline: true, - }, - { - name: 'Exploded Dice:', - value: `${counts.exploded}`, - inline: true, - }, - ], + color: infoColor1, + title: 'Roll Count Details:', + fields: [ + { + name: 'Total Rolls:', + value: `${counts.total}`, + inline: true, + }, + { + name: 'Successful Rolls:', + value: `${counts.successful}`, + inline: true, + }, + { + name: 'Failed Rolls:', + value: `${counts.failed}`, + inline: true, + }, + { + name: 'Rerolled Dice:', + value: `${counts.rerolled}`, + inline: true, + }, + { + name: 'Dropped Dice:', + value: `${counts.dropped}`, + inline: true, + }, + { + name: 'Exploded Dice:', + value: `${counts.exploded}`, + inline: true, + }, + ], }); export const generateRollEmbed = async (authorId: bigint, returnDetails: SolvedRoll, modifiers: RollModifiers) => { - if (returnDetails.error) { - // Roll had an error, send error embed - return { - embed: { - color: failColor, - title: 'Roll failed:', - description: `${returnDetails.errorMsg}`, - footer: { - text: `Code: ${returnDetails.errorCode}`, - }, - }, - hasAttachment: false, - attachment: { - 'blob': await new Blob(['' as BlobPart], { 'type': 'text' }), - 'name': 'rollDetails.txt', - }, - }; - } else { - if (modifiers.gmRoll) { - // Roll is a GM Roll, send this in the pub channel (this funciton will be ran again to get details for the GMs) - return { - embed: { - color: infoColor2, - description: `<@${authorId}>${returnDetails.line1} + if (returnDetails.error) { + // Roll had an error, send error embed + return { + embed: { + color: failColor, + title: 'Roll failed:', + description: `${returnDetails.errorMsg}`, + footer: { + text: `Code: ${returnDetails.errorCode}`, + }, + }, + hasAttachment: false, + attachment: { + 'blob': await new Blob(['' as BlobPart], { 'type': 'text' }), + 'name': 'rollDetails.txt', + }, + }; + } else { + if (modifiers.gmRoll) { + // Roll is a GM Roll, send this in the pub channel (this funciton will be ran again to get details for the GMs) + return { + embed: { + color: infoColor2, + description: `<@${authorId}>${returnDetails.line1} Results have been messaged to the following GMs: ${modifiers.gms.join(' ')}`, - }, - hasAttachment: false, - attachment: { - 'blob': await new Blob(['' as BlobPart], { 'type': 'text' }), - 'name': 'rollDetails.txt', - }, - }; - } else { - // Roll is normal, make normal embed - const line2Details = returnDetails.line2.split(': '); - let details = ''; + }, + hasAttachment: false, + attachment: { + 'blob': await new Blob(['' as BlobPart], { 'type': 'text' }), + 'name': 'rollDetails.txt', + }, + }; + } else { + // Roll is normal, make normal embed + const line2Details = returnDetails.line2.split(': '); + let details = ''; - if (!modifiers.superNoDetails) { - if (modifiers.noDetails) { - details = `**Details:** + if (!modifiers.superNoDetails) { + if (modifiers.noDetails) { + details = `**Details:** Suppressed by -nd flag`; - } else { - details = `**Details:** + } else { + details = `**Details:** ${modifiers.spoiler}${returnDetails.line3}${modifiers.spoiler}`; - } - } + } + } - const baseDesc = `<@${authorId}>${returnDetails.line1} + const baseDesc = `<@${authorId}>${returnDetails.line1} **${line2Details.shift()}:** ${line2Details.join(': ')}`; - if (baseDesc.length + details.length < 4090) { - return { - embed: { - color: infoColor2, - description: `${baseDesc} + if (baseDesc.length + details.length < 4090) { + return { + embed: { + color: infoColor2, + description: `${baseDesc} ${details}`, - }, - hasAttachment: false, - attachment: { - '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.'; - 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.'; - return { - embed: { - color: infoColor2, - description: `${baseDesc} + }, + hasAttachment: false, + attachment: { + '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.'; + 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.'; + return { + embed: { + color: infoColor2, + description: `${baseDesc} ${details}`, - }, - hasAttachment: false, - attachment: { - '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.'; - return { - embed: { - color: infoColor2, - description: `${baseDesc} + }, + hasAttachment: false, + attachment: { + '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.'; + return { + embed: { + color: infoColor2, + description: `${baseDesc} ${details}`, - }, - hasAttachment: true, - attachment: { - 'blob': b, - 'name': 'rollDetails.txt', - }, - }; - } - } - } - } + }, + hasAttachment: true, + attachment: { + 'blob': b, + 'name': 'rollDetails.txt', + }, + }; + } + } + } + } }; diff --git a/src/commands/_index.ts b/src/commands/_index.ts index 2395f4e..4b4df96 100644 --- a/src/commands/_index.ts +++ b/src/commands/_index.ts @@ -18,22 +18,22 @@ import { optOut } from './optOut.ts'; import { optIn } from './optIn.ts'; export default { - ping, - rip, - rollHelp, - rollDecorators, - help, - info, - privacy, - version, - report, - stats, - api, - emoji, - roll, - handleMentions, - audit, - heatmap, - optOut, - optIn, + ping, + rip, + rollHelp, + rollDecorators, + help, + info, + privacy, + version, + report, + stats, + api, + emoji, + roll, + handleMentions, + audit, + heatmap, + optOut, + optIn, }; diff --git a/src/commands/apiCmd.ts b/src/commands/apiCmd.ts index 2b556d2..3a1b9e0 100644 --- a/src/commands/apiCmd.ts +++ b/src/commands/apiCmd.ts @@ -74,8 +74,7 @@ export const api = async (message: DiscordenoMessage, args: string[]) => { { color: failColor, title: 'API commands are powerful and can only be used by guild Owners and Admins.', - description: - 'For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer).', + description: 'For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer).', }, ], }) diff --git a/src/commands/apiCmd/_index.ts b/src/commands/apiCmd/_index.ts index fde6a3d..ea2d14e 100644 --- a/src/commands/apiCmd/_index.ts +++ b/src/commands/apiCmd/_index.ts @@ -5,9 +5,9 @@ import { status } from './status.ts'; import { showHideWarn } from './showHideWarn.ts'; export default { - help, - allowBlock, - deleteGuild, - status, - showHideWarn, + help, + allowBlock, + deleteGuild, + status, + showHideWarn, }; diff --git a/src/commands/apiCmd/apiHelp.ts b/src/commands/apiCmd/apiHelp.ts index afcefc4..17b39ce 100644 --- a/src/commands/apiCmd/apiHelp.ts +++ b/src/commands/apiCmd/apiHelp.ts @@ -1,65 +1,65 @@ import config from '../../../config.ts'; import { - // Discordeno deps - DiscordenoMessage, + // Discordeno deps + DiscordenoMessage, } from '../../../deps.ts'; import { infoColor1, infoColor2 } from '../../commandUtils.ts'; import utils from '../../utils.ts'; export const help = (message: DiscordenoMessage) => { - message.send({ - embeds: [ - { - color: infoColor2, - title: 'The Artificer\'s API Details:', - description: - `The Artificer has a built in API that allows user to roll dice into Discord using third party programs. By default, API rolls are blocked from being sent in your guild. The API warning is also enabled by default. These commands may only be used by the Owner or Admins of your guild. + message.send({ + embeds: [ + { + color: infoColor2, + title: "The Artificer's API Details:", + description: + `The Artificer has a built in API that allows user to roll dice into Discord using third party programs. By default, API rolls are blocked from being sent in your guild. The API warning is also enabled by default. These commands may only be used by the Owner or Admins of your guild. For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer). You may enable and disable the API rolls for your guild as needed.`, - }, - { - color: infoColor1, - title: 'Available API Commands:', - fields: [ - { - name: `\`${config.prefix}api help\``, - value: 'This command', - inline: true, - }, - { - name: `\`${config.prefix}api status\``, - value: 'Shows the current status of the API for the channel this was run in', - inline: true, - }, - { - name: `\`${config.prefix}api allow/enable\``, - value: 'Allows API Rolls to be sent to the channel this was run in', - inline: true, - }, - { - name: `\`${config.prefix}api block/disable\``, - value: 'Blocks API Rolls from being sent to the channel this was run in', - inline: true, - }, - { - name: `\`${config.prefix}api delete\``, - value: 'Deletes this channel\'s settings from The Artificer\'s database', - inline: true, - }, - { - name: `\`${config.prefix}api show-warn\``, - value: 'Shows the API warning on all rolls sent to the channel this was run in', - inline: true, - }, - { - name: `\`${config.prefix}api hide-warn\``, - value: 'Hides the API warning on all rolls sent to the channel this was run in', - inline: true, - }, - ], - }, - ], - }).catch((e: Error) => utils.commonLoggers.messageSendError('apiHelp.ts:67', message, e)); + }, + { + color: infoColor1, + title: 'Available API Commands:', + fields: [ + { + name: `\`${config.prefix}api help\``, + value: 'This command', + inline: true, + }, + { + name: `\`${config.prefix}api status\``, + value: 'Shows the current status of the API for the channel this was run in', + inline: true, + }, + { + name: `\`${config.prefix}api allow/enable\``, + value: 'Allows API Rolls to be sent to the channel this was run in', + inline: true, + }, + { + name: `\`${config.prefix}api block/disable\``, + value: 'Blocks API Rolls from being sent to the channel this was run in', + inline: true, + }, + { + name: `\`${config.prefix}api delete\``, + value: "Deletes this channel's settings from The Artificer's database", + inline: true, + }, + { + name: `\`${config.prefix}api show-warn\``, + value: 'Shows the API warning on all rolls sent to the channel this was run in', + inline: true, + }, + { + name: `\`${config.prefix}api hide-warn\``, + value: 'Hides the API warning on all rolls sent to the channel this was run in', + inline: true, + }, + ], + }, + ], + }).catch((e: Error) => utils.commonLoggers.messageSendError('apiHelp.ts:67', message, e)); }; diff --git a/src/commands/auditCmd/_index.ts b/src/commands/auditCmd/_index.ts index 8142da6..42291da 100644 --- a/src/commands/auditCmd/_index.ts +++ b/src/commands/auditCmd/_index.ts @@ -3,7 +3,7 @@ import { auditDB } from './auditDB.ts'; import { auditGuilds } from './auditGuilds.ts'; export default { - auditHelp, - auditDB, - auditGuilds, + auditHelp, + auditDB, + auditGuilds, }; diff --git a/src/commands/auditCmd/auditGuilds.ts b/src/commands/auditCmd/auditGuilds.ts index 9db6fc3..c9393ad 100644 --- a/src/commands/auditCmd/auditGuilds.ts +++ b/src/commands/auditCmd/auditGuilds.ts @@ -1,94 +1,94 @@ import config from '../../../config.ts'; import { - // Discordeno deps - cache, - cacheHandlers, - DiscordenoMessage, + // Discordeno deps + cache, + cacheHandlers, + DiscordenoMessage, } from '../../../deps.ts'; import { infoColor2 } from '../../commandUtils.ts'; import utils from '../../utils.ts'; export const auditGuilds = async (message: DiscordenoMessage) => { - const cachedGuilds = await cacheHandlers.size('guilds'); - let totalCount = 0; - let realCount = 0; - let botsCount = 0; + const cachedGuilds = await cacheHandlers.size('guilds'); + let totalCount = 0; + let realCount = 0; + let botsCount = 0; - let auditText = ''; + let auditText = ''; - cache.guilds.forEach((guild) => { - totalCount += guild.memberCount; - let localBotCount = 0; - let localRealCount = 0; - guild.members.forEach((member) => { - if (member.bot) { - botsCount++; - localBotCount++; - } else { - realCount++; - localRealCount++; - } - }); + cache.guilds.forEach((guild) => { + totalCount += guild.memberCount; + let localBotCount = 0; + let localRealCount = 0; + guild.members.forEach((member) => { + if (member.bot) { + botsCount++; + localBotCount++; + } else { + realCount++; + localRealCount++; + } + }); - auditText += `Guild: ${guild.name} (${guild.id}) + auditText += `Guild: ${guild.name} (${guild.id}) Owner: ${guild.owner?.username}#${guild.owner?.discriminator} (${guild.ownerId}) Tot mem: ${guild.memberCount} | Real: ${localRealCount} | Bot: ${localBotCount} `; - }); + }); - const b = await new Blob([auditText as BlobPart], { 'type': 'text' }); - const tooBig = await new Blob(['tooBig' as BlobPart], { 'type': 'text' }); + const b = await new Blob([auditText as BlobPart], { 'type': 'text' }); + const tooBig = await new Blob(['tooBig' as BlobPart], { 'type': 'text' }); - message.send({ - embeds: [{ - color: infoColor2, - title: 'Guilds Audit', - description: `Shows details of the guilds that ${config.name} serves. + message.send({ + embeds: [{ + color: infoColor2, + title: 'Guilds Audit', + description: `Shows details of the guilds that ${config.name} serves. Please see attached file for audit details on cached guilds and members.`, - timestamp: new Date().toISOString(), - fields: [ - { - name: 'Total Guilds:', - value: `${cache.guilds.size}`, - inline: true, - }, - { - name: 'Cached Guilds:', - value: `${cachedGuilds}`, - inline: true, - }, - { - name: 'Uncached Guilds:', - value: `${cache.dispatchedGuildIds.size}`, - inline: true, - }, - { - name: 'Total Members\n(may be artificially higher if 1 user is in multiple guilds the bot is in):', - value: `${totalCount}`, - inline: true, - }, - { - name: 'Cached Real People:', - value: `${realCount}`, - inline: true, - }, - { - name: 'Cached Bots:', - value: `${botsCount}`, - inline: true, - }, - { - name: 'Average members per guild:', - value: `${(totalCount / cache.guilds.size).toFixed(2)}`, - inline: true, - }, - ], - }], - file: { - 'blob': b.size > 8388290 ? tooBig : b, - 'name': 'auditDetails.txt', - }, - }).catch((e: Error) => utils.commonLoggers.messageSendError('auditGuild.ts:19', message, e)); + timestamp: new Date().toISOString(), + fields: [ + { + name: 'Total Guilds:', + value: `${cache.guilds.size}`, + inline: true, + }, + { + name: 'Cached Guilds:', + value: `${cachedGuilds}`, + inline: true, + }, + { + name: 'Uncached Guilds:', + value: `${cache.dispatchedGuildIds.size}`, + inline: true, + }, + { + name: 'Total Members\n(may be artificially higher if 1 user is in multiple guilds the bot is in):', + value: `${totalCount}`, + inline: true, + }, + { + name: 'Cached Real People:', + value: `${realCount}`, + inline: true, + }, + { + name: 'Cached Bots:', + value: `${botsCount}`, + inline: true, + }, + { + name: 'Average members per guild:', + value: `${(totalCount / cache.guilds.size).toFixed(2)}`, + inline: true, + }, + ], + }], + file: { + 'blob': b.size > 8388290 ? tooBig : b, + 'name': 'auditDetails.txt', + }, + }).catch((e: Error) => utils.commonLoggers.messageSendError('auditGuild.ts:19', message, e)); }; diff --git a/src/commands/auditCmd/auditHelp.ts b/src/commands/auditCmd/auditHelp.ts index 5255cab..2582f67 100644 --- a/src/commands/auditCmd/auditHelp.ts +++ b/src/commands/auditCmd/auditHelp.ts @@ -1,33 +1,33 @@ import config from '../../../config.ts'; import { - // Discordeno deps - DiscordenoMessage, + // Discordeno deps + DiscordenoMessage, } from '../../../deps.ts'; import { infoColor1 } from '../../commandUtils.ts'; import utils from '../../utils.ts'; export const auditHelp = (message: DiscordenoMessage) => { - message.send({ - embeds: [{ - color: infoColor1, - title: 'Audit Help', - fields: [ - { - name: `\`${config.prefix}audit help\``, - value: 'This command', - inline: true, - }, - { - name: `\`${config.prefix}audit db\``, - value: 'Shows current DB table sizes', - inline: true, - }, - { - name: `\`${config.prefix}audit guilds\``, - value: 'Shows breakdown of guilds and detials on them', - inline: true, - }, - ], - }], - }).catch((e: Error) => utils.commonLoggers.messageSendError('auditHelp.ts:35', message, e)); + message.send({ + embeds: [{ + color: infoColor1, + title: 'Audit Help', + fields: [ + { + name: `\`${config.prefix}audit help\``, + value: 'This command', + inline: true, + }, + { + name: `\`${config.prefix}audit db\``, + value: 'Shows current DB table sizes', + inline: true, + }, + { + name: `\`${config.prefix}audit guilds\``, + value: 'Shows breakdown of guilds and detials on them', + inline: true, + }, + ], + }], + }).catch((e: Error) => utils.commonLoggers.messageSendError('auditHelp.ts:35', message, e)); }; diff --git a/src/commands/help.ts b/src/commands/help.ts index a6bd98b..c44e6ac 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -91,7 +91,8 @@ export const help = (message: DiscordenoMessage) => { }, { name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`, - value: `Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`), run \`${config.prefix}??\` for more details`, + value: + `Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`), run \`${config.prefix}??\` for more details`, inline: true, }, ], diff --git a/src/commands/optIn.ts b/src/commands/optIn.ts index bc270fa..e105102 100644 --- a/src/commands/optIn.ts +++ b/src/commands/optIn.ts @@ -1,6 +1,6 @@ import config from '../../config.ts'; import dbClient from '../db/client.ts'; -import { queries, ignoreList } from '../db/common.ts'; +import { ignoreList, queries } from '../db/common.ts'; import { // Discordeno deps DiscordenoMessage, diff --git a/src/commands/optOut.ts b/src/commands/optOut.ts index 1a39375..4b2b9b9 100644 --- a/src/commands/optOut.ts +++ b/src/commands/optOut.ts @@ -1,6 +1,6 @@ import config from '../../config.ts'; import dbClient from '../db/client.ts'; -import { queries, ignoreList } from '../db/common.ts'; +import { ignoreList, queries } from '../db/common.ts'; import { // Discordeno deps DiscordenoMessage, diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index 9d3806a..4395aab 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -21,7 +21,8 @@ export const privacy = (message: DiscordenoMessage) => { fields: [ { name: 'The Artificer does not track or collect user information via Discord.', - value: `The only user submitted information that is stored is submitted via the \`${config.prefix}report\` command. This information is only stored for a short period of time in a location that only the Developer of The Artificer can see. + value: + `The only user submitted information that is stored is submitted via the \`${config.prefix}report\` command. This information is only stored for a short period of time in a location that only the Developer of The Artificer can see. For more details, please check out the Privacy Policy on the GitHub [here](https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md). diff --git a/src/commands/roll.ts b/src/commands/roll.ts index 98fa4aa..cb9d298 100644 --- a/src/commands/roll.ts +++ b/src/commands/roll.ts @@ -53,13 +53,15 @@ export const roll = async (message: DiscordenoMessage, args: string[], command: // Rejoin all of the args and send it into the solver, if solver returns a falsy item, an error object will be substituded in const rollCmd = message.content.substring(2); - queueRoll({ - apiRoll: false, - dd: { m, message }, - rollCmd, - modifiers, - originalCommand, - }); + queueRoll( + { + apiRoll: false, + dd: { m, message }, + rollCmd, + modifiers, + originalCommand, + }, + ); } catch (e) { log(LT.ERROR, `Undandled Error: ${JSON.stringify(e)}`); } diff --git a/src/commands/roll/_index.ts b/src/commands/roll/_index.ts index 71ee943..ae504a3 100644 --- a/src/commands/roll/_index.ts +++ b/src/commands/roll/_index.ts @@ -1,5 +1,5 @@ import { getModifiers } from './getModifiers.ts'; export default { - getModifiers, + getModifiers, }; diff --git a/src/commands/roll/getModifiers.ts b/src/commands/roll/getModifiers.ts index d310dde..e9c46a2 100644 --- a/src/commands/roll/getModifiers.ts +++ b/src/commands/roll/getModifiers.ts @@ -64,9 +64,7 @@ export const getModifiers = (m: DiscordenoMessage, args: string[], command: stri } if (modifiers.gms.length < 1) { // If -gm is on and none were found, throw an error - m.edit(generateRollError(errorType, 'Must specifiy at least one GM by @mentioning them')).catch((e) => - utils.commonLoggers.messageEditError('getModifiers.ts:66', m, e) - ); + m.edit(generateRollError(errorType, 'Must specifiy at least one GM by @mentioning them')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:66', m, e)); if (DEVMODE && config.logRolls) { // If enabled, log rolls so we can verify the bots math @@ -83,9 +81,7 @@ export const getModifiers = (m: DiscordenoMessage, args: string[], command: stri if (!args[i] || (args[i].toLowerCase()[0] !== 'd' && args[i].toLowerCase()[0] !== 'a')) { // If -o is on and asc or desc was not specified, error out - m.edit(generateRollError(errorType, 'Must specifiy `a` or `d` to order the rolls ascending or descending')).catch((e) => - utils.commonLoggers.messageEditError('getModifiers.ts:81', m, e) - ); + m.edit(generateRollError(errorType, 'Must specifiy `a` or `d` to order the rolls ascending or descending')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:81', m, e)); if (DEVMODE && config.logRolls) { // If enabled, log rolls so we can verify the bots math @@ -112,9 +108,7 @@ export const getModifiers = (m: DiscordenoMessage, args: string[], command: stri // maxRoll and nominalRoll cannot both be on, throw an error if (modifiers.maxRoll && modifiers.nominalRoll) { - m.edit(generateRollError(errorType, 'Cannot maximise and nominise the roll at the same time')).catch((e) => - utils.commonLoggers.messageEditError('getModifiers.ts:106', m, e) - ); + m.edit(generateRollError(errorType, 'Cannot maximise and nominise the roll at the same time')).catch((e) => utils.commonLoggers.messageEditError('getModifiers.ts:106', m, e)); if (DEVMODE && config.logRolls) { // If enabled, log rolls so we can verify the bots math diff --git a/src/commands/rollDecorators.ts b/src/commands/rollDecorators.ts index d77b35a..e33f1c6 100644 --- a/src/commands/rollDecorators.ts +++ b/src/commands/rollDecorators.ts @@ -49,8 +49,7 @@ Examples: \`${config.prefix}d20${config.postfix} -nd\`, \`${config.prefix}d20${c }, { name: '`-gm @user1 @user2 @usern` - GM Roll', - value: - 'Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs', + value: 'Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs', inline: true, }, { diff --git a/src/commands/rollHelp.ts b/src/commands/rollHelp.ts index 0d56380..9f2ea44 100644 --- a/src/commands/rollHelp.ts +++ b/src/commands/rollHelp.ts @@ -72,32 +72,27 @@ Additionally, replace \`x\` with \`F\` to roll Fate dice`, }, { name: '`rq` [Optional]', - value: - 'Rerolls any rolls that are greater than or equal to `a`, `r3` will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with `ro`', + value: 'Rerolls any rolls that are greater than or equal to `a`, `r3` will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with `ro`', inline: true, }, { name: '`roa` or `ro=q` [Optional]', - value: - 'Rerolls any rolls that match `a`, `ro3` will reroll each die that lands on 3 ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', + value: 'Rerolls any rolls that match `a`, `ro3` will reroll each die that lands on 3 ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', inline: true, }, { name: '`roq` [Optional]', - value: - 'Rerolls any rolls that are greater than or equal to `a`, `ro3` will reroll each die that lands on 3 or greater ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', + value: 'Rerolls any rolls that are greater than or equal to `a`, `ro3` will reroll each die that lands on 3 or greater ONLY ONE TIME, throwing out old rolls, cannot be used with `r`', inline: true, }, { @@ -192,32 +187,27 @@ Additionally, replace \`x\` with \`F\` to roll Fate dice`, }, { name: '`!p>u` [Optional]', - value: - 'Penetrating Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but subtracts one from each resulting explosion', + value: 'Penetrating Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but subtracts one from each resulting explosion', inline: true, }, { name: '`!pu` [Optional]', - value: - 'Compounding Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but adds the resulting explosion to the die that caused this explosion', + value: 'Compounding Explosion on `u` and greater, rolls one `dy` for each die that lands on `u` or greater, but adds the resulting explosion to the die that caused this explosion', inline: true, }, { name: '`!! { rolls, total - rolls, rollRate, - totalRate - rollRate - ) + totalRate - rollRate, + ), ).catch((e: Error) => utils.commonLoggers.messageEditError('stats.ts:38', m, e)); } catch (e) { utils.commonLoggers.messageSendError('stats.ts:41', message, e); diff --git a/src/commonEmbeds.ts b/src/commonEmbeds.ts index 2dcd9a4..f80b3bc 100644 --- a/src/commonEmbeds.ts +++ b/src/commonEmbeds.ts @@ -1,8 +1,8 @@ import { warnColor } from './commandUtils.ts'; export const compilingStats = { - embeds: [{ - color: warnColor, - title: 'Compiling latest statistics . . .', - }], + embeds: [{ + color: warnColor, + title: 'Compiling latest statistics . . .', + }], }; diff --git a/src/endpoints/_index.ts b/src/endpoints/_index.ts index c621ed7..3407cd3 100644 --- a/src/endpoints/_index.ts +++ b/src/endpoints/_index.ts @@ -10,22 +10,22 @@ import { apiChannelManageBan } from './puts/apiChannelManageBan.ts'; import { apiChannelManageActive } from './puts/apiChannelManageActive.ts'; export default { - delete: { - apiKeyDelete, - }, - get: { - apiKey, - apiRoll, - apiKeyAdmin, - apiChannel, - heatmapPng, - }, - post: { - apiChannelAdd, - }, - put: { - apiKeyManage, - apiChannelManageBan, - apiChannelManageActive, - }, + delete: { + apiKeyDelete, + }, + get: { + apiKey, + apiRoll, + apiKeyAdmin, + apiChannel, + heatmapPng, + }, + post: { + apiChannelAdd, + }, + put: { + apiKeyManage, + apiChannelManageBan, + apiChannelManageActive, + }, }; diff --git a/src/endpoints/deletes/apiKeyDelete.ts b/src/endpoints/deletes/apiKeyDelete.ts index c88bab6..f7bd307 100644 --- a/src/endpoints/deletes/apiKeyDelete.ts +++ b/src/endpoints/deletes/apiKeyDelete.ts @@ -15,7 +15,7 @@ export const apiKeyDelete = async ( query: Map, apiUserid: BigInt, apiUserEmail: string, - apiUserDelCode: string + apiUserDelCode: string, ) => { if (query.has('user') && (query.get('user') || '').length > 0 && query.has('email') && (query.get('email') || '').length > 0) { if (apiUserid === BigInt(query.get('user') || '0') && apiUserEmail === query.get('email')) { diff --git a/src/endpoints/gets/apiRoll.ts b/src/endpoints/gets/apiRoll.ts index b93c392..ced6844 100644 --- a/src/endpoints/gets/apiRoll.ts +++ b/src/endpoints/gets/apiRoll.ts @@ -103,13 +103,15 @@ export const apiRoll = async (requestEvent: Deno.RequestEvent, query: Map{ - apiRoll: true, - api: { requestEvent, channelId: BigInt(query.get('channel') || '0'), userId: BigInt(query.get('user') || '') }, - rollCmd, - modifiers, - originalCommand, - }); + await queueRoll( + { + apiRoll: true, + api: { requestEvent, channelId: BigInt(query.get('channel') || '0'), userId: BigInt(query.get('user') || '') }, + rollCmd, + modifiers, + originalCommand, + }, + ); } catch (err) { // Handle any errors we missed log(LT.ERROR, `Unhandled Error: ${JSON.stringify(err)}`); @@ -119,8 +121,8 @@ export const apiRoll = async (requestEvent: Deno.RequestEvent, query: Map { status: STATUS_CODE.OK, statusText: STATUS_TEXT[STATUS_CODE.OK], headers: imageHeaders, - }) + }), ); }; diff --git a/src/endpoints/stdResponses.ts b/src/endpoints/stdResponses.ts index 0a7c90c..aa42a2f 100644 --- a/src/endpoints/stdResponses.ts +++ b/src/endpoints/stdResponses.ts @@ -1,12 +1,11 @@ import { - // httpd deps - StatusCode, STATUS_CODE, STATUS_TEXT, + // httpd deps + StatusCode, } from '../../deps.ts'; -const genericResponse = (customText: string, status: StatusCode) => - new Response(customText || STATUS_TEXT[status], { status: status, statusText: STATUS_TEXT[status] }); +const genericResponse = (customText: string, status: StatusCode) => new Response(customText || STATUS_TEXT[status], { status: status, statusText: STATUS_TEXT[status] }); export default { BadRequest: (customText: string) => genericResponse(customText, STATUS_CODE.BadRequest), diff --git a/src/intervals.ts b/src/intervals.ts index 0607e18..89363c4 100644 --- a/src/intervals.ts +++ b/src/intervals.ts @@ -196,7 +196,7 @@ const updateHeatmapPng = async () => { hourPixels[hour][0] + 1, dayPixels[day][1] - dayPixels[day][0] + 1, hourPixels[hour][1] - hourPixels[hour][0] + 1, - is.Image.rgbToColor(255 * (1 - percent), 255 * percent, 0) + is.Image.rgbToColor(255 * (1 - percent), 255 * percent, 0), ); } } diff --git a/src/mod.d.ts b/src/mod.d.ts index fa26758..4af00ba 100644 --- a/src/mod.d.ts +++ b/src/mod.d.ts @@ -3,46 +3,46 @@ import { DiscordenoMessage } from '../deps.ts'; // EmojiConf is used as a structure for the emojis stored in config.ts export type EmojiConf = { - name: string; - aliases: Array; - id: string; - animated: boolean; - deleteSender: boolean; + name: string; + aliases: Array; + id: string; + animated: boolean; + deleteSender: boolean; }; // RollModifiers is the structure to keep track of the decorators applied to a roll command export type RollModifiers = { - noDetails: boolean; - superNoDetails: boolean; - spoiler: string; - maxRoll: boolean; - nominalRoll: boolean; - gmRoll: boolean; - gms: string[]; - order: string; - count: boolean; - valid: boolean; - apiWarn: string; + noDetails: boolean; + superNoDetails: boolean; + spoiler: string; + maxRoll: boolean; + nominalRoll: boolean; + gmRoll: boolean; + gms: string[]; + order: string; + count: boolean; + valid: boolean; + apiWarn: string; }; // QueuedRoll is the structure to track rolls we could not immediately handle export type QueuedRoll = { - apiRoll: boolean; - api: { - requestEvent: Deno.RequestEvent; - channelId: bigint; - userId: bigint; - }; - dd: { - m: DiscordenoMessage; - message: DiscordenoMessage; - }; - originalCommand: string; - rollCmd: string; - modifiers: RollModifiers; + apiRoll: boolean; + api: { + requestEvent: Deno.RequestEvent; + channelId: bigint; + userId: bigint; + }; + dd: { + m: DiscordenoMessage; + message: DiscordenoMessage; + }; + originalCommand: string; + rollCmd: string; + modifiers: RollModifiers; }; export type PastCommandCount = { - command: string; - count: number; + command: string; + count: number; }; diff --git a/src/solver/_index.ts b/src/solver/_index.ts index d8a4c4d..70ddfed 100644 --- a/src/solver/_index.ts +++ b/src/solver/_index.ts @@ -1,5 +1,5 @@ import { parseRoll } from './parser.ts'; export default { - parseRoll, + parseRoll, }; diff --git a/src/solver/counter.ts b/src/solver/counter.ts index 3ad92d6..f398d99 100644 --- a/src/solver/counter.ts +++ b/src/solver/counter.ts @@ -1,23 +1,23 @@ import { CountDetails, RollSet } from './solver.d.ts'; export const rollCounter = (rollSet: RollSet[]): CountDetails => { - const countDetails = { - total: 0, - successful: 0, - failed: 0, - rerolled: 0, - dropped: 0, - exploded: 0, - }; + const countDetails = { + total: 0, + successful: 0, + failed: 0, + rerolled: 0, + dropped: 0, + exploded: 0, + }; - rollSet.forEach((roll) => { - countDetails.total++; - if (roll.critHit) countDetails.successful++; - if (roll.critFail) countDetails.failed++; - if (roll.rerolled) countDetails.rerolled++; - if (roll.dropped) countDetails.dropped++; - if (roll.exploding) countDetails.exploded++; - }); + rollSet.forEach((roll) => { + countDetails.total++; + if (roll.critHit) countDetails.successful++; + if (roll.critFail) countDetails.failed++; + if (roll.rerolled) countDetails.rerolled++; + if (roll.dropped) countDetails.dropped++; + if (roll.exploding) countDetails.exploded++; + }); - return countDetails; + return countDetails; }; diff --git a/src/solver/parser.ts b/src/solver/parser.ts index 71b312e..8c49ef8 100644 --- a/src/solver/parser.ts +++ b/src/solver/parser.ts @@ -1,7 +1,7 @@ import { - log, - // Log4Deno deps - LT, + log, + // Log4Deno deps + LT, } from '../../deps.ts'; import config from '../../config.ts'; @@ -15,328 +15,328 @@ 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 operators = ['^', '*', '/', '%', '+', '-', '(', ')']; - const returnmsg = { - error: false, - errorCode: '', - errorMsg: '', - line1: '', - line2: '', - line3: '', - counts: { - total: 0, - successful: 0, - failed: 0, - rerolled: 0, - dropped: 0, - exploded: 0, - }, - }; + const operators = ['^', '*', '/', '%', '+', '-', '(', ')']; + const returnmsg = { + error: false, + errorCode: '', + errorMsg: '', + line1: '', + line2: '', + line3: '', + counts: { + total: 0, + successful: 0, + failed: 0, + rerolled: 0, + dropped: 0, + exploded: 0, + }, + }; - // 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); + // 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[] = []; - const tempCountDetails: CountDetails[] = [{ - total: 0, - successful: 0, - failed: 0, - rerolled: 0, - dropped: 0, - exploded: 0, - }]; + const tempReturnData: ReturnData[] = []; + 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 - const [tempConf, tempFormat] = sepRoll.split(config.postfix); + // 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 + 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); + // 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) => { - loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Checking parenthesis balance ${e}`); - if (e === '(') { - parenCnt++; - } else if (e === ')') { - parenCnt--; - } - }); + // 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) => { + loggingEnabled && 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'); - } + // 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++) { - loggingEnabled && 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 (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() === 'fart' || mathConf[i].toString().toLowerCase() === '💩') { - mathConf[i] = { - total: 7, - details: '💩', - containsCrit: false, - containsFail: false, - }; - } 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, - details: '∞', - 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, - }]); - } 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); - mathConf[i] = formattedRoll.solvedStep; - tempCountDetails.push(formattedRoll.countDetails); - } + // Evaluate all rolls into stepSolve format and all numbers into floats + for (let i = 0; i < mathConf.length; i++) { + loggingEnabled && 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 (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() === 'fart' || mathConf[i].toString().toLowerCase() === '💩') { + mathConf[i] = { + total: 7, + details: '💩', + containsCrit: false, + containsFail: false, + }; + } 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, + details: '∞', + 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, + }]); + } 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); + mathConf[i] = formattedRoll.solvedStep; + tempCountDetails.push(formattedRoll.countDetails); + } - if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || mathConf[i - 2] === '(')) { - if (typeof mathConf[i] === 'number') { - mathConf[i] = mathConf[i] * -1; - } else { - ( mathConf[i]).total = ( mathConf[i]).total * -1; - ( mathConf[i]).details = `-${( mathConf[i]).details}`; - } - mathConf.splice(i - 1, 1); - i--; - } - } + if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || mathConf[i - 2] === '(')) { + if (typeof mathConf[i] === 'number') { + mathConf[i] = mathConf[i] * -1; + } else { + ( mathConf[i]).total = ( mathConf[i]).total * -1; + ( mathConf[i]).details = `-${( mathConf[i]).details}`; + } + mathConf.splice(i - 1, 1); + i--; + } + } - // Now that mathConf is parsed, send it into the solver - const tempSolved = fullSolver(mathConf, 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, - }); - } + // 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 + // 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); - } + // 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, ''); + // 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 = ''; + 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:\n\`${config.prefix}${fullCmd}\``; - line2 = 'Theoretical Maximum Results: '; - } else if (modifiers.nominalRoll) { - line1 = ` requested the theoretical nominal of:\n\`${config.prefix}${fullCmd}\``; - line2 = 'Theoretical Nominal Results: '; - } else if (modifiers.order === 'a') { - line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${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:\n\`${config.prefix}${fullCmd}\``; - line2 = 'Results: '; - tempReturnData.sort(compareTotalRolls); - tempReturnData.reverse(); - } else { - line1 = ` rolled:\n\`${config.prefix}${fullCmd}\``; - line2 = 'Results: '; - } + // If maximiseRoll or nominalRoll are on, mark the output as such, else use default formatting + if (modifiers.maxRoll) { + line1 = ` requested the theoretical maximum of:\n\`${config.prefix}${fullCmd}\``; + line2 = 'Theoretical Maximum Results: '; + } else if (modifiers.nominalRoll) { + line1 = ` requested the theoretical nominal of:\n\`${config.prefix}${fullCmd}\``; + line2 = 'Theoretical Nominal Results: '; + } else if (modifiers.order === 'a') { + line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${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:\n\`${config.prefix}${fullCmd}\``; + line2 = 'Results: '; + tempReturnData.sort(compareTotalRolls); + tempReturnData.reverse(); + } else { + line1 = ` rolled:\n\`${config.prefix}${fullCmd}\``; + line2 = 'Results: '; + } - // Fill out all of the details and results now - tempReturnData.forEach((e) => { - loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Making return text ${JSON.stringify(e)}`); - let preFormat = ''; - let postFormat = ''; + // Fill out all of the details and results now + tempReturnData.forEach((e) => { + loggingEnabled && 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}__`; - } + // 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}, `; - } + // 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, '~~ ~~'); + line2 = line2.replace(/\*\*\*\*/g, '** **').replace(/____/g, '__ __').replace(/~~~~/g, '~~ ~~'); - line3 += `\`${e.initConfig}\` = ${e.rollDetails} = ${preFormat}${e.rollTotal}${postFormat}\n`; - }); + 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); - } + // 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; + // Fill in the return block + returnmsg.line1 = line1; + returnmsg.line2 = line2; + returnmsg.line3 = line3; - // Reduce counts to a single object - returnmsg.counts = tempCountDetails.reduce((acc, cnt) => ({ - total: acc.total + cnt.total, - successful: acc.successful + cnt.successful, - failed: acc.failed + cnt.failed, - rerolled: acc.rerolled + cnt.rerolled, - dropped: acc.dropped + cnt.dropped, - exploded: acc.exploded + cnt.exploded, - })); - } catch (solverError) { - // Welp, the unthinkable happened, we hit an error + // Reduce counts to a single object + returnmsg.counts = tempCountDetails.reduce((acc, cnt) => ({ + total: acc.total + cnt.total, + successful: acc.successful + cnt.successful, + failed: acc.failed + cnt.failed, + rerolled: acc.rerolled + cnt.rerolled, + dropped: acc.dropped + cnt.dropped, + exploded: acc.exploded + cnt.exploded, + })); + } 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('_'); + // Split on _ for the error messages that have more info than just their name + const [errorName, errorDetails] = solverError.message.split('_'); - let errorMsg = ''; + 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; - } + // 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; - } + // Fill in the return block + returnmsg.error = true; + returnmsg.errorCode = solverError.message; + returnmsg.errorMsg = errorMsg; + } - return returnmsg; + return returnmsg; }; diff --git a/src/solver/rollFormatter.ts b/src/solver/rollFormatter.ts index 42fe837..cdafd00 100644 --- a/src/solver/rollFormatter.ts +++ b/src/solver/rollFormatter.ts @@ -1,7 +1,7 @@ import { - log, - // Log4Deno deps - LT, + log, + // Log4Deno deps + LT, } from '../../deps.ts'; import { roll } from './roller.ts'; @@ -12,77 +12,77 @@ import { loggingEnabled } from './rollUtils.ts'; // formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep // formatRoll handles creating and formatting the completed rolls into the SolvedStep format export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: boolean): RollFormat => { - let tempTotal = 0; - let tempDetails = '['; - let tempCrit = false; - let tempFail = false; + let tempTotal = 0; + let tempDetails = '['; + let tempCrit = false; + let tempFail = false; - // Generate the roll, passing flags thru - const tempRollSet = roll(rollConf, maximiseRoll, nominalRoll); + // Generate the roll, passing flags thru + const tempRollSet = roll(rollConf, maximiseRoll, nominalRoll); - // Loop thru all parts of the roll to document everything that was done to create the total roll - tempRollSet.forEach((e) => { - loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); - let preFormat = ''; - let postFormat = ''; + // Loop thru all parts of the roll to document everything that was done to create the total roll + tempRollSet.forEach((e) => { + loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); + let preFormat = ''; + let postFormat = ''; - if (!e.dropped && !e.rerolled) { - // If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail - switch (e.type) { - case 'ova': - case 'roll20': - case 'fate': - tempTotal += e.roll; - break; - case 'cwod': - tempTotal += e.critHit ? 1 : 0; - break; - } - if (e.critHit) { - tempCrit = true; - } - if (e.critFail) { - tempFail = true; - } - } - // If the roll was a crit hit or fail, or dropped/rerolled, add the formatting needed - if (e.critHit) { - // Bold for crit success - preFormat = `**${preFormat}`; - postFormat = `${postFormat}**`; - } - if (e.critFail) { - // Underline for crit fail - preFormat = `__${preFormat}`; - postFormat = `${postFormat}__`; - } - if (e.dropped || e.rerolled) { - // Strikethrough for dropped/rerolled rolls - preFormat = `~~${preFormat}`; - postFormat = `${postFormat}~~`; - } - if (e.exploding) { - // Add ! to indicate the roll came from an explosion - postFormat = `!${postFormat}`; - } + if (!e.dropped && !e.rerolled) { + // If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail + switch (e.type) { + case 'ova': + case 'roll20': + case 'fate': + tempTotal += e.roll; + break; + case 'cwod': + tempTotal += e.critHit ? 1 : 0; + break; + } + if (e.critHit) { + tempCrit = true; + } + if (e.critFail) { + tempFail = true; + } + } + // If the roll was a crit hit or fail, or dropped/rerolled, add the formatting needed + if (e.critHit) { + // Bold for crit success + preFormat = `**${preFormat}`; + postFormat = `${postFormat}**`; + } + if (e.critFail) { + // Underline for crit fail + preFormat = `__${preFormat}`; + postFormat = `${postFormat}__`; + } + if (e.dropped || e.rerolled) { + // Strikethrough for dropped/rerolled rolls + preFormat = `~~${preFormat}`; + postFormat = `${postFormat}~~`; + } + if (e.exploding) { + // Add ! to indicate the roll came from an explosion + postFormat = `!${postFormat}`; + } - // Finally add this to the roll's details - tempDetails += `${preFormat}${e.roll}${postFormat} + `; - }); - // After the looping is done, remove the extra " + " from the details and cap it with the closing ] - tempDetails = tempDetails.substring(0, tempDetails.length - 3); - if (tempRollSet[0]?.type === 'cwod') { - tempDetails += `, ${tempRollSet.filter((e) => e.critHit).length} Successes, ${tempRollSet.filter((e) => e.critFail).length} Fails`; - } - tempDetails += ']'; + // Finally add this to the roll's details + tempDetails += `${preFormat}${e.roll}${postFormat} + `; + }); + // After the looping is done, remove the extra " + " from the details and cap it with the closing ] + tempDetails = tempDetails.substring(0, tempDetails.length - 3); + if (tempRollSet[0]?.type === 'cwod') { + tempDetails += `, ${tempRollSet.filter((e) => e.critHit).length} Successes, ${tempRollSet.filter((e) => e.critFail).length} Fails`; + } + tempDetails += ']'; - return { - solvedStep: { - total: tempTotal, - details: tempDetails, - containsCrit: tempCrit, - containsFail: tempFail, - }, - countDetails: rollCounter(tempRollSet), - }; + return { + solvedStep: { + total: tempTotal, + details: tempDetails, + containsCrit: tempCrit, + containsFail: tempFail, + }, + countDetails: rollCounter(tempRollSet), + }; }; diff --git a/src/solver/rollQueue.ts b/src/solver/rollQueue.ts index 88e0c6a..ca20bbf 100644 --- a/src/solver/rollQueue.ts +++ b/src/solver/rollQueue.ts @@ -43,12 +43,12 @@ const handleRollWorker = async (rq: QueuedRoll) => { ( await generateRollEmbed( rq.dd.message.authorId, - { + { error: true, errorCode: 'TooComplex', errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts', }, - {} + {}, ) ).embed, ], @@ -170,14 +170,14 @@ const handleRollWorker = async (rq: QueuedRoll) => { JSON.stringify( rq.modifiers.count ? { - counts: countEmbed, - details: pubEmbedDetails, - } + counts: countEmbed, + details: pubEmbedDetails, + } : { - details: pubEmbedDetails, - } - ) - ) + details: pubEmbedDetails, + }, + ), + ), ); } } @@ -218,7 +218,7 @@ The results for this roll will replace this message when it is done.`, setInterval(async () => { log( LT.LOG, - `Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}` + `Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${currentWorkers}, config.limits.maxWorkers: ${config.limits.maxWorkers}`, ); if (rollQueue.length && currentWorkers < config.limits.maxWorkers) { const temp = rollQueue.shift(); diff --git a/src/solver/rollUtils.ts b/src/solver/rollUtils.ts index 58d5b9d..ecc4ccd 100644 --- a/src/solver/rollUtils.ts +++ b/src/solver/rollUtils.ts @@ -1,7 +1,7 @@ import { - log, - // Log4Deno deps - LT, + log, + // Log4Deno deps + LT, } from '../../deps.ts'; import { ReturnData, RollSet } from './solver.d.ts'; @@ -11,70 +11,70 @@ export const loggingEnabled = false; // genRoll(size) returns number // genRoll rolls a die of size size and returns the result export const genRoll = (size: number, maximiseRoll: boolean, nominalRoll: boolean): number => { - if (maximiseRoll) { - 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); - } + if (maximiseRoll) { + 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); + } }; // genFateRoll returns -1|0|1 // genFateRoll turns a d6 into a fate die, with sides: -1, -1, 0, 0, 1, 1 export const genFateRoll = (maximiseRoll: boolean, nominalRoll: boolean): number => { - if (nominalRoll) { - return 0; - } else { - const sides = [-1, -1, 0, 0, 1, 1]; - return sides[genRoll(6, maximiseRoll, nominalRoll) - 1]; - } + if (nominalRoll) { + return 0; + } else { + const sides = [-1, -1, 0, 0, 1, 1]; + return sides[genRoll(6, maximiseRoll, nominalRoll) - 1]; + } }; // compareRolls(a, b) returns -1|0|1 // compareRolls is used to order an array of RollSets by RollSet.roll export const compareRolls = (a: RollSet, b: RollSet): number => { - if (a.roll < b.roll) { - return -1; - } - if (a.roll > b.roll) { - return 1; - } - return 0; + if (a.roll < b.roll) { + return -1; + } + if (a.roll > b.roll) { + return 1; + } + return 0; }; // compareTotalRolls(a, b) returns -1|0|1 // compareTotalRolls is used to order an array of RollSets by RollSet.roll export const compareTotalRolls = (a: ReturnData, b: ReturnData): number => { - if (a.rollTotal < b.rollTotal) { - return -1; - } - if (a.rollTotal > b.rollTotal) { - return 1; - } - return 0; + if (a.rollTotal < b.rollTotal) { + return -1; + } + if (a.rollTotal > b.rollTotal) { + return 1; + } + return 0; }; // compareRolls(a, b) returns -1|0|1 // compareRolls is used to order an array of RollSets by RollSet.origidx export const compareOrigidx = (a: RollSet, b: RollSet): number => { - if (a.origidx < b.origidx) { - return -1; - } - if (a.origidx > b.origidx) { - return 1; - } - return 0; + if (a.origidx < b.origidx) { + return -1; + } + if (a.origidx > b.origidx) { + return 1; + } + return 0; }; // escapeCharacters(str, esc) returns str // escapeCharacters escapes all characters listed in esc export const escapeCharacters = (str: string, esc: string): string => { - // Loop thru each esc char one at a time - for (const e of esc) { - loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`); - // Create a new regex to look for that char that needs replaced and escape it - const temprgx = new RegExp(`[${e}]`, 'g'); - str = str.replace(temprgx, `\\${e}`); - } - return str; + // Loop thru each esc char one at a time + for (const e of esc) { + loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`); + // Create a new regex to look for that char that needs replaced and escape it + const temprgx = new RegExp(`[${e}]`, 'g'); + str = str.replace(temprgx, `\\${e}`); + } + return str; }; diff --git a/src/solver/rollWorker.ts b/src/solver/rollWorker.ts index 9be7dde..f0b29e9 100644 --- a/src/solver/rollWorker.ts +++ b/src/solver/rollWorker.ts @@ -5,23 +5,23 @@ self.postMessage('ready'); // Handle the roll self.onmessage = async (e: any) => { - const payload = e.data; - const returnmsg = parseRoll(payload.rollCmd, payload.modifiers) || { - error: true, - errorCode: 'EmptyMessage', - errorMsg: 'Error: Empty message', - line1: '', - line2: '', - line3: '', - counts: { - total: 0, - successful: 0, - failed: 0, - rerolled: 0, - dropped: 0, - exploded: 0, - }, - }; - self.postMessage(returnmsg); - self.close(); + const payload = e.data; + const returnmsg = parseRoll(payload.rollCmd, payload.modifiers) || { + error: true, + errorCode: 'EmptyMessage', + errorMsg: 'Error: Empty message', + line1: '', + line2: '', + line3: '', + counts: { + total: 0, + successful: 0, + failed: 0, + rerolled: 0, + dropped: 0, + exploded: 0, + }, + }; + self.postMessage(returnmsg); + self.close(); }; diff --git a/src/solver/roller.ts b/src/solver/roller.ts index e3e1c28..451261b 100644 --- a/src/solver/roller.ts +++ b/src/solver/roller.ts @@ -1,8 +1,8 @@ import config from '../../config.ts'; import { - // Log4Deno deps - log, - LT, + // Log4Deno deps + log, + LT, } from '../../deps.ts'; import { RollConf, RollSet, RollType } from './solver.d.ts'; @@ -11,371 +11,371 @@ import { compareOrigidx, compareRolls, genFateRoll, genRoll, loggingEnabled } fr // roll(rollStr, maximiseRoll, nominalRoll) returns RollSet // 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 + /* 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. */ - // Make entire roll lowercase for ease of parsing - rollStr = rollStr.toLowerCase(); + // Make entire roll lowercase for ease of parsing + rollStr = rollStr.toLowerCase(); - // Split the roll on the die size (and the drop if its there) - const dpts = rollStr.split('d'); + // Split the roll on the die size (and the drop if its there) + const dpts = rollStr.split('d'); - // Initialize the configuration to store the parsed data - let rollType: RollType = ''; - const rollConf: RollConf = { - dieCount: 0, - dieSize: 0, - drop: { - on: false, - count: 0, - }, - keep: { - on: false, - count: 0, - }, - dropHigh: { - on: false, - count: 0, - }, - keepLow: { - on: false, - count: 0, - }, - reroll: { - on: false, - once: false, - nums: [], - }, - critScore: { - on: false, - range: [], - }, - critFail: { - on: false, - range: [], - }, - exploding: { - on: false, - once: false, - compounding: false, - penetrating: false, - nums: [], - }, - }; + // Initialize the configuration to store the parsed data + let rollType: RollType = ''; + const rollConf: RollConf = { + dieCount: 0, + dieSize: 0, + drop: { + on: false, + count: 0, + }, + keep: { + on: false, + count: 0, + }, + dropHigh: { + on: false, + count: 0, + }, + keepLow: { + on: false, + count: 0, + }, + reroll: { + on: false, + once: false, + nums: [], + }, + critScore: { + on: false, + range: [], + }, + critFail: { + on: false, + range: [], + }, + exploding: { + on: false, + once: false, + compounding: false, + penetrating: false, + nums: [], + }, + }; - // If the dpts is not long enough, throw error - if (dpts.length < 2) { - throw new Error('YouNeedAD'); - } + // If the dpts is not long enough, throw error + if (dpts.length < 2) { + throw new Error('YouNeedAD'); + } - // Fill out the die count, first item will either be an int or empty string, short circuit execution will take care of replacing the empty string with a 1 - const rawDC = dpts.shift() || '1'; - const tempDC = rawDC.replace(/\D/g, ''); - // Rejoin all remaining parts - let remains = dpts.join('d'); + // Fill out the die count, first item will either be an int or empty string, short circuit execution will take care of replacing the empty string with a 1 + const rawDC = dpts.shift() || '1'; + const tempDC = rawDC.replace(/\D/g, ''); + // Rejoin all remaining parts + let remains = dpts.join('d'); - // Manual Parsing for custom roll types - let manualParse = false; - if (rawDC.endsWith('cwo')) { - // CWOD dice parsing - rollType = 'cwod'; - manualParse = true; + // Manual Parsing for custom roll types + let manualParse = false; + if (rawDC.endsWith('cwo')) { + // CWOD dice parsing + rollType = 'cwod'; + manualParse = true; - // Get CWOD parts, setting count and getting difficulty - const cwodParts = rollStr.split('cwod'); - rollConf.dieCount = parseInt(cwodParts[0] || '1'); - rollConf.dieSize = 10; + // Get CWOD parts, setting count and getting difficulty + const cwodParts = rollStr.split('cwod'); + rollConf.dieCount = parseInt(cwodParts[0] || '1'); + rollConf.dieSize = 10; - // Use critScore to set the difficulty - rollConf.critScore.on = true; - const difficulty = parseInt(cwodParts[1] || '10'); - for (let i = difficulty; i <= rollConf.dieSize; i++) { - loggingEnabled && log(LT.LOG, `handling cwod ${rollStr} | Parsing difficulty ${i}`); - rollConf.critScore.range.push(i); - } - } else if (rawDC.endsWith('ova')) { - // OVA dice parsing - rollType = 'ova'; - manualParse = true; + // Use critScore to set the difficulty + rollConf.critScore.on = true; + const difficulty = parseInt(cwodParts[1] || '10'); + for (let i = difficulty; i <= rollConf.dieSize; i++) { + loggingEnabled && log(LT.LOG, `handling cwod ${rollStr} | Parsing difficulty ${i}`); + rollConf.critScore.range.push(i); + } + } else if (rawDC.endsWith('ova')) { + // OVA dice parsing + rollType = 'ova'; + manualParse = true; - // Get CWOD parts, setting count and getting difficulty - const ovaParts = rollStr.split('ovad'); - rollConf.dieCount = parseInt(ovaParts[0] || '1'); - rollConf.dieSize = parseInt(ovaParts[1] || '6'); - } else if (remains.startsWith('f')) { - // fate dice setup - rollType = 'fate'; - rollConf.dieCount = parseInt(tempDC); - // dieSize set to 1 as 1 is max face value, a six sided die is used internally - rollConf.dieSize = 1; + // Get CWOD parts, setting count and getting difficulty + const ovaParts = rollStr.split('ovad'); + rollConf.dieCount = parseInt(ovaParts[0] || '1'); + rollConf.dieSize = parseInt(ovaParts[1] || '6'); + } else if (remains.startsWith('f')) { + // fate dice setup + rollType = 'fate'; + rollConf.dieCount = parseInt(tempDC); + // dieSize set to 1 as 1 is max face value, a six sided die is used internally + rollConf.dieSize = 1; - // remove F from the remains - remains = remains.slice(1); - } else { - // roll20 dice setup - rollType = 'roll20'; - rollConf.dieCount = parseInt(tempDC); + // remove F from the remains + remains = remains.slice(1); + } else { + // roll20 dice setup + rollType = 'roll20'; + rollConf.dieCount = parseInt(tempDC); - // Finds the end of the die size/beginnning of the additional options - let afterDieIdx = dpts[0].search(/\D/); - if (afterDieIdx === -1) { - afterDieIdx = dpts[0].length; - } + // Finds the end of the die size/beginnning of the additional options + let afterDieIdx = dpts[0].search(/\D/); + if (afterDieIdx === -1) { + afterDieIdx = dpts[0].length; + } - // Get the die size out of the remains and into the rollConf - rollConf.dieSize = parseInt(remains.slice(0, afterDieIdx)); - remains = remains.slice(afterDieIdx); - } + // Get the die size out of the remains and into the rollConf + rollConf.dieSize = parseInt(remains.slice(0, afterDieIdx)); + remains = remains.slice(afterDieIdx); + } - if (!rollConf.dieCount || !rollConf.dieSize) { - throw new Error('YouNeedAD'); - } + if (!rollConf.dieCount || !rollConf.dieSize) { + throw new Error('YouNeedAD'); + } - loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`); - loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`); + loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`); + loggingEnabled && log(LT.LOG, `Handling ${rollType} ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`); - // Finish parsing the roll - if (!manualParse && remains.length > 0) { - // Determine if the first item is a drop, and if it is, add the d back in - if (remains.search(/\D/) !== 0 || remains.indexOf('l') === 0 || remains.indexOf('h') === 0) { - remains = `d${remains}`; - } + // Finish parsing the roll + if (!manualParse && remains.length > 0) { + // Determine if the first item is a drop, and if it is, add the d back in + if (remains.search(/\D/) !== 0 || remains.indexOf('l') === 0 || remains.indexOf('h') === 0) { + remains = `d${remains}`; + } - // Loop until all remaining args are parsed - while (remains.length > 0) { - 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) { - afterSepIdx = remains.length; - } - // Save the rule name to tSep and remove it from remains - const tSep = remains.slice(0, afterSepIdx); - remains = remains.slice(afterSepIdx); - // Find the next non-number in the remains to be able to cut out the count/num - let afterNumIdx = remains.search(/\D/); - if (afterNumIdx < 0) { - afterNumIdx = remains.length; - } - // Save the count/num to tNum leaving it in remains for the time being - const tNum = parseInt(remains.slice(0, afterNumIdx)); + // Loop until all remaining args are parsed + while (remains.length > 0) { + 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) { + afterSepIdx = remains.length; + } + // Save the rule name to tSep and remove it from remains + const tSep = remains.slice(0, afterSepIdx); + remains = remains.slice(afterSepIdx); + // Find the next non-number in the remains to be able to cut out the count/num + let afterNumIdx = remains.search(/\D/); + if (afterNumIdx < 0) { + afterNumIdx = remains.length; + } + // Save the count/num to tNum leaving it in remains for the time being + const tNum = parseInt(remains.slice(0, afterNumIdx)); - // Switch on rule name - switch (tSep) { - case 'dl': - case 'd': - // Configure Drop (Lowest) - rollConf.drop.on = true; - rollConf.drop.count = tNum; - break; - case 'kh': - case 'k': - // Configure Keep (Highest) - rollConf.keep.on = true; - rollConf.keep.count = tNum; - break; - case 'dh': - // Configure Drop (Highest) - rollConf.dropHigh.on = true; - rollConf.dropHigh.count = tNum; - break; - case 'kl': - // Configure Keep (Lowest) - rollConf.keepLow.on = true; - rollConf.keepLow.count = tNum; - break; - case 'ro': - case 'ro=': - rollConf.reroll.once = true; - // 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) - rollConf.reroll.on = true; - rollConf.reroll.nums.push(tNum); - break; - case 'ro>': - rollConf.reroll.once = true; - // 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}`); - rollConf.reroll.nums.push(i); - } - break; - case 'ro<': - rollConf.reroll.once = true; - // 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}`); - rollConf.reroll.nums.push(i); - } - break; - case 'cs': - case 'cs=': - // Configure CritScore for one number (this can happen multiple times) - rollConf.critScore.on = true; - rollConf.critScore.range.push(tNum); - break; - case 'cs>': - // 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}`); - rollConf.critScore.range.push(i); - } - break; - case 'cs<': - // 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}`); - rollConf.critScore.range.push(i); - } - break; - case 'cf': - case 'cf=': - // Configure CritFail for one number (this can happen multiple times) - rollConf.critFail.on = true; - rollConf.critFail.range.push(tNum); - break; - case 'cf>': - // 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}`); - rollConf.critFail.range.push(i); - } - break; - case 'cf<': - // 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}`); - rollConf.critFail.range.push(i); - } - break; - case '!': - case '!o': - case '!p': - case '!!': - // Configure Exploding - rollConf.exploding.on = true; - if (afterNumIdx > 0) { - // User gave a number to explode on, save it - rollConf.exploding.nums.push(tNum); - } else { - // User did not give number, use cs - afterNumIdx = 1; - } - break; - case '!=': - case '!o=': - case '!p=': - case '!!=': - // Configure Exploding (this can happen multiple times) - rollConf.exploding.on = true; - rollConf.exploding.nums.push(tNum); - break; - case '!>': - case '!o>': - case '!p>': - case '!!>': - // 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}`); - rollConf.exploding.nums.push(i); - } - break; - case '!<': - case '!o<': - case '!p<': - case '!!<': - // 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}`); - rollConf.exploding.nums.push(i); - } - break; - default: - // Throw error immediately if unknown op is encountered - throw new Error(`UnknownOperation_${tSep}`); - } + // Switch on rule name + switch (tSep) { + case 'dl': + case 'd': + // Configure Drop (Lowest) + rollConf.drop.on = true; + rollConf.drop.count = tNum; + break; + case 'kh': + case 'k': + // Configure Keep (Highest) + rollConf.keep.on = true; + rollConf.keep.count = tNum; + break; + case 'dh': + // Configure Drop (Highest) + rollConf.dropHigh.on = true; + rollConf.dropHigh.count = tNum; + break; + case 'kl': + // Configure Keep (Lowest) + rollConf.keepLow.on = true; + rollConf.keepLow.count = tNum; + break; + case 'ro': + case 'ro=': + rollConf.reroll.once = true; + // 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) + rollConf.reroll.on = true; + rollConf.reroll.nums.push(tNum); + break; + case 'ro>': + rollConf.reroll.once = true; + // 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}`); + rollConf.reroll.nums.push(i); + } + break; + case 'ro<': + rollConf.reroll.once = true; + // 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}`); + rollConf.reroll.nums.push(i); + } + break; + case 'cs': + case 'cs=': + // Configure CritScore for one number (this can happen multiple times) + rollConf.critScore.on = true; + rollConf.critScore.range.push(tNum); + break; + case 'cs>': + // 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}`); + rollConf.critScore.range.push(i); + } + break; + case 'cs<': + // 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}`); + rollConf.critScore.range.push(i); + } + break; + case 'cf': + case 'cf=': + // Configure CritFail for one number (this can happen multiple times) + rollConf.critFail.on = true; + rollConf.critFail.range.push(tNum); + break; + case 'cf>': + // 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}`); + rollConf.critFail.range.push(i); + } + break; + case 'cf<': + // 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}`); + rollConf.critFail.range.push(i); + } + break; + case '!': + case '!o': + case '!p': + case '!!': + // Configure Exploding + rollConf.exploding.on = true; + if (afterNumIdx > 0) { + // User gave a number to explode on, save it + rollConf.exploding.nums.push(tNum); + } else { + // User did not give number, use cs + afterNumIdx = 1; + } + break; + case '!=': + case '!o=': + case '!p=': + case '!!=': + // Configure Exploding (this can happen multiple times) + rollConf.exploding.on = true; + rollConf.exploding.nums.push(tNum); + break; + case '!>': + case '!o>': + case '!p>': + case '!!>': + // 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}`); + rollConf.exploding.nums.push(i); + } + break; + case '!<': + case '!o<': + case '!p<': + case '!!<': + // 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}`); + rollConf.exploding.nums.push(i); + } + break; + default: + // Throw error immediately if unknown op is encountered + throw new Error(`UnknownOperation_${tSep}`); + } - // Exploding flags get set in their own switch statement to avoid weird duplicated code - switch (tSep) { - case '!o': - case '!o=': - case '!o>': - case '!o<': - rollConf.exploding.once = true; - break; - case '!p': - case '!p=': - case '!p>': - case '!p<': - rollConf.exploding.penetrating = true; - break; - case '!!': - case '!!=': - case '!!>': - case '!!<': - rollConf.exploding.compounding = true; - break; - } + // Exploding flags get set in their own switch statement to avoid weird duplicated code + switch (tSep) { + case '!o': + case '!o=': + case '!o>': + case '!o<': + rollConf.exploding.once = true; + break; + case '!p': + case '!p=': + case '!p>': + case '!p<': + rollConf.exploding.penetrating = true; + break; + case '!!': + case '!!=': + case '!!>': + case '!!<': + rollConf.exploding.compounding = true; + break; + } - // Finally slice off everything else parsed this loop - remains = remains.slice(afterNumIdx); - } - } + // Finally slice off everything else parsed this loop + remains = remains.slice(afterNumIdx); + } + } - // Verify the parse, throwing errors for every invalid config - if (rollConf.dieCount < 0) { - throw new Error('NoZerosAllowed_base'); - } - if (rollConf.dieCount === 0 || rollConf.dieSize === 0) { - throw new Error('NoZerosAllowed_base'); - } - // 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}`); - if (e) { - dkdkCnt++; - } - }); - if (dkdkCnt > 1) { - throw new Error('FormattingError_dk'); - } - if (rollConf.drop.on && rollConf.drop.count === 0) { - throw new Error('NoZerosAllowed_drop'); - } - if (rollConf.keep.on && rollConf.keep.count === 0) { - throw new Error('NoZerosAllowed_keep'); - } - if (rollConf.dropHigh.on && rollConf.dropHigh.count === 0) { - throw new Error('NoZerosAllowed_dropHigh'); - } - if (rollConf.keepLow.on && rollConf.keepLow.count === 0) { - throw new Error('NoZerosAllowed_keepLow'); - } - if (rollConf.reroll.on && rollConf.reroll.nums.indexOf(0) >= 0) { - throw new Error('NoZerosAllowed_reroll'); - } + // Verify the parse, throwing errors for every invalid config + if (rollConf.dieCount < 0) { + throw new Error('NoZerosAllowed_base'); + } + if (rollConf.dieCount === 0 || rollConf.dieSize === 0) { + throw new Error('NoZerosAllowed_base'); + } + // 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}`); + if (e) { + dkdkCnt++; + } + }); + if (dkdkCnt > 1) { + throw new Error('FormattingError_dk'); + } + if (rollConf.drop.on && rollConf.drop.count === 0) { + throw new Error('NoZerosAllowed_drop'); + } + if (rollConf.keep.on && rollConf.keep.count === 0) { + throw new Error('NoZerosAllowed_keep'); + } + if (rollConf.dropHigh.on && rollConf.dropHigh.count === 0) { + throw new Error('NoZerosAllowed_dropHigh'); + } + if (rollConf.keepLow.on && rollConf.keepLow.count === 0) { + throw new Error('NoZerosAllowed_keepLow'); + } + if (rollConf.reroll.on && rollConf.reroll.nums.indexOf(0) >= 0) { + throw new Error('NoZerosAllowed_reroll'); + } - // Roll the roll - const rollSet = []; - /* Roll will contain objects of the following format: + // Roll the roll + const rollSet = []; + /* Roll will contain objects of the following format: * { * origidx: 0, * roll: 0, @@ -398,271 +398,271 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea * } */ - // Initialize a templet rollSet to copy multiple times - const templateRoll: RollSet = { - type: rollType, - origidx: 0, - roll: 0, - dropped: false, - rerolled: false, - exploding: false, - critHit: false, - critFail: false, - }; + // Initialize a templet rollSet to copy multiple times + const templateRoll: RollSet = { + type: rollType, + origidx: 0, + roll: 0, + dropped: false, + rerolled: false, + exploding: false, + critHit: false, + critFail: false, + }; - // Begin counting the number of loops to prevent from getting into an infinite loop - let loopCount = 0; + // Begin counting the number of loops to prevent from getting into an infinite loop + let loopCount = 0; - // 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)}`); - // If loopCount gets too high, stop trying to calculate infinity - if (loopCount > config.limits.maxLoops) { - throw new Error('MaxLoopsExceeded'); - } + // 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)}`); + // If loopCount gets too high, stop trying to calculate infinity + if (loopCount > config.limits.maxLoops) { + throw new Error('MaxLoopsExceeded'); + } - // Copy the template to fill out for this iteration - const rolling = JSON.parse(JSON.stringify(templateRoll)); - // If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll - rolling.roll = rollType === 'fate' ? genFateRoll(maximiseRoll, nominalRoll) : genRoll(rollConf.dieSize, maximiseRoll, nominalRoll); - // Set origidx of roll - rolling.origidx = i; + // Copy the template to fill out for this iteration + const rolling = JSON.parse(JSON.stringify(templateRoll)); + // If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll + rolling.roll = rollType === 'fate' ? genFateRoll(maximiseRoll, nominalRoll) : genRoll(rollConf.dieSize, maximiseRoll, nominalRoll); + // Set origidx of roll + rolling.origidx = i; - // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size - if (rollConf.critScore.on && rollConf.critScore.range.indexOf(rolling.roll) >= 0) { - rolling.critHit = true; - } else if (!rollConf.critScore.on) { - rolling.critHit = rolling.roll === rollConf.dieSize; - } - // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1 - if (rollConf.critFail.on && rollConf.critFail.range.indexOf(rolling.roll) >= 0) { - rolling.critFail = true; - } else if (!rollConf.critFail.on) { - if (rollType === 'fate') { - rolling.critFail = rolling.roll === -1; - } else { - rolling.critFail = rolling.roll === 1; - } - } + // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size + if (rollConf.critScore.on && rollConf.critScore.range.indexOf(rolling.roll) >= 0) { + rolling.critHit = true; + } else if (!rollConf.critScore.on) { + rolling.critHit = rolling.roll === rollConf.dieSize; + } + // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1 + if (rollConf.critFail.on && rollConf.critFail.range.indexOf(rolling.roll) >= 0) { + rolling.critFail = true; + } else if (!rollConf.critFail.on) { + if (rollType === 'fate') { + rolling.critFail = rolling.roll === -1; + } else { + rolling.critFail = rolling.roll === 1; + } + } - // Push the newly created roll and loop again - rollSet.push(rolling); - loopCount++; - } + // 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])}`); - // If loopCount gets too high, stop trying to calculate infinity - if (loopCount > config.limits.maxLoops) { - throw new Error('MaxLoopsExceeded'); - } + // 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])}`); + // If loopCount gets too high, stop trying to calculate infinity + 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)) { - rollSet[i].rerolled = true; + // 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)) { + rollSet[i].rerolled = true; - // Copy the template to fill out for this iteration - const newReroll = JSON.parse(JSON.stringify(templateRoll)); - // If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll - newReroll.roll = genRoll(rollConf.dieSize, maximiseRoll, nominalRoll); + // Copy the template to fill out for this iteration + const newReroll = JSON.parse(JSON.stringify(templateRoll)); + // If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll + newReroll.roll = genRoll(rollConf.dieSize, maximiseRoll, nominalRoll); - // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size - if (rollConf.critScore.on && rollConf.critScore.range.indexOf(newReroll.roll) >= 0) { - newReroll.critHit = true; - } else if (!rollConf.critScore.on) { - newReroll.critHit = newReroll.roll === rollConf.dieSize; - } - // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1 - if (rollConf.critFail.on && rollConf.critFail.range.indexOf(newReroll.roll) >= 0) { - newReroll.critFail = true; - } else if (!rollConf.critFail.on) { - newReroll.critFail = newReroll.roll === 1; - } + // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size + if (rollConf.critScore.on && rollConf.critScore.range.indexOf(newReroll.roll) >= 0) { + newReroll.critHit = true; + } else if (!rollConf.critScore.on) { + newReroll.critHit = newReroll.roll === rollConf.dieSize; + } + // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1 + if (rollConf.critFail.on && rollConf.critFail.range.indexOf(newReroll.roll) >= 0) { + newReroll.critFail = true; + } else if (!rollConf.critFail.on) { + newReroll.critFail = newReroll.roll === 1; + } - // 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.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 - // If it exploded, we keep both, so no flags need to be set + // 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.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 + // If it exploded, we keep both, so no flags need to be set - // Copy the template to fill out for this iteration - const newExplodingRoll = JSON.parse(JSON.stringify(templateRoll)); - // If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll - newExplodingRoll.roll = genRoll(rollConf.dieSize, maximiseRoll, nominalRoll); - // Always mark this roll as exploding - newExplodingRoll.exploding = true; + // Copy the template to fill out for this iteration + const newExplodingRoll = JSON.parse(JSON.stringify(templateRoll)); + // If maximiseRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll + newExplodingRoll.roll = genRoll(rollConf.dieSize, maximiseRoll, nominalRoll); + // Always mark this roll as exploding + newExplodingRoll.exploding = true; - // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size - if (rollConf.critScore.on && rollConf.critScore.range.indexOf(newExplodingRoll.roll) >= 0) { - newExplodingRoll.critHit = true; - } else if (!rollConf.critScore.on) { - newExplodingRoll.critHit = newExplodingRoll.roll === rollConf.dieSize; - } - // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1 - if (rollConf.critFail.on && rollConf.critFail.range.indexOf(newExplodingRoll.roll) >= 0) { - newExplodingRoll.critFail = true; - } else if (!rollConf.critFail.on) { - newExplodingRoll.critFail = newExplodingRoll.roll === 1; - } + // If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size + if (rollConf.critScore.on && rollConf.critScore.range.indexOf(newExplodingRoll.roll) >= 0) { + newExplodingRoll.critHit = true; + } else if (!rollConf.critScore.on) { + newExplodingRoll.critHit = newExplodingRoll.roll === rollConf.dieSize; + } + // If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1 + if (rollConf.critFail.on && rollConf.critFail.range.indexOf(newExplodingRoll.roll) >= 0) { + newExplodingRoll.critFail = true; + } else if (!rollConf.critFail.on) { + newExplodingRoll.critFail = newExplodingRoll.roll === 1; + } - // Slot this new roll in after the current iteration so it can be processed in the next loop - rollSet.splice(i + 1, 0, newExplodingRoll); - } + // 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++; - } - } + 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)}`); - // If loopCount gets too high, stop trying to calculate infinity - if (loopCount > config.limits.maxLoops) { - throw new Error('MaxLoopsExceeded'); - } + // 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)}`); + // If loopCount gets too high, stop trying to calculate infinity + if (loopCount > config.limits.maxLoops) { + throw new Error('MaxLoopsExceeded'); + } - // If the die was from an explosion, decrement it by one - if (penRoll.exploding) { - penRoll.roll--; - } + // If the die was from an explosion, decrement it by one + if (penRoll.exploding) { + penRoll.roll--; + } - loopCount++; - } - } + 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])}`); - // If loopCount gets too high, stop trying to calculate infinity - if (loopCount > config.limits.maxLoops) { - throw new Error('MaxLoopsExceeded'); - } + // 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])}`); + // If loopCount gets too high, stop trying to calculate infinity + if (loopCount > config.limits.maxLoops) { + throw new Error('MaxLoopsExceeded'); + } - // Compound the exploding rolls, including the exploding flag and - if (rollSet[i].exploding) { - rollSet[i - 1].roll = rollSet[i - 1].roll + rollSet[i].roll; - rollSet[i - 1].exploding = true; - rollSet[i - 1].critFail = rollSet[i - 1].critFail || rollSet[i].critFail; - rollSet[i - 1].critHit = rollSet[i - 1].critHit || rollSet[i].critHit; - rollSet.splice(i, 1); - i--; - } + // Compound the exploding rolls, including the exploding flag and + if (rollSet[i].exploding) { + rollSet[i - 1].roll = rollSet[i - 1].roll + rollSet[i].roll; + rollSet[i - 1].exploding = true; + rollSet[i - 1].critFail = rollSet[i - 1].critFail || rollSet[i].critFail; + rollSet[i - 1].critHit = rollSet[i - 1].critHit || rollSet[i].critHit; + rollSet.splice(i, 1); + i--; + } - loopCount++; - } - } + loopCount++; + } + } - // If we need to handle the drop/keep flags - if (dkdkCnt > 0) { - // Count how many rerolled dice there are if the reroll flag was on - let rerollCount = 0; - if (rollConf.reroll.on) { - for (let j = 0; j < rollSet.length; j++) { - // If loopCount gets too high, stop trying to calculate infinity - if (loopCount > config.limits.maxLoops) { - throw new Error('MaxLoopsExceeded'); - } + // If we need to handle the drop/keep flags + if (dkdkCnt > 0) { + // Count how many rerolled dice there are if the reroll flag was on + let rerollCount = 0; + if (rollConf.reroll.on) { + for (let j = 0; j < rollSet.length; j++) { + // If loopCount gets too high, stop trying to calculate infinity + if (loopCount > config.limits.maxLoops) { + throw new Error('MaxLoopsExceeded'); + } - loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`); - rollSet[j].origidx = j; + loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`); + rollSet[j].origidx = j; - if (rollSet[j].rerolled) { - rerollCount++; - } + if (rollSet[j].rerolled) { + rerollCount++; + } - loopCount++; - } - } + loopCount++; + } + } - // Order the rolls from least to greatest (by RollSet.roll) - rollSet.sort(compareRolls); + // Order the rolls from least to greatest (by RollSet.roll) + rollSet.sort(compareRolls); - // Determine how many valid rolls there are to drop from (may not be equal to dieCount due to exploding) - const validRolls = rollSet.length - rerollCount; - let dropCount = 0; + // Determine how many valid rolls there are to drop from (may not be equal to dieCount due to exploding) + const validRolls = rollSet.length - rerollCount; + let dropCount = 0; - // For normal drop and keep, simple subtraction is enough to determine how many to drop - // Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop - if (rollConf.drop.on) { - dropCount = rollConf.drop.count; - if (dropCount > validRolls) { - dropCount = validRolls; - } - } else if (rollConf.keep.on) { - dropCount = validRolls - rollConf.keep.count; - if (dropCount < 0) { - dropCount = 0; - } - } // For inverted drop and keep, order must be flipped to greatest to least before the simple subtraction can determine how many to drop - // Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop - else if (rollConf.dropHigh.on) { - rollSet.reverse(); - dropCount = rollConf.dropHigh.count; - if (dropCount > validRolls) { - dropCount = validRolls; - } - } else if (rollConf.keepLow.on) { - rollSet.reverse(); - dropCount = validRolls - rollConf.keepLow.count; - if (dropCount < 0) { - dropCount = 0; - } - } + // For normal drop and keep, simple subtraction is enough to determine how many to drop + // Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop + if (rollConf.drop.on) { + dropCount = rollConf.drop.count; + if (dropCount > validRolls) { + dropCount = validRolls; + } + } else if (rollConf.keep.on) { + dropCount = validRolls - rollConf.keep.count; + if (dropCount < 0) { + dropCount = 0; + } + } // For inverted drop and keep, order must be flipped to greatest to least before the simple subtraction can determine how many to drop + // Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop + else if (rollConf.dropHigh.on) { + rollSet.reverse(); + dropCount = rollConf.dropHigh.count; + if (dropCount > validRolls) { + dropCount = validRolls; + } + } else if (rollConf.keepLow.on) { + rollSet.reverse(); + dropCount = validRolls - rollConf.keepLow.count; + if (dropCount < 0) { + dropCount = 0; + } + } - // Now its time to drop all dice needed - let i = 0; - while (dropCount > 0 && i < rollSet.length) { - // If loopCount gets too high, stop trying to calculate infinity - if (loopCount > config.limits.maxLoops) { - throw new Error('MaxLoopsExceeded'); - } + // Now its time to drop all dice needed + let i = 0; + while (dropCount > 0 && i < rollSet.length) { + // If loopCount gets too high, stop trying to calculate infinity + if (loopCount > config.limits.maxLoops) { + throw new Error('MaxLoopsExceeded'); + } - loggingEnabled && log(LT.LOG, `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++; - } + loggingEnabled && log(LT.LOG, `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 - rollSet.sort(compareOrigidx); - } + // Finally, return the rollSet to its original order + rollSet.sort(compareOrigidx); + } - // Handle OVA dropping/keeping - if (rollType === 'ova') { - // Make "empty" vals array to easily sum up which die value is the greatest - const rollVals: Array = new Array(rollConf.dieSize).fill(0); + // Handle OVA dropping/keeping + if (rollType === 'ova') { + // Make "empty" vals array to easily sum up which die value is the greatest + const rollVals: Array = new Array(rollConf.dieSize).fill(0); - // Sum up all rolls - for (const ovaRoll of rollSet) { - loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | incrementing rollVals for ${ovaRoll}`); - rollVals[ovaRoll.roll - 1] += ovaRoll.roll; - } + // Sum up all rolls + for (const ovaRoll of rollSet) { + loggingEnabled && log(LT.LOG, `handling ${rollType} ${rollStr} | incrementing rollVals for ${ovaRoll}`); + rollVals[ovaRoll.roll - 1] += ovaRoll.roll; + } - // Find max value, using lastIndexOf to use the greatest die size max in case of duplicate maximums - const maxRoll = rollVals.lastIndexOf(Math.max(...rollVals)) + 1; + // Find max value, using lastIndexOf to use the greatest die size max in case of duplicate maximums + const maxRoll = rollVals.lastIndexOf(Math.max(...rollVals)) + 1; - // 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}`); - if (ovaRoll.roll !== maxRoll) { - ovaRoll.dropped = true; - ovaRoll.critFail = false; - ovaRoll.critHit = false; - } - } - } + // 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}`); + if (ovaRoll.roll !== maxRoll) { + ovaRoll.dropped = true; + ovaRoll.critFail = false; + ovaRoll.critHit = false; + } + } + } - return rollSet; + return rollSet; }; diff --git a/src/solver/solver.d.ts b/src/solver/solver.d.ts index 194e09e..345bf10 100644 --- a/src/solver/solver.d.ts +++ b/src/solver/solver.d.ts @@ -4,99 +4,99 @@ export type RollType = '' | 'roll20' | 'fate' | 'cwod' | 'ova'; // RollSet is used to preserve all information about a calculated roll export type RollSet = { - type: RollType; - origidx: number; - roll: number; - dropped: boolean; - rerolled: boolean; - exploding: boolean; - critHit: boolean; - critFail: boolean; + type: RollType; + origidx: number; + roll: number; + dropped: boolean; + rerolled: boolean; + exploding: boolean; + critHit: boolean; + critFail: boolean; }; // SolvedStep is used to preserve information while math is being performed on the roll export type SolvedStep = { - total: number; - details: string; - containsCrit: boolean; - containsFail: boolean; + total: number; + details: string; + containsCrit: boolean; + containsFail: boolean; }; // ReturnData is the temporary internal type used before getting turned into SolvedRoll export type ReturnData = { - rollTotal: number; - rollPostFormat: string; - rollDetails: string; - containsCrit: boolean; - containsFail: boolean; - initConfig: string; + rollTotal: number; + rollPostFormat: string; + rollDetails: string; + containsCrit: boolean; + containsFail: boolean; + initConfig: string; }; // CountDetails is the object holding the count data for creating the Count Embed export type CountDetails = { - total: number; - successful: number; - failed: number; - rerolled: number; - dropped: number; - exploded: number; + total: number; + successful: number; + failed: number; + rerolled: number; + dropped: number; + exploded: number; }; // RollFormat is the return structure for the rollFormatter export type RollFormat = { - solvedStep: SolvedStep; - countDetails: CountDetails; + solvedStep: SolvedStep; + countDetails: CountDetails; }; // SolvedRoll is the complete solved and formatted roll, or the error said roll created export type SolvedRoll = { - error: boolean; - errorMsg: string; - errorCode: string; - line1: string; - line2: string; - line3: string; - counts: CountDetails; + error: boolean; + errorMsg: string; + errorCode: string; + line1: string; + line2: string; + line3: string; + counts: CountDetails; }; // RollConf is used by the roll20 setup export type RollConf = { - dieCount: number; - dieSize: number; - drop: { - on: boolean; - count: number; - }; - keep: { - on: boolean; - count: number; - }; - dropHigh: { - on: boolean; - count: number; - }; - keepLow: { - on: boolean; - count: number; - }; - reroll: { - on: boolean; - once: boolean; - nums: number[]; - }; - critScore: { - on: boolean; - range: number[]; - }; - critFail: { - on: boolean; - range: number[]; - }; - exploding: { - on: boolean; - once: boolean; - compounding: boolean; - penetrating: boolean; - nums: number[]; - }; + dieCount: number; + dieSize: number; + drop: { + on: boolean; + count: number; + }; + keep: { + on: boolean; + count: number; + }; + dropHigh: { + on: boolean; + count: number; + }; + keepLow: { + on: boolean; + count: number; + }; + reroll: { + on: boolean; + once: boolean; + nums: number[]; + }; + critScore: { + on: boolean; + range: number[]; + }; + critFail: { + on: boolean; + range: number[]; + }; + exploding: { + on: boolean; + once: boolean; + compounding: boolean; + penetrating: boolean; + nums: number[]; + }; }; diff --git a/src/solver/solver.ts b/src/solver/solver.ts index 7a186af..f1f76e0 100644 --- a/src/solver/solver.ts +++ b/src/solver/solver.ts @@ -5,9 +5,9 @@ */ import { - log, - // Log4Deno deps - LT, + log, + // Log4Deno deps + LT, } from '../../deps.ts'; import { SolvedStep } from './solver.d.ts'; @@ -16,188 +16,188 @@ import { loggingEnabled } from './rollUtils.ts'; // fullSolver(conf, wrapDetails) returns one condensed SolvedStep // fullSolver is a function that recursively solves the full roll and math export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: boolean): SolvedStep => { - // Initialize PEMDAS - const signs = ['^', '*', '/', '%', '+', '-']; - const stepSolve = { - total: 0, - details: '', - containsCrit: false, - containsFail: false, - }; + // Initialize PEMDAS + const signs = ['^', '*', '/', '%', '+', '-']; + const stepSolve = { + total: 0, + details: '', + containsCrit: false, + containsFail: false, + }; - // If entering with a single number, note it now - let singleNum = false; - if (conf.length === 1) { - singleNum = true; - } + // If entering with a single number, note it now + let singleNum = false; + if (conf.length === 1) { + singleNum = true; + } - // Evaluate all parenthesis - while (conf.indexOf('(') > -1) { - loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`); - // Get first open parenthesis - const openParen = conf.indexOf('('); - let closeParen = -1; - let nextParen = 0; + // Evaluate all parenthesis + while (conf.indexOf('(') > -1) { + loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`); + // Get first open parenthesis + const openParen = conf.indexOf('('); + let closeParen = -1; + let nextParen = 0; - // Using nextParen to count the opening/closing parens, find the matching paren to openParen above - for (let i = openParen; i < conf.length; i++) { - loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`); - // If we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one - if (conf[i] === '(') { - nextParen++; - } else if (conf[i] === ')') { - nextParen--; - } + // Using nextParen to count the opening/closing parens, find the matching paren to openParen above + for (let i = openParen; i < conf.length; i++) { + loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for matching ) openIdx: ${openParen} checking: ${i}`); + // If we hit an open, add one (this includes the openParen we start with), if we hit a close, subtract one + if (conf[i] === '(') { + nextParen++; + } else if (conf[i] === ')') { + nextParen--; + } - // When nextParen reaches 0 again, we will have found the matching closing parenthesis and can safely exit the for loop - if (nextParen === 0) { - closeParen = i; - break; - } - } + // When nextParen reaches 0 again, we will have found the matching closing parenthesis and can safely exit the for loop + if (nextParen === 0) { + closeParen = i; + break; + } + } - // Make sure we did find the correct closing paren, if not, error out now - if (closeParen === -1 || closeParen < openParen) { - throw new Error('UnbalancedParens'); - } + // Make sure we did find the correct closing paren, if not, error out now + if (closeParen === -1 || closeParen < openParen) { + throw new Error('UnbalancedParens'); + } - // Call the solver on the items between openParen and closeParen (excluding the parens) - const parenSolve = fullSolver(conf.slice(openParen + 1, closeParen), true); - // Replace the itemes between openParen and closeParen (including the parens) with its solved equilvalent - conf.splice(openParen, closeParen - openParen + 1, parenSolve); + // Call the solver on the items between openParen and closeParen (excluding the parens) + const parenSolve = fullSolver(conf.slice(openParen + 1, closeParen), true); + // Replace the itemes between openParen and closeParen (including the parens) with its solved equilvalent + conf.splice(openParen, closeParen - openParen + 1, parenSolve); - // Determing if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8) - // insertedMult flags if there was a multiplication sign inserted before the parens - let insertedMult = false; - // Check if a number was directly before openParen and slip in the "*" if needed - if (((openParen - 1) > -1) && (signs.indexOf(conf[openParen - 1].toString()) === -1)) { - insertedMult = true; - conf.splice(openParen, 0, '*'); - } - // Check if a number is directly after closeParen and slip in the "*" if needed - if (!insertedMult && (((openParen + 1) < conf.length) && (signs.indexOf(conf[openParen + 1].toString()) === -1))) { - conf.splice(openParen + 1, 0, '*'); - } else if (insertedMult && (((openParen + 2) < conf.length) && (signs.indexOf(conf[openParen + 2].toString()) === -1))) { - // insertedMult is utilized here to let us account for an additional item being inserted into the array (the "*" from before openParn) - conf.splice(openParen + 2, 0, '*'); - } - } + // Determing if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8) + // insertedMult flags if there was a multiplication sign inserted before the parens + let insertedMult = false; + // Check if a number was directly before openParen and slip in the "*" if needed + if (((openParen - 1) > -1) && (signs.indexOf(conf[openParen - 1].toString()) === -1)) { + insertedMult = true; + conf.splice(openParen, 0, '*'); + } + // Check if a number is directly after closeParen and slip in the "*" if needed + if (!insertedMult && (((openParen + 1) < conf.length) && (signs.indexOf(conf[openParen + 1].toString()) === -1))) { + conf.splice(openParen + 1, 0, '*'); + } else if (insertedMult && (((openParen + 2) < conf.length) && (signs.indexOf(conf[openParen + 2].toString()) === -1))) { + // insertedMult is utilized here to let us account for an additional item being inserted into the array (the "*" from before openParn) + conf.splice(openParen + 2, 0, '*'); + } + } - // Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest) - const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']]; - allCurOps.forEach((curOps) => { - loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`); - // Iterate thru all operators/operands in the conf - for (let i = 0; i < conf.length; i++) { - loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`); - // Check if the current index is in the active teir of operators - if (curOps.indexOf(conf[i].toString()) > -1) { - // Grab the operands from before and after the operator - const operand1 = conf[i - 1]; - const operand2 = conf[i + 1]; - // Init temp math to NaN to catch bad parsing - let oper1 = NaN; - let oper2 = NaN; - const subStepSolve = { - total: NaN, - details: '', - containsCrit: false, - containsFail: false, - }; + // Evaluate all EMMDAS by looping thru each teir of operators (exponential is the higehest teir, addition/subtraction the lowest) + const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']]; + allCurOps.forEach((curOps) => { + loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`); + // Iterate thru all operators/operands in the conf + for (let i = 0; i < conf.length; i++) { + loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)} | Checking ${JSON.stringify(conf[i])}`); + // Check if the current index is in the active teir of operators + if (curOps.indexOf(conf[i].toString()) > -1) { + // Grab the operands from before and after the operator + const operand1 = conf[i - 1]; + const operand2 = conf[i + 1]; + // Init temp math to NaN to catch bad parsing + let oper1 = NaN; + let oper2 = NaN; + const subStepSolve = { + total: NaN, + details: '', + containsCrit: false, + containsFail: false, + }; - // If operand1 is a SolvedStep, populate our subStepSolve with its details and crit/fail flags - if (typeof operand1 === 'object') { - oper1 = operand1.total; - subStepSolve.details = `${operand1.details}\\${conf[i]}`; - subStepSolve.containsCrit = operand1.containsCrit; - subStepSolve.containsFail = operand1.containsFail; - } else { - // else parse it as a number and add it to the subStep details - if (operand1 || operand1 == 0) { - oper1 = parseFloat(operand1.toString()); - subStepSolve.details = `${oper1.toString()}\\${conf[i]}`; - } - } + // If operand1 is a SolvedStep, populate our subStepSolve with its details and crit/fail flags + if (typeof operand1 === 'object') { + oper1 = operand1.total; + subStepSolve.details = `${operand1.details}\\${conf[i]}`; + subStepSolve.containsCrit = operand1.containsCrit; + subStepSolve.containsFail = operand1.containsFail; + } else { + // else parse it as a number and add it to the subStep details + if (operand1 || operand1 == 0) { + oper1 = parseFloat(operand1.toString()); + subStepSolve.details = `${oper1.toString()}\\${conf[i]}`; + } + } - // If operand2 is a SolvedStep, populate our subStepSolve with its details without overriding what operand1 filled in - if (typeof operand2 === 'object') { - oper2 = operand2.total; - subStepSolve.details += operand2.details; - subStepSolve.containsCrit = subStepSolve.containsCrit || operand2.containsCrit; - subStepSolve.containsFail = subStepSolve.containsFail || operand2.containsFail; - } else { - // else parse it as a number and add it to the subStep details - oper2 = parseFloat(operand2.toString()); - subStepSolve.details += oper2; - } + // If operand2 is a SolvedStep, populate our subStepSolve with its details without overriding what operand1 filled in + if (typeof operand2 === 'object') { + oper2 = operand2.total; + subStepSolve.details += operand2.details; + subStepSolve.containsCrit = subStepSolve.containsCrit || operand2.containsCrit; + subStepSolve.containsFail = subStepSolve.containsFail || operand2.containsFail; + } else { + // else parse it as a number and add it to the subStep details + oper2 = parseFloat(operand2.toString()); + subStepSolve.details += oper2; + } - // Make sure neither operand is NaN before continuing - if (isNaN(oper1) || isNaN(oper2)) { - throw new Error('OperandNaN'); - } + // Make sure neither operand is NaN before continuing + if (isNaN(oper1) || isNaN(oper2)) { + throw new Error('OperandNaN'); + } - // Verify a second time that both are numbers before doing math, throwing an error if necessary - if ((typeof oper1 === 'number') && (typeof oper2 === 'number')) { - // Finally do the operator on the operands, throw an error if the operator is not found - switch (conf[i]) { - case '^': - subStepSolve.total = Math.pow(oper1, oper2); - break; - case '*': - subStepSolve.total = oper1 * oper2; - break; - case '/': - subStepSolve.total = oper1 / oper2; - break; - case '%': - subStepSolve.total = oper1 % oper2; - break; - case '+': - subStepSolve.total = oper1 + oper2; - break; - case '-': - subStepSolve.total = oper1 - oper2; - break; - default: - throw new Error('OperatorWhat'); - } - } else { - throw new Error('EMDASNotNumber'); - } + // Verify a second time that both are numbers before doing math, throwing an error if necessary + if ((typeof oper1 === 'number') && (typeof oper2 === 'number')) { + // Finally do the operator on the operands, throw an error if the operator is not found + switch (conf[i]) { + case '^': + subStepSolve.total = Math.pow(oper1, oper2); + break; + case '*': + subStepSolve.total = oper1 * oper2; + break; + case '/': + subStepSolve.total = oper1 / oper2; + break; + case '%': + subStepSolve.total = oper1 % oper2; + break; + case '+': + subStepSolve.total = oper1 + oper2; + break; + case '-': + subStepSolve.total = oper1 - oper2; + break; + default: + throw new Error('OperatorWhat'); + } + } else { + throw new Error('EMDASNotNumber'); + } - // Replace the two operands and their operator with our subStepSolve - conf.splice(i - 1, 3, subStepSolve); - // Because we are messing around with the array we are iterating thru, we need to back up one idx to make sure every operator gets processed - i--; - } - } - }); + // Replace the two operands and their operator with our subStepSolve + conf.splice(i - 1, 3, subStepSolve); + // Because we are messing around with the array we are iterating thru, we need to back up one idx to make sure every operator gets processed + i--; + } + } + }); - // If we somehow have more than one item left in conf at this point, something broke, throw an error - if (conf.length > 1) { - loggingEnabled && log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`); - throw new Error('ConfWhat'); - } else if (singleNum && (typeof (conf[0]) === 'number')) { - // If we are only left with a number, populate the stepSolve with it - stepSolve.total = conf[0]; - stepSolve.details = conf[0].toString(); - } else { - // Else fully populate the stepSolve with what was computed - stepSolve.total = ( conf[0]).total; - stepSolve.details = ( conf[0]).details; - stepSolve.containsCrit = ( conf[0]).containsCrit; - stepSolve.containsFail = ( conf[0]).containsFail; - } + // If we somehow have more than one item left in conf at this point, something broke, throw an error + if (conf.length > 1) { + loggingEnabled && log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`); + throw new Error('ConfWhat'); + } else if (singleNum && (typeof (conf[0]) === 'number')) { + // If we are only left with a number, populate the stepSolve with it + stepSolve.total = conf[0]; + stepSolve.details = conf[0].toString(); + } else { + // Else fully populate the stepSolve with what was computed + stepSolve.total = ( conf[0]).total; + stepSolve.details = ( conf[0]).details; + stepSolve.containsCrit = ( conf[0]).containsCrit; + stepSolve.containsFail = ( conf[0]).containsFail; + } - // If this was a nested call, add on parens around the details to show what math we've done - if (wrapDetails) { - stepSolve.details = `(${stepSolve.details})`; - } + // If this was a nested call, add on parens around the details to show what math we've done + if (wrapDetails) { + stepSolve.details = `(${stepSolve.details})`; + } - // If our total has reached undefined for some reason, error out now - if (stepSolve.total === undefined) { - throw new Error('UndefinedStep'); - } + // If our total has reached undefined for some reason, error out now + if (stepSolve.total === undefined) { + throw new Error('UndefinedStep'); + } - return stepSolve; + return stepSolve; }; diff --git a/src/utils.ts b/src/utils.ts index dd68720..cb2e41d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,81 +5,81 @@ */ import { - // Discordeno deps - DiscordenoMessage, - // Log4Deno deps - log, - LT, - // Discordeno deps - sendMessage, + // Discordeno deps + DiscordenoMessage, + // Log4Deno deps + log, + LT, + // Discordeno deps + sendMessage, } from '../deps.ts'; // ask(prompt) returns string // ask prompts the user at command line for message const ask = async (question: string, stdin = Deno.stdin, stdout = Deno.stdout): Promise => { - const buf = new Uint8Array(1024); + const buf = new Uint8Array(1024); - // Write question to console - await stdout.write(new TextEncoder().encode(question)); + // Write question to console + await stdout.write(new TextEncoder().encode(question)); - // Read console's input into answer - const n = await stdin.read(buf); - const answer = new TextDecoder().decode(buf.subarray(0, n)); + // Read console's input into answer + const n = await stdin.read(buf); + const answer = new TextDecoder().decode(buf.subarray(0, n)); - return answer.trim(); + return answer.trim(); }; // cmdPrompt(logChannel, botName) returns nothing // cmdPrompt creates an interactive CLI for the bot, commands can vary const cmdPrompt = async (logChannel: bigint, botName: string): Promise => { - let done = false; + let done = false; - while (!done) { - // Get a command and its args - const fullCmd = await ask('cmd> '); + while (!done) { + // Get a command and its args + const fullCmd = await ask('cmd> '); - // Split the args off of the command and prep the command - const args = fullCmd.split(' '); - const command = args.shift()?.toLowerCase(); + // Split the args off of the command and prep the command + const args = fullCmd.split(' '); + const command = args.shift()?.toLowerCase(); - // All commands below here + // All commands below here - // exit or e - // Fully closes the bot - if (command === 'exit' || command === 'e') { - console.log(`${botName} Shutting down.\n\nGoodbye.`); - done = true; - Deno.exit(0); - } // stop - // Closes the CLI only, leaving the bot running truly headless - else if (command === 'stop') { - console.log(`Closing ${botName} CLI. Bot will continue to run.\n\nGoodbye.`); - done = true; - } // m [channel] [message] - // Sends [message] to specified [channel] - else if (command === 'm') { - try { - const channelId = args.shift() || ''; - const message = args.join(' '); + // exit or e + // Fully closes the bot + if (command === 'exit' || command === 'e') { + console.log(`${botName} Shutting down.\n\nGoodbye.`); + done = true; + Deno.exit(0); + } // stop + // Closes the CLI only, leaving the bot running truly headless + else if (command === 'stop') { + console.log(`Closing ${botName} CLI. Bot will continue to run.\n\nGoodbye.`); + done = true; + } // m [channel] [message] + // Sends [message] to specified [channel] + else if (command === 'm') { + try { + const channelId = args.shift() || ''; + const message = args.join(' '); - sendMessage(BigInt(channelId), message).catch((reason) => { - console.error(reason); - }); - } catch (e) { - console.error(e); - } - } // ml [message] - // Sends a message to the specified log channel - else if (command === 'ml') { - const message = args.join(' '); + sendMessage(BigInt(channelId), message).catch((reason) => { + console.error(reason); + }); + } catch (e) { + console.error(e); + } + } // ml [message] + // Sends a message to the specified log channel + else if (command === 'ml') { + const message = args.join(' '); - sendMessage(logChannel, message).catch((reason) => { - console.error(reason); - }); - } // help or h - // Shows a basic help menu - else if (command === 'help' || command === 'h') { - console.log(`${botName} CLI Help: + sendMessage(logChannel, message).catch((reason) => { + console.error(reason); + }); + } // help or h + // Shows a basic help menu + else if (command === 'help' || command === 'h') { + console.log(`${botName} CLI Help: Available Commands: exit - closes bot @@ -87,28 +87,28 @@ Available Commands: m [ChannelID] [messgae] - sends message to specific ChannelID as the bot ml [message] sends a message to the specified botlog help - this message`); - } // Unhandled commands die here - else { - console.log('undefined command'); - } - } + } // Unhandled commands die here + else { + console.log('undefined command'); + } + } }; const genericLogger = (level: LT, message: string) => log(level, message); const messageEditError = (location: string, message: DiscordenoMessage | string, err: Error) => - genericLogger(LT.ERROR, `${location} | Failed to edit message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`); + genericLogger(LT.ERROR, `${location} | Failed to edit message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`); const messageSendError = (location: string, message: DiscordenoMessage | string, err: Error) => - genericLogger(LT.ERROR, `${location} | Failed to send message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`); + genericLogger(LT.ERROR, `${location} | Failed to send message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`); const messageDeleteError = (location: string, message: DiscordenoMessage | string, err: Error) => - genericLogger(LT.ERROR, `${location} | Failed to delete message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`); + genericLogger(LT.ERROR, `${location} | Failed to delete message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`); const dbError = (location: string, type: string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to ${type} database | Error: ${err.name} - ${err.message}`); export default { - commonLoggers: { - dbError, - messageEditError, - messageSendError, - messageDeleteError, - }, - cmdPrompt, + commonLoggers: { + dbError, + messageEditError, + messageSendError, + messageDeleteError, + }, + cmdPrompt, };