mirror of
https://github.com/arthur-pbty/xiao.git
synced 2026-06-03 23:36:43 +02:00
Port Storyteller to Xiao
This commit is contained in:
@@ -54,9 +54,8 @@ Xiao is a Discord bot coded in JavaScript with
|
|||||||
## Related Bots
|
## Related Bots
|
||||||
|
|
||||||
* [Rando Cardrissian](https://github.com/dragonfire535/rando-cardrissian) is a Cards Against Humanity bot, whose features were originally built into Xiao.
|
* [Rando Cardrissian](https://github.com/dragonfire535/rando-cardrissian) is a Cards Against Humanity bot, whose features were originally built into Xiao.
|
||||||
* [Storyteller](https://github.com/dragonfire535/storyteller) is a Mafia bot made for Discord's 2019 Hack Week, whose features were originally built into Xiao.
|
|
||||||
|
|
||||||
## Commands (344)
|
## Commands (346)
|
||||||
### Utility:
|
### Utility:
|
||||||
|
|
||||||
* **eval:** Executes JavaScript code.
|
* **eval:** Executes JavaScript code.
|
||||||
@@ -284,6 +283,8 @@ Xiao is a Discord bot coded in JavaScript with
|
|||||||
* **battle:** Engage in a turn-based battle against another user or the AI.
|
* **battle:** Engage in a turn-based battle against another user or the AI.
|
||||||
* **emoji-emoji-revolution:** Can you type arrow emoji faster than anyone else has ever typed them before?
|
* **emoji-emoji-revolution:** Can you type arrow emoji faster than anyone else has ever typed them before?
|
||||||
* **gunfight:** Engage in a western gunfight against another user. High noon.
|
* **gunfight:** Engage in a western gunfight against another user. High noon.
|
||||||
|
* **mafia-role:** Displays your current role during Mafia games.
|
||||||
|
* **mafia:** Who is the Mafia? Who is the detective? Will the Mafia kill them all?
|
||||||
* **quiz-duel:** Answer a series of quiz questions against an opponent.
|
* **quiz-duel:** Answer a series of quiz questions against an opponent.
|
||||||
* **russian-roulette:** Who will pull the trigger and die first?
|
* **russian-roulette:** Who will pull the trigger and die first?
|
||||||
* **tic-tac-toe:** Play a game of tic-tac-toe with another user.
|
* **tic-tac-toe:** Play a game of tic-tac-toe with another user.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
|||||||
|
const Command = require('../../structures/Command');
|
||||||
|
|
||||||
|
module.exports = class MafiaRoleCommand extends Command {
|
||||||
|
constructor(client) {
|
||||||
|
super(client, {
|
||||||
|
name: 'mafia-role',
|
||||||
|
aliases: ['mafia-me'],
|
||||||
|
group: 'mp-games',
|
||||||
|
memberName: 'mafia-role',
|
||||||
|
description: 'Displays your current role during Mafia games.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(msg) {
|
||||||
|
const games = this.client.games.filter(game => game.players.has(msg.author.id) && game.name === 'mafia');
|
||||||
|
if (!games.size) return msg.reply('You aren\'t a member of any games.');
|
||||||
|
return msg.reply(games.map(game => {
|
||||||
|
const { role } = game.players.get(msg.author.id);
|
||||||
|
return `**${game.channel.guild.name} (${game.channel}):** ${role}`;
|
||||||
|
}).join('\n'));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
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: 'mp-games',
|
||||||
|
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 voiceChannel = msg.member.voice.channel;
|
||||||
|
if (!voiceChannel) return msg.reply('You must be in a voice channel to start a game.');
|
||||||
|
for (const member of voiceChannel.members.values()) await msg.guild.members.fetch(member.id);
|
||||||
|
if (voiceChannel.members.size > 15) return msg.reply('Please do not have more than 15 players.');
|
||||||
|
if (voiceChannel.members.size < 6) return msg.reply('Please have at least 5 players before starting.');
|
||||||
|
const game = new Game(this.client, msg.channel, voiceChannel);
|
||||||
|
this.client.games.set(msg.channel.id, game);
|
||||||
|
try {
|
||||||
|
await game.init();
|
||||||
|
await game.generate(voiceChannel.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');
|
||||||
|
game.end();
|
||||||
|
return null;
|
||||||
|
} catch (err) {
|
||||||
|
game.end();
|
||||||
|
return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "xiao",
|
"name": "xiao",
|
||||||
"version": "109.0.6",
|
"version": "109.1.0",
|
||||||
"description": "Your personal server companion.",
|
"description": "Your personal server companion.",
|
||||||
"main": "Xiao.js",
|
"main": "Xiao.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"moment-duration-format": "^2.3.2",
|
"moment-duration-format": "^2.3.2",
|
||||||
"moment-timezone": "^0.5.27",
|
"moment-timezone": "^0.5.27",
|
||||||
|
"node-opus": "^0.3.3",
|
||||||
"node-superfetch": "^0.1.9",
|
"node-superfetch": "^0.1.9",
|
||||||
"random-js": "^2.1.0",
|
"random-js": "^2.1.0",
|
||||||
"soap": "^0.30.0",
|
"soap": "^0.30.0",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const { CommandoClient } = require('discord.js-commando');
|
const { CommandoClient } = require('discord.js-commando');
|
||||||
const { WebhookClient } = require('discord.js');
|
const { WebhookClient, Collection } = require('discord.js');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const PokemonStore = require('./pokemon/PokemonStore');
|
const PokemonStore = require('./pokemon/PokemonStore');
|
||||||
const MemePoster = require('./MemePoster');
|
const MemePoster = require('./MemePoster');
|
||||||
@@ -19,6 +19,6 @@ module.exports = class XiaoClient extends CommandoClient {
|
|||||||
this.webhook = new WebhookClient(XIAO_WEBHOOK_ID, XIAO_WEBHOOK_TOKEN, { disableEveryone: true });
|
this.webhook = new WebhookClient(XIAO_WEBHOOK_ID, XIAO_WEBHOOK_TOKEN, { disableEveryone: true });
|
||||||
this.pokemon = new PokemonStore();
|
this.pokemon = new PokemonStore();
|
||||||
this.memePoster = new MemePoster(this);
|
this.memePoster = new MemePoster(this);
|
||||||
this.games = new Map();
|
this.games = new Collection();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
const { Collection } = require('discord.js');
|
||||||
|
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, voiceChannel) {
|
||||||
|
this.name = 'mafia';
|
||||||
|
this.client = client;
|
||||||
|
this.players = new Collection();
|
||||||
|
this.channel = channel;
|
||||||
|
this.voiceChannelRaw = voiceChannel;
|
||||||
|
this.connection = null;
|
||||||
|
this.dispatcher = null;
|
||||||
|
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 allow DMs and use the \`mafia-role\` command to see your role.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const player = new Player(this, user, roles[i]);
|
||||||
|
this.players.set(user.id, player);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return this.players;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.connection = await this.voiceChannel.join();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
end() {
|
||||||
|
if (this.voiceChannel) this.voiceChannel.leave();
|
||||||
|
this.client.games.delete(this.channel.id);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ? this.connection.channel : this.voiceChannelRaw;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user