import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, StringSelectMenuOptionBuilder } from "discord.js"; /** * Quest entry with quest details and progress */ interface QuestEntry { progress: number | null; completedAt: Date | null; quest: { id: number; name: string; description: string | null; triggerEvent: string; requirements: any; rewards: any; }; } /** * Available quest interface */ interface AvailableQuest { id: number; name: string; description: string | null; rewards: any; requirements: any; } /** * Formats quest rewards object into a human-readable string */ function formatQuestRewards(rewards: { xp?: number, balance?: number }): string { const rewardStr: string[] = []; if (rewards?.xp) rewardStr.push(`${rewards.xp} XP`); if (rewards?.balance) rewardStr.push(`${rewards.balance} πŸͺ™`); return rewardStr.join(", "); } /** * Returns the quest status display string */ function getQuestStatus(completedAt: Date | null): string { return completedAt ? "βœ… Completed" : "πŸ“ In Progress"; } /** * Renders a simple progress bar */ function renderProgressBar(current: number, total: number, size: number = 10): string { const percentage = Math.min(current / total, 1); const progress = Math.round(size * percentage); const empty = size - progress; const progressText = "β–°".repeat(progress); const emptyText = "β–±".repeat(empty); return `${progressText}${emptyText} ${Math.round(percentage * 100)}% (${current}/${total})`; } /** * Creates an embed displaying a user's quest log */ export function getQuestListEmbed(userQuests: QuestEntry[]): EmbedBuilder { const embed = new EmbedBuilder() .setTitle("πŸ“œ Quest Log") .setDescription("Your active and completed quests.") .setColor(0x3498db); // Blue if (userQuests.length === 0) { embed.setDescription("You have no active quests. Check available quests!"); } userQuests.forEach(entry => { const status = getQuestStatus(entry.completedAt); const rewards = entry.quest.rewards as { xp?: number, balance?: number }; const rewardsText = formatQuestRewards(rewards); const requirements = entry.quest.requirements as { target?: number }; const target = requirements?.target || 1; const progress = entry.progress || 0; const progressBar = entry.completedAt ? "βœ… Fully completed" : renderProgressBar(progress, target); embed.addFields({ name: `${entry.quest.name} (${status})`, value: `${entry.quest.description}\n**Rewards:** ${rewardsText}\n**Progress:** ${progressBar}`, inline: false }); }); return embed; } /** * Creates an embed for available quests */ export function getAvailableQuestsEmbed(availableQuests: AvailableQuest[]): EmbedBuilder { const embed = new EmbedBuilder() .setTitle("πŸ—ΊοΈ Available Quests") .setDescription("Quests you can accept right now.") .setColor(0x2ecc71); // Green if (availableQuests.length === 0) { embed.setDescription("There are no new quests available for you at the moment."); } availableQuests.forEach(quest => { const rewards = quest.rewards as { xp?: number, balance?: number }; const rewardsText = formatQuestRewards(rewards); const requirements = quest.requirements as { target?: number }; const target = requirements?.target || 1; embed.addFields({ name: quest.name, value: `${quest.description}\n**Goal:** Reach ${target} for this activity.\n**Rewards:** ${rewardsText}`, inline: false }); }); return embed; } /** * Returns action rows for the quest view */ export function getQuestActionRows(viewType: 'active' | 'available', availableQuests: AvailableQuest[] = []): ActionRowBuilder[] { const rows: ActionRowBuilder[] = []; const navRow = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId("quest_view_active") .setLabel("Active Quests") .setStyle(viewType === 'active' ? ButtonStyle.Primary : ButtonStyle.Secondary) .setDisabled(viewType === 'active'), new ButtonBuilder() .setCustomId("quest_view_available") .setLabel("Available Quests") .setStyle(viewType === 'available' ? ButtonStyle.Primary : ButtonStyle.Secondary) .setDisabled(viewType === 'available') ); rows.push(navRow); if (viewType === 'available' && availableQuests.length > 0) { const selectMenu = new StringSelectMenuBuilder() .setCustomId("quest_accept_select") .setPlaceholder("Select a quest to accept") .addOptions( availableQuests.slice(0, 25).map(q => new StringSelectMenuOptionBuilder() .setLabel(q.name) .setDescription(q.description?.substring(0, 100) || "") .setValue(q.id.toString()) ) ); rows.push(new ActionRowBuilder().addComponents(selectMenu)); } return rows; }