diff --git a/assets/sounds/mafia/credits.mp3 b/assets/sounds/mafia/credits.mp3 new file mode 100644 index 00000000..1b791065 Binary files /dev/null and b/assets/sounds/mafia/credits.mp3 differ diff --git a/assets/sounds/mafia/day-1.mp3 b/assets/sounds/mafia/day-1.mp3 new file mode 100644 index 00000000..9cc5bd1d Binary files /dev/null and b/assets/sounds/mafia/day-1.mp3 differ diff --git a/assets/sounds/mafia/day-10.mp3 b/assets/sounds/mafia/day-10.mp3 new file mode 100644 index 00000000..5a7929ee Binary files /dev/null and b/assets/sounds/mafia/day-10.mp3 differ diff --git a/assets/sounds/mafia/day-11.mp3 b/assets/sounds/mafia/day-11.mp3 new file mode 100644 index 00000000..6898b8f3 Binary files /dev/null and b/assets/sounds/mafia/day-11.mp3 differ diff --git a/assets/sounds/mafia/day-12.mp3 b/assets/sounds/mafia/day-12.mp3 new file mode 100644 index 00000000..7bc8748c Binary files /dev/null and b/assets/sounds/mafia/day-12.mp3 differ diff --git a/assets/sounds/mafia/day-2.mp3 b/assets/sounds/mafia/day-2.mp3 new file mode 100644 index 00000000..56ec910f Binary files /dev/null and b/assets/sounds/mafia/day-2.mp3 differ diff --git a/assets/sounds/mafia/day-3.mp3 b/assets/sounds/mafia/day-3.mp3 new file mode 100644 index 00000000..313a1d51 Binary files /dev/null and b/assets/sounds/mafia/day-3.mp3 differ diff --git a/assets/sounds/mafia/day-4.mp3 b/assets/sounds/mafia/day-4.mp3 new file mode 100644 index 00000000..3562f4ef Binary files /dev/null and b/assets/sounds/mafia/day-4.mp3 differ diff --git a/assets/sounds/mafia/day-5.mp3 b/assets/sounds/mafia/day-5.mp3 new file mode 100644 index 00000000..6c7c77a5 Binary files /dev/null and b/assets/sounds/mafia/day-5.mp3 differ diff --git a/assets/sounds/mafia/day-6.mp3 b/assets/sounds/mafia/day-6.mp3 new file mode 100644 index 00000000..bd8ed46a Binary files /dev/null and b/assets/sounds/mafia/day-6.mp3 differ diff --git a/assets/sounds/mafia/day-7.mp3 b/assets/sounds/mafia/day-7.mp3 new file mode 100644 index 00000000..5786def0 Binary files /dev/null and b/assets/sounds/mafia/day-7.mp3 differ diff --git a/assets/sounds/mafia/day-8.mp3 b/assets/sounds/mafia/day-8.mp3 new file mode 100644 index 00000000..e05b92fa Binary files /dev/null and b/assets/sounds/mafia/day-8.mp3 differ diff --git a/assets/sounds/mafia/day-9.mp3 b/assets/sounds/mafia/day-9.mp3 new file mode 100644 index 00000000..15f4b5be Binary files /dev/null and b/assets/sounds/mafia/day-9.mp3 differ diff --git a/assets/sounds/mafia/detective-decision-made.mp3 b/assets/sounds/mafia/detective-decision-made.mp3 new file mode 100644 index 00000000..8ed6fdd1 Binary files /dev/null and b/assets/sounds/mafia/detective-decision-made.mp3 differ diff --git a/assets/sounds/mafia/detective.mp3 b/assets/sounds/mafia/detective.mp3 new file mode 100644 index 00000000..014ecf50 Binary files /dev/null and b/assets/sounds/mafia/detective.mp3 differ diff --git a/assets/sounds/mafia/hanged.mp3 b/assets/sounds/mafia/hanged.mp3 new file mode 100644 index 00000000..746f3b93 Binary files /dev/null and b/assets/sounds/mafia/hanged.mp3 differ diff --git a/assets/sounds/mafia/init.mp3 b/assets/sounds/mafia/init.mp3 new file mode 100644 index 00000000..80f0b1fa Binary files /dev/null and b/assets/sounds/mafia/init.mp3 differ diff --git a/assets/sounds/mafia/mafia-decision-made.mp3 b/assets/sounds/mafia/mafia-decision-made.mp3 new file mode 100644 index 00000000..5d4d49e9 Binary files /dev/null and b/assets/sounds/mafia/mafia-decision-made.mp3 differ diff --git a/assets/sounds/mafia/mafia-loses.mp3 b/assets/sounds/mafia/mafia-loses.mp3 new file mode 100644 index 00000000..07dbfd53 Binary files /dev/null and b/assets/sounds/mafia/mafia-loses.mp3 differ diff --git a/assets/sounds/mafia/mafia-wins.mp3 b/assets/sounds/mafia/mafia-wins.mp3 new file mode 100644 index 00000000..8cdcd494 Binary files /dev/null and b/assets/sounds/mafia/mafia-wins.mp3 differ diff --git a/assets/sounds/mafia/mafia.mp3 b/assets/sounds/mafia/mafia.mp3 new file mode 100644 index 00000000..dc9fe455 Binary files /dev/null and b/assets/sounds/mafia/mafia.mp3 differ diff --git a/assets/sounds/mafia/night-1.mp3 b/assets/sounds/mafia/night-1.mp3 new file mode 100644 index 00000000..2c22d235 Binary files /dev/null and b/assets/sounds/mafia/night-1.mp3 differ diff --git a/assets/sounds/mafia/night-10.mp3 b/assets/sounds/mafia/night-10.mp3 new file mode 100644 index 00000000..354d2599 Binary files /dev/null and b/assets/sounds/mafia/night-10.mp3 differ diff --git a/assets/sounds/mafia/night-11.mp3 b/assets/sounds/mafia/night-11.mp3 new file mode 100644 index 00000000..fd07c807 Binary files /dev/null and b/assets/sounds/mafia/night-11.mp3 differ diff --git a/assets/sounds/mafia/night-12.mp3 b/assets/sounds/mafia/night-12.mp3 new file mode 100644 index 00000000..45037cdb Binary files /dev/null and b/assets/sounds/mafia/night-12.mp3 differ diff --git a/assets/sounds/mafia/night-2.mp3 b/assets/sounds/mafia/night-2.mp3 new file mode 100644 index 00000000..ad8c9023 Binary files /dev/null and b/assets/sounds/mafia/night-2.mp3 differ diff --git a/assets/sounds/mafia/night-3.mp3 b/assets/sounds/mafia/night-3.mp3 new file mode 100644 index 00000000..ea66faaf Binary files /dev/null and b/assets/sounds/mafia/night-3.mp3 differ diff --git a/assets/sounds/mafia/night-4.mp3 b/assets/sounds/mafia/night-4.mp3 new file mode 100644 index 00000000..710b68fd Binary files /dev/null and b/assets/sounds/mafia/night-4.mp3 differ diff --git a/assets/sounds/mafia/night-5.mp3 b/assets/sounds/mafia/night-5.mp3 new file mode 100644 index 00000000..0357f12d Binary files /dev/null and b/assets/sounds/mafia/night-5.mp3 differ diff --git a/assets/sounds/mafia/night-6.mp3 b/assets/sounds/mafia/night-6.mp3 new file mode 100644 index 00000000..8be9fe8d Binary files /dev/null and b/assets/sounds/mafia/night-6.mp3 differ diff --git a/assets/sounds/mafia/night-7.mp3 b/assets/sounds/mafia/night-7.mp3 new file mode 100644 index 00000000..0a1907fc Binary files /dev/null and b/assets/sounds/mafia/night-7.mp3 differ diff --git a/assets/sounds/mafia/night-8.mp3 b/assets/sounds/mafia/night-8.mp3 new file mode 100644 index 00000000..139b6d26 Binary files /dev/null and b/assets/sounds/mafia/night-8.mp3 differ diff --git a/assets/sounds/mafia/night-9.mp3 b/assets/sounds/mafia/night-9.mp3 new file mode 100644 index 00000000..472ea8e6 Binary files /dev/null and b/assets/sounds/mafia/night-9.mp3 differ diff --git a/assets/sounds/mafia/no-deceased.mp3 b/assets/sounds/mafia/no-deceased.mp3 new file mode 100644 index 00000000..ef3476e1 Binary files /dev/null and b/assets/sounds/mafia/no-deceased.mp3 differ diff --git a/assets/sounds/mafia/no-vote.mp3 b/assets/sounds/mafia/no-vote.mp3 new file mode 100644 index 00000000..e8d05fc9 Binary files /dev/null and b/assets/sounds/mafia/no-vote.mp3 differ diff --git a/assets/sounds/mafia/reveal-deceased.mp3 b/assets/sounds/mafia/reveal-deceased.mp3 new file mode 100644 index 00000000..e230bc6d Binary files /dev/null and b/assets/sounds/mafia/reveal-deceased.mp3 differ diff --git a/assets/sounds/mafia/rule-ask.mp3 b/assets/sounds/mafia/rule-ask.mp3 new file mode 100644 index 00000000..a7065462 Binary files /dev/null and b/assets/sounds/mafia/rule-ask.mp3 differ diff --git a/assets/sounds/mafia/rules.mp3 b/assets/sounds/mafia/rules.mp3 new file mode 100644 index 00000000..0a714b9a Binary files /dev/null and b/assets/sounds/mafia/rules.mp3 differ diff --git a/assets/sounds/mafia/story-1.mp3 b/assets/sounds/mafia/story-1.mp3 new file mode 100644 index 00000000..a5acc444 Binary files /dev/null and b/assets/sounds/mafia/story-1.mp3 differ diff --git a/assets/sounds/mafia/story-10.mp3 b/assets/sounds/mafia/story-10.mp3 new file mode 100644 index 00000000..55bf1921 Binary files /dev/null and b/assets/sounds/mafia/story-10.mp3 differ diff --git a/assets/sounds/mafia/story-11.mp3 b/assets/sounds/mafia/story-11.mp3 new file mode 100644 index 00000000..e242c023 Binary files /dev/null and b/assets/sounds/mafia/story-11.mp3 differ diff --git a/assets/sounds/mafia/story-12.mp3 b/assets/sounds/mafia/story-12.mp3 new file mode 100644 index 00000000..884ea169 Binary files /dev/null and b/assets/sounds/mafia/story-12.mp3 differ diff --git a/assets/sounds/mafia/story-13.mp3 b/assets/sounds/mafia/story-13.mp3 new file mode 100644 index 00000000..50e929db Binary files /dev/null and b/assets/sounds/mafia/story-13.mp3 differ diff --git a/assets/sounds/mafia/story-14.mp3 b/assets/sounds/mafia/story-14.mp3 new file mode 100644 index 00000000..a64bb1d1 Binary files /dev/null and b/assets/sounds/mafia/story-14.mp3 differ diff --git a/assets/sounds/mafia/story-15.mp3 b/assets/sounds/mafia/story-15.mp3 new file mode 100644 index 00000000..3bc2a8b2 Binary files /dev/null and b/assets/sounds/mafia/story-15.mp3 differ diff --git a/assets/sounds/mafia/story-16.mp3 b/assets/sounds/mafia/story-16.mp3 new file mode 100644 index 00000000..9c12d55c Binary files /dev/null and b/assets/sounds/mafia/story-16.mp3 differ diff --git a/assets/sounds/mafia/story-17.mp3 b/assets/sounds/mafia/story-17.mp3 new file mode 100644 index 00000000..4a533cd9 Binary files /dev/null and b/assets/sounds/mafia/story-17.mp3 differ diff --git a/assets/sounds/mafia/story-18.mp3 b/assets/sounds/mafia/story-18.mp3 new file mode 100644 index 00000000..bdf1fcdc Binary files /dev/null and b/assets/sounds/mafia/story-18.mp3 differ diff --git a/assets/sounds/mafia/story-19.mp3 b/assets/sounds/mafia/story-19.mp3 new file mode 100644 index 00000000..24919963 Binary files /dev/null and b/assets/sounds/mafia/story-19.mp3 differ diff --git a/assets/sounds/mafia/story-2.mp3 b/assets/sounds/mafia/story-2.mp3 new file mode 100644 index 00000000..98f80777 Binary files /dev/null and b/assets/sounds/mafia/story-2.mp3 differ diff --git a/assets/sounds/mafia/story-20.mp3 b/assets/sounds/mafia/story-20.mp3 new file mode 100644 index 00000000..a8b5983c Binary files /dev/null and b/assets/sounds/mafia/story-20.mp3 differ diff --git a/assets/sounds/mafia/story-21.mp3 b/assets/sounds/mafia/story-21.mp3 new file mode 100644 index 00000000..25487d76 Binary files /dev/null and b/assets/sounds/mafia/story-21.mp3 differ diff --git a/assets/sounds/mafia/story-3.mp3 b/assets/sounds/mafia/story-3.mp3 new file mode 100644 index 00000000..13846cfd Binary files /dev/null and b/assets/sounds/mafia/story-3.mp3 differ diff --git a/assets/sounds/mafia/story-4.mp3 b/assets/sounds/mafia/story-4.mp3 new file mode 100644 index 00000000..c00df3f0 Binary files /dev/null and b/assets/sounds/mafia/story-4.mp3 differ diff --git a/assets/sounds/mafia/story-5.mp3 b/assets/sounds/mafia/story-5.mp3 new file mode 100644 index 00000000..ed7c75c4 Binary files /dev/null and b/assets/sounds/mafia/story-5.mp3 differ diff --git a/assets/sounds/mafia/story-6.mp3 b/assets/sounds/mafia/story-6.mp3 new file mode 100644 index 00000000..f23cae3e Binary files /dev/null and b/assets/sounds/mafia/story-6.mp3 differ diff --git a/assets/sounds/mafia/story-7.mp3 b/assets/sounds/mafia/story-7.mp3 new file mode 100644 index 00000000..3cad1bd3 Binary files /dev/null and b/assets/sounds/mafia/story-7.mp3 differ diff --git a/assets/sounds/mafia/story-8.mp3 b/assets/sounds/mafia/story-8.mp3 new file mode 100644 index 00000000..a77a24a4 Binary files /dev/null and b/assets/sounds/mafia/story-8.mp3 differ diff --git a/assets/sounds/mafia/story-9.mp3 b/assets/sounds/mafia/story-9.mp3 new file mode 100644 index 00000000..98b4e273 Binary files /dev/null and b/assets/sounds/mafia/story-9.mp3 differ diff --git a/assets/sounds/mafia/vote.mp3 b/assets/sounds/mafia/vote.mp3 new file mode 100644 index 00000000..609c1243 Binary files /dev/null and b/assets/sounds/mafia/vote.mp3 differ diff --git a/commands/games-mp/mafia.js b/commands/games-mp/mafia.js new file mode 100644 index 00000000..e9bc414c --- /dev/null +++ b/commands/games-mp/mafia.js @@ -0,0 +1,86 @@ +const Command = require('../../structures/Command'); +const Game = require('../../structures/mafia/Game'); +const { verify } = require('../../util/Util'); +const storyCount = 21; + +module.exports = class MafiaCommand extends Command { + constructor(client) { + super(client, { + name: 'mafia', + group: 'mafia', + memberName: 'mafia', + description: 'Who is the Mafia? Who is the detective? Will the Mafia kill them all?', + guildOnly: true + }); + } + + async run(msg) { + 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.`); + const connection = this.client.voice.connections.get(msg.guild.id); + if (!connection) { + const usage = this.client.registry.commands.get('join').usage(); + return msg.reply(`I am not in a voice channel. Use ${usage} to fix that!`); + } + for (const member of connection.channel.members.values()) await msg.guild.members.fetch(member.id); + if (connection.channel.members.size > 16) return msg.reply('Please do not have more than 15 players.'); + if (connection.channel.members.size < 7) return msg.reply('Please have at least 5 players before starting.'); + const game = new Game(this.client, msg.channel, connection); + this.client.games.set(msg.channel.id, game); + try { + await game.generate(connection.channel.members.filter(m => !m.user.bot).map(m => m.user)); + await game.playAudio('init'); + await game.playAudio('rule-ask'); + await msg.say('Type `yes` to hear a rule explanation.'); + const rules = await verify(msg.channel, msg.author); + if (rules) await game.playAudio('rules'); + while (!game.shouldEnd) { + let killed = null; + await game.playAudio(`night-${game.turn}`); + await game.playAudio('mafia'); + const mafia = game.players.filter(p => p.role === 'mafia'); + const choices = await Promise.all(mafia.map(player => player.dmRound())); + const randomizer = choices.filter(c => c !== null); + if (randomizer.length) killed = game.players.get(randomizer[Math.floor(Math.random() * randomizer.length)]); + await game.playAudio('mafia-decision-made'); + const detective = game.players.find(p => p.role === 'detective'); + if (detective) { + await game.playAudio('detective'); + await detective.dmRound(); + await game.playAudio('detective-decision-made'); + } + await game.playAudio(`day-${game.turn}`); + if (killed) { + const story = Math.floor(Math.random() * storyCount) + 1; + await game.playAudio(`story-${story}`); + await game.playAudio('reveal-deceased'); + await msg.say(`Deceased: **${killed}**`); + game.players.delete(killed.id); + } else { + await game.playAudio('no-deceased'); + } + await game.playAudio('vote'); + const playersArr = Array.from(game.players.values()); + const votes = await game.getVotes(playersArr); + if (!votes) { + await game.playAudio('no-votes'); + continue; + } + const hanged = game.getHanged(votes, playersArr); + await game.playAudio('hanged'); + await msg.say(`Hanged: **${hanged.user}**`); + game.players.delete(hanged.id); + ++game.turn; + } + const mafia = game.players.find(p => p.role === 'mafia'); + if (mafia) await game.playAudio('mafia-wins'); + else await game.playAudio('mafia-loses'); + await game.playAudio('credits'); + this.client.games.delete(msg.channel.id); + return null; + } catch (err) { + this.client.games.delete(msg.channel.id); + return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); + } + } +}; diff --git a/package.json b/package.json index 6fff8c32..cdbc3857 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "132.5.0", + "version": "132.6.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": { diff --git a/structures/mafia/Game.js b/structures/mafia/Game.js new file mode 100644 index 00000000..b3c7d124 --- /dev/null +++ b/structures/mafia/Game.js @@ -0,0 +1,109 @@ +const Collection = require('@discordjs/collection'); +const path = require('path'); +const { stripIndents } = require('common-tags'); +const Player = require('./Player'); +const { shuffle } = require('../../util/Util'); +const { SUCCESS_EMOJI_ID } = process.env; + +module.exports = class Game { + constructor(client, channel, connection) { + Object.defineProperty(this, 'client', { value: client }); + + this.name = 'mafia'; + this.players = new Collection(); + this.channel = channel; + this.connection = connection; + this.turn = 1; + } + + determineRoles(playerCount) { + const roles = ['detective', 'mafia', 'mafia']; + for (let i = 0; i < (playerCount - 3); i++) roles.push('innocent'); + return shuffle(roles); + } + + async generate(list) { + const roles = this.determineRoles(list.length); + let i = 0; + for (const user of list) { + try { + await user.send(`You are ${roles[i] === 'detective' ? 'the' : 'a part of the'} **${roles[i]}**.`); + } catch (err) { + await this.channel.send( + `${user}, I couldn't send a DM to you. Please open your DMs and use the \`me\` command to see your role.` + ); + } + const player = new Player(this, user, roles[i]); + this.players.set(user.id, player); + i++; + } + return this.players; + } + + playAudio(id) { + this.dispatcher = this.connection.play( + path.join(__dirname, '..', '..', 'assets', 'sounds', 'mafia', `${id}.mp3`), { volume: 2 } + ); + return new Promise((res, rej) => { + this.dispatcher.once('finish', () => { + this.dispatcher = null; + return res(true); + }); + this.dispatcher.once('error', err => { + this.dispatcher = null; + return rej(err); + }); + }); + } + + async getVotes(playersArr) { + await this.channel.send(stripIndents` + Who do you think is a Mafia member? Please type the number. + ${playersArr.map((p, i) => `**${i + 1}.** ${p.user.tag}`).join('\n')} + `); + const voted = []; + const filter = res => { + if (!this.players.some(p => p.user.id === res.author.id)) return false; + if (voted.includes(res.author.id)) return false; + if (!playersArr[Number.parseInt(res.content, 10) - 1]) return false; + voted.push(res.author.id); + res.react(SUCCESS_EMOJI_ID || '✅').catch(() => null); + return true; + }; + const votes = await this.channel.awaitMessages(filter, { + max: this.players.size, + time: 90000 + }); + if (!votes.size) return null; + return votes; + } + + getHanged(votes, playersArr) { + const counts = new Collection(); + for (const vote of votes.values()) { + const player = this.players.get(playersArr[Number.parseInt(vote.content, 10) - 1].id); + if (counts.has(player.id)) { + ++counts.get(player.id).votes; + } else { + counts.set(player.id, { + id: player.id, + votes: 1, + user: player.user + }); + } + } + return counts.sort((a, b) => b.votes - a.votes).first(); + } + + get shouldEnd() { + return this.players.size < 4 && !this.players.some(p => p.role === 'mafia'); + } + + get voiceChannel() { + return this.connection.channel; + } + + get dispatcher() { + return this.client.dispatchers.get(this.channel.guild.id); + } +}; diff --git a/structures/mafia/Player.js b/structures/mafia/Player.js new file mode 100644 index 00000000..af7a2809 --- /dev/null +++ b/structures/mafia/Player.js @@ -0,0 +1,43 @@ +const { stripIndents } = require('common-tags'); +const questions = { + mafia: 'Who would you like to kill?', + detective: 'Who do you think is a Mafia member?' +}; + +module.exports = class Player { + constructor(game, user, role) { + this.game = game; + this.user = user; + this.id = user.id; + this.role = role; + } + + toString() { + return this.user.toString(); + } + + async dmRound() { + const valid = Array.from(this.game.players.filter(p => p.role !== this.role).values()); + await this.user.send(stripIndents` + ${questions[this.role]} Please type the number. + ${valid.map((p, i) => `**${i + 1}.** ${p.user.tag}`).join('\n')} + `); + const filter = res => valid[Number.parseInt(res.content, 10) - 1]; + const decision = await this.user.dmChannel.awaitMessages(filter, { + max: 1, + time: 120000 + }); + if (!decision.size) { + await this.user.send('Sorry, time is up!'); + return null; + } + const choice = valid[Number.parseInt(decision.first().content, 10) - 1].id; + if (this.role === 'detective') { + const isMafia = this.game.players.get(choice).role === 'mafia'; + await this.user.send(isMafia ? 'Yes, they are a Mafioso.' : 'No, they are not a Mafioso.'); + } else { + await this.user.send(`**${this.game.players.get(choice).user.tag}** is your choice...`); + } + return choice; + } +};