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