Skip to content

Commit

Permalink
Merge pull request #6 from DTCurrie/3-create-melee-roll-for-melee-att…
Browse files Browse the repository at this point in the history
…ack-rolls

Add /melee and /extra-melee commands
  • Loading branch information
DTCurrie committed Sep 18, 2022
2 parents 4988c5d + b9cbc1c commit 8ed8c50
Show file tree
Hide file tree
Showing 20 changed files with 929 additions and 344 deletions.
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ docs/

config.json

# editor

.vscode

# linting output

*.sarif
16 changes: 16 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"eslint.alwaysShowStatus": true,
"eslint.format.enable": true,
"eslint.runtime": "",
"eslint.options": {
"extensions": [".js"]
},
"eslint.validate": [ "javascript" ],
"cSpell.words": [
"rerollable",
"swade"
]
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ Then follow these steps:
4. Development commands will have a `-dev` suffix and will be deployed to the server you used for the `guildId` in `config.json`
5. Log messages are outputted to the terminal `console`
6. Commands will not become global until the next release

It is recommended you use vscode, project settings are committed in the `.vscode` directory to assist with development.
141 changes: 141 additions & 0 deletions commands/extra-melee.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* A module for creating the `/extra-melee` command for the bot.
* @module extra-melee
*/

const {
ActionRowBuilder,
SlashCommandBuilder,
} = require('discord.js');

const {log, error} = require('../lib/logger');
const {createCommandName} = require('../lib/command-name');
const {createTraitResultEmbed} = require('../embeds/trait-result');
const {setTraitOption, setModifierOption, setNicknameOption, getTraitRollOptions} = require('../lib/trait-roll-options');
const {extraTraitRoll, extraTraitReroll} = require('../lib/rolls');
const {createTraitEmbed} = require('../embeds/trait');
const {createMeleeResultEmbed} = require('../embeds/melee-result');
const {createRerollButton, REROLL_BUTTON_ID} = require('../lib/reroll-button');
const {setParryOption, getParryOption} = require('../lib/melee-roll-options');
const {createAcceptButton, ACCEPT_BUTTON_ID} = require('../lib/accept-button');

const commandName = createCommandName('extra-melee');

module.exports = {
data: new SlashCommandBuilder()
.setName(commandName)
.setDescription('Makes an extra melee roll!')
.addIntegerOption(setTraitOption)
.addStringOption(setModifierOption)
.addIntegerOption(setParryOption)
.addStringOption(setNicknameOption),
async execute(interaction) {
const {trait, modifier, nickname} = getTraitRollOptions(interaction);
const parry = getParryOption(interaction);
const title = `${nickname}'s extra melee roll`;
let rerolled = 0;

try {
const {
result,
critical,
roll,
} = extraTraitRoll(commandName, trait, modifier);

if (critical) {
await interaction.reply({
embeds: [createTraitResultEmbed(`${nickname} critically failed!`, 1)],
components: [],
});
return;
}

let currentBest = result;

const actionRow = new ActionRowBuilder().addComponents(
createRerollButton(),
createAcceptButton(),
);

const message = await interaction.reply({
embeds: [createTraitEmbed(title, currentBest, roll)],
components: [actionRow],
});

const collector = message.createMessageComponentCollector({time: 15000});

collector.on('collect', async i => {
if (i.user.id !== interaction.user.id) {
i.reply({content: 'This isn\'t your roll!', ephemeral: true});
return;
}

if (i.customId === ACCEPT_BUTTON_ID) {
collector.stop('accept');
return;
}

if (i.customId === REROLL_BUTTON_ID) {
const {
rerollResult,
criticalReroll,
reroll,
} = extraTraitReroll(commandName, trait, modifier, currentBest);

if (criticalReroll) {
collector.stop('critical-failure');
return;
}

try {
await i.update({
content: `Rerolled ${++rerolled} times!`,
embeds: [
createTraitEmbed(
title,
rerollResult,
reroll,
undefined,
currentBest,
),
],
components: [actionRow],
});

currentBest = rerollResult;
} catch (err) {
error(commandName, 'Error updating interaction', err);
collector.stop('error');
}
}
});

collector.on('end', async (collected, reason) => {
log(commandName, 'collector end', {reason, collected});

if (reason === 'critical-failure') {
await message.interaction.editReply({
embeds: [createTraitResultEmbed(`${nickname} critically failed!`, 1)],
components: [],
});

return;
}

message.interaction.editReply({
content: `Final result (rerolled ${rerolled} times)!`,
embeds: [
createMeleeResultEmbed(
`${nickname}'s extra melee roll!`,
currentBest,
parry,
),
],
components: [],
});
});
} catch (err) {
error(commandName, 'error replying to interaction', err);
}
},
};
143 changes: 50 additions & 93 deletions commands/extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,151 +5,108 @@

const {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
SlashCommandBuilder,
} = require('discord.js');

const {DiceRoll} = require('@dice-roller/rpg-dice-roller');

const {addModifier} = require('../lib/add-modifier');
const {log, error} = require('../lib/logger');
const {createCommandName} = require('../lib/command-name');
const {createResultEmbed} = require('../embeds/result');
const {createRollEmbed, createRerollEmbed} = require('../embeds/rolls');
const {createTraitResultEmbed} = require('../embeds/trait-result');
const {setTraitOption, setModifierOption, setNicknameOption, getTraitRollOptions} = require('../lib/trait-roll-options');
const {extraTraitRoll, extraTraitReroll} = require('../lib/rolls');
const {createTraitEmbed} = require('../embeds/trait');
const {createRerollButton, REROLL_BUTTON_ID} = require('../lib/reroll-button');
const {createAcceptButton, ACCEPT_BUTTON_ID} = require('../lib/accept-button');

