jillo-bot/src/lib/game.ts

174 lines
5.7 KiB
TypeScript

import { Interaction, Message, TextBasedChannel, User } from 'discord.js';
import * as log from '../lib/log';
import { chunks } from './util';
export const RANDOM_WORDS = [
'tarsorado', 'aboba', 'radiation', 'extreme', 'glogging', 'glogged', 'penis', 'easy', 'glue', 'contaminated water',
'centrifuge', 'plutonium', 'uranium', 'thorium', 'imposter', '💥', '🥵', '🎊', '!!!', '...', '???', '?..', '?!',
'!', '?', 'balls itch', 'gas leak', 'among us', 'overclock', 'mandelbrot', 'novosibirsk', 'oops!', 'memory leak',
'pepsi can'
];
export function randomWord() {
return RANDOM_WORDS[Math.floor(Math.random() * RANDOM_WORDS.length)];
}
const DONE_EMOJI = '👍';
const BAD_EMOJI = '👎';
const DEFAULT_EMOJI = '🪙';
const STOP_EMOJI = '⏹️';
const CANCEL_EMOJI = '❌';
function formatMessage(users: User[], time: number, name: string, ended = false, cancelled = false) {
return `Starting a **${name}** game (${users.length} player${users.length !== 1 ? 's' : ''})\n`
+ users.map(user => `- ${user.toString()}`).join('\n') + '\n'
+ (time <= 0 ?
(cancelled ? ('**Game was cancelled**') : (ended ? '**Already ended!**' : '**Already started**')) :
`Starting in **${Math.ceil(time / 1000)}s** - react ${STOP_EMOJI} to begin now`
);
}
let membersInGame: string[] = [];
export async function startGame(interaction: Interaction, startingUser: User, name: string, callback: (players: User[], channel: TextBasedChannel) => Promise<void>) {
if (!interaction.isChatInputCommand()) return;
if (membersInGame.includes(startingUser.id)) {
await interaction.reply({
ephemeral: true,
content: 'You are already in a game!'
});
return;
}
membersInGame.push(startingUser.id);
let participants: User[] = [startingUser];
const duration = 25_000;
const m = await interaction.reply({
fetchReply: true,
content: formatMessage(participants, duration, name)
});
if (!(m instanceof Message)) return;
const emoji = m.guild?.emojis.cache.random();
const started = Date.now();
const collector = m.createReactionCollector({
filter: (reaction, user) =>
!user.bot && (
(
(emoji ? reaction.emoji.id === emoji.id : reaction.emoji.name === DEFAULT_EMOJI) &&
!participants.find(u => user.id === u.id) &&
!membersInGame.includes(user.id)
)
|| (reaction.emoji.name === STOP_EMOJI && user.id === startingUser.id)
|| (reaction.emoji.name === CANCEL_EMOJI && user.id === startingUser.id)
),
time: duration,
dispose: true
});
await Promise.all([
m.react(emoji || DEFAULT_EMOJI),
m.react(STOP_EMOJI),
m.react(CANCEL_EMOJI),
]);
const updateInterval = setInterval(() => {
m.edit(formatMessage(participants, duration - (Date.now() - started), name));
}, 3_000);
collector.on('collect', (reaction, user) => {
if (reaction.emoji.name === STOP_EMOJI) {
collector.stop('force-started');
} else if (reaction.emoji.name === CANCEL_EMOJI) {
collector.stop('cancelled');
} else {
participants.push(user);
membersInGame.push(user.id);
m.edit(formatMessage(participants, duration - (Date.now() - started), name));
}
});
collector.on('remove', (_, user) => {
participants = participants.filter(u => u.id !== user.id);
membersInGame = membersInGame.filter(u => u !== user.id);
m.edit(formatMessage(participants, duration - (Date.now() - started), name));
});
collector.on('end', async (_, reason) => {
clearInterval(updateInterval);
await m.reactions.removeAll().catch(error => log.error(error));
if (reason === 'cancelled') {
m.edit(formatMessage(participants, 0, name, false, true));
} else {
m.edit(formatMessage(participants, 0, name));
await callback(participants, m.channel);
m.edit(formatMessage(participants, 0, name, true));
}
membersInGame = membersInGame.filter(id => !participants.find(u => u.id === id));
});
}
export async function getTextResponse(user: User, prompt: string, filter: (content: string) => boolean = () => true): Promise<string | null> {
const msg = await user.send(prompt);
try {
const collected = await msg.channel.awaitMessages({
max: 1,
time: 45_000,
errors: ['time'],
filter: (msg) => {
const valid = msg.content !== '' && msg.content.length <= 2000 && filter(msg.content);
if (!valid) msg.react(BAD_EMOJI);
return valid;
}
});
const message = collected.first() as Message;
await message.react(DONE_EMOJI);
return message.content;
} catch (err) {
return null;
}
}
export async function getTextResponsePrettyPlease(user: User, prompt: string, filter: (content: string) => boolean = () => true): Promise<string> {
const resp = await getTextResponse(user, prompt, filter);
if (resp) return resp;
user.send('Took too long... Surprise... Added...... :)');
return randomWord();
}
export async function sendSegments(segments: string[], channel: TextBasedChannel) {
const content = [];
let contentBuffer = '';
while (segments.length > 0) {
const segment = segments.splice(0, 1)[0];
const newMsg = contentBuffer + '\n' + segment;
if (newMsg.length > 2000) {
content.push(contentBuffer);
contentBuffer = '';
if (segment.length > 2000) {
content.push(...([...(chunks(segment.split(''), 2000))].map(s => s.join(''))));
} else {
contentBuffer = segment;
}
} else {
contentBuffer = newMsg;
}
}
if (contentBuffer !== '') content.push(contentBuffer);
return Promise.all(content.map(async content =>
await channel.send({
content: content,
allowedMentions: {
parse: ['users']
}
})
));
}