generated from nhcarrigan/template
650 lines
16 KiB
TypeScript
650 lines
16 KiB
TypeScript
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<AchievementId, Achievement>;
|
|
totalUnlocked: number;
|
|
lastUnlocked: Achievement | null;
|
|
}
|
|
|
|
// Initial achievement definitions
|
|
const achievementDefinitions: Record<
|
|
AchievementId,
|
|
Omit<Achievement, "unlocked" | "unlockedAt">
|
|
> = {
|
|
// 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<AchievementId, Achievement> = {} 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<AchievementState>({
|
|
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<string, Achievement[]> = {
|
|
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<AchievementUnlockedEvent>("achievement:unlocked", (event) => {
|
|
achievementsStore.unlockAchievement(event.payload);
|
|
});
|
|
|
|
// Load saved achievements from persistent storage
|
|
try {
|
|
const savedAchievements = await invoke<AchievementUnlockedEvent[]>("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[],
|
|
},
|
|
];
|