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
486 lines
18 KiB
JavaScript
486 lines
18 KiB
JavaScript
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
|
|
const { getServerMode, EMBED_COLORS } = require('../utils/modeHelper');
|
|
const { getStandaloneXp, prestigeStandalone, calculateLevel } = require('../utils/standaloneXp');
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName('prestige')
|
|
.setDescription('Prestige system - reset your level for permanent rewards')
|
|
.addSubcommand(sub =>
|
|
sub.setName('view')
|
|
.setDescription('View your prestige status and rewards')
|
|
.addUserOption(opt =>
|
|
opt.setName('user')
|
|
.setDescription('User to check')
|
|
.setRequired(false)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('up')
|
|
.setDescription('Prestige up! Reset your XP for permanent rewards')
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName('rewards')
|
|
.setDescription('View all prestige rewards')
|
|
),
|
|
|
|
async execute(interaction, supabase, client) {
|
|
if (!supabase) {
|
|
return interaction.reply({ content: 'Database not configured.', ephemeral: true });
|
|
}
|
|
|
|
const sub = interaction.options.getSubcommand();
|
|
const mode = await getServerMode(supabase, interaction.guildId);
|
|
|
|
if (sub === 'view') {
|
|
return viewPrestige(interaction, supabase, mode);
|
|
} else if (sub === 'up') {
|
|
return prestigeUp(interaction, supabase, client, mode);
|
|
} else if (sub === 'rewards') {
|
|
return viewRewards(interaction, mode);
|
|
}
|
|
},
|
|
};
|
|
|
|
async function viewPrestige(interaction, supabase, mode) {
|
|
const target = interaction.options.getUser('user') || interaction.user;
|
|
await interaction.deferReply();
|
|
|
|
try {
|
|
if (mode === 'standalone') {
|
|
const data = await getStandaloneXp(supabase, target.id, interaction.guildId);
|
|
|
|
if (!data) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.standalone)
|
|
.setDescription(`${target.id === interaction.user.id ? 'You have' : `${target.tag} has`} no XP data yet.`)
|
|
]
|
|
});
|
|
}
|
|
|
|
const prestige = data.prestige_level || 0;
|
|
const currentXp = data.xp || 0;
|
|
const totalXpEarned = data.total_xp_earned || currentXp;
|
|
const level = calculateLevel(currentXp, 'normal');
|
|
|
|
const prestigeInfo = getPrestigeInfo(prestige);
|
|
const canPrestige = level >= 50;
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(prestigeInfo.color)
|
|
.setTitle(`${prestigeInfo.icon} ${target.tag}'s Prestige`)
|
|
.setThumbnail(target.displayAvatarURL({ size: 256 }))
|
|
.addFields(
|
|
{ name: 'Prestige Level', value: `**${prestigeInfo.name}** (${prestige})`, inline: true },
|
|
{ name: 'XP Bonus', value: `+${prestige * 5}%`, inline: true },
|
|
{ name: 'Current Level', value: `${level}`, inline: true },
|
|
{ name: 'Total XP Earned', value: totalXpEarned.toLocaleString(), inline: true },
|
|
{ name: 'Current XP', value: currentXp.toLocaleString(), inline: true },
|
|
{ name: 'Can Prestige?', value: canPrestige ? 'Yes (Level 50+)' : `Need Level 50 (${level}/50)`, inline: true }
|
|
)
|
|
.setFooter({ text: `🏠 Standalone Mode • ${interaction.guild.name}` })
|
|
.setTimestamp();
|
|
|
|
if (prestige > 0) {
|
|
embed.addFields({
|
|
name: 'Prestige Rewards Unlocked',
|
|
value: getUnlockedRewards(prestige).join('\n') || 'None yet',
|
|
inline: false
|
|
});
|
|
}
|
|
|
|
return interaction.editReply({ embeds: [embed] });
|
|
}
|
|
|
|
const { data: link } = await supabase
|
|
.from('discord_links')
|
|
.select('user_id')
|
|
.eq('discord_id', target.id)
|
|
.single();
|
|
|
|
if (!link) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0xff6b6b)
|
|
.setDescription(`${target.id === interaction.user.id ? 'You are' : `${target.tag} is`} not linked to AeThex.`)
|
|
]
|
|
});
|
|
}
|
|
|
|
const { data: profile } = await supabase
|
|
.from('user_profiles')
|
|
.select('username, xp, prestige_level, total_xp_earned')
|
|
.eq('id', link.user_id)
|
|
.single();
|
|
|
|
const prestige = profile?.prestige_level || 0;
|
|
const totalXpEarned = profile?.total_xp_earned || profile?.xp || 0;
|
|
const currentXp = profile?.xp || 0;
|
|
const level = Math.floor(Math.sqrt(currentXp / 100));
|
|
|
|
const prestigeInfo = getPrestigeInfo(prestige);
|
|
const canPrestige = level >= 50;
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setColor(prestigeInfo.color)
|
|
.setTitle(`${prestigeInfo.icon} ${profile?.username || target.tag}'s Prestige`)
|
|
.setThumbnail(target.displayAvatarURL({ size: 256 }))
|
|
.addFields(
|
|
{ name: 'Prestige Level', value: `**${prestigeInfo.name}** (${prestige})`, inline: true },
|
|
{ name: 'XP Bonus', value: `+${prestige * 5}%`, inline: true },
|
|
{ name: 'Current Level', value: `${level}`, inline: true },
|
|
{ name: 'Total XP Earned', value: totalXpEarned.toLocaleString(), inline: true },
|
|
{ name: 'Current XP', value: currentXp.toLocaleString(), inline: true },
|
|
{ name: 'Can Prestige?', value: canPrestige ? 'Yes (Level 50+)' : `Need Level 50 (${level}/50)`, inline: true }
|
|
)
|
|
.setFooter({ text: `🌐 Federation • Next prestige requirement: Level 50` })
|
|
.setTimestamp();
|
|
|
|
if (prestige > 0) {
|
|
embed.addFields({
|
|
name: 'Prestige Rewards Unlocked',
|
|
value: getUnlockedRewards(prestige).join('\n') || 'None yet',
|
|
inline: false
|
|
});
|
|
}
|
|
|
|
await interaction.editReply({ embeds: [embed] });
|
|
} catch (error) {
|
|
console.error('Prestige view error:', error);
|
|
await interaction.editReply({ content: 'Failed to fetch prestige data.' });
|
|
}
|
|
}
|
|
|
|
async function prestigeUp(interaction, supabase, client, mode) {
|
|
await interaction.deferReply();
|
|
|
|
try {
|
|
if (mode === 'standalone') {
|
|
const data = await getStandaloneXp(supabase, interaction.user.id, interaction.guildId);
|
|
|
|
if (!data) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.error)
|
|
.setDescription('You have no XP data yet. Start chatting to earn XP!')
|
|
]
|
|
});
|
|
}
|
|
|
|
const currentXp = data.xp || 0;
|
|
const level = calculateLevel(currentXp, 'normal');
|
|
const prestige = data.prestige_level || 0;
|
|
|
|
if (level < 50) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.error)
|
|
.setTitle('Cannot Prestige')
|
|
.setDescription(`You need to reach **Level 50** to prestige.\nCurrent level: **${level}**/50`)
|
|
]
|
|
});
|
|
}
|
|
|
|
if (prestige >= 10) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0xffd700)
|
|
.setTitle('Maximum Prestige!')
|
|
.setDescription('You have reached the maximum prestige level!')
|
|
]
|
|
});
|
|
}
|
|
|
|
const newPrestige = prestige + 1;
|
|
const newPrestigeInfo = getPrestigeInfo(newPrestige);
|
|
|
|
const confirmEmbed = new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.warning)
|
|
.setTitle('Confirm Prestige')
|
|
.setDescription(`Are you sure you want to prestige?\n\n**What will happen:**\n• Your XP will reset to **0**\n• Your level will reset to **0**\n• You gain **Prestige ${newPrestige}** (${newPrestigeInfo.name})\n• You get a permanent **+${newPrestige * 5}%** XP bonus\n\n**Current Stats:**\n• Level: ${level}\n• XP: ${currentXp.toLocaleString()}`)
|
|
.setFooter({ text: 'This action cannot be undone!' });
|
|
|
|
const row = new ActionRowBuilder()
|
|
.addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('prestige_confirm_standalone')
|
|
.setLabel('Prestige Up!')
|
|
.setStyle(ButtonStyle.Success),
|
|
new ButtonBuilder()
|
|
.setCustomId('prestige_cancel_standalone')
|
|
.setLabel('Cancel')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
);
|
|
|
|
const response = await interaction.editReply({ embeds: [confirmEmbed], components: [row] });
|
|
|
|
try {
|
|
const confirmation = await response.awaitMessageComponent({
|
|
filter: i => i.user.id === interaction.user.id,
|
|
time: 60000
|
|
});
|
|
|
|
if (confirmation.customId === 'prestige_confirm_standalone') {
|
|
const result = await prestigeStandalone(supabase, interaction.user.id, interaction.guildId);
|
|
|
|
if (!result.success) {
|
|
return confirmation.update({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.error)
|
|
.setDescription(result.message)
|
|
],
|
|
components: []
|
|
});
|
|
}
|
|
|
|
const successEmbed = new EmbedBuilder()
|
|
.setColor(newPrestigeInfo.color)
|
|
.setTitle(`${newPrestigeInfo.icon} Prestige ${result.newPrestige} Achieved!`)
|
|
.setDescription(`Congratulations! You are now **${newPrestigeInfo.name}**!`)
|
|
.addFields(
|
|
{ name: 'XP Bonus', value: `+${result.bonus}%`, inline: true }
|
|
)
|
|
.setThumbnail(interaction.user.displayAvatarURL({ size: 256 }))
|
|
.setFooter({ text: '🏠 Standalone Mode' })
|
|
.setTimestamp();
|
|
|
|
await confirmation.update({ embeds: [successEmbed], components: [] });
|
|
} else {
|
|
await confirmation.update({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.standalone)
|
|
.setDescription('Prestige cancelled.')
|
|
],
|
|
components: []
|
|
});
|
|
}
|
|
} catch (e) {
|
|
await interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(EMBED_COLORS.standalone)
|
|
.setDescription('Prestige request timed out.')
|
|
],
|
|
components: []
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
const { data: link } = await supabase
|
|
.from('discord_links')
|
|
.select('user_id')
|
|
.eq('discord_id', interaction.user.id)
|
|
.single();
|
|
|
|
if (!link) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0xff6b6b)
|
|
.setDescription('You must link your Discord to AeThex first. Use `/verify` to link.')
|
|
]
|
|
});
|
|
}
|
|
|
|
const { data: profile } = await supabase
|
|
.from('user_profiles')
|
|
.select('username, xp, prestige_level, total_xp_earned')
|
|
.eq('id', link.user_id)
|
|
.single();
|
|
|
|
const currentXp = profile?.xp || 0;
|
|
const level = Math.floor(Math.sqrt(currentXp / 100));
|
|
const prestige = profile?.prestige_level || 0;
|
|
const totalEarned = profile?.total_xp_earned || currentXp;
|
|
|
|
if (level < 50) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0xff6b6b)
|
|
.setTitle('Cannot Prestige')
|
|
.setDescription(`You need to reach **Level 50** to prestige.\nCurrent level: **${level}**/50`)
|
|
]
|
|
});
|
|
}
|
|
|
|
if (prestige >= 10) {
|
|
return interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0xffd700)
|
|
.setTitle('Maximum Prestige!')
|
|
.setDescription('You have reached the maximum prestige level! You are a true legend.')
|
|
]
|
|
});
|
|
}
|
|
|
|
const newPrestige = prestige + 1;
|
|
const newPrestigeInfo = getPrestigeInfo(newPrestige);
|
|
const xpBonus = newPrestige * 5;
|
|
|
|
const confirmEmbed = new EmbedBuilder()
|
|
.setColor(0xf59e0b)
|
|
.setTitle('Confirm Prestige')
|
|
.setDescription(`Are you sure you want to prestige?\n\n**What will happen:**\n• Your XP will reset to **0**\n• Your level will reset to **0**\n• You gain **Prestige ${newPrestige}** (${newPrestigeInfo.name})\n• You get a permanent **+${xpBonus}%** XP bonus\n• You unlock new **prestige rewards**\n\n**Current Stats:**\n• Level: ${level}\n• XP: ${currentXp.toLocaleString()}`)
|
|
.setFooter({ text: 'This action cannot be undone!' });
|
|
|
|
const row = new ActionRowBuilder()
|
|
.addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('prestige_confirm')
|
|
.setLabel('Prestige Up!')
|
|
.setStyle(ButtonStyle.Success),
|
|
new ButtonBuilder()
|
|
.setCustomId('prestige_cancel')
|
|
.setLabel('Cancel')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
);
|
|
|
|
const response = await interaction.editReply({ embeds: [confirmEmbed], components: [row] });
|
|
|
|
try {
|
|
const confirmation = await response.awaitMessageComponent({
|
|
filter: i => i.user.id === interaction.user.id,
|
|
time: 60000
|
|
});
|
|
|
|
if (confirmation.customId === 'prestige_confirm') {
|
|
const { error } = await supabase
|
|
.from('user_profiles')
|
|
.update({
|
|
xp: 0,
|
|
prestige_level: newPrestige
|
|
})
|
|
.eq('id', link.user_id);
|
|
|
|
if (error) {
|
|
console.error('Prestige update error:', error);
|
|
return confirmation.update({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0xff0000)
|
|
.setDescription('Failed to prestige. Please try again.')
|
|
],
|
|
components: []
|
|
});
|
|
}
|
|
|
|
const successEmbed = new EmbedBuilder()
|
|
.setColor(newPrestigeInfo.color)
|
|
.setTitle(`${newPrestigeInfo.icon} Prestige ${newPrestige} Achieved!`)
|
|
.setDescription(`Congratulations! You are now **${newPrestigeInfo.name}**!\n\n**Rewards Unlocked:**\n${getNewRewards(newPrestige).join('\n')}`)
|
|
.addFields(
|
|
{ name: 'XP Bonus', value: `+${xpBonus}%`, inline: true },
|
|
{ name: 'Total XP Earned (All Time)', value: totalEarned.toLocaleString(), inline: true }
|
|
)
|
|
.setThumbnail(interaction.user.displayAvatarURL({ size: 256 }))
|
|
.setFooter({ text: '🌐 Federation' })
|
|
.setTimestamp();
|
|
|
|
await confirmation.update({ embeds: [successEmbed], components: [] });
|
|
} else {
|
|
await confirmation.update({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0x6b7280)
|
|
.setDescription('Prestige cancelled.')
|
|
],
|
|
components: []
|
|
});
|
|
}
|
|
} catch (e) {
|
|
await interaction.editReply({
|
|
embeds: [
|
|
new EmbedBuilder()
|
|
.setColor(0x6b7280)
|
|
.setDescription('Prestige request timed out.')
|
|
],
|
|
components: []
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Prestige up error:', error);
|
|
await interaction.editReply({ content: 'Failed to process prestige.' });
|
|
}
|
|
}
|
|
|
|
async function viewRewards(interaction, mode) {
|
|
const embed = new EmbedBuilder()
|
|
.setColor(mode === 'standalone' ? EMBED_COLORS.standalone : 0x7c3aed)
|
|
.setTitle('Prestige Rewards')
|
|
.setDescription('Each prestige level grants permanent rewards!\n\n**Requirements:** Level 50 to prestige')
|
|
.addFields(
|
|
{ name: 'Prestige 1 - Bronze', value: '+5% XP bonus\nBronze Prestige badge', inline: true },
|
|
{ name: 'Prestige 2 - Silver', value: '+10% XP bonus\nSilver Prestige badge', inline: true },
|
|
{ name: 'Prestige 3 - Gold', value: '+15% XP bonus\nGold Prestige badge', inline: true },
|
|
{ name: 'Prestige 4 - Platinum', value: '+20% XP bonus\nPlatinum badge\nBonus daily XP', inline: true },
|
|
{ name: 'Prestige 5 - Diamond', value: '+25% XP bonus\nDiamond badge\nReduced cooldowns', inline: true },
|
|
{ name: 'Prestige 6 - Master', value: '+30% XP bonus\nMaster badge\nXP milestone rewards', inline: true },
|
|
{ name: 'Prestige 7 - Grandmaster', value: '+35% XP bonus\nGrandmaster badge\nSpecial profile effects', inline: true },
|
|
{ name: 'Prestige 8 - Champion', value: '+40% XP bonus\nChampion badge\nLeaderboard priority', inline: true },
|
|
{ name: 'Prestige 9 - Legend', value: '+45% XP bonus\nLegend badge\nLegendary profile aura', inline: true },
|
|
{ name: 'Prestige 10 - Mythic', value: '+50% XP bonus\nMythic badge\nAll prestige perks!', inline: true }
|
|
)
|
|
.setFooter({ text: `${mode === 'standalone' ? '🏠 Standalone' : '🌐 Federation'} • Use /prestige up when you reach Level 50!` })
|
|
.setTimestamp();
|
|
|
|
await interaction.reply({ embeds: [embed] });
|
|
}
|
|
|
|
function getPrestigeInfo(level) {
|
|
const prestiges = [
|
|
{ name: 'Unprestiged', icon: '', color: 0x6b7280 },
|
|
{ name: 'Bronze', icon: '', color: 0xcd7f32 },
|
|
{ name: 'Silver', icon: '', color: 0xc0c0c0 },
|
|
{ name: 'Gold', icon: '', color: 0xffd700 },
|
|
{ name: 'Platinum', icon: '', color: 0xe5e4e2 },
|
|
{ name: 'Diamond', icon: '', color: 0xb9f2ff },
|
|
{ name: 'Master', icon: '', color: 0xff4500 },
|
|
{ name: 'Grandmaster', icon: '', color: 0x9400d3 },
|
|
{ name: 'Champion', icon: '', color: 0xffd700 },
|
|
{ name: 'Legend', icon: '', color: 0xff69b4 },
|
|
{ name: 'Mythic', icon: '', color: 0x7c3aed }
|
|
];
|
|
return prestiges[Math.min(level, 10)] || prestiges[0];
|
|
}
|
|
|
|
function getUnlockedRewards(prestige) {
|
|
const rewards = [];
|
|
if (prestige >= 1) rewards.push('Bronze Prestige badge');
|
|
if (prestige >= 2) rewards.push('Silver Prestige badge');
|
|
if (prestige >= 3) rewards.push('Gold Prestige badge');
|
|
if (prestige >= 4) rewards.push('Platinum badge + Bonus daily XP');
|
|
if (prestige >= 5) rewards.push('Diamond badge + Reduced cooldowns');
|
|
if (prestige >= 6) rewards.push('Master badge + XP milestones');
|
|
if (prestige >= 7) rewards.push('Grandmaster badge + Profile effects');
|
|
if (prestige >= 8) rewards.push('Champion badge + Leaderboard priority');
|
|
if (prestige >= 9) rewards.push('Legend badge + Legendary aura');
|
|
if (prestige >= 10) rewards.push('Mythic badge + All perks unlocked');
|
|
return rewards;
|
|
}
|
|
|
|
function getNewRewards(prestige) {
|
|
const rewardMap = {
|
|
1: ['Bronze Prestige badge', '+5% XP bonus on all XP gains'],
|
|
2: ['Silver Prestige badge', '+10% XP bonus on all XP gains'],
|
|
3: ['Gold Prestige badge', '+15% XP bonus on all XP gains'],
|
|
4: ['Platinum badge', '+20% XP bonus', '+25 bonus daily XP'],
|
|
5: ['Diamond badge', '+25% XP bonus', '10% reduced XP cooldowns'],
|
|
6: ['Master badge', '+30% XP bonus', 'XP milestone rewards'],
|
|
7: ['Grandmaster badge', '+35% XP bonus', 'Special profile effects'],
|
|
8: ['Champion badge', '+40% XP bonus', 'Leaderboard priority display'],
|
|
9: ['Legend badge', '+45% XP bonus', 'Legendary profile aura'],
|
|
10: ['Mythic badge', '+50% XP bonus', 'All prestige perks unlocked!']
|
|
};
|
|
return rewardMap[prestige] || [];
|
|
}
|