package world import ( "fmt" "sync" "eq2emu/internal/achievements" "eq2emu/internal/database" ) // AchievementManager manages achievements for the world server type AchievementManager struct { masterList *achievements.MasterList playerManagers map[int32]*achievements.PlayerManager // CharacterID -> PlayerManager database *database.Database world *World // Reference to world server for notifications mutex sync.RWMutex } // NewAchievementManager creates a new achievement manager func NewAchievementManager(db *database.Database) *AchievementManager { return &AchievementManager{ masterList: achievements.NewMasterList(), playerManagers: make(map[int32]*achievements.PlayerManager), database: db, world: nil, // Set by world server after creation } } // SetWorld sets the world server reference for notifications func (am *AchievementManager) SetWorld(world *World) { am.world = world } // LoadAchievements loads all achievements from database func (am *AchievementManager) LoadAchievements() error { fmt.Println("Loading master achievement list...") err := achievements.LoadAllAchievements(am.database, am.masterList) if err != nil { return fmt.Errorf("failed to load achievements: %w", err) } fmt.Printf("Loaded %d achievements\n", am.masterList.Size()) return nil } // GetPlayerManager gets or creates a player achievement manager func (am *AchievementManager) GetPlayerManager(characterID int32) *achievements.PlayerManager { am.mutex.RLock() playerMgr, exists := am.playerManagers[characterID] am.mutex.RUnlock() if exists { return playerMgr } // Create new player manager and load data am.mutex.Lock() defer am.mutex.Unlock() // Double-check after acquiring write lock if playerMgr, exists := am.playerManagers[characterID]; exists { return playerMgr } playerMgr = achievements.NewPlayerManager() am.playerManagers[characterID] = playerMgr // Load player achievement data from database go am.loadPlayerAchievements(characterID, playerMgr) return playerMgr } // loadPlayerAchievements loads achievement data for a specific player func (am *AchievementManager) loadPlayerAchievements(characterID int32, playerMgr *achievements.PlayerManager) { // Load player achievements err := achievements.LoadPlayerAchievements(am.database, uint32(characterID), playerMgr.Achievements) if err != nil { fmt.Printf("Error loading achievements for character %d: %v\n", characterID, err) } // Load player progress err = achievements.LoadPlayerAchievementUpdates(am.database, uint32(characterID), playerMgr.Updates) if err != nil { fmt.Printf("Error loading achievement progress for character %d: %v\n", characterID, err) } } // UpdateProgress updates player progress for an achievement func (am *AchievementManager) UpdateProgress(characterID int32, achievementID uint32, progress uint32) error { playerMgr := am.GetPlayerManager(characterID) if playerMgr == nil { return fmt.Errorf("failed to get player manager for character %d", characterID) } // Update progress playerMgr.Updates.UpdateProgress(achievementID, progress) // Check if achievement is completed achievement := am.masterList.GetAchievement(achievementID) if achievement != nil { completed, err := playerMgr.CheckRequirements(achievement) if err != nil { return fmt.Errorf("failed to check requirements: %w", err) } if completed && !playerMgr.Updates.IsCompleted(achievementID) { // Complete the achievement playerMgr.Updates.CompleteAchievement(achievementID) // Save progress to database go am.savePlayerProgress(characterID, achievementID, playerMgr) // Trigger achievement completion event go am.onAchievementCompleted(characterID, achievement) fmt.Printf("Character %d completed achievement: %s\n", characterID, achievement.Title) } else if progress > 0 { // Save progress update to database go am.savePlayerProgress(characterID, achievementID, playerMgr) } } return nil } // savePlayerProgress saves player achievement progress to database func (am *AchievementManager) savePlayerProgress(characterID int32, achievementID uint32, playerMgr *achievements.PlayerManager) { update := playerMgr.Updates.GetUpdate(achievementID) if update == nil { return } err := achievements.SavePlayerAchievementUpdate(am.database, uint32(characterID), update) if err != nil { fmt.Printf("Error saving achievement progress for character %d, achievement %d: %v\n", characterID, achievementID, err) } } // onAchievementCompleted handles achievement completion events func (am *AchievementManager) onAchievementCompleted(characterID int32, achievement *achievements.Achievement) { // Award points if achievement.PointValue > 0 { // Increment player's achievement points fmt.Printf("Character %d earned %d achievement points\n", characterID, achievement.PointValue) } // Process rewards for _, reward := range achievement.Rewards { am.processReward(characterID, reward) } // Notify other systems about achievement completion am.notifyAchievementCompleted(characterID, achievement.ID) } // notifyAchievementCompleted notifies other systems about achievement completion func (am *AchievementManager) notifyAchievementCompleted(characterID int32, achievementID uint32) { // Notify title system if available if am.world != nil && am.world.titleMgr != nil { integrationMgr := am.world.titleMgr.GetIntegrationManager() if integrationMgr != nil { achievementIntegration := integrationMgr.GetAchievementIntegration() if achievementIntegration != nil { err := achievementIntegration.OnAchievementCompleted(characterID, achievementID) if err != nil { fmt.Printf("Error processing achievement completion for titles: %v\n", err) } } } } } // processReward processes an achievement reward func (am *AchievementManager) processReward(characterID int32, reward achievements.Reward) { // Basic reward processing - extend based on reward types switch reward.Reward { case "title": // Award title fmt.Printf("Character %d earned a title reward\n", characterID) case "item": // Award item fmt.Printf("Character %d earned an item reward\n", characterID) case "experience": // Award experience fmt.Printf("Character %d earned experience reward\n", characterID) default: fmt.Printf("Character %d earned reward: %s\n", characterID, reward.Reward) } } // GetAchievement gets an achievement by ID from master list func (am *AchievementManager) GetAchievement(achievementID uint32) *achievements.Achievement { return am.masterList.GetAchievement(achievementID) } // GetAchievementsByCategory gets achievements filtered by category func (am *AchievementManager) GetAchievementsByCategory(category string) []*achievements.Achievement { return am.masterList.GetAchievementsByCategory(category) } // GetAchievementsByExpansion gets achievements filtered by expansion func (am *AchievementManager) GetAchievementsByExpansion(expansion string) []*achievements.Achievement { return am.masterList.GetAchievementsByExpansion(expansion) } // GetPlayerProgress gets player's progress for an achievement func (am *AchievementManager) GetPlayerProgress(characterID int32, achievementID uint32) uint32 { playerMgr := am.GetPlayerManager(characterID) if playerMgr == nil { return 0 } return playerMgr.Updates.GetProgress(achievementID) } // IsPlayerCompleted checks if player has completed an achievement func (am *AchievementManager) IsPlayerCompleted(characterID int32, achievementID uint32) bool { playerMgr := am.GetPlayerManager(characterID) if playerMgr == nil { return false } return playerMgr.Updates.IsCompleted(achievementID) } // GetPlayerCompletedAchievements gets all completed achievement IDs for a player func (am *AchievementManager) GetPlayerCompletedAchievements(characterID int32) []uint32 { playerMgr := am.GetPlayerManager(characterID) if playerMgr == nil { return nil } return playerMgr.Updates.GetCompletedAchievements() } // GetPlayerInProgressAchievements gets all in-progress achievement IDs for a player func (am *AchievementManager) GetPlayerInProgressAchievements(characterID int32) []uint32 { playerMgr := am.GetPlayerManager(characterID) if playerMgr == nil { return nil } return playerMgr.Updates.GetInProgressAchievements() } // GetCompletionPercentage gets completion percentage for player's achievement func (am *AchievementManager) GetCompletionPercentage(characterID int32, achievementID uint32) float64 { playerMgr := am.GetPlayerManager(characterID) if playerMgr == nil { return 0.0 } achievement := am.masterList.GetAchievement(achievementID) if achievement == nil { return 0.0 } return playerMgr.GetCompletionStatus(achievement) } // RemovePlayerManager removes a player manager (called when player logs out) func (am *AchievementManager) RemovePlayerManager(characterID int32) { am.mutex.Lock() defer am.mutex.Unlock() delete(am.playerManagers, characterID) } // GetStatistics returns achievement system statistics func (am *AchievementManager) GetStatistics() map[string]any { am.mutex.RLock() defer am.mutex.RUnlock() stats := map[string]any{ "total_achievements": am.masterList.Size(), "online_players": len(am.playerManagers), "categories": am.masterList.GetCategories(), "expansions": am.masterList.GetExpansions(), } return stats } // Shutdown gracefully shuts down the achievement manager func (am *AchievementManager) Shutdown() { fmt.Println("Shutting down achievement manager...") am.mutex.Lock() defer am.mutex.Unlock() // Save all player progress before shutdown for characterID, playerMgr := range am.playerManagers { for _, achievementID := range playerMgr.Updates.GetInProgressAchievements() { am.savePlayerProgress(characterID, achievementID, playerMgr) } } // Clear player managers am.playerManagers = make(map[int32]*achievements.PlayerManager) fmt.Println("Achievement manager shutdown complete") }