const commandName = createCommandName('extra');
const rerollButtonId = 'reroll';

/**
* Handles an extra trait roll
* @param {number} trait The trait die to roll
* @param {string} modifier The modifier to apply to the roll
* @returns {{ critical: boolean, result: number, output: string }}
*/
function handleExtraRoll(trait, modifier) {
log(commandName, 'handleExtraRoll', {trait, modifier});

const command = addModifier(`d${trait}`, modifier);
log(commandName, 'handleExtraRoll command', command);

const roll = new DiceRoll(command);
log(commandName, 'handleExtraRoll roll', roll.output);

const critical = roll.rolls[0].rolls[0].initialValue === 1;

return {
critical,
result: roll.total,
output: roll.output,
};
}

/**
* Makes an extra trait reroll
* @param {number} trait the trait die to reroll
* @param {modifier} modifier the modifiers to apply to the reroll
* @returns {{ reroll: number, criticalReroll: boolean, rerollOutput: string}}
*/
function handleExtraReroll(trait, modifier, last) {
const {critical, result, output} = handleExtraRoll(trait, modifier);

const reroll = result >= last ? result : last;
log(commandName, 'handleExtraReroll reroll', reroll);

return {
reroll,
criticalReroll: critical,
rerollOutput: output,
};
}

module.exports = {
data: new SlashCommandBuilder()
.setName(commandName)
.setDescription('Makes an extra roll!')
.addIntegerOption(option =>
option.setName('trait')
.setDescription('The trait value to use. For example, if you want to roll for a trait with a d8 just enter "8".')
.setRequired(true))
.addStringOption(option =>
option.setName('modifier')
.setDescription('The modifier to use. For example, you would enter "+1" for a bonus of 1, or "-2" for a penalty of 2.')
.setRequired(false))
.addStringOption(option =>
option.setName('extra')
.setDescription('The extra\'s character name to use for the roll; if blank it will use your server nickname')
.setRequired(false)),
.setDescription('Makes an extra trait roll!')
.addIntegerOption(setTraitOption)
.addStringOption(setModifierOption)
.addStringOption(setNicknameOption),
async execute(interaction) {
const trait = interaction.options.getInteger('trait');
const modifier = interaction.options.getString('modifier');
const extra = interaction.options.getString('extra');
const nickname = extra ?? interaction.member.nick;
const {trait, modifier, nickname} = getTraitRollOptions(interaction);
const title = `${nickname}'s extra roll`;
let rerolled = 0;

try {
const {critical, result, output} = handleExtraRoll(trait, modifier);
const {result, critical, roll} = extraTraitRoll(commandName, trait, modifier);

if (critical) {
await interaction.reply({
embeds: [createResultEmbed(`${nickname} critically failed!`, 1)],
embeds: [createTraitResultEmbed(`${nickname} critically failed!`, 1)],
components: [],
});
return;
}

let currentBest = result;
const actionRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(rerollButtonId)
.setLabel('Reroll')
.setStyle(ButtonStyle.Success),
);

const actionRow = new ActionRowBuilder().addComponents(
createRerollButton(),
createAcceptButton(),
);

const message = await interaction.reply({
embeds: [createRollEmbed(nickname, result, output)],
embeds: [createTraitEmbed(title, result, roll)],
components: [actionRow],
});

const filter = i => i.customId === rerollButtonId && i.user.id === interaction.user.id;
const collector = message.createMessageComponentCollector({filter, time: 15000});
const collector = message.createMessageComponentCollector({time: 15000});

collector.on('collect', async i => {
if (i.user.id !== interaction.user.id) {
i.reply({content: 'This isn\'t your roll!', ephemeral: true});
return;
}

const {reroll, criticalReroll, rerollOutput} = handleExtraReroll(trait, modifier, currentBest);

if (criticalReroll) {
collector.stop('critical-failure');
if (i.customId === ACCEPT_BUTTON_ID) {
collector.stop('accept');
return;
}

await i.update({
content: `Rerolled ${++rerolled} times!`,
embeds: [createRerollEmbed(nickname, reroll, rerollOutput, currentBest)],
components: [actionRow],
});

currentBest = reroll;
if (i.customId === REROLL_BUTTON_ID) {
const {
rerollResult,
criticalReroll,
reroll,
} = extraTraitReroll(commandName, trait, modifier, currentBest);

if (criticalReroll) {
collector.stop('critical-failure');
return;
}

try {
await i.update({
content: `Rerolled ${++rerolled} times!`,
embeds: [createTraitEmbed(title, rerollResult, reroll, undefined, currentBest)],
components: [actionRow],
});

currentBest = rerollResult;
} catch (err) {
error(commandName, 'Error updating interaction', err);
collector.stop('error');
}
}
});

collector.on('end', async (collected, reason) => {
log(commandName, 'collector end', {reason, collected});
if (reason === 'critical-failure') {
await message.interaction.editReply({
embeds: [createResultEmbed(`${nickname} critically failed!`, 1)],
embeds: [createTraitResultEmbed(`${nickname} critically failed!`, 1)],
components: [],
});
return;
}

message.interaction.editReply({
content: `Final result (rerolled ${rerolled} times)!`,
embeds: [createResultEmbed(`${nickname}'s roll!`, currentBest)],
embeds: [createTraitResultEmbed(`${nickname}'s roll!`, currentBest)],
components: [],
});
});
Expand Down
Loading

0 comments on commit 8ed8c50

Please sign in to comment.