From 3139307306370559f0fbd4018fad6d707743f433 Mon Sep 17 00:00:00 2001 From: "Ronald M. Clifford" Date: Sun, 21 Jan 2024 15:14:37 -0800 Subject: [PATCH] Various bug fixes and enhancements. --- node/src/discord/commands/h.js | 1 + node/src/discord/commands/help.js | 4 +- node/src/discord/commands/t.js | 1 + node/src/discord/index.js | 29 +++++++-- node/src/models/countdown.js | 19 +++--- node/src/models/race.js | 97 +++++++++++++++++++++++++------ 6 files changed, 119 insertions(+), 32 deletions(-) create mode 100644 node/src/discord/commands/h.js create mode 100644 node/src/discord/commands/t.js diff --git a/node/src/discord/commands/h.js b/node/src/discord/commands/h.js new file mode 100644 index 0000000..b79e6ca --- /dev/null +++ b/node/src/discord/commands/h.js @@ -0,0 +1 @@ +module.exports = require("./help"); diff --git a/node/src/discord/commands/help.js b/node/src/discord/commands/help.js index 1c84d24..bd491e9 100644 --- a/node/src/discord/commands/help.js +++ b/node/src/discord/commands/help.js @@ -39,7 +39,7 @@ class HelpCmd { fields: [ { name: "General Commands", - value: "`.help` - This help text.\n`.race` - Start a new race.", + value: "`.help`/`.h` - This help text.\n`.race` - Start a new race.", inline: true }, { @@ -48,7 +48,7 @@ class HelpCmd { }, { name: "Race Room Commands - During the Race", - value: "`.done`/`.d` - Indicate that you have finished the race.\n`.forfeit`/`.f` - Forfeit the race.\n`.notdone`/`.n` - If you did `.done` or `.forfeit`, this undoes that, re-entering you into the race.\n`.time` - Get the current elapsed time of the race." + value: "`.done`/`.d` - Indicate that you have finished the race.\n`.forfeit`/`.f` - Forfeit the race.\n`.notdone`/`.n` - If you did `.done` or `.forfeit`, this undoes that, re-entering you into the race.\n`.time`/`.t` - Get the current elapsed time of the race." }, { name: "Race Room Commands - After the Race", diff --git a/node/src/discord/commands/t.js b/node/src/discord/commands/t.js new file mode 100644 index 0000000..0a5e2fc --- /dev/null +++ b/node/src/discord/commands/t.js @@ -0,0 +1 @@ +module.exports = require("./time"); diff --git a/node/src/discord/index.js b/node/src/discord/index.js index c878ef6..4426b7f 100644 --- a/node/src/discord/index.js +++ b/node/src/discord/index.js @@ -9,17 +9,21 @@ const DiscordJs = require("discord.js"), discord = new DiscordJs.Client({ intents: [ DiscordJs.IntentsBitField.Flags.Guilds, - DiscordJs.IntentsBitField.Flags.GuildMessages + DiscordJs.IntentsBitField.Flags.GuildMessages, + DiscordJs.IntentsBitField.Flags.MessageContent ], partials: [DiscordJs.Partials.Channel] }), - messageParse = /\.(?[a-z]+)\s*(?.*)/i; + messageParse = /^\.(?[a-z]+)\s*(?.*)/i; let readied = false; /** @type {DiscordJs.CategoryChannel} */ let raceCategory; +/** @type {DiscordJs.TextChannel} */ +let resultsChannel; + /** @type {DiscordJs.Guild} */ let sprintGuild; @@ -70,6 +74,20 @@ class Discord { return raceCategory; } + // ## # ## # ## + // # # # # # # + // ### ## ### # # # ### ### # ### ### ### ### ## # + // # # # ## ## # # # # ## # # # # # # # # # # ## # + // # ## ## # # # # ## # # # # # ## # # # # ## # + // # ## ### ### ### ## ### ## # # # # # # # # ## ### + /** + * Returns the results channel. + * @returns {DiscordJs.TextChannel} The results channel. + */ + static get resultsChannel() { + return resultsChannel; + } + // # # # ### ## // # # # # # # # # // ### ### ### # # # # ## # ## @@ -105,7 +123,8 @@ class Discord { readied = true; } - raceCategory = /** @type {DiscordJs.CategoryChannel} */ (sprintGuild.channels.cache.find((r) => r.name === "Race Rooms")); // eslint-disable-line @stylistic/no-extra-parens + raceCategory = /** @type {DiscordJs.CategoryChannel} */ (sprintGuild.channels.cache.find((c) => c.name === "Race Rooms")); // eslint-disable-line @stylistic/no-extra-parens + resultsChannel = /** @type {DiscordJs.TextChannel} */ (sprintGuild.channels.cache.find((c) => c.name === "racebot-results")); // eslint-disable-line @stylistic/no-extra-parens staffRole = sprintGuild.roles.cache.find((r) => r.name === "SPRINT Staff"); }); @@ -190,7 +209,7 @@ class Discord { * @returns {Promise} A promise that returns the new channel. */ static async createNumberedChannel(name) { - const channelSuffixes = sprintGuild.channels.cache.filter((c) => c.name.startsWith(name)).map((c) => parseInt(c.name.split("-")[1], 10)).sort((a, b) => a - b); + const channelSuffixes = sprintGuild.channels.cache.filter((c) => c.parentId && c.parentId === raceCategory.id && c.name.startsWith(name)).map((c) => parseInt(c.name.split("-")[1], 10)).sort((a, b) => a - b); let suffix = 1; for (let i = 0; i < channelSuffixes.length; i++) { @@ -273,7 +292,7 @@ class Discord { let msg; try { - msg = await Discord.richQueue(Discord.embedBuilder({description: message}), channel); + msg = await channel.send(message); } catch {} return msg; } diff --git a/node/src/models/countdown.js b/node/src/models/countdown.js index 965fde0..dc368ba 100644 --- a/node/src/models/countdown.js +++ b/node/src/models/countdown.js @@ -31,12 +31,15 @@ class Countdown { this.race.start = Date.now() + 10000; - Discord.embedBuilder({ - title: "Race Starts in 10 Seconds", - description: "Everyone is ready! The race will start in 10 seconds!" - }); - setTimeout(async () => { + await Discord.richQueue( + Discord.embedBuilder({ + title: "Race Starts in 10 Seconds", + description: "Everyone is ready! The race will start in 10 seconds!" + }), + this.race.channel + ); + if (this.cancelled) { return; } @@ -72,9 +75,9 @@ class Countdown { // ## # # # # ## ## ### /** * Cancels a countdown. - * @returns {void} + * @returns {Promise} A promise that resolves when the countdown is cancelled. */ - cancel() { + async cancel() { if (this.race.started) { return; } @@ -82,7 +85,7 @@ class Countdown { this.cancelled = true; this.race.start = 0; - Discord.queue("Countdown aborted.", this.race.channel); + await Discord.queue("Countdown aborted.", this.race.channel); } } diff --git a/node/src/models/race.js b/node/src/models/race.js index 1c787de..2d6861b 100644 --- a/node/src/models/race.js +++ b/node/src/models/race.js @@ -34,6 +34,8 @@ class Race { await race.setup(); + race.channel.setTopic("Type .help or .h for help. See the pinned post for current race status.", "Creating a race room."); + Race.races.push(race); return race; } @@ -150,6 +152,11 @@ class Race { * @returns {Promise} A promise that resolves when the race room is closed. */ async close(member) { + // Record the race results, if there were any. + if (this.end > 0) { + await postResult(); + } + // Remove this race from the races array. Race.races = Race.races.filter((r) => r !== this); @@ -231,7 +238,7 @@ class Race { player.finish = Date.now(); // Get the number of finished racers. - const finishedPlayers = this.players.filter((p) => p.finish > 0), + const finishedPlayers = this.players.filter((p) => p.finish > 0).sort((a, b) => a.finish - b.finish), finished = finishedPlayers.length; const ordinal = finished % 100 >= 11 && finished % 100 <= 13 ? "th" : finished % 10 === 1 ? "st" : finished % 10 === 2 ? "nd" : finished % 10 === 3 ? "rd" : "th"; @@ -311,7 +318,7 @@ class Race { } if (this.countdown) { - this.countdown.cancel(); + await this.countdown.cancel(); this.countdown = null; } @@ -321,7 +328,7 @@ class Race { await Discord.richQueue( Discord.embedBuilder({ - title: `${member} Entered`, + title: `${member.displayName} Entered`, description: `${member} has entered! There ${this.players.length === 1 ? "is" : "are"} now ${this.players.length} ${this.players.length === 1 ? "entry" : "entries"}, with ${this.players.filter((p) => !p.ready).length} remaining to be ready.` }), this.channel @@ -411,7 +418,7 @@ class Race { this.end = Date.now(); // Get the finished and forfeited racers. - const finishedPlayers = this.players.filter((p) => p.finish > 0), + const finishedPlayers = this.players.filter((p) => p.finish > 0).sort((a, b) => a.finish - b.finish), forfeitPlayers = this.players.filter((p) => p.forfeit); const message = Discord.embedBuilder({ @@ -483,6 +490,14 @@ class Race { this.players = this.players.filter((p) => p.discordId !== kickedMember.id); + await Discord.richQueue( + Discord.embedBuilder({ + title: "Player Kicked", + description: `${kickedMember} has been removed from the race.` + }), + this.channel + ); + if (this.start === 0) { // Get the number of unreadied racers. const unreadied = this.players.filter((p) => !p.ready).length; @@ -498,7 +513,7 @@ class Race { this.end = Date.now(); // Get the finished and forfeited racers. - const finishedPlayers = this.players.filter((p) => p.finish > 0), + const finishedPlayers = this.players.filter((p) => p.finish > 0).sort((a, b) => a.finish - b.finish), forfeitPlayers = this.players.filter((p) => p.forfeit); const message = Discord.embedBuilder({ @@ -598,6 +613,49 @@ class Race { return player; } + // # ### ## # + // # # # # # + // ### ## ### ### # # ## ### # # # ### + // # # # # ## # ### # ## ## # # # # + // # # # # ## # # # ## ## # # # # + // ### ## ### ## # # ## ### ### ### ## + // # + /** + * Posts the results to the results channel. + * @returns {Promise} A promise that resolves when the results have been posted. + */ + async postResult() { + const message = Discord.embedBuilder({ + title: "Completed Race", + description: `Race completed \nSeed: ${this.seed}`, + fields: [] + }); + + const finishedPlayers = this.players.filter((p) => p.finish > 0).sort((a, b) => a.finish - b.finish), + forfeitPlayers = this.players.filter((p) => p.forfeit); + + if (finishedPlayers.length > 0) { + message.addFields({ + name: "Standings", + value: finishedPlayers.map((p, index) => `${index + 1}) **${Race.formatTime(p.finish - this.start)}** - <@${p.discordId}>`).join("\n"), + inline: true + }); + } + + if (forfeitPlayers.length > 0) { + message.addFields({ + name: "Forfeited Players", + value: forfeitPlayers.map((p) => `<@${p.discordId}>`).join("\n"), + inline: true + }); + } + + await Discord.richQueue( + message, + Discord.resultsChannel + ); + } + // # // # // ### ## ### ### # # @@ -628,14 +686,14 @@ class Race { await Discord.richQueue( Discord.embedBuilder({ - title: `${member} Ready`, + title: `${member.displayName} Ready`, description: `${member} has is now ready! There ${this.players.length === 1 ? "is" : "are"} ${this.players.length} ${this.players.length === 1 ? "entry" : "entries"}, with ${this.players.filter((p) => !p.ready).length} remaining to be ready.` }), this.channel ); } else { if (this.countdown) { - this.countdown.cancel(); + await this.countdown.cancel(); this.countdown = null; } @@ -646,7 +704,7 @@ class Race { await Discord.richQueue( Discord.embedBuilder({ - title: `${member} Entered and Ready`, + title: `${member.displayName} Entered and Ready`, description: `${member} has entered and is now ready! There ${this.players.length === 1 ? "is" : "are"} ${this.players.length} ${this.players.length === 1 ? "entry" : "entries"}, with ${this.players.filter((p) => !p.ready).length} remaining to be ready.` }), this.channel @@ -700,11 +758,16 @@ class Race { * @returns {Promise} A promise that resolves when the race has been setup. */ async setup() { + if (this.end > 0) { + await this.postResult(); + } + this.seed = Math.floor(Math.random() * 100000000).toString().padStart(8, "0"); this.players = []; this.started = false; this.start = 0; this.end = 0; + this.countdown = null; this.pinnedPost = await Discord.richQueue( Discord.embedBuilder({ @@ -772,7 +835,7 @@ class Race { return; } - Discord.queue(`The current race time is **${Race.formatTime(Date.now() - this.start)}**.`, this.channel); + await Discord.queue(`The current race time is **${Race.formatTime(Date.now() - this.start)}**.`, this.channel); } // # @@ -812,7 +875,7 @@ class Race { } if (this.countdown) { - this.countdown.cancel(); + await this.countdown.cancel(); this.countdown = null; } @@ -820,7 +883,7 @@ class Race { await Discord.richQueue( Discord.embedBuilder({ - title: `${member} Unready`, + title: `${member.displayName} Unready`, description: `${member} is no longer ready. There ${this.players.length === 1 ? "is" : "are"} ${this.players.length} ${this.players.length === 1 ? "entry" : "entries"}, with ${this.players.filter((p) => !p.ready).length} remaining to be ready.` }), this.channel @@ -898,7 +961,7 @@ class Race { // Build message. const message = Discord.embedBuilder({ title: "Race Starting", - description: `Seed: ${this.seed}\nStarting `, + description: `Seed: ${this.seed}\nStarting `, fields: [ { name: "Players", @@ -929,7 +992,7 @@ class Race { const message = Discord.embedBuilder({ title: "Race Started", - description: `Seed: ${this.seed}\nStarted `, + description: `Seed: ${this.seed}\nStarted `, fields: [] }); @@ -959,7 +1022,7 @@ class Race { message.addFields({ name: "Commands", - value: "`.done` or `.d` - Indicate you completed the race\n`.forfeit` or `.f` - Forfeit the race\n`.undone` or `.u` - Reenter the race if you accidentally completed or forfeited\n`.time` - Get the time elapsed in the race." + value: "`.done` or `.d` - Indicate you completed the race\n`.forfeit` or `.f` - Forfeit the race\n`.undone` or `.u` - Reenter the race if you accidentally completed or forfeited\n`.time` or `.t` - Get the time elapsed in the race." }); await Discord.richEdit(this.pinnedPost, message); @@ -978,7 +1041,7 @@ class Race { const message = Discord.embedBuilder({ title: "Race Complete", - description: `Seed: ${this.seed}\nStarted \nEnded `, + description: `Seed: ${this.seed}\nStarted \nEnded `, fields: [] }); @@ -1048,7 +1111,7 @@ class Race { await Discord.richQueue( Discord.embedBuilder({ - title: `${member} Withdrawn`, + title: `${member.displayName} Withdrawn`, description: `${member} has withdrawn. There ${this.players.length === 1 ? "is" : "are"} now ${this.players.length} ${this.players.length === 1 ? "entry" : "entries"}, with ${remainingPlayers.length} remaining to be ready.` }), this.channel @@ -1059,7 +1122,7 @@ class Race { } if (this.countdown && this.players.length < 2) { - this.countdown.cancel(); + await this.countdown.cancel(); this.countdown = null; }