jillo-bot/src/lib/game.ts

181 lines
6.1 KiB
TypeScript

import { Interaction, Message, TextBasedChannel, User } from 'discord.js';
export const RANDOM_WORDS = [
'tarsorado', 'aboba', 'robtop', 'viprin', 'milk', 'milking station', 'radiation', 'extreme', 'glogging', 'glogged',
'penis', 'deadlocked', 'cream', 'dragon cream', 'urine', 'communal', 'piss', 'matpat', 'big and round', 'easy',
'cum', 'glue', 'tampon', 'contaminated water', 'centrifuge', 'inflation', 'plutonium', 'uranium', 'thorium',
'imposter', 'sounding', '💥', '🥵', '🎊', '!!!', '...', '???', '?..', '?!', '!', '?', 'balls itch', 'robert',
'gas leak', 'among us', 'stick a finger', 'overclock', 'breed', 'gay sex', 'breedable', 'cock vore', 'appendix',
'mukbang', 'edging', 'onlyfans', 'productive', 'mandelbrot', 'novosibirsk', 'oops!', 'farting', '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 => console.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();
}
function* chunks(arr: unknown[], n: number) {
for (let i = 0; i < arr.length; i += n) {
yield arr.slice(i, i + n);
}
}
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']
}
})
));
}