eq2go/internal/world/achievement_manager.go
2025-08-07 11:21:56 -05:00

325 lines
10 KiB
Go

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...")
pool := am.database.GetPool()
if pool == nil {
return fmt.Errorf("database pool is nil")
}
err := achievements.LoadAllAchievements(pool, 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) {
pool := am.database.GetPool()
if pool == nil {
fmt.Printf("Error: database pool is nil for character %d\n", characterID)
return
}
// Load player achievements
err := achievements.LoadPlayerAchievements(pool, 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(pool, 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
}
pool := am.database.GetPool()
if pool == nil {
fmt.Printf("Error: database pool is nil for character %d\n", characterID)
return
}
err := achievements.SavePlayerAchievementUpdate(pool, 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")
}