diff --git a/README.md b/README.md index 0b5cd419..8a8cbb93 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ in the appropriate channel's topic to use it. ## Commands -Total: 542 +Total: 543 ### Utility: @@ -580,6 +580,7 @@ Total: 542 * **guesspionage:** Tests your knowledge of humans as you guess how people responded to poll questions. * **gunfight:** Engage in a western gunfight against another user. High noon. * **island:** Who will be the final two left on the island after a series of vote-kicks? +* **imposter:** Who is the imposter among us? * **lie-swatter:** Players are given a fact and must quickly decide if it's True or a Lie. * **pick-a-number:** Two players pick a number between 1 and 10. Whoever's closer wins. * **poker:** Play poker with up to 5 other users. diff --git a/assets/json/imposter.json b/assets/json/imposter.json new file mode 100644 index 00000000..dd9e7f50 --- /dev/null +++ b/assets/json/imposter.json @@ -0,0 +1,53 @@ +[ + "restart", + "imposter", + "among", + "fart", + "xiao", + "squid", + "redo", + "discord", + "pony", + "jesus", + "killer", + "death", + "die", + "explode", + "difficult", + "figure", + "yeet", + "random", + "cow", + "chicken", + "help", + "work", + "talk", + "fork", + "sandwhich", + "anime", + "phone", + "friend", + "pog", + "owl", + "strange", + "chair", + "gaming", + "gamer", + "portal", + "butt", + "steam", + "computer", + "politics", + "car", + "idiot", + "moron", + "donkey", + "shrek", + "word", + "kill", + "server", + "channel", + "role", + "loser", + "pneumonoultramicroscopicsilicovolcanoconiosis" +] diff --git a/commands/games-mp/imposter.js b/commands/games-mp/imposter.js new file mode 100644 index 00000000..027a720f --- /dev/null +++ b/commands/games-mp/imposter.js @@ -0,0 +1,154 @@ +const Command = require('../../structures/Command'); +const { stripIndents } = require('common-tags'); +const Collection = require('@discordjs/collection'); +const { delay, awaitPlayers, list } = require('../../util/Util'); +const words = require('../../assets/json/imposter'); + +module.exports = class ImposterCommand extends Command { + constructor(client) { + super(client, { + name: 'imposter', + aliases: ['among-us'], + group: 'games-mp', + memberName: 'imposter', + description: 'Who is the imposter among us?', + guildOnly: true, + clientPermissions: ['ADD_REACTIONS', 'READ_MESSAGE_HISTORY'], + args: [ + { + key: 'playersCount', + label: 'players', + prompt: 'How many players are you expecting to have?', + type: 'integer', + min: 3, + max: 20 + } + ] + }); + } + + async run(msg, { playersCount }) { + const current = this.client.games.get(msg.channel.id); + if (current) return msg.reply(`Please wait until the current game of \`${current.name}\` is finished.`); + this.client.games.set(msg.channel.id, { name: this.name }); + try { + const awaitedPlayers = await awaitPlayers(msg, playersCount, 3); + if (!awaitedPlayers) { + this.client.games.delete(msg.channel.id); + return msg.say('Game could not be started...'); + } + const word = words[Math.floor(Math.random() * words.length)]; + const wordRegex = new RegExp(`\\b${word}\\b`, 'i'); + const players = new Collection(); + const imposter = awaitedPlayers[Math.floor(Math.random() * awaitedPlayers.length)]; + for (const player of awaitedPlayers) { + players.set(player, { + id: player, + user: await this.client.users.fetch(player), + killed: false, + imposter: imposter === player + }); + const newPlayer = players.get(player); + if (imposter === player) newPlayer.user.send(`You are the imposter. The kill word is ${word}.`); + else newPlayer.user.send('You are not the imposter.'); + } + let lastTurnTimeout = false; + const winners = []; + while (players.some(player => !player.killed) > 2) { + ++turn; + const playersLeft = players.filter(player => !player.killed).size; + await msg.say(`There are **${playersLeft}** players left. Talk until someone says the kill word.`); + const filter = res => { + const player = players.get(res.author.id); + if (!player || player.killed || player.imposter) return false; + if (res.content && wordRegex.test(res.content)) return true; + return false; + }; + const msgs = await msg.channel.awaitMessages(filter, { + max: 1, + time: 600000 + }); + if (msgs.size) { + const killedMsg = msgs.first(); + try { + await killedMsg.react('🔪'); + } catch { + await killedMsg.reply('🔪'); + } + players.get(killedMsg.author.id).killed = true; + await msg.say(stripIndents` + ${killedMsg.author} has been murdered for saying the kill word! + Talk amongst yourselves, who is the imposter? Voting begins in 1 minute. + `); + } else { + await msg.say(stripIndents` + No has said the word for 10 minutes. We're voting anyway! Who looks suspicious? + Talk amongst yourselves, who is the imposter? Voting begins in 1 minute. + `); + } + await delay(60000); + const choices = players.filter(player => !player.killed); + await msg.say(stripIndents` + Alright, who do you think the imposter is? You have 1 minute to vote. + + _Type the number of the player you think is the imposter._ + ${choices.map((player, i) => `**${i + 1}.** ${player.user.tag}`).join('\n')} + `) + const votes = new Collection(); + const voteFilter = res => { + const player = players.get(res.author.id); + if (!player || player.killed) return false; + const int = Number.parseInt(res.content, 10); + if (int >= 1 && int <= players.filter(player => !player.killed).size) { + const currentVotes = votes.get(choices[int - 1]); + votes.set(ids[int - 1], { + votes: currentVotes ? currentVotes + 1 : 1, + id: ids[int - 1] + }); + res.react(SUCCESS_EMOJI_ID || '✅').catch(() => null); + return true; + } + return false; + }; + const vote = await msg.channel.awaitMessages(voteFilter, { + max: players.filter(player => !player.killed).size, + time: 60000 + }); + if (!vote.size) { + if (lastTurnTimeout) { + await msg.say('Game ended due to inactivity.'); + break; + } else { + await msg.say('Come on guys, get in the game!'); + lastTurnTimeout = true; + continue; + } + } + const kicked = players.get(votes.sort((a, b) => b.votes - a.votes).first().id); + players.get(kicked.id).killed = true; + if (kicked.id === players.find(player => player.imposter).id) { + await msg.say(`**${kicked.user.tag}** was the imposter.`); + winners.push(...players.filter(player => !player.killed).map(player => player.user.tag)); + break; + } + const amountLeft = players.filter(player => !player.killed); + await msg.say(stripIndents` + **${kicked.user.tag}** was not the imposter. + + ${amountLeft.size > 2 ? '_Next round starts in 30 seconds._' : ''} + `); + if (amountLeft.size > 2) { + await delay(30000); + } else { + winners.push(players.find(player => player.imposter).user.tag); + break; + } + } + this.client.games.delete(msg.channel.id); + return msg.say(`Congrats, ${list(winners)}! The kill word was **${word}**.`); + } catch (err) { + this.client.games.delete(msg.channel.id); + throw err; + } + } +}; diff --git a/package.json b/package.json index e2726db7..0d6f872a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "119.25.2", + "version": "119.26.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {