diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3a7c5b1..b2166ea 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -17,6 +17,7 @@
"Dists",
"dkdk",
"EMDAS",
+ "Exponentials",
"funciton",
"guildid",
"hidewarn",
diff --git a/README.md b/README.md
index ed9f1b5..5609b37 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,8 @@ The Artificer comes with a few supplemental commands to the main rolling command
* `-o a` or `-o d` - Order Roll - Rolls the requested roll and orders the results in the requested direction
* `-ct` - Comma Totals - Adds commas to totals for readability
* `-cc` - Confirm Critical Hits - Automatically rerolls whenever a crit hits, cannot be used with `-sn`
- - `-rd` - Roll Distribution - Shows a raw roll distribution of all dice in roll
+ * `-rd` - Roll Distribution - Shows a raw roll distribution of all dice in roll
+ * `-hr` - Hide Raw - Hide the raw input, showing only the results/details of the roll
* The results have some formatting applied on them to provide details on what happened during this roll.
* Critical successes will be **bolded**
* Critical fails will be underlined
diff --git a/config.example.ts b/config.example.ts
index 3bc34af..6749bbd 100644
--- a/config.example.ts
+++ b/config.example.ts
@@ -39,6 +39,7 @@ export const config = {
sourceCode: 'https://github.com/Burn-E99/TheArtificer', // Link to the repository
supportServer: '', // Invite link to the Discord support server
roll20Formatting: 'https://help.roll20.net/hc/en-us/articles/360037773133-Dice-Reference', // Link to Roll20 Dice Reference
+ mathDocs: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math', // Link to the MDN docs for Math
homePage: '', // Link to the bot's home/ad page
privacyPolicy: '', // Link to the current Privacy Policy
termsOfService: '', // Link to the current Terms of Service
diff --git a/src/commands/_index.ts b/src/commands/_index.ts
index 0171444..3b1678a 100644
--- a/src/commands/_index.ts
+++ b/src/commands/_index.ts
@@ -12,7 +12,6 @@ import { privacy } from 'commands/privacy.ts';
import { rip } from 'commands/rip.ts';
import { report } from 'commands/report.ts';
import { roll } from 'commands/roll.ts';
-import { rollDecorators } from 'commands/rollDecorators.ts';
import { rollHelp } from 'commands/rollHelp.ts';
import { stats } from 'commands/stats.ts';
import { version } from 'commands/version.ts';
@@ -32,7 +31,6 @@ export default {
rip,
report,
roll,
- rollDecorators,
rollHelp,
stats,
version,
diff --git a/src/commands/help.ts b/src/commands/help.ts
index 8012ec7..6b970f3 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -30,11 +30,6 @@ export const help = (message: DiscordenoMessage) => {
value: `Details on how to use the roll command, listed as \`${config.prefix}xdy...${config.postfix}\` below`,
inline: true,
},
- {
- name: `\`${config.prefix}rollDecorators\` or \`${config.prefix}???\``,
- value: `Details on how to use decorators on the roll command`,
- inline: true,
- },
{
name: `\`${config.prefix}api [subcommand]\``,
value: `Administrative tools for the bots's API, run \`${config.prefix}api help\` for more details`,
diff --git a/src/commands/helpLibrary/_rootHelp.ts b/src/commands/helpLibrary/_rootHelp.ts
new file mode 100644
index 0000000..eefd35b
--- /dev/null
+++ b/src/commands/helpLibrary/_rootHelp.ts
@@ -0,0 +1,41 @@
+import config from '~config';
+
+import { HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+import { DecoratorsHelpPages } from 'commands/helpLibrary/decorators.ts';
+import { DiceOptionsHelpPages } from 'commands/helpLibrary/diceOptions.ts';
+import { DiceTypesHelpPages } from 'commands/helpLibrary/diceTypes.ts';
+import { FormattingHelpPages } from 'commands/helpLibrary/formatting.ts';
+import { LegalMathComplexFuncsHelpPages } from 'commands/helpLibrary/legalMathComplexFuncs.ts';
+import { LegalMathConstsHelpPages } from 'commands/helpLibrary/legalMathConsts.ts';
+import { LegalMathFuncsHelpPages } from 'commands/helpLibrary/legalMathFuncs.ts';
+import { LegalMathOperators } from 'commands/helpLibrary/legalMathOperators.ts';
+import { LegalMathTrigFuncsHelpPages } from 'commands/helpLibrary/legalMathTrigFuncs.ts';
+import { MiscFeaturesHelpPages } from 'commands/helpLibrary/miscFeatures.ts';
+
+const name = `${config.name}'s Roll Command Details`;
+const description = `You can chain as many of these options as you want, as long as the option does not disallow it. This command also can fully solve math equations with parenthesis.
+
+The help options in this group use the notation \`xdy\` to indicate the basic/required dice notation for die count and size as detailed in the \`Basic/Standard Dice Options\` page.
+
+As this supports the [Roll20 formatting](${config.links.roll20Formatting}) syntax fully, more details and examples can be found [here](${config.links.roll20Formatting}).
+
+Please use the dropdown/select menus to search through the provided documentation.`;
+const dict = new Map([
+ ['dice-types', DiceTypesHelpPages],
+ ['dice-options', DiceOptionsHelpPages],
+ ['decorators', DecoratorsHelpPages],
+ ['formatting', FormattingHelpPages],
+ ['misc-features', MiscFeaturesHelpPages],
+ ['legal-math-operators', LegalMathOperators],
+ ['legal-math-consts', LegalMathConstsHelpPages],
+ ['legal-math-funcs', LegalMathFuncsHelpPages],
+ ['legal-math-trig-funcs', LegalMathTrigFuncsHelpPages],
+ ['legal-math-complex-funcs', LegalMathComplexFuncsHelpPages],
+]);
+
+export const RootHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/decorators.ts b/src/commands/helpLibrary/decorators.ts
new file mode 100644
index 0000000..0b4d23d
--- /dev/null
+++ b/src/commands/helpLibrary/decorators.ts
@@ -0,0 +1,172 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Roll Command Decorators';
+const description = `This command also has some useful decorators that can used. These decorators simply need to be placed after all rolls in the message.
+
+**Examples:** \`${config.prefix}d20${config.postfix} -nd\`, \`${config.prefix}d20${config.postfix} -nd -s\`, \`${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} -o a\``;
+const dict = new Map([
+ [
+ '-nd',
+ {
+ name: 'No Details',
+ description: `**Usage:** \`-nd\`
+
+Hides some of the details of the requested roll, but will still show the list of roll results along with the formatted results.`,
+ example: ['`[[2000d20]] -nd` => Limits the details shown to just be `[[2000d20]]` = **__20935__**'],
+ },
+ ],
+ [
+ '-snd',
+ {
+ name: 'Super No Details',
+ description: `**Usage:** \`-snd\`
+
+Suppresses all details of the requested roll.`,
+ example: ['`[[2000d20]] -nd` => Removes the details section entirely'],
+ },
+ ],
+ [
+ '-s',
+ {
+ name: 'Spoiler',
+ description: `**Usage:** \`-s\`
+
+Spoilers all details of the requested roll`,
+ example: ['`[[d20]] -s`'],
+ },
+ ],
+ [
+ '-max',
+ {
+ name: 'Maximize Roll',
+ description: `**Usage:** \`-max\` or \`-m\`
+
+Rolls the theoretical maximum roll.
+
+**Notice:** Cannot be used with \`-n\`, \`-min\`, or \`-sn\``,
+ example: ['`[[d20]] -max` => **20**'],
+ },
+ ],
+ [
+ '-min',
+ {
+ name: 'Minimize Roll',
+ description: `**Usage:** \`-min\`
+
+Rolls the theoretical minimum roll.
+
+**Notice:** Cannot be used with \`-m\`, \`-max\`, \`-n\`, or \`-sn\``,
+ example: ['`[[d20]] -min` => __1__'],
+ },
+ ],
+ [
+ '-n',
+ {
+ name: 'Nominal Roll',
+ description: `**Usage:** \`-n\`
+
+Rolls the theoretical nominal roll.
+
+**Notice:** Cannot be used with \`-m\`, \`-max\`, \`-min\`, or \`-sn\``,
+ example: ['`[[d20]] -n` => 10.5'],
+ },
+ ],
+ [
+ '-sn',
+ {
+ name: 'Simulated Nominal Roll',
+ description: `**Usage:** \`-sn\`
+
+Rolls the requests roll many times to approximately simulate the nominal of complex rolls, can specify the amount or accept default amount by not specify the amount.
+
+**Notice:** Cannot be used with \`-m\`, \`-max\`, \`-min\`, \`-n\`, or \`-cc\``,
+ example: ['`[[4d6d1]] -sn` => 12.274'],
+ },
+ ],
+ [
+ '-o',
+ {
+ name: 'Order Roll',
+ description: `**Usage:** \`-o a\` or \`-o d\`
+\`a\` - Ascending (least to greatest)
+\`d\` - Descending (greatest to least)
+
+Rolls the requested roll and orders the results in the requested direction.`,
+ example: [
+ '`[[4d6d1]] [[4d6d1]] [[4d6d1]] [[4d6d1]] -o a` => 9, __9__, 12, **15**',
+ '`[[4d6d1]] [[4d6d1]] [[4d6d1]] [[4d6d1]] -o a` => **17**, 14, **13**, 9',
+ ],
+ },
+ ],
+ [
+ '-c',
+ {
+ name: 'Count Rolls',
+ description: `**Usage:** \`-c\`
+
+Shows the Count Embed, containing the count of successful rolls, failed rolls, rerolls, drops, and explosions.`,
+ example: ['`[[40d20]] -c`'],
+ },
+ ],
+ [
+ '-cc',
+ {
+ name: 'Confirm Critical Hits',
+ description: `**Usage:** \`-cc\`
+
+Automatically rerolls whenever a crit hits.
+
+**Notice:** Cannot be used with \`-sn\``,
+ example: ['`[[d20]] -cc` => **20** Auto-Confirming Crit: **20** Auto-Confirming Crit: 7'],
+ },
+ ],
+ [
+ '-ct',
+ {
+ name: 'Comma Totals',
+ description: `**Usage:** \`-ct\`
+
+Adds commas to totals for readability.`,
+ example: ['`[[100d20]] -ct` => Shows **__1,110__** instead of **__1110__**'],
+ },
+ ],
+ [
+ '-gm',
+ {
+ name: 'GM Roll',
+ description: `**Usage:** \`-gm @user1 @user2 ... @userN\`
+
+Rolls the requested roll in GM mode, suppressing all publicly shown results/details and instead sending the results directly to the specified GMs.`,
+ example: ['[[d20]] -gm @$'],
+ },
+ ],
+ [
+ '-hr',
+ {
+ name: 'Hide Raw',
+ description: `**Usage:** \`-hr\`
+
+Hide the raw input, showing only the results/details of the roll.`,
+ example: ['`[[d20]] -hr`'],
+ },
+ ],
+ [
+ '-rd',
+ {
+ name: 'Roll Distribution',
+ description: `**Usage:** \`-rd\`
+
+Shows a raw roll distribution of all dice in roll.`,
+ example: ['`[[1000d20]] -rd`'],
+ },
+ ],
+]);
+
+export const DecoratorsHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/diceOptions.ts b/src/commands/helpLibrary/diceOptions.ts
new file mode 100644
index 0000000..e1620a2
--- /dev/null
+++ b/src/commands/helpLibrary/diceOptions.ts
@@ -0,0 +1,239 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Roll20 Dice Options';
+const description = `\`${config.prefix}xdydzracsq!${config.postfix}\` Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`).`;
+const dict = new Map([
+ [
+ 'dice',
+ {
+ name: 'Basic Dice Options',
+ description: `**Usage:** \`xdy\`
+\`x\` - Number of dice to roll, if omitted, 1 is used
+\`y\` - Size of dice to roll, can be replaced with \`F\` to roll Fate dice`,
+ example: ['`[[4d6]]` => Rolls four 6-sided dice', '`[[5dF]]` => Rolls five Fate dice'],
+ },
+ ],
+ [
+ 'drop-lowest',
+ {
+ name: 'Drop (Lowest)',
+ description: `**Usage:** \`xdydz\` or \`xdydlz\`
+\`z\` - Number of dice to drop
+
+**Notice:** Cannot be combined with other drop or keep options`,
+ example: ['`[[6d8d2]]` => Rolls six 8-sided dice, dropping the two lowest rolled dice'],
+ },
+ ],
+ [
+ 'drop-highest',
+ {
+ name: 'Drop Highest',
+ description: `**Usage:** \`xdydhz\`
+\`z\` - Number of dice to drop
+
+**Notice:** Cannot be combined with other drop or keep options`,
+ example: ['`[[6d8dh2]]` => Rolls six 8-sided dice, dropping the two highest rolled dice'],
+ },
+ ],
+ [
+ 'keep-highest',
+ {
+ name: 'Keep (Highest)',
+ description: `**Usage:** \`xdykz\` or \`xdykhz\`
+\`z\` - Number of dice to keep
+
+**Notice:** Cannot be combined with other drop or keep options`,
+ example: ['`[[6d8k2]]` => Rolls six 8-sided dice, keeping the two highest rolled dice'],
+ },
+ ],
+ [
+ 'keep-lowest',
+ {
+ name: 'Keep Lowest',
+ description: `**Usage:** \`xdyklz\`
+\`z\` - Number of dice to keep
+
+**Notice:** Cannot be combined with other drop or keep options`,
+ example: ['`[[6d8dh2]]` => Rolls six 8-sided dice, keeping the two lowest rolled dice'],
+ },
+ ],
+ [
+ 'reroll',
+ {
+ name: '(Normal) Reroll',
+ description: `**Usage:** \`xdyrz\`, \`xdyr=z\`, \`xdyrz\`
+\`z\` - Number to compare to for rerolls, see examples for how each setup above works
+
+The (Normal) Reroll option will reroll any dice that matches the specified pattern. Old rolls will be discarded.
+
+**Notice:** Cannot be combined with the Reroll Once option`,
+ example: [
+ '`[[6d8r2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2',
+ '`[[6d8r2r4]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 or 4',
+ '`[[6d8r=2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2',
+ '`[[6d8r<2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 or 1',
+ '`[[6d8r>2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 thru 8 inclusive',
+ ],
+ },
+ ],
+ [
+ 'reroll-once',
+ {
+ name: 'Reroll Once',
+ description: `**Usage:** \`xdyroz\`, \`xdyro=z\`, \`xdyroz\`
+\`z\` - Number to compare to for rerolls, see examples for how each setup above works
+
+The Reroll Once option will reroll any dice that matches the specified pattern for the first time. If a die that has already been rerolled lands on a side matching the pattern, it will not be rerolled again. Old rolls will be discarded.
+
+For example, if \`4d8ro2\` is rolled with initial results of \`[1, 2, 6, 7]\`, the \`[1, 6, 7]\` will be kept and the \`2\` will be rerolled. If this die lands on a \`2\` again, it will not be rerolled and will be kept.
+
+**Notice:** Cannot be combined with the (Normal) Reroll options`,
+ example: [
+ '`[[6d8ro2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 for the first time',
+ '`[[6d8ro2ro4]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 or 4 for the first time',
+ '`[[6d8ro=2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 for the first time',
+ '`[[6d8ro<2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 or 1 for the first time',
+ '`[[6d8ro>2]]` => Rolls six 8-sided dice, rerolling any dice that land on 2 thru 8 inclusive for the first time',
+ ],
+ },
+ ],
+ [
+ 'crit-score',
+ {
+ name: 'Critical (Success) Score',
+ description: `**Usage:** \`xdycsz\`, \`xdycs=z\`, \`xdycsz\`
+\`z\` - Number to compare to for changing the critical success score, see examples for how each setup above works
+
+The Critical (Success) Score option will not change what is marked as a critical success, if combined with an Exploding option, it will also explode on the newly set critical success score specified.
+
+**Notice:** This overrides the implicit critical success settings, meaning unless specified, a 8 on a \`d8\` will not be marked as a crit`,
+ example: [
+ '`[[6d8cs2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 as a critical success',
+ '`[[6d8cs2cs4]]` => Rolls six 8-sided dice, marking any die that lands on a 2 or 4 as a critical success',
+ '`[[6d8cs=2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 as a critical success',
+ '`[[6d8cs<2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 or 1 as a critical success',
+ '`[[6d8cs>2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 thru 8 inclusive as a critical success',
+ ],
+ },
+ ],
+ [
+ 'crit-fail',
+ {
+ name: 'Critical Fail Score',
+ description: `**Usage:** \`xdycfz\`, \`xdycf=z\`, \`xdycfz\`
+\`z\` - Number to compare to for changing the critical fail score, see examples for how each setup above works
+
+**Notice:** This overrides the implicit critical fail settings, meaning unless specified, a 1 on a \`d8\` will not be marked as a fail`,
+ example: [
+ '`[[6d8cf2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 as a critical fail',
+ '`[[6d8cf2cf4]]` => Rolls six 8-sided dice, marking any die that lands on a 2 or 4 as a critical fail',
+ '`[[6d8cf=2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 as a critical fail',
+ '`[[6d8cf<2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 or 1 as a critical fail',
+ '`[[6d8cf>2]]` => Rolls six 8-sided dice, marking any die that lands on a 2 thru 8 inclusive as a critical fail',
+ ],
+ },
+ ],
+ [
+ 'exploding',
+ {
+ name: '(Standard) Exploding',
+ description: `**Usage:** \`xdy!\`, \`xdy!z\`, \`xdy!=z\`, \`xdy!z\`
+\`z\` - Number to compare to for changing the score to explode on, if omitted, will explode on critical successes, see examples for how each setup above works
+
+(Standard) Exploding is when you roll another die when one lands on a certain number, and keep both results. This theoretically could happen infinitely since if the new die also lands on a number set to explode it will also explode.
+
+**Notice:** Cannot be combined with other types of explosion/exploding options`,
+ example: [
+ '`[[6d8!]]` => Rolls six 8-sided dice, exploding any die that lands on a 8 (the max size of the die)',
+ '`[[6d10!]]` => Rolls six 10-sided dice, exploding any die that lands on a 10 (the max size of the die)',
+ '`[[6d8cs4!]]` => Rolls six 8-sided dice, exploding any die that lands on a 4 (the critical success score specified by `cs4`)',
+ '`[[6d8cs>4!]]` => Rolls six 8-sided dice, exploding any die that lands on a 4 thru 8 inclusive (the critical success score specified by `cs>4`)',
+ '`[[6d8!2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2',
+ '`[[6d8!2!4]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 or 4',
+ '`[[6d8!=2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2',
+ '`[[6d8!<2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 or 1',
+ '`[[6d8!>2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 thru 8 inclusive',
+ ],
+ },
+ ],
+ [
+ 'explode-once',
+ {
+ name: 'Explode Once',
+ description: `**Usage:** \`xdy!o\`, \`xdy!oz\`, \`xdy!o=z\`, \`xdy!oz\`
+\`z\` - Number to compare to for changing the score to explode on, if omitted, will explode on critical successes, see examples for how each setup above works
+
+The Explode Once option is when you roll another die when one lands on a certain number, and keep both results. This will not cascade to infinity though, as once a die has exploded, it cannot explode again.
+
+**Notice:** Cannot be combined with other types of explosion/exploding options`,
+ example: [
+ '`[[6d8!o]]` => Rolls six 8-sided dice, exploding any die that lands on a 8 (the max size of the die) for the first time',
+ '`[[6d10!o]]` => Rolls six 10-sided dice, exploding any die that lands on a 10 (the max size of the die) for the first time',
+ '`[[6d8cs4!o]]` => Rolls six 8-sided dice, exploding any die that lands on a 4 (the critical success score specified by `cs4`) for the first time',
+ '`[[6d8cs>4!o]]` => Rolls six 8-sided dice, exploding any die that lands on a 4 thru 8 inclusive (the critical success score specified by `cs>4`) for the first time',
+ '`[[6d8!o2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 for the first time',
+ '`[[6d8!o2!o4]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 or 4 for the first time',
+ '`[[6d8!o=2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 for the first time',
+ '`[[6d8!o<2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 or 1 for the first time',
+ '`[[6d8!o>2]]` => Rolls six 8-sided dice, exploding any die that lands on a 2 thru 8 inclusive for the first time',
+ ],
+ },
+ ],
+ [
+ 'explode-penetrating',
+ {
+ name: 'Penetrating Explosion',
+ description: `**Usage:** \`xdy!p\`, \`xdy!pz\`, \`xdy!p=z\`, \`xdy!pz\`
+\`z\` - Number to compare to for changing the score to explode on, if omitted, will explode on critical successes, see examples for how each setup above works
+
+The Penetrating Explosion option is when you roll another die when one lands on a certain number, but you subtract 1 from the new die, and keep both results. This may cascade to infinity if the exploding range is very large.
+
+**Notice:** Cannot be combined with other types of explosion/exploding options`,
+ example: [
+ '`[[6d8!p]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 8 (the max size of the die)',
+ '`[[6d10!p]]` => Rolls six 10-sided dice, exploding+penetrating any die that lands on a 10 (the max size of the die)',
+ '`[[6d8cs4!p]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 4 (the critical success score specified by `cs4`)',
+ '`[[6d8cs>4!p]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 4 thru 8 inclusive (the critical success score specified by `cs>4`)',
+ '`[[6d8!p2]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 2',
+ '`[[6d8!p2!p4]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 2 or 4',
+ '`[[6d8!p=2]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 2',
+ '`[[6d8!p<2]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 2 or 1',
+ '`[[6d8!p>2]]` => Rolls six 8-sided dice, exploding+penetrating any die that lands on a 2 thru 8 inclusive',
+ ],
+ },
+ ],
+ [
+ 'explode-compounding',
+ {
+ name: 'Compounding Explosion',
+ description: `**Usage:** \`xdy!!\`, \`xdy!!z\`, \`xdy!!=z\`, \`xdy!!z\`
+\`z\` - Number to compare to for changing the score to explode on, if omitted, will explode on critical successes, see examples for how each setup above works
+
+The Compounding Explosion option is when you roll another die when one lands on a certain number, and add the new result to the initial die. This theoretically could happen infinitely since if the new die also lands on a number set to explode it will also explode.
+
+Any time a Compounding Explosion happens, the formatting on the die will still show if the underlying dice were a critical success or critical failure.
+
+**Notice:** Cannot be combined with other types of explosion/exploding options`,
+ example: [
+ '`[[6d8!!]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 8 (the max size of the die)',
+ '`[[6d10!!]]` => Rolls six 10-sided dice, exploding+compounding any die that lands on a 10 (the max size of the die)',
+ '`[[6d8cs4!!]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 4 (the critical success score specified by `cs4`)',
+ '`[[6d8cs>4!!]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 4 thru 8 inclusive (the critical success score specified by `cs>4`)',
+ '`[[6d8!!2]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 2',
+ '`[[6d8!!2!!4]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 2 or 4',
+ '`[[6d8!!=2]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 2',
+ '`[[6d8!!<2]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 2 or 1',
+ '`[[6d8!!>2]]` => Rolls six 8-sided dice, exploding+compounding any die that lands on a 2 thru 8 inclusive',
+ ],
+ },
+ ],
+]);
+
+export const DiceOptionsHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/diceTypes.ts b/src/commands/helpLibrary/diceTypes.ts
new file mode 100644
index 0000000..4003e0c
--- /dev/null
+++ b/src/commands/helpLibrary/diceTypes.ts
@@ -0,0 +1,69 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Custom Dice Shapes';
+const description = `${config.name} supports also a couple other types of dice such as Fate or OVA dice.`;
+const dict = new Map([
+ [
+ 'd20',
+ {
+ name: 'Roll20/Normal Dice',
+ description: `**Usage:** \`${config.prefix}xdy${config.postfix}\`
+\`x\` - Number of dice to roll, defaults to 1
+\`y\` - Size of the die, required`,
+ example: ['`[[d20]]` => [5] = 5', '`[[6d20]]` => [**20** + 16 + 17 + 19 + 10 + 15] = **97**'],
+ },
+ ],
+ [
+ 'fate',
+ {
+ name: 'Fate Dice',
+ description: `**Usage:** \`${config.prefix}xdF${config.postfix}\`
+\`x\` - Number of Fate dice to roll, defaults to 1
+
+Rolls a Fate die, has 6 sides with values \`[-1, -1, 0, 0, 1, 1]\`.`,
+ example: ['`[[dF]]` => [**1**] = **1**', '`[[4dF]]` => [**1** + __-1__ + 0 + 0] = **__0__**'],
+ },
+ ],
+ [
+ 'cwod',
+ {
+ name: 'CWOD Dice',
+ description: `**Usage:** \`${config.prefix}xcwody${config.postfix}\`
+\`x\` - Number of CWOD dice to roll, defaults to 1
+\`y\` - Difficulty to roll at, defaults to 10
+
+Rolls a specified number of 10 sided dice and counts successful and failed rolls.`,
+ example: [
+ '`[[cwod]]` => [3, 0 Successes, 0 Fails] = 0',
+ '`[[4cwod]]` => [**10** + __1__ + 9 + __1__, 1 Success, 2 Fails] = **__1__**',
+ '`[[5cwod8]]` => [**10** + 2 + 5 + **8** + 4, 2 Successes, 0 Fails] = **2**',
+ ],
+ },
+ ],
+ [
+ 'ova',
+ {
+ name: 'OVA Dice',
+ description: `**Usage:** \`${config.prefix}xovady${config.postfix}\`
+\`x\` - Number of OVA dice to roll, defaults to 1
+\`y\` - Size of the die to roll, defaults to 6
+
+Rolls a specified number of dice and returns the greatest sum of repeated dice. The \`[[8ovad]]\` example shows that even though there are two 5's, the sum of those is less than four 3's, and thus the result is the four 3's, summed to 12.`,
+ example: [
+ '`[[ovad]]` => [4] = 4',
+ '`[[4ovad]]` => [~~4~~ + 5 + ~~6~~ + 5] = 10',
+ '`[[8ovad]]` => [~~2~~ + 3 + 3 + ~~5~~ + ~~4~~ + 3 + 3 + ~~5~~] = 12',
+ '`[[5ovad20]]` => [~~18~~ + ~~17~~ + 19 + 19 + ~~10~~] = 38',
+ ],
+ },
+ ],
+]);
+
+export const DiceTypesHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/formatting.ts b/src/commands/helpLibrary/formatting.ts
new file mode 100644
index 0000000..5e027c1
--- /dev/null
+++ b/src/commands/helpLibrary/formatting.ts
@@ -0,0 +1,46 @@
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Results Formatting';
+const description =
+ 'The results have some formatting applied on them to provide details on what happened during this roll. These options can be stacked on each other to show complicated results.';
+const dict = new Map([
+ [
+ 'bold',
+ {
+ name: 'Bold',
+ description: 'Individual critical successes and any results containing a critical success will be **bolded**.',
+ example: ['`[[2d6]]` => [2 + **6**] = **8**'],
+ },
+ ],
+ [
+ 'underline',
+ {
+ name: 'Underline',
+ description: 'Individual critical fails and any results containing a critical fail will be __underlined__.',
+ example: ['`[[2d6]]` => [__1__ + 4] = __5__'],
+ },
+ ],
+ [
+ 'strikethrough',
+ {
+ name: 'Strikethrough',
+ description: 'Rolls that were dropped or rerolled ~~crossed out~~.',
+ example: ['`[[4d6d1]]` => [~~__1__~~ + 2 + 4 + **6**] = **12**', '`4d6r2` => [__1__ + ~~2~~ + 3 + 4 + **6**] = **__14__**'],
+ },
+ ],
+ [
+ 'exclamation-mark',
+ {
+ name: 'Exclamation mark (`!`)',
+ description: 'Rolls that were caused by an explosion have an exclamation mark (`!`) after them.',
+ example: ['`[[4d6!]]` => [3 + 4 + **6** + 4! + 5] = **22**'],
+ },
+ ],
+]);
+
+export const FormattingHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/generateHelpMessage.ts b/src/commands/helpLibrary/generateHelpMessage.ts
new file mode 100644
index 0000000..c6f9cdd
--- /dev/null
+++ b/src/commands/helpLibrary/generateHelpMessage.ts
@@ -0,0 +1,85 @@
+import { ActionRow, botId, CreateMessage, Embed, MessageComponentTypes, SelectOption } from '@discordeno';
+
+import config from '~config';
+
+import { RootHelpPages } from 'commands/helpLibrary/_rootHelp.ts';
+import { HelpContents, HelpDict, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+import { infoColor1 } from 'embeds/colors.ts';
+
+import { InteractionValueSeparator } from 'events/interactionCreate.ts';
+
+export const helpCustomId = 'help';
+
+const generateActionRowWithSelectMenu = (selected: string, helpDict: HelpDict, parent?: string): ActionRow => ({
+ type: MessageComponentTypes.ActionRow,
+ components: [
+ {
+ type: MessageComponentTypes.SelectMenu,
+ customId: `${helpCustomId}${InteractionValueSeparator}${selected}`,
+ options: helpDict
+ .entries()
+ .toArray()
+ .map(
+ (page): SelectOption => ({
+ label: page[1].name,
+ value: parent ? `${parent}${InteractionValueSeparator}${page[0]}` : page[0],
+ default: page[0] === selected,
+ })
+ ),
+ },
+ ],
+});
+
+const makeHelpEmbed = (helpDict: HelpContents | HelpPage, parentTitle?: string): Embed => ({
+ color: infoColor1,
+ author: {
+ name: `Roll Command Help${parentTitle ? ' - ' : ''}${parentTitle}`,
+ },
+ title: helpDict.name,
+ description: helpDict.description,
+ fields:
+ !helpDict.isPage && helpDict.example
+ ? [
+ {
+ name: `Example${helpDict.example.length > 1 ? 's' : ''}:`,
+ value: helpDict.example.join('\n').replaceAll('@$', `<@${botId}>`).replaceAll('[[', config.prefix).replaceAll(']]', config.postfix),
+ },
+ ]
+ : [],
+});
+
+const defaultHelpMessage = (showError = ''): CreateMessage => ({
+ embeds: [
+ {
+ ...makeHelpEmbed(RootHelpPages),
+ footer: {
+ text: showError ? `Error${showError}: Something went wrong, please try again.` : '',
+ },
+ },
+ ],
+ components: [generateActionRowWithSelectMenu('', RootHelpPages.dict)],
+});
+
+export const generateHelpMessage = (helpPath?: string): CreateMessage => {
+ if (helpPath) {
+ const [page, item] = helpPath.split(InteractionValueSeparator);
+
+ // Get the first layer dictionary
+ const rootHelpDict = RootHelpPages.dict.get(page);
+ if (!rootHelpDict || !rootHelpDict.isPage) return defaultHelpMessage('1');
+
+ // Get second layer dictionary
+ const helpDict = item ? rootHelpDict.dict.get(item) : rootHelpDict;
+ if (!helpDict) return defaultHelpMessage('2');
+ return {
+ embeds: [makeHelpEmbed(helpDict, helpDict.isPage ? '' : rootHelpDict.name)],
+ components: [
+ generateActionRowWithSelectMenu(page, RootHelpPages.dict),
+ helpDict.isPage ? generateActionRowWithSelectMenu('', helpDict.dict, page) : generateActionRowWithSelectMenu(item, rootHelpDict.dict, page),
+ ],
+ };
+ } else {
+ return defaultHelpMessage();
+ }
+};
diff --git a/src/commands/helpLibrary/helpLibrary.d.ts b/src/commands/helpLibrary/helpLibrary.d.ts
new file mode 100644
index 0000000..fa9b206
--- /dev/null
+++ b/src/commands/helpLibrary/helpLibrary.d.ts
@@ -0,0 +1,16 @@
+interface HelpItem {
+ name: string;
+ description: string;
+}
+
+export type HelpDict = Map;
+
+export interface HelpContents extends HelpItem {
+ isPage?: false;
+ example?: string[];
+}
+
+export interface HelpPage extends HelpItem {
+ isPage: true;
+ dict: HelpDict;
+}
diff --git a/src/commands/helpLibrary/legalMathComplexFuncs.ts b/src/commands/helpLibrary/legalMathComplexFuncs.ts
new file mode 100644
index 0000000..e278a57
--- /dev/null
+++ b/src/commands/helpLibrary/legalMathComplexFuncs.ts
@@ -0,0 +1,109 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Complex Math Functions';
+const description = `All the weird extras.
+
+I really have no idea what these could be used for, but if you find a cool use, please use \`${config.prefix}report\` to let me know what cool things you are doing!
+
+Have fun!
+
+Documentation from the [MDN Web Docs](${config.links.mathDocs}).`;
+const dict = new Map([
+ [
+ 'cbrt',
+ {
+ name: 'Cube Root',
+ description: 'Returns the cube root of a number.',
+ example: ['`[[cbrt(27)]]` => 3', '`[[cbrt(d20 * 4)]]` => cbrt([10] * 6.4) = 4'],
+ },
+ ],
+ [
+ 'exp',
+ {
+ name: 'e^x',
+ description: 'Returns `e` raised to the power of a number.',
+ example: ['`[[exp(2)]]` => 7.38905609893065', '`[[exp(d8)]]` => exp([6]) = 403.4287934927351'],
+ },
+ ],
+ [
+ 'expm1',
+ {
+ name: 'e^x - 1',
+ description: 'Returns `e` raised to the power of a number, subtracted by `1`.',
+ example: ['`[[expm1(2)]]` => 6.38905609893065', '`[[expm1(d8)]]` => expm1([6]) = 402.4287934927351'],
+ },
+ ],
+ [
+ 'sign',
+ {
+ name: 'Sign',
+ description: 'Returns `1` or `-1`, indicating the sign of the number passed as argument.',
+ example: ['`[[sign(-456)]]` => -1', '`[[sign(d20)]]` => sign([14]) = 1'],
+ },
+ ],
+ [
+ 'f16round',
+ {
+ name: '16bit Float Round',
+ description: 'Returns the nearest 16-bit half precision float representation of a number.',
+ example: ['`[[f16round(4.1)]]` => 4.1015625', '`[[f16round(d4 / 3)]]` => f16round([2] / 3) = 0.66650390625'],
+ },
+ ],
+ [
+ 'fround',
+ {
+ name: '32bit Float Round',
+ description: 'Returns the nearest 32-bit single precision float representation of a number.',
+ example: ['`[[fround(4.1)]]` => 4.099999904632568', '`[[fround(d4 / 3)]]` => fround([2] / 3) = 0.6666666865348816'],
+ },
+ ],
+ [
+ 'log',
+ {
+ name: 'Natural Log (ln(x))',
+ description: 'Returns the natural logarithm (base e) of a number.',
+ example: ['`[[log(2)]]` => 0.6931471805599453', '`[[log(d8)]]` => log([4]) = 1.3862943611198906'],
+ },
+ ],
+ [
+ 'log1p',
+ {
+ name: 'Natural Log (ln(x + 1))',
+ description: 'Returns the natural logarithm (base e) of `1 + x`, where `x` is the argument.',
+ example: ['`[[log1p(2)]]` => 1.0986122886681096', '`[[log1p(d8)]]` => log1p([4]) = 1.6094379124341003'],
+ },
+ ],
+ [
+ 'log2',
+ {
+ name: 'Log Base 2',
+ description: 'Returns the base 2 logarithm of a number.',
+ example: ['`[[log2(2)]]` => 1', '`[[log2(d8)]]` => log2([4]) = 2'],
+ },
+ ],
+ [
+ 'log10',
+ {
+ name: 'Log Base 10',
+ description: 'Returns the base 10 logarithm of a number.',
+ example: ['`[[log10(2)]]` => 0.3010299956639812', '`[[log10(d8)]]` => log10([4]) = 0.6020599913279624'],
+ },
+ ],
+ [
+ 'clz32',
+ {
+ name: 'Count Leading Zeros 32',
+ description: 'Returns the number of leading zero bits in the 32-bit binary representation of a number.',
+ example: ['`[[clz32(1)]]` => 31', '`[[clz32(d20)]]` => clz32([19] * 4) = 25'],
+ },
+ ],
+]);
+
+export const LegalMathComplexFuncsHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/legalMathConsts.ts b/src/commands/helpLibrary/legalMathConsts.ts
new file mode 100644
index 0000000..2117172
--- /dev/null
+++ b/src/commands/helpLibrary/legalMathConsts.ts
@@ -0,0 +1,37 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Math Constants';
+const description = `Available math constants.
+
+Documentation from [MDN Web Docs](${config.links.mathDocs}).`;
+const dict = new Map([
+ [
+ 'e',
+ {
+ name: "Euler's number",
+ description: `**Usage:** \`e\`
+
+Represents Euler's number, the base of natural logarithms, \`e\`, which is approximately \`2.718\`.`,
+ example: ['`[[e]]` => 2.718281828459045', '`[[e*2]]` => 5.43656365691809'],
+ },
+ ],
+ [
+ 'pi',
+ {
+ name: 'Pi/𝜋',
+ description: `**Usage:** \`pi\` or \`𝜋\`
+
+Represents the ratio of the circumference of a circle to its diameter, approximately \`3.14159\`.`,
+ example: ['`[[pi]]` => 3.141592653589793', '`[[𝜋*2]]` => 6.283185307179586'],
+ },
+ ],
+]);
+
+export const LegalMathConstsHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/legalMathFuncs.ts b/src/commands/helpLibrary/legalMathFuncs.ts
new file mode 100644
index 0000000..61c6b96
--- /dev/null
+++ b/src/commands/helpLibrary/legalMathFuncs.ts
@@ -0,0 +1,65 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Basic Math Functions';
+const description = `Basic math functions akin to what Roll20 provides, such as Round or Ceil.
+
+Documentation from the [MDN Web Docs](${config.links.mathDocs}).`;
+const dict = new Map([
+ [
+ 'abs',
+ {
+ name: 'Absolute Value',
+ description: 'Returns the absolute value of a number.',
+ example: ['`[[abs(-4)]]` => 4', '`[[abs(-56 * d20)]]` => abs(-56 * [12]) = 672'],
+ },
+ ],
+ [
+ 'ceil',
+ {
+ name: 'Ceiling',
+ description: 'Always rounds up to nearest whole number.',
+ example: ['`[[ceil(4.3)]]` => 5', '`[[ceil(d20 / 3)]]` => ceil([13] / 3) = 5'],
+ },
+ ],
+ [
+ 'floor',
+ {
+ name: 'Floor',
+ description: 'Always rounds down to the nearest whole number.',
+ example: ['`[[floor(4.8)]]` => 4', '`[[floor(d20 / 3)]]` => floor([14] / 3) = 4'],
+ },
+ ],
+ [
+ 'round',
+ {
+ name: 'Round',
+ description: 'Returns the value of a number rounded to the nearest whole number.',
+ example: ['`[[round(4.3)]]` => 4', '`[[round(4.8)]]` => 5', '`[[round(d20 / 3)]]` => round([13] / 3) = 4', '`[[round(d20 / 3)]]` => round([14] / 3) = 5'],
+ },
+ ],
+ [
+ 'trunc',
+ {
+ name: 'Truncate',
+ description: 'Returns the integer part of a number by removing any fractional digits.',
+ example: ['`[[trunc(4.3)]]` => 4', '`[[trunc(4.8)]]` => 4', '`[[trunc(d20 / 3)]]` => trunc([13] / 3) = 4', '`[[trunc(d20 / 3)]]` => trunc([14] / 3) = 5'],
+ },
+ ],
+ [
+ 'sqrt',
+ {
+ name: 'Square Root',
+ description: 'Returns the square root of a number.',
+ example: ['`[[sqrt(4)]]` => 2', '`[[sqrt(d20 + 2)]]` => sqrt([14] + 2) = 4'],
+ },
+ ],
+]);
+
+export const LegalMathFuncsHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/legalMathOperators.ts b/src/commands/helpLibrary/legalMathOperators.ts
new file mode 100644
index 0000000..800b4eb
--- /dev/null
+++ b/src/commands/helpLibrary/legalMathOperators.ts
@@ -0,0 +1,70 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Math Operators';
+const description = `Basic math operators. In the following examples, spaces are included for readability and are not required as they are stripped from the command when ${config.name} parses it.
+
+When multiple are included in one command, they are executed in PEMDAS (Parenthesis, Exponentials, Multiplication, Division, Addition, Subtraction) order.`;
+const dict = new Map([
+ [
+ 'parenthesis',
+ {
+ name: 'Parenthesis',
+ description: 'Used to group parts of an equation, can be nested as much as needed. Supports implicit multiplication.',
+ example: [
+ '`[[4(12 + 3)]]` => 60',
+ '`[[(12 + 3)4]]` => 60',
+ '`[[d20(4 + d4)]]` => [14] * (4 + 3) = 98',
+ '`[[d20(4 + (d4 * (4 + d20) ^ 3))]]` => [19] * (4 + ([__1__] * (4 + 8) ^ 3)) = __32908__',
+ ],
+ },
+ ],
+ [
+ 'exponentials',
+ {
+ name: 'Exponentials',
+ description: 'A base number multiplied by itself a specified number of times.',
+ example: ['`[[10 ^ 2]]` => 100', '`[[d20 ^ 2]]` => [13] ^ 2 = 169'],
+ },
+ ],
+ [
+ 'multiplication',
+ {
+ name: 'Multiplication',
+ description: 'Also known as repeated addition.',
+ example: ['`[[4 * 5]]` => 20', '`[[d20 * 6]]` => [11] * 6 = 66'],
+ },
+ ],
+ [
+ 'division',
+ {
+ name: 'Division',
+ description: 'The inverse of multiplication',
+ example: ['`[[20 / 4]]` => 5', '`[[d20 / 4]]` => [12] / 4 = 3'],
+ },
+ ],
+ [
+ 'addition',
+ {
+ name: 'Addition',
+ description: 'The process of adding things together.',
+ example: ['`[[3 + 4]]` => 7', '`[[d20 + 4]]` => [13] + 4 = 17'],
+ },
+ ],
+ [
+ 'subtraction',
+ {
+ name: 'Subtraction',
+ description: 'Gives the difference between two numbers.',
+ example: ['`[[5 - 2]]` => 3', '`[[d20 - 3]]` => [17] - 3 = 14'],
+ },
+ ],
+]);
+
+export const LegalMathOperators: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/legalMathTrigFuncs.ts b/src/commands/helpLibrary/legalMathTrigFuncs.ts
new file mode 100644
index 0000000..d0bd432
--- /dev/null
+++ b/src/commands/helpLibrary/legalMathTrigFuncs.ts
@@ -0,0 +1,115 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Trigonometric Math Funcs';
+const description = `Math functions such as sine and cosine.
+
+I have no idea why you may need this in a dice rolling bot, but have fun!
+
+Documentation from the [MDN Web Docs](${config.links.mathDocs}).`;
+const dict = new Map([
+ [
+ 'sin',
+ {
+ name: 'Sine',
+ description: 'Returns the sine of a number in radians.',
+ example: ['`[[sin(2)]]` => 0.9092974268256817', '`[[sin(d4)]]` => sin([3]) = 0.1411200080598672'],
+ },
+ ],
+ [
+ 'sinh',
+ {
+ name: 'Hyperbolic Sine',
+ description: 'Returns the hyperbolic sine of a number.',
+ example: ['`[[sinh(2)]]` => 3.626860407847019', '`[[sinh(d4)]]` => sinh([3]) = 10.017874927409903'],
+ },
+ ],
+ [
+ 'asin',
+ {
+ name: 'Arc Sine',
+ description: 'Returns the inverse/arc sine (in radians) of a number.',
+ example: ['`[[asin(1)]]` => 1.5707963267948966', '`[[asin(d4 / 4)]]` => asin([3] / 4) = 0.848062078981481'],
+ },
+ ],
+ [
+ 'asinh',
+ {
+ name: 'Arc Hyperbolic Sine',
+ description: 'Returns the inverse/arc hyperbolic sine of a number.',
+ example: ['`[[asinh(2)]]` => 1.4436354751788103', '`[[asinh(d4)]]` => asinh([3]) = 1.8184464592320668'],
+ },
+ ],
+ [
+ 'cos',
+ {
+ name: 'Cosine',
+ description: 'Returns the cosine of a number in radians.',
+ example: ['`[[cos(2)]]` => -0.4161468365471424', '`[[cos(d4)]]` => cos([3]) = -0.9899924966004454'],
+ },
+ ],
+ [
+ 'cosh',
+ {
+ name: 'Hyperbolic Cosine',
+ description: 'Returns the hyperbolic cosine of a number.',
+ example: ['`[[cosh(2)]]` => 3.7621956910836314', '`[[cosh(d4)]]` => cosh([3]) = 10.067661995777765'],
+ },
+ ],
+ [
+ 'acos',
+ {
+ name: 'Arc Cosine',
+ description: 'Returns the inverse/arc cosine (in radians) of a number.',
+ example: ['`[[acos(0.5)]]` => 1.0471975511965979', '`[[acos(d4 / 4)]]` => acos([3] / 4) = 0.7227342478134157'],
+ },
+ ],
+ [
+ 'acosh',
+ {
+ name: 'Arc Hyperbolic Cosine',
+ description: 'Returns the inverse/arc hyperbolic cosine of a number.',
+ example: ['`[[acosh(2)]]` => 1.3169578969248166', '`[[acosh(d4)]]` => acosh([3]) = 1.7627471740390859'],
+ },
+ ],
+ [
+ 'tan',
+ {
+ name: 'Tangent',
+ description: 'Returns the tangent of a number in radians.',
+ example: ['`[[tan(2)]]` => -2.185039863261519', '`[[tan(d4)]]` => tan([3]) = -0.1425465430742778'],
+ },
+ ],
+ [
+ 'tanh',
+ {
+ name: 'Hyperbolic Tangent',
+ description: 'Returns the hyperbolic tangent of a number.',
+ example: ['`[[tanh(2)]]` => 0.9640275800758169', '`[[tanh(d4)]]` => tanh([3]) = 0.9950547536867305'],
+ },
+ ],
+ [
+ 'atan',
+ {
+ name: 'Arc Tangent',
+ description: 'Returns the inverse/arc tangent (in radians) of a number.',
+ example: ['`[[atan(2)]]` => 1.1071487177940904', '`[[atan(d4)]]` => atan([3]) = 1.2490457723982544'],
+ },
+ ],
+ [
+ 'atanh',
+ {
+ name: 'Arc Hyperbolic Tangent',
+ description: 'Returns the inverse/arc hyperbolic tangent of a number.',
+ example: ['`[[atanh(0.5)]]` => 0.5493061443340548', '`[[atanh(d4 / 4)]]` => atanh([3] / 4) = 0.9729550745276566'],
+ },
+ ],
+]);
+
+export const LegalMathTrigFuncsHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/helpLibrary/miscFeatures.ts b/src/commands/helpLibrary/miscFeatures.ts
new file mode 100644
index 0000000..2634b47
--- /dev/null
+++ b/src/commands/helpLibrary/miscFeatures.ts
@@ -0,0 +1,87 @@
+import config from '~config';
+
+import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
+
+const name = 'Miscellaneous Features';
+const description = `This section includes any other features I can't group elsewhere.`;
+const dict = new Map([
+ [
+ 'user-formatting',
+ {
+ name: 'User Formatting',
+ description: `Any formatting/characters outside of roll commands will be preserved in the output.
+
+The first characters in a message must be a valid roll command, you can pad your message with something like \`[[0]]\` as shown in the example. The example uses the "Super No Details" flag (\`-snd\`) in combination with the "Hide Raw" flag (\`-hr\`) to only show the formatted results.`,
+ example: [
+ `\`\`\`[[0]] ${config.name} attacks the dragon with their Sword!
+To Hit: [[d20 + 4 - 1 + 8]]
+Damage: [[d8 + 10]] -snd -hr\`\`\`
+**Results:**
+0 ${config.name} attacks the dragon with their Sword!
+To Hit: 18
+Damage: 14`,
+ ],
+ },
+ ],
+ [
+ 'nested',
+ {
+ name: 'Nested Rolls',
+ description: `${config.name} supports nesting dice rolls inside of each other. This means you can roll a random number of die, dice of random sizes, etc.`,
+ example: [
+ 'Roll a `d10`, then roll that number of `d20`s.\n`[[ [[d10]]d20 ]]` => `[[d10 = 3]]d20` = [2 + 12 + 11] = 25',
+ '',
+ 'Roll a `d4` and add 4 to it, then use that number as the die size.\nResults in rolling `4d5d1` thru `4d8d1`.\n`[[ 4d[[ d4 + 4 ]]d1 ]]` => `4d[[d4+4 = 6]]d1` = [~~__1__~~ + 3 + 3 + 3] = 9',
+ '',
+ 'Roll 5 `d10`s, but reroll a random side of the die determine by a different `d10`.\n`[[5d10r[[d10]] ]]` => `5d10r[[d10 = 10]]` = [3 + 9 + 5 + 5 + ~~**10**~~ + 6] = 28',
+ ],
+ },
+ ],
+ [
+ 'variable',
+ {
+ name: 'Variables',
+ description: `${config.name}'s variable system allows reusing results as a value inside one message. This is useful when you want to use a result as a part of the final message, but also want to use that result in a follow-up roll.
+
+This message must contain multiple roll commands in it (such as \`[[d4]] [[d8]]\`). Nested dice rolls are not able to be used as a variable, but can use variables inside them.
+
+Variables are numbered from \`x0\` to \`xN\`, where \`N\` equals two less than the total number of roll commands in the message.
+
+**Notes about this example:**
+- The example below starts with \`[[0]]\` so that it is a valid roll command. See the \`Miscellaneous Features>User Formatting\` help page for more details.
+- It is recommended to use the "Super No Details" flag (\`-snd\`) in combination with the "Hide Raw" flag (\`-hr\`) to only show the formatted results. This example does not use it to show exactly what is happening.
+- The example makes use of Nested Roll Commands to use the "To Hit" as the number of dice to roll for the "Explosion".`,
+ example: [
+ `\`\`\`[[0]]<=(this is x0) ${config.name} attacks the dragon with their Magical Sword of Extra Strength and Explosions!
+Strength Check: [[d20 + 8 + 2 - 1]]<=(this is x1)
+To Hit: [[d20 + 4 - 1 + 8]]<=(this is x2)
+Damage: [[(d8 + 10) * x1]]<=(this is x3)
+Explosion: [[ [[x2]]d10! * x3 ]]\`\`\`
+The above results in the following:
+
+@$ rolled:
+\`[[0]] ${config.name} attacks the dragon with their Magical Sword of Extra Strength and Explosions! Strength Check: [[d20 + 8 + 2 - 1]] To Hit: [[d20 + 4 - 1 + 8]] Damage: [[(d8 + 10) * x1]] Explosion: [[ [[x2]]d10! * x3 ]]\`
+**Results:**
+0 ${config.name} attacks the dragon with their Magical Sword of Extra Strength and Explosions!
+Strength Check: 27
+To Hit: __12__
+Damage: 324
+Explosion: __19764__
+
+**Details:**
+\`0\` = 0 = 0
+\`d20+8+2-1\` = [18]+8+2-1 = 27
+\`d20+4-1+8\` = [1]+4-1+8 = 12
+\`(d8+10)*x1\` = ([2]+10)\\*27 = 324
+\`[[x2=12]]d10!*x3\` = [6 + 5 + 9 + 5 + 4 + __1__ + 3 + **10** + 4! + 6 + 2 + 5 + 6]\\*324 = __21384__`,
+ ],
+ },
+ ],
+]);
+
+export const MiscFeaturesHelpPages: HelpPage = {
+ name,
+ description,
+ isPage: true,
+ dict,
+};
diff --git a/src/commands/rollDecorators.ts b/src/commands/rollDecorators.ts
deleted file mode 100644
index 1f4f303..0000000
--- a/src/commands/rollDecorators.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-import { DiscordenoMessage } from '@discordeno';
-
-import config from '~config';
-
-import dbClient from 'db/client.ts';
-import { queries } from 'db/common.ts';
-
-import { infoColor2 } from 'embeds/colors.ts';
-
-import utils from 'utils/utils.ts';
-
-export const rollDecorators = (message: DiscordenoMessage) => {
- // Light telemetry to see how many times a command is being run
- dbClient.execute(queries.callIncCnt('rolldecorators')).catch((e) => utils.commonLoggers.dbError('rollHelp.ts:15', 'call sproc INC_CNT on', e));
-
- message
- .send({
- embeds: [
- {
- color: infoColor2,
- title: 'Roll Command Decorators:',
- description: `This command also has some useful decorators that can used. These decorators simply need to be placed after all rolls in the message.
-
-Examples: \`${config.prefix}d20${config.postfix} -nd\`, \`${config.prefix}d20${config.postfix} -nd -s\`, \`${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} -o a\``,
- fields: [
- {
- name: '`-nd` - No Details',
- value: 'Suppresses all details of the requested roll',
- inline: true,
- },
- {
- name: '`-snd` - Super No Details',
- value: 'Suppresses all details of the requested roll and hides no details message',
- inline: true,
- },
- {
- name: '`-s` - Spoiler',
- value: 'Spoilers all details of the requested roll',
- inline: true,
- },
- {
- name: '`-m` or `-max` - Maximize Roll',
- value: 'Rolls the theoretical maximum roll, cannot be used with `-n` or `-min`',
- inline: true,
- },
- {
- name: '`-min` - Minimize Roll',
- value: 'Rolls the theoretical minimum roll, cannot be used with `-m`, `-max`, or `-n`',
- inline: true,
- },
- {
- name: '`-n` - Nominal Roll',
- value: 'Rolls the theoretical nominal roll, cannot be used with `-m`, `-max`, or `-min`',
- inline: true,
- },
- {
- 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',
- inline: true,
- },
- {
- name: '`-c` - Count Rolls',
- value: 'Shows the Count Embed, containing the count of successful rolls, failed rolls, rerolls, drops, and explosions',
- inline: true,
- },
- {
- name: '`-o [direction]` - Order Roll',
- value: `Rolls the requested roll and orders the results in the requested direction
-
-Available directions:
-\`a\` - Ascending (least to greatest)
-\`d\` - Descending (greatest to least)`,
- inline: true,
- },
- {
- name: '`-ct` - Comma Totals',
- value: 'Adds commas to totals for readability',
- inline: true,
- },
- ],
- },
- ],
- })
- .catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
-};
diff --git a/src/commands/rollHelp.ts b/src/commands/rollHelp.ts
index eb5e3cb..f7f02b6 100644
--- a/src/commands/rollHelp.ts
+++ b/src/commands/rollHelp.ts
@@ -1,266 +1,15 @@
import { DiscordenoMessage } from '@discordeno';
-import config from '~config';
+import { generateHelpMessage } from 'commands/helpLibrary/generateHelpMessage.ts';
import dbClient from 'db/client.ts';
import { queries } from 'db/common.ts';
-import { infoColor1, infoColor2, successColor } from 'embeds/colors.ts';
-
import utils from 'utils/utils.ts';
export const rollHelp = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('rollhelp')).catch((e) => utils.commonLoggers.dbError('rollHelp.ts:15', 'call sproc INC_CNT on', e));
- message
- .send({
- embeds: [
- {
- color: infoColor1,
- title: `${config.name}'s Roll Command Details:`,
- description: `You can chain as many of these options as you want, as long as the option does not disallow it.
-
-This command also can fully solve math equations with parenthesis.
-
-${config.name} supports most of the [Roll20 formatting](${config.links.roll20Formatting}). More details and examples can be found [here](${config.links.roll20Formatting}).
-
-Run \`[[???\` or \`[[rollDecorators\` for details on the roll decorators.`,
- },
- {
- color: infoColor2,
- title: 'Roll20 Dice Options:',
- fields: [
- {
- 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}\`)`,
- },
- {
- name: '`x` [Optional]',
- value: `Number of dice to roll, if omitted, 1 is used
-Additionally, replace \`x\` with \`F\` to roll Fate dice`,
- inline: true,
- },
- {
- name: '`dy` [Required]',
- value: 'Size of dice to roll, `d20` = 20 sided die',
- inline: true,
- },
- {
- name: '`dz` or `dlz` [Optional]',
- value: 'Drops the lowest `z` dice, cannot be used with `kz`',
- inline: true,
- },
- {
- name: '`kz` or `khz` [Optional]',
- value: 'Keeps the highest `z` dice, cannot be used with `dz`',
- inline: true,
- },
- {
- name: '`dhz` [Optional]',
- value: 'Drops the highest `z` dice, cannot be used with `kz`',
- inline: true,
- },
- {
- name: '`klz` [Optional]',
- value: 'Keeps the lowest `z` dice, cannot be used with `dz`',
- inline: true,
- },
- {
- name: '`rq` or `r=q` [Optional]',
- value: 'Rerolls any rolls that match `q`, `r3` will reroll every die that land on 3, throwing out old rolls, cannot be used with `ro`',
- inline: true,
- },
- {
- name: '`rq` [Optional]',
- value: 'Rerolls any rolls that are greater than or equal to `q`, `r3` will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with `ro`',
- inline: true,
- },
- {
- name: '`roq` or `ro=q` [Optional]',
- value: 'Rerolls any rolls that match `q`, `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 `q`, `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,
- },
- {
- name: '`csq` or `cs=q` [Optional]',
- value: 'Changes crit score to `q`',
- inline: true,
- },
- {
- name: '`csq` [Optional]',
- value: 'Changes crit score to be greater than or equal to `q`',
- inline: true,
- },
- {
- name: '`cfq` or `cf=q` [Optional]',
- value: 'Changes crit fail to `q`',
- inline: true,
- },
- {
- name: '`cfq` [Optional]',
- value: 'Changes crit fail to be greater than or equal to `q`',
- inline: true,
- },
- {
- name: '`!` [Optional]',
- value: 'Exploding, rolls another `dy` for every crit success',
- inline: true,
- },
- {
- name: '`!o` [Optional]',
- value: 'Exploding Once, rolls one `dy` for each original crit success',
- inline: true,
- },
- {
- name: '`!p` [Optional]',
- value: 'Penetrating Explosion, rolls one `dy` for each crit success, but subtracts one from each resulting explosion',
- inline: true,
- },
- {
- name: '`!!` [Optional]',
- value: 'Compounding Explosion, rolls one `dy` for each crit success, but adds the resulting explosion to the die that caused this explosion',
- inline: true,
- },
- {
- name: '`!=u` [Optional]',
- value: 'Explode on `u`, rolls another `dy` for every die that lands on `u`',
- inline: true,
- },
- {
- name: '`!>u` [Optional]',
- value: 'Explode on `u` and greater, rolls another `dy` for every die that lands on `u` or greater',
- inline: true,
- },
- ],
- },
- {
- color: infoColor2,
- fields: [
- {
- name: '`!u` [Optional]',
- value: 'Explode Once on `u` and greater, rolls another `dy` for each original die that landed on `u` or greater',
- inline: true,
- },
- {
- name: '`!ou` [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',
- 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',
- inline: true,
- },
- {
- name: '`!! utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
+ message.send(generateHelpMessage()).catch((e: Error) => utils.commonLoggers.messageSendError('rollHelp.ts:247', message, e));
};
diff --git a/src/events.ts b/src/events.ts
index a219067..a6fa2f9 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -5,6 +5,7 @@ import { DEVMODE } from '~flags';
import { debugHandler } from 'events/debug.ts';
import { guildCreateHandler } from 'events/guildCreate.ts';
import { guildDeleteHandler } from 'events/guildDelete.ts';
+import { interactionCreateHandler } from 'events/interactionCreate.ts';
import { messageCreateHandler } from 'events/messageCreate.ts';
import { readyHandler } from 'events/ready.ts';
import { rawHandler } from 'events/raw.ts';
@@ -13,6 +14,7 @@ const eventHandlers: Partial = {};
eventHandlers.guildCreate = guildCreateHandler;
eventHandlers.guildDelete = guildDeleteHandler;
+eventHandlers.interactionCreate = interactionCreateHandler;
eventHandlers.messageCreate = messageCreateHandler;
eventHandlers.ready = readyHandler;
diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts
new file mode 100644
index 0000000..9f0c588
--- /dev/null
+++ b/src/events/interactionCreate.ts
@@ -0,0 +1,43 @@
+import {
+ ButtonData,
+ DiscordMessageComponentTypes,
+ editMessage,
+ Interaction,
+ InteractionResponseTypes,
+ SelectMenuData,
+ sendInteractionResponse,
+} from '@discordeno';
+import { log, LogTypes as LT } from '@Log4Deno';
+
+import { generateHelpMessage, helpCustomId } from 'commands/helpLibrary/generateHelpMessage.ts';
+
+import utils from 'utils/utils.ts';
+
+export const InteractionValueSeparator = '\u205a';
+
+export const interactionCreateHandler = (interaction: Interaction) => {
+ try {
+ if (interaction.data) {
+ const parsedData = JSON.parse(JSON.stringify(interaction.data)) as SelectMenuData | ButtonData;
+
+ if (parsedData.customId.startsWith(helpCustomId) && parsedData.componentType === DiscordMessageComponentTypes.SelectMenu) {
+ // Acknowledge the request since we're editing the original message
+ sendInteractionResponse(interaction.id, interaction.token, {
+ type: InteractionResponseTypes.DeferredUpdateMessage,
+ }).catch((e: Error) => utils.commonLoggers.messageEditError('interactionCreate.ts:26', interaction, e));
+
+ // Edit original message
+ editMessage(BigInt(interaction.channelId ?? '0'), BigInt(interaction.message?.id ?? '0'), generateHelpMessage(parsedData.values[0])).catch((e: Error) =>
+ utils.commonLoggers.messageEditError('interactionCreate.ts:30', interaction, e)
+ );
+ return;
+ }
+
+ log(LT.WARN, `UNHANDLED INTERACTION!!! data: ${JSON.stringify(interaction.data)}`);
+ } else {
+ log(LT.WARN, `UNHANDLED INTERACTION!!! Missing data! ${JSON.stringify(interaction)}`);
+ }
+ } catch (e) {
+ log(LT.ERROR, `UNHANDLED INTERACTION!!! ERR! interaction: ${JSON.stringify(interaction)} error: ${JSON.stringify(e)}`);
+ }
+};
diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts
index b879b36..e748600 100644
--- a/src/events/messageCreate.ts
+++ b/src/events/messageCreate.ts
@@ -59,21 +59,18 @@ export const messageCreateHandler = (message: DiscordenoMessage) => {
// Displays a short message I wanted to include
commands.rip(message);
break;
- case 'rollhelp':
- case 'rh':
- case 'hr':
- case '??':
- // [[rollhelp or [[rh or [[hr or [[??
- // Help command specifically for the roll command
- commands.rollHelp(message);
- break;
case 'rolldecorators':
case 'rd':
case 'dr':
case '???':
- // [[rollDecorators or [[rd or [[dr or [[???
- // Help command specifically for the roll command decorators
- commands.rollDecorators(message);
+ case 'rollhelp':
+ case 'rh':
+ case 'hr':
+ case '??':
+ // Legacy: [[rollDecorators or [[rd or [[dr or [[???
+ // [[rollhelp or [[rh or [[hr or [[??
+ // Help command specifically for the roll command
+ commands.rollHelp(message);
break;
case 'help':
case 'h':
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index af3532c..1d52c28 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -4,7 +4,7 @@
* December 21, 2020
*/
import { log, LogTypes as LT } from '@Log4Deno';
-import { DiscordenoMessage, sendMessage } from '@discordeno';
+import { DiscordenoMessage, Interaction, sendMessage } from '@discordeno';
// ask(prompt) returns string
// ask prompts the user at command line for message
@@ -87,7 +87,7 @@ Available Commands:
};
const genericLogger = (level: LT, message: string) => log(level, message);
-const messageEditError = (location: string, message: DiscordenoMessage | string, err: Error) =>
+const messageEditError = (location: string, message: DiscordenoMessage | Interaction | string, err: Error) =>
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}`);