import { writable, derived } from "svelte/store"; import { listen } from "@tauri-apps/api/event"; import { invoke } from "@tauri-apps/api/core"; import type { Achievement, AchievementUnlockedEvent, AchievementId } from "$lib/types/achievements"; import { playAchievementSound } from "$lib/sounds/achievement"; interface AchievementState { achievements: Record; totalUnlocked: number; lastUnlocked: Achievement | null; } // Initial achievement definitions const achievementDefinitions: Record< AchievementId, Omit > = { // Token milestones FirstSteps: { id: "FirstSteps", name: "First Steps", description: "Generated your first 1,000 tokens", icon: "👶", rarity: "common", maxProgress: 1000, }, GrowingStrong: { id: "GrowingStrong", name: "Growing Strong", description: "Reached 10,000 tokens total", icon: "🌱", rarity: "common", maxProgress: 10000, }, BlossomingCoder: { id: "BlossomingCoder", name: "Blossoming Coder", description: "Generated 100,000 tokens - you're really growing!", icon: "🌸", rarity: "rare", maxProgress: 100000, }, TokenMaster: { id: "TokenMaster", name: "Token Master", description: "One million tokens! You're unstoppable!", icon: "👑", rarity: "legendary", maxProgress: 1000000, }, // Code generation HelloWorld: { id: "HelloWorld", name: "Hello, World!", description: "Generated your first code block", icon: "👋", rarity: "common", maxProgress: 1, }, CodeWizard: { id: "CodeWizard", name: "Code Wizard", description: "100 code blocks generated", icon: "🧙‍♀️", rarity: "rare", maxProgress: 100, }, ThousandBlocks: { id: "ThousandBlocks", name: "Thousand Blocks", description: "1,000 code blocks! You're a code machine!", icon: "🏗️", rarity: "epic", maxProgress: 1000, }, // File operations FileManipulator: { id: "FileManipulator", name: "File Manipulator", description: "Edited 10 files", icon: "📝", rarity: "common", maxProgress: 10, }, FileArchitect: { id: "FileArchitect", name: "File Architect", description: "Created or edited 100 files", icon: "🏛️", rarity: "rare", maxProgress: 100, }, // Conversation milestones ConversationStarter: { id: "ConversationStarter", name: "Conversation Starter", description: "Exchanged 10 messages", icon: "💬", rarity: "common", maxProgress: 10, }, ChattyKathy: { id: "ChattyKathy", name: "Chatty Kathy", description: "100 messages exchanged", icon: "🗣️", rarity: "common", maxProgress: 100, }, Conversationalist: { id: "Conversationalist", name: "Master Conversationalist", description: "1,000 messages! We're really connecting!", icon: "💖", rarity: "rare", maxProgress: 1000, }, // Tool usage Toolsmith: { id: "Toolsmith", name: "Toolsmith", description: "Used 5 different tools", icon: "🔨", rarity: "common", maxProgress: 5, }, ToolMaster: { id: "ToolMaster", name: "Tool Master", description: "Used 10 different tools efficiently", icon: "🛠️", rarity: "rare", maxProgress: 10, }, // Time-based achievements EarlyBird: { id: "EarlyBird", name: "Early Bird", description: "Started a session between 5 AM and 7 AM", icon: "🌅", rarity: "common", }, NightOwl: { id: "NightOwl", name: "Night Owl", description: "Coding after midnight", icon: "🦉", rarity: "common", }, AllNighter: { id: "AllNighter", name: "All Nighter", description: "Worked through the night (2 AM - 5 AM)", icon: "🌙", rarity: "rare", }, WeekendWarrior: { id: "WeekendWarrior", name: "Weekend Warrior", description: "Coding on a weekend", icon: "⚔️", rarity: "common", }, DedicatedDeveloper: { id: "DedicatedDeveloper", name: "Dedicated Developer", description: "Coded for 30 days in a row", icon: "🏆", rarity: "legendary", }, // Search and exploration Explorer: { id: "Explorer", name: "Explorer", description: "Used search tools 50 times", icon: "🔍", rarity: "common", maxProgress: 50, }, MasterSearcher: { id: "MasterSearcher", name: "Master Searcher", description: "Searched 500 times across files", icon: "🕵️‍♀️", rarity: "rare", maxProgress: 500, }, // Session achievements QuickSession: { id: "QuickSession", name: "Quick Session", description: "Completed a productive session in under 5 minutes", icon: "⚡", rarity: "common", }, FocusedWork: { id: "FocusedWork", name: "Focused Work", description: "Worked for 30 minutes straight", icon: "🎯", rarity: "common", }, DeepDive: { id: "DeepDive", name: "Deep Dive", description: "Worked for 2 hours continuously", icon: "🏊‍♀️", rarity: "rare", }, MarathonSession: { id: "MarathonSession", name: "Marathon Session", description: "5+ hour coding session!", icon: "🏃‍♀️", rarity: "epic", }, // Special achievements FirstMessage: { id: "FirstMessage", name: "First Message", description: "Sent your first message to Hikari", icon: "✨", rarity: "common", maxProgress: 1, }, FirstTool: { id: "FirstTool", name: "First Tool", description: "Used your first tool", icon: "🔧", rarity: "common", maxProgress: 1, }, FirstCodeBlock: { id: "FirstCodeBlock", name: "First Code", description: "Generated your first code block", icon: "📦", rarity: "common", maxProgress: 1, }, FirstFileEdit: { id: "FirstFileEdit", name: "First Edit", description: "Made your first file edit", icon: "✏️", rarity: "common", maxProgress: 1, }, Polyglot: { id: "Polyglot", name: "Polyglot", description: "Generated code in 5+ languages in one session", icon: "🌍", rarity: "rare", maxProgress: 5, }, SpeedCoder: { id: "SpeedCoder", name: "Speed Coder", description: "Generated 10 code blocks in 10 minutes", icon: "🚀", rarity: "rare", }, ClaudeConnoisseur: { id: "ClaudeConnoisseur", name: "Claude Connoisseur", description: "Used all available Claude models", icon: "🎨", rarity: "epic", maxProgress: 5, // Adjust based on available models }, MarathonCoder: { id: "MarathonCoder", name: "Marathon Coder", description: "10,000 tokens in a single session", icon: "🏃‍♂️", rarity: "epic", maxProgress: 10000, }, // Relationship & Greetings GoodMorning: { id: "GoodMorning", name: "Good Morning!", description: "Greeted Hikari with a good morning", icon: "🌅", rarity: "common", maxProgress: 1, }, GoodNight: { id: "GoodNight", name: "Sweet Dreams", description: "Said good night to Hikari", icon: "🌙", rarity: "common", maxProgress: 1, }, ThankYou: { id: "ThankYou", name: "Grateful Heart", description: "Thanked Hikari for her help", icon: "🙏", rarity: "common", maxProgress: 1, }, LoveYou: { id: "LoveYou", name: "Heartfelt", description: "Expressed love to Hikari", icon: "💕", rarity: "rare", maxProgress: 1, }, // Personality & Fun EmojiUser: { id: "EmojiUser", name: "Emoji Master", description: "Used 20+ emojis in messages", icon: "😄", rarity: "common", maxProgress: 20, }, CapsLock: { id: "CapsLock", name: "CAPS LOCK", description: "SENT A MESSAGE IN ALL CAPS", icon: "🔊", rarity: "common", maxProgress: 1, }, QuestionMaster: { id: "QuestionMaster", name: "Question Master", description: "Asked 50 questions", icon: "❓", rarity: "common", maxProgress: 50, }, PleaseAndThankYou: { id: "PleaseAndThankYou", name: "Polite Programmer", description: "Always says please and thank you", icon: "🎩", rarity: "common", maxProgress: 10, }, // Git & Development CommitMaster: { id: "CommitMaster", name: "Commit Master", description: "Made 100 commits through Hikari", icon: "📝", rarity: "rare", maxProgress: 100, }, PRO: { id: "PRO", name: "PRO", description: "Created 10 pull requests", icon: "🔀", rarity: "rare", maxProgress: 10, }, Reviewer: { id: "Reviewer", name: "Code Reviewer", description: "Reviewed 10 pull requests", icon: "👀", rarity: "rare", maxProgress: 10, }, IssueTracker: { id: "IssueTracker", name: "Issue Tracker", description: "Created 25 issues", icon: "🎯", rarity: "rare", maxProgress: 25, }, GitGuru: { id: "GitGuru", name: "Git Guru", description: "Mastered git operations", icon: "🌲", rarity: "epic", }, // Tool Mastery BashMaster: { id: "BashMaster", name: "Bash Master", description: "Used bash commands 100 times", icon: "💻", rarity: "rare", maxProgress: 100, }, FileExplorer: { id: "FileExplorer", name: "File Explorer", description: "Explored files 100 times", icon: "📂", rarity: "common", maxProgress: 100, }, SearchExpert: { id: "SearchExpert", name: "Search Expert", description: "Mastered advanced search queries", icon: "🔎", rarity: "rare", }, AgentCommander: { id: "AgentCommander", name: "Agent Commander", description: "Used task agents effectively", icon: "🤖", rarity: "rare", }, MCPMaster: { id: "MCPMaster", name: "MCP Master", description: "Mastered MCP tool usage", icon: "🛠️", rarity: "epic", }, }; // Initialize all achievements as locked const initialAchievements: Record = {} as Record< AchievementId, Achievement >; for (const [id, def] of Object.entries(achievementDefinitions)) { initialAchievements[id as AchievementId] = { ...def, unlocked: false, progress: 0, }; } // Create the main store function createAchievementsStore() { const { subscribe, update } = writable({ achievements: initialAchievements, totalUnlocked: 0, lastUnlocked: null, }); return { subscribe, unlockAchievement: (event: AchievementUnlockedEvent, playSound: boolean = true) => { update((state) => { const achievement = state.achievements[event.achievement.id]; if (achievement && !achievement.unlocked) { achievement.unlocked = true; achievement.unlockedAt = event.achievement.unlocked_at ? new Date(event.achievement.unlocked_at) : new Date(); state.totalUnlocked++; state.lastUnlocked = achievement; // Play achievement sound only for new unlocks, not when loading saved ones if (playSound) { try { playAchievementSound(); } catch (error) { console.error("Failed to play achievement sound:", error); } } } return state; }); }, updateProgress: (id: AchievementId, progress: number) => { update((state) => { const achievement = state.achievements[id]; if (achievement) { achievement.progress = progress; } return state; }); }, reset: () => { update(() => ({ achievements: initialAchievements, totalUnlocked: 0, lastUnlocked: null, })); }, }; } export const achievementsStore = createAchievementsStore(); // Derived stores for different views export const unlockedAchievements = derived(achievementsStore, ($store) => Object.values($store.achievements).filter((a) => a.unlocked) ); export const lockedAchievements = derived(achievementsStore, ($store) => Object.values($store.achievements).filter((a) => !a.unlocked) ); export const achievementsByRarity = derived(achievementsStore, ($store) => { const byRarity: Record = { common: [], rare: [], epic: [], legendary: [], }; for (const achievement of Object.values($store.achievements)) { byRarity[achievement.rarity].push(achievement); } return byRarity; }); export const achievementProgress = derived(achievementsStore, ($store) => ({ unlocked: $store.totalUnlocked, total: Object.keys($store.achievements).length, percentage: Math.round(($store.totalUnlocked / Object.keys($store.achievements).length) * 100), })); // Initialize achievement listener export async function initAchievementsListener() { // Listen for achievement unlocked events await listen("achievement:unlocked", (event) => { achievementsStore.unlockAchievement(event.payload); }); // Load saved achievements from persistent storage try { const savedAchievements = await invoke("load_saved_achievements"); // Update the store with saved achievements (don't play sounds) for (const event of savedAchievements) { achievementsStore.unlockAchievement(event, false); } } catch (error) { console.error("Failed to load saved achievements:", error); } } // Export achievement categories for the display panel export const achievementCategories = [ { name: "Token Milestones", description: "Track your token generation progress", ids: ["FirstSteps", "GrowingStrong", "BlossomingCoder", "TokenMaster"] as AchievementId[], }, { name: "Code Generation", description: "Achievements for generating code", ids: ["HelloWorld", "CodeWizard", "ThousandBlocks"] as AchievementId[], }, { name: "File Operations", description: "Working with files and projects", ids: ["FileManipulator", "FileArchitect"] as AchievementId[], }, { name: "Conversations", description: "Building our relationship through chat", ids: ["ConversationStarter", "ChattyKathy", "Conversationalist"] as AchievementId[], }, { name: "Tools & Skills", description: "Mastering different tools", ids: ["Toolsmith", "ToolMaster"] as AchievementId[], }, { name: "Time-Based", description: "When you code matters too!", ids: [ "EarlyBird", "NightOwl", "AllNighter", "WeekendWarrior", "DedicatedDeveloper", ] as AchievementId[], }, { name: "Search & Explore", description: "Finding what you need", ids: ["Explorer", "MasterSearcher"] as AchievementId[], }, { name: "Session Records", description: "Your coding session achievements", ids: [ "QuickSession", "FocusedWork", "DeepDive", "MarathonSession", "MarathonCoder", ] as AchievementId[], }, { name: "Relationship & Greetings", description: "Our special moments together", ids: ["GoodMorning", "GoodNight", "ThankYou", "LoveYou"] as AchievementId[], }, { name: "Personality & Fun", description: "Express yourself!", ids: ["EmojiUser", "CapsLock", "QuestionMaster", "PleaseAndThankYou"] as AchievementId[], }, { name: "Git & Development", description: "Version control mastery", ids: ["CommitMaster", "PRO", "Reviewer", "IssueTracker", "GitGuru"] as AchievementId[], }, { name: "Tool Mastery", description: "Master of all tools", ids: [ "BashMaster", "FileExplorer", "SearchExpert", "AgentCommander", "MCPMaster", ] as AchievementId[], }, { name: "Special", description: "Unique accomplishments", ids: [ "FirstMessage", "FirstTool", "FirstCodeBlock", "FirstFileEdit", "Polyglot", "SpeedCoder", "ClaudeConnoisseur", ] as AchievementId[], }, ];