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
189 lines
7.3 KiB
JavaScript
189 lines
7.3 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 activeHeists = new Map();
|
|
|
|
const TARGETS = [
|
|
{ name: 'Corner Store', emoji: '🏪', difficulty: 0.7, minReward: 20, maxReward: 50 },
|
|
{ name: 'Gas Station', emoji: '⛽', difficulty: 0.6, minReward: 30, maxReward: 70 },
|
|
{ name: 'Jewelry Store', emoji: '💎', difficulty: 0.5, minReward: 50, maxReward: 120 },
|
|
{ name: 'Bank', emoji: '🏦', difficulty: 0.4, minReward: 80, maxReward: 200 },
|
|
{ name: 'Casino', emoji: '🎰', difficulty: 0.3, minReward: 100, maxReward: 300 },
|
|
{ name: 'Federal Reserve', emoji: '🏛️', difficulty: 0.2, minReward: 150, maxReward: 500 },
|
|
];
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('heist')
|
|
.setDescription('Start a group heist!')
|
|
.addStringOption(option =>
|
|
option.setName('target')
|
|
.setDescription('Choose your heist target')
|
|
.setRequired(true)
|
|
.addChoices(
|
|
{ name: '🏪 Corner Store (Easy)', value: '0' },
|
|
{ name: '⛽ Gas Station', value: '1' },
|
|
{ name: '💎 Jewelry Store', value: '2' },
|
|
{ name: '🏦 Bank (Medium)', value: '3' },
|
|
{ name: '🎰 Casino', value: '4' },
|
|
{ name: '🏛️ Federal Reserve (Hard)', value: '5' }
|
|
)
|
|
),
|
|
|
|
async execute(interaction, supabase, client) {
|
|
const targetIndex = parseInt(interaction.options.getString('target'));
|
|
const target = TARGETS[targetIndex];
|
|
const leader = interaction.user;
|
|
const mode = await getServerMode(supabase, interaction.guildId);
|
|
const guildId = interaction.guildId;
|
|
|
|
const heistKey = `${guildId}-heist`;
|
|
if (activeHeists.has(heistKey)) {
|
|
return interaction.reply({
|
|
content: 'There\'s already an active heist in this server! Wait for it to finish.',
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
const participants = new Set([leader.id]);
|
|
activeHeists.set(heistKey, {
|
|
target,
|
|
leader: leader.id,
|
|
participants,
|
|
usernames: { [leader.id]: leader.username }
|
|
});
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(getEmbedColor(mode))
|
|
.setTitle(`${target.emoji} Heist: ${target.name}`)
|
|
.setDescription(`${leader} is planning a heist!\n\nClick **Join Heist** to participate.\nMore participants = higher success chance!`)
|
|
.addFields(
|
|
{ name: '🎯 Target', value: target.name, inline: true },
|
|
{ name: '💰 Reward', value: `${target.minReward}-${target.maxReward} XP each`, inline: true },
|
|
{ name: '📊 Base Success', value: `${Math.round(target.difficulty * 100)}%`, inline: true },
|
|
{ name: '👥 Participants', value: `1. ${leader.username}` }
|
|
)
|
|
.setFooter({ text: 'Heist starts in 60 seconds!' })
|
|
.setTimestamp();
|
|
|
|
const row = new ActionRowBuilder()
|
|
.addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId(`heist_join_${guildId}`)
|
|
.setLabel('Join Heist')
|
|
.setStyle(ButtonStyle.Primary)
|
|
.setEmoji('🔫'),
|
|
new ButtonBuilder()
|
|
.setCustomId(`heist_start_${guildId}`)
|
|
.setLabel('Start Now')
|
|
.setStyle(ButtonStyle.Success)
|
|
.setEmoji('🚀')
|
|
);
|
|
|
|
const message = await interaction.reply({ embeds: [embed], components: [row], fetchReply: true });
|
|
|
|
const collector = message.createMessageComponentCollector({
|
|
filter: i => i.customId.startsWith('heist_'),
|
|
time: 60000
|
|
});
|
|
|
|
collector.on('collect', async (i) => {
|
|
const heistData = activeHeists.get(heistKey);
|
|
if (!heistData) return;
|
|
|
|
if (i.customId.startsWith('heist_join')) {
|
|
if (heistData.participants.has(i.user.id)) {
|
|
return i.reply({ content: 'You\'re already in this heist!', ephemeral: true });
|
|
}
|
|
|
|
heistData.participants.add(i.user.id);
|
|
heistData.usernames[i.user.id] = i.user.username;
|
|
|
|
const participantList = Array.from(heistData.participants)
|
|
.map((id, idx) => `${idx + 1}. ${heistData.usernames[id]}`)
|
|
.join('\n');
|
|
|
|
const bonusChance = (heistData.participants.size - 1) * 5;
|
|
const totalChance = Math.min(95, Math.round(target.difficulty * 100) + bonusChance);
|
|
|
|
embed.spliceFields(3, 1, { name: '👥 Participants', value: participantList });
|
|
embed.spliceFields(2, 1, { name: '📊 Success Chance', value: `${totalChance}%`, inline: true });
|
|
|
|
await i.update({ embeds: [embed] });
|
|
}
|
|
|
|
if (i.customId.startsWith('heist_start') && i.user.id === heistData.leader) {
|
|
collector.stop('started');
|
|
}
|
|
});
|
|
|
|
collector.on('end', async (collected, reason) => {
|
|
const heistData = activeHeists.get(heistKey);
|
|
if (!heistData) return;
|
|
|
|
activeHeists.delete(heistKey);
|
|
|
|
const participantCount = heistData.participants.size;
|
|
const bonusChance = (participantCount - 1) * 5;
|
|
const successChance = Math.min(95, target.difficulty * 100 + bonusChance) / 100;
|
|
|
|
const success = Math.random() < successChance;
|
|
|
|
if (success) {
|
|
const baseReward = Math.floor(Math.random() * (target.maxReward - target.minReward + 1)) + target.minReward;
|
|
const teamBonus = Math.floor(baseReward * (participantCount - 1) * 0.1);
|
|
const totalReward = baseReward + teamBonus;
|
|
|
|
for (const participantId of heistData.participants) {
|
|
if (mode === 'standalone') {
|
|
await updateStandaloneXp(supabase, participantId, guildId, totalReward, heistData.usernames[participantId]);
|
|
} else if (supabase) {
|
|
try {
|
|
const { data: profile } = await supabase
|
|
.from('user_profiles')
|
|
.select('xp')
|
|
.eq('discord_id', participantId)
|
|
.maybeSingle();
|
|
|
|
if (profile) {
|
|
await supabase
|
|
.from('user_profiles')
|
|
.update({ xp: (profile.xp || 0) + totalReward })
|
|
.eq('discord_id', participantId);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
const participantMentions = Array.from(heistData.participants)
|
|
.map(id => `<@${id}>`)
|
|
.join(', ');
|
|
|
|
const successEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.success)
|
|
.setTitle(`${target.emoji} Heist Successful!`)
|
|
.setDescription(`The crew pulled off the ${target.name} heist!`)
|
|
.addFields(
|
|
{ name: '👥 Crew', value: participantMentions },
|
|
{ name: '💰 Each Earned', value: `${totalReward} XP` }
|
|
)
|
|
.setTimestamp();
|
|
|
|
await interaction.editReply({ embeds: [successEmbed], components: [] });
|
|
} else {
|
|
const failEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.error)
|
|
.setTitle(`${target.emoji} Heist Failed!`)
|
|
.setDescription(`The ${target.name} heist went wrong! The crew escaped empty-handed.`)
|
|
.addFields({
|
|
name: '👥 Crew',
|
|
value: Array.from(heistData.participants).map(id => heistData.usernames[id]).join(', ')
|
|
})
|
|
.setTimestamp();
|
|
|
|
await interaction.editReply({ embeds: [failEmbed], components: [] });
|
|
}
|
|
});
|
|
},
|
|
};
|