mirror of
https://github.com/aydenjahola/discord-multipurpose-bot.git
synced 2024-11-22 08:45:55 +00:00
402 lines
12 KiB
JavaScript
402 lines
12 KiB
JavaScript
|
const {
|
||
|
SlashCommandBuilder,
|
||
|
EmbedBuilder,
|
||
|
ButtonBuilder,
|
||
|
ActionRowBuilder,
|
||
|
ButtonStyle,
|
||
|
} = require("discord.js");
|
||
|
const SpyfallGame = require("../../models/SpyfallGame");
|
||
|
const SpyfallLocation = require("../../models/SpyfallLocation");
|
||
|
const { v4: uuidv4 } = require("uuid");
|
||
|
|
||
|
module.exports = {
|
||
|
data: new SlashCommandBuilder()
|
||
|
.setName("spyfall")
|
||
|
.setDescription("Start a game of Spyfall."),
|
||
|
|
||
|
async execute(interaction) {
|
||
|
const guildId = interaction.guild.id;
|
||
|
const gameId = uuidv4();
|
||
|
|
||
|
const existingGame = await SpyfallGame.findOne({ gameId });
|
||
|
if (existingGame && existingGame.status === "ongoing") {
|
||
|
return interaction.reply(
|
||
|
"A game is already in progress! Please finish the current game first."
|
||
|
);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
const locations = await SpyfallLocation.find({});
|
||
|
|
||
|
if (!locations || locations.length === 0) {
|
||
|
return interaction.reply("No locations found. Please try again later.");
|
||
|
}
|
||
|
|
||
|
const selectedLocation =
|
||
|
locations[Math.floor(Math.random() * locations.length)].name;
|
||
|
|
||
|
const players = new Set();
|
||
|
|
||
|
const joinEmbed = new EmbedBuilder()
|
||
|
.setColor("#ffcc00")
|
||
|
.setTitle("Join the Spyfall Game!")
|
||
|
.setDescription("Click the button below to join the game.")
|
||
|
.addFields({ name: "Current Players:", value: "None yet." })
|
||
|
.setFooter({ text: "You need at least 2 players to start!" })
|
||
|
.setTimestamp();
|
||
|
|
||
|
const joinButton = new ButtonBuilder()
|
||
|
.setCustomId("join_game")
|
||
|
.setLabel("Join Game")
|
||
|
.setStyle(ButtonStyle.Success);
|
||
|
|
||
|
const leaveButton = new ButtonBuilder()
|
||
|
.setCustomId("leave_game")
|
||
|
.setLabel("Leave Game")
|
||
|
.setStyle(ButtonStyle.Danger);
|
||
|
|
||
|
const startButton = new ButtonBuilder()
|
||
|
.setCustomId("start_game")
|
||
|
.setLabel("Start Game")
|
||
|
.setStyle(ButtonStyle.Primary)
|
||
|
.setDisabled(true);
|
||
|
|
||
|
const buttonRow = new ActionRowBuilder().addComponents(
|
||
|
joinButton,
|
||
|
leaveButton,
|
||
|
startButton
|
||
|
);
|
||
|
|
||
|
const joinMessage = await interaction.reply({
|
||
|
embeds: [joinEmbed],
|
||
|
components: [buttonRow],
|
||
|
fetchReply: true,
|
||
|
});
|
||
|
|
||
|
const filter = (i) =>
|
||
|
i.customId === "join_game" ||
|
||
|
i.customId === "leave_game" ||
|
||
|
i.customId === "start_game";
|
||
|
|
||
|
const collector = joinMessage.createMessageComponentCollector({ filter });
|
||
|
|
||
|
collector.on("collect", async (i) => {
|
||
|
try {
|
||
|
if (!players.has(i.user.id) && i.customId !== "join_game") {
|
||
|
return i.reply({
|
||
|
content: "You cannot interact with this button.",
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (i.customId === "join_game") {
|
||
|
if (players.has(i.user.id)) {
|
||
|
return i.reply({
|
||
|
content: "You are already in the game!",
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
players.add(i.user.id);
|
||
|
const currentPlayers =
|
||
|
Array.from(players)
|
||
|
.map(
|
||
|
(id) => interaction.guild.members.cache.get(id).user.username
|
||
|
)
|
||
|
.join(", ") || "None yet.";
|
||
|
|
||
|
joinEmbed
|
||
|
.setDescription("Click the button below to join the game.")
|
||
|
.setFields([{ name: "Current Players:", value: currentPlayers }]);
|
||
|
|
||
|
if (players.size >= 2) {
|
||
|
startButton.setDisabled(false);
|
||
|
}
|
||
|
|
||
|
await i.update({ embeds: [joinEmbed], components: [buttonRow] });
|
||
|
} else if (i.customId === "leave_game") {
|
||
|
if (players.has(i.user.id)) {
|
||
|
players.delete(i.user.id);
|
||
|
const currentPlayers =
|
||
|
Array.from(players)
|
||
|
.map(
|
||
|
(id) =>
|
||
|
interaction.guild.members.cache.get(id).user.username
|
||
|
)
|
||
|
.join(", ") || "None yet.";
|
||
|
|
||
|
joinEmbed
|
||
|
.setDescription("Click the button below to join the game.")
|
||
|
.setFields([
|
||
|
{ name: "Current Players:", value: currentPlayers },
|
||
|
]);
|
||
|
await i.update({ embeds: [joinEmbed], components: [buttonRow] });
|
||
|
} else {
|
||
|
await i.reply({
|
||
|
content: "You are not part of the game.",
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
}
|
||
|
} else if (i.customId === "start_game") {
|
||
|
if (players.size < 2) {
|
||
|
return i.reply({
|
||
|
content: "You need at least 2 players to start the game.",
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
const spyIndex = Math.floor(Math.random() * players.size);
|
||
|
const spy = Array.from(players)[spyIndex];
|
||
|
|
||
|
const newGame = new SpyfallGame({
|
||
|
gameId,
|
||
|
guildId,
|
||
|
location: selectedLocation,
|
||
|
spy,
|
||
|
players: Array.from(players),
|
||
|
status: "ongoing",
|
||
|
});
|
||
|
|
||
|
await newGame.save();
|
||
|
|
||
|
const locationMessage = `The game has started! The location is **${selectedLocation}**.`;
|
||
|
|
||
|
for (const playerId of players) {
|
||
|
const player = await interaction.guild.members.cache.get(playerId)
|
||
|
.user;
|
||
|
|
||
|
if (playerId !== spy) {
|
||
|
await player.send(locationMessage);
|
||
|
} else {
|
||
|
await player.send(
|
||
|
"The game has started! You are the spy! Try to blend in!"
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const embed = new EmbedBuilder()
|
||
|
.setColor("#ffcc00")
|
||
|
.setTitle("Spyfall Game Started!")
|
||
|
.setDescription("The game has started! One of you is the spy!")
|
||
|
.setFooter({
|
||
|
text: "The spy must figure out the location without revealing themselves!",
|
||
|
})
|
||
|
.setTimestamp();
|
||
|
|
||
|
await interaction.followUp({ embeds: [embed], components: [] });
|
||
|
collector.stop();
|
||
|
await interaction.channel.send({
|
||
|
content: "The game has started! Use `/stopspyfall` to end it.",
|
||
|
});
|
||
|
|
||
|
await startQuestioningPhase(
|
||
|
interaction,
|
||
|
spy,
|
||
|
players,
|
||
|
selectedLocation,
|
||
|
gameId
|
||
|
);
|
||
|
return;
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error("Error during button interaction:", error);
|
||
|
await i.reply({
|
||
|
content:
|
||
|
"There was an error processing your request. Please try again later.",
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
} catch (error) {
|
||
|
console.error("Error fetching locations or starting the game:", error);
|
||
|
await interaction.reply(
|
||
|
"There was an error fetching locations or starting the game. Please try again later."
|
||
|
);
|
||
|
}
|
||
|
},
|
||
|
};
|
||
|
|
||
|
async function startQuestioningPhase(
|
||
|
interaction,
|
||
|
spy,
|
||
|
players,
|
||
|
selectedLocation,
|
||
|
gameId
|
||
|
) {
|
||
|
const ongoingGame = await SpyfallGame.findOne({ gameId });
|
||
|
|
||
|
if (!ongoingGame || ongoingGame.status !== "ongoing") {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const playerArray = Array.from(players);
|
||
|
let currentIndex = 0;
|
||
|
|
||
|
async function askQuestion() {
|
||
|
if (currentIndex >= playerArray.length) {
|
||
|
return startVotingPhase(interaction, spy, playerArray, gameId);
|
||
|
}
|
||
|
|
||
|
const currentPlayerId = playerArray[currentIndex];
|
||
|
const currentPlayer =
|
||
|
interaction.guild.members.cache.get(currentPlayerId).user;
|
||
|
|
||
|
const questionEmbed = new EmbedBuilder()
|
||
|
.setColor("#ffcc00")
|
||
|
.setTitle("Your Turn to Ask a Question!")
|
||
|
.setDescription(
|
||
|
`It's **${currentPlayer.username}**'s turn to ask a question about the location.`
|
||
|
)
|
||
|
.setFooter({ text: "Click 'Finish Turn' when you're done." });
|
||
|
|
||
|
const finishButton = new ButtonBuilder()
|
||
|
.setCustomId(`finish_turn_${currentPlayerId}`)
|
||
|
.setLabel("Finish Turn")
|
||
|
.setStyle(ButtonStyle.Primary);
|
||
|
|
||
|
const buttonRow = new ActionRowBuilder().addComponents(finishButton);
|
||
|
|
||
|
await interaction.channel.send({
|
||
|
embeds: [questionEmbed],
|
||
|
components: [buttonRow],
|
||
|
});
|
||
|
|
||
|
const filter = (i) =>
|
||
|
i.customId === `finish_turn_${currentPlayerId}` &&
|
||
|
i.user.id === currentPlayerId;
|
||
|
const collector = interaction.channel.createMessageComponentCollector({
|
||
|
filter,
|
||
|
time: 30000,
|
||
|
});
|
||
|
|
||
|
collector.on("collect", async (i) => {
|
||
|
await i.reply({ content: "Your turn has ended!", ephemeral: true });
|
||
|
collector.stop();
|
||
|
});
|
||
|
|
||
|
collector.on("end", async () => {
|
||
|
currentIndex++;
|
||
|
await askQuestion();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
askQuestion();
|
||
|
}
|
||
|
|
||
|
async function startVotingPhase(
|
||
|
interaction,
|
||
|
spy,
|
||
|
players,
|
||
|
gameId,
|
||
|
selectedLocation
|
||
|
) {
|
||
|
// Ensure players is an array
|
||
|
if (!Array.isArray(players)) {
|
||
|
console.error("Players is not an array:", players);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const ongoingGame = await SpyfallGame.findOne({ gameId });
|
||
|
|
||
|
if (!ongoingGame || ongoingGame.status !== "ongoing") {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const votes = new Map();
|
||
|
const voteEmbed = new EmbedBuilder()
|
||
|
.setColor("#ffcc00")
|
||
|
.setTitle("Voting Phase")
|
||
|
.setDescription(
|
||
|
"Vote for who you think the spy is! (You cannot vote for yourself)"
|
||
|
)
|
||
|
.setFooter({ text: "Click the buttons below to vote." });
|
||
|
|
||
|
const voteButtons = players.map((playerId) =>
|
||
|
new ButtonBuilder()
|
||
|
.setCustomId(`vote_${playerId}`)
|
||
|
.setLabel(interaction.guild.members.cache.get(playerId).user.username)
|
||
|
.setStyle(ButtonStyle.Primary)
|
||
|
);
|
||
|
|
||
|
const voteRow = new ActionRowBuilder().addComponents(voteButtons);
|
||
|
const voteMessage = await interaction.channel.send({
|
||
|
embeds: [voteEmbed],
|
||
|
components: [voteRow],
|
||
|
});
|
||
|
|
||
|
const voteFilter = (i) =>
|
||
|
i.customId.startsWith("vote_") && players.includes(i.user.id);
|
||
|
const voteCollector = voteMessage.createMessageComponentCollector({
|
||
|
filter: voteFilter,
|
||
|
});
|
||
|
|
||
|
voteCollector.on("collect", async (i) => {
|
||
|
const votedPlayerId = i.customId.split("_")[1];
|
||
|
|
||
|
if (i.user.id === votedPlayerId) {
|
||
|
await i.reply({
|
||
|
content: "You cannot vote for yourself.",
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Add the vote
|
||
|
votes.set(votedPlayerId, (votes.get(votedPlayerId) || 0) + 1);
|
||
|
|
||
|
await i.reply({
|
||
|
content: `You voted for ${
|
||
|
interaction.guild.members.cache.get(votedPlayerId).user.username
|
||
|
}.`,
|
||
|
ephemeral: true,
|
||
|
});
|
||
|
|
||
|
if (votes.size === players.length) {
|
||
|
voteCollector.stop();
|
||
|
await revealSpy(interaction, spy, votes, selectedLocation, gameId);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
voteCollector.on("end", async () => {
|
||
|
if (votes.size > 0) {
|
||
|
await revealSpy(interaction, spy, votes, selectedLocation, gameId);
|
||
|
} else {
|
||
|
await interaction.channel.send("Voting timed out! No votes were cast.");
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async function revealSpy(interaction, spy, votes, selectedLocation, gameId) {
|
||
|
const voteEntries = Array.from(votes.entries());
|
||
|
const highestVote = Math.max(...voteEntries.map(([_, v]) => v));
|
||
|
const suspectedSpy = voteEntries.find(([_, v]) => v === highestVote)[0];
|
||
|
|
||
|
const resultEmbed = new EmbedBuilder()
|
||
|
.setColor("#ffcc00")
|
||
|
.setTitle("Voting Results")
|
||
|
.addFields(
|
||
|
{
|
||
|
name: "Suspected Spy:",
|
||
|
value: interaction.guild.members.cache.get(suspectedSpy).user.username,
|
||
|
},
|
||
|
{
|
||
|
name: "Actual Spy:",
|
||
|
value: interaction.guild.members.cache.get(spy).user.username,
|
||
|
}
|
||
|
)
|
||
|
.setFooter({
|
||
|
text:
|
||
|
suspectedSpy === spy
|
||
|
? "The spy has been caught!"
|
||
|
: "The spy has escaped!",
|
||
|
})
|
||
|
.setTimestamp();
|
||
|
|
||
|
const result = await SpyfallGame.updateOne({ gameId }, { status: "ended" });
|
||
|
if (result.modifiedCount === 0) {
|
||
|
console.log("No game found or status was already 'ended'.");
|
||
|
}
|
||
|
|
||
|
await interaction.channel.send({ embeds: [resultEmbed] });
|
||
|
}
|