Introduces a new server mode configuration system (Federation/Standalone) with associated command changes, dynamic status rotation for the bot, and adds new commands and features. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: b08e6ba5-7498-4b9f-b1c9-7dc11b362ddd Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/R9PkDi8 Replit-Helium-Checkpoint-Created: true
176 lines
5.9 KiB
JavaScript
176 lines
5.9 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
|
const { getServerMode, getEmbedColor, EMBED_COLORS } = require('../utils/modeHelper');
|
|
const { updateStandaloneXp } = require('../utils/standaloneXp');
|
|
|
|
const activeDuels = new Map();
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('duel')
|
|
.setDescription('Challenge someone to a duel!')
|
|
.addUserOption(option =>
|
|
option.setName('opponent')
|
|
.setDescription('The user to challenge')
|
|
.setRequired(true)
|
|
)
|
|
.addIntegerOption(option =>
|
|
option.setName('bet')
|
|
.setDescription('XP to bet (0-100)')
|
|
.setRequired(false)
|
|
.setMinValue(0)
|
|
.setMaxValue(100)
|
|
),
|
|
|
|
async execute(interaction, supabase, client) {
|
|
const opponent = interaction.options.getUser('opponent');
|
|
const bet = interaction.options.getInteger('bet') || 0;
|
|
const challenger = interaction.user;
|
|
const mode = await getServerMode(supabase, interaction.guildId);
|
|
const guildId = interaction.guildId;
|
|
|
|
if (opponent.id === challenger.id) {
|
|
return interaction.reply({ content: "You can't duel yourself!", ephemeral: true });
|
|
}
|
|
|
|
if (opponent.bot) {
|
|
return interaction.reply({ content: "You can't duel bots!", ephemeral: true });
|
|
}
|
|
|
|
const duelKey = `${guildId}-${challenger.id}`;
|
|
if (activeDuels.has(duelKey)) {
|
|
return interaction.reply({ content: 'You already have an active duel!', ephemeral: true });
|
|
}
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(getEmbedColor(mode))
|
|
.setTitle('⚔️ Duel Challenge!')
|
|
.setDescription(`${challenger} challenges ${opponent} to a duel!`)
|
|
.addFields(
|
|
{ name: '💰 Bet', value: bet > 0 ? `${bet} XP` : 'No bet', inline: true },
|
|
{ name: '⏱️ Expires', value: 'In 60 seconds', inline: true }
|
|
)
|
|
.setTimestamp();
|
|
|
|
const row = new ActionRowBuilder()
|
|
.addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId(`duel_accept_${challenger.id}_${opponent.id}`)
|
|
.setLabel('Accept')
|
|
.setStyle(ButtonStyle.Success),
|
|
new ButtonBuilder()
|
|
.setCustomId(`duel_decline_${challenger.id}_${opponent.id}`)
|
|
.setLabel('Decline')
|
|
.setStyle(ButtonStyle.Danger)
|
|
);
|
|
|
|
const message = await interaction.reply({
|
|
content: `${opponent}`,
|
|
embeds: [embed],
|
|
components: [row],
|
|
fetchReply: true
|
|
});
|
|
|
|
activeDuels.set(duelKey, { opponent: opponent.id, bet });
|
|
|
|
const collector = message.createMessageComponentCollector({
|
|
filter: i => i.user.id === opponent.id && i.customId.includes(challenger.id),
|
|
time: 60000,
|
|
max: 1
|
|
});
|
|
|
|
collector.on('collect', async (i) => {
|
|
activeDuels.delete(duelKey);
|
|
|
|
if (i.customId.startsWith('duel_decline')) {
|
|
const declineEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.error)
|
|
.setTitle('⚔️ Duel Declined')
|
|
.setDescription(`${opponent} declined the duel challenge.`)
|
|
.setTimestamp();
|
|
|
|
await i.update({ embeds: [declineEmbed], components: [] });
|
|
return;
|
|
}
|
|
|
|
const challengerRoll = Math.floor(Math.random() * 100) + 1;
|
|
const opponentRoll = Math.floor(Math.random() * 100) + 1;
|
|
|
|
let winner, loser, winnerRoll, loserRoll;
|
|
if (challengerRoll > opponentRoll) {
|
|
winner = challenger;
|
|
loser = opponent;
|
|
winnerRoll = challengerRoll;
|
|
loserRoll = opponentRoll;
|
|
} else if (opponentRoll > challengerRoll) {
|
|
winner = opponent;
|
|
loser = challenger;
|
|
winnerRoll = opponentRoll;
|
|
loserRoll = challengerRoll;
|
|
} else {
|
|
const tieEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.warning)
|
|
.setTitle('⚔️ It\'s a Tie!')
|
|
.setDescription(`Both rolled **${challengerRoll}**! Nobody wins.`)
|
|
.addFields(
|
|
{ name: `${challenger.username}`, value: `🎲 ${challengerRoll}`, inline: true },
|
|
{ name: `${opponent.username}`, value: `🎲 ${opponentRoll}`, inline: true }
|
|
)
|
|
.setTimestamp();
|
|
|
|
await i.update({ embeds: [tieEmbed], components: [] });
|
|
return;
|
|
}
|
|
|
|
if (bet > 0) {
|
|
if (mode === 'standalone') {
|
|
await updateStandaloneXp(supabase, winner.id, guildId, bet, winner.username);
|
|
} else if (supabase) {
|
|
try {
|
|
const { data: winnerProfile } = await supabase
|
|
.from('user_profiles')
|
|
.select('xp')
|
|
.eq('discord_id', winner.id)
|
|
.maybeSingle();
|
|
|
|
if (winnerProfile) {
|
|
await supabase
|
|
.from('user_profiles')
|
|
.update({ xp: (winnerProfile.xp || 0) + bet })
|
|
.eq('discord_id', winner.id);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
const resultEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.success)
|
|
.setTitle('⚔️ Duel Results!')
|
|
.setDescription(`🏆 **${winner.username}** wins the duel!`)
|
|
.addFields(
|
|
{ name: `${challenger.username}`, value: `🎲 Rolled: **${challengerRoll}**`, inline: true },
|
|
{ name: `${opponent.username}`, value: `🎲 Rolled: **${opponentRoll}**`, inline: true }
|
|
)
|
|
.setTimestamp();
|
|
|
|
if (bet > 0) {
|
|
resultEmbed.addFields({ name: '💰 Reward', value: `${winner.username} wins **${bet} XP**!` });
|
|
}
|
|
|
|
await i.update({ embeds: [resultEmbed], components: [] });
|
|
});
|
|
|
|
collector.on('end', async (collected) => {
|
|
if (collected.size === 0) {
|
|
activeDuels.delete(duelKey);
|
|
|
|
const timeoutEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.warning)
|
|
.setTitle('⚔️ Duel Expired')
|
|
.setDescription(`${opponent} didn't respond in time.`)
|
|
.setTimestamp();
|
|
|
|
await interaction.editReply({ embeds: [timeoutEmbed], components: [] });
|
|
}
|
|
});
|
|
},
|
|
};
|