package player import ( "fmt" "sync" "time" "eq2emu/internal/entity" ) // Manager handles player management operations type Manager struct { // Players indexed by various keys playersLock sync.RWMutex players map[int32]*Player // playerID -> Player playersByName map[string]*Player // name -> Player (case insensitive) playersByCharID map[int32]*Player // characterID -> Player playersByZone map[int32][]*Player // zoneID -> []*Player // Player statistics stats PlayerStats statsLock sync.RWMutex // Event handlers eventHandlers []PlayerEventHandler eventLock sync.RWMutex // Validators validators []PlayerValidator // Database interface database PlayerDatabase // Packet handler packetHandler PlayerPacketHandler // Notifier notifier PlayerNotifier // Statistics tracker statistics PlayerStatistics // Configuration config ManagerConfig // Shutdown channel shutdown chan struct{} // Background goroutines wg sync.WaitGroup } // PlayerStats holds various player statistics type PlayerStats struct { TotalPlayers int64 ActivePlayers int64 PlayersLoggedIn int64 PlayersLoggedOut int64 AverageLevel float64 MaxLevel int8 TotalPlayTime time.Duration } // ManagerConfig holds configuration for the player manager type ManagerConfig struct { // Maximum number of players MaxPlayers int32 // Player save interval SaveInterval time.Duration // Statistics update interval StatsInterval time.Duration // Enable player validation EnableValidation bool // Enable event handling EnableEvents bool // Enable statistics tracking EnableStatistics bool } // NewManager creates a new player manager func NewManager(config ManagerConfig) *Manager { return &Manager{ players: make(map[int32]*Player), playersByName: make(map[string]*Player), playersByCharID: make(map[int32]*Player), playersByZone: make(map[int32][]*Player), eventHandlers: make([]PlayerEventHandler, 0), validators: make([]PlayerValidator, 0), config: config, shutdown: make(chan struct{}), } } // Start starts the player manager func (m *Manager) Start() error { // Start background processes if m.config.SaveInterval > 0 { m.wg.Add(1) go m.savePlayersLoop() } if m.config.StatsInterval > 0 { m.wg.Add(1) go m.updateStatsLoop() } m.wg.Add(1) go m.processPlayersLoop() return nil } // Stop stops the player manager func (m *Manager) Stop() error { close(m.shutdown) m.wg.Wait() return nil } // AddPlayer adds a player to management func (m *Manager) AddPlayer(player *Player) error { if player == nil { return fmt.Errorf("player cannot be nil") } m.playersLock.Lock() defer m.playersLock.Unlock() // Check if we're at capacity if m.config.MaxPlayers > 0 && int32(len(m.players)) >= m.config.MaxPlayers { return fmt.Errorf("server at maximum player capacity") } playerID := player.GetSpawnID() characterID := player.GetCharacterID() name := player.GetName() zoneID := player.GetZone() // Check for duplicates if _, exists := m.players[playerID]; exists { return fmt.Errorf("player with ID %d already exists", playerID) } if _, exists := m.playersByCharID[characterID]; exists { return fmt.Errorf("player with character ID %d already exists", characterID) } if _, exists := m.playersByName[name]; exists { return fmt.Errorf("player with name %s already exists", name) } // Add to maps m.players[playerID] = player m.playersByCharID[characterID] = player m.playersByName[name] = player // Add to zone map if m.playersByZone[zoneID] == nil { m.playersByZone[zoneID] = make([]*Player, 0) } m.playersByZone[zoneID] = append(m.playersByZone[zoneID], player) // Update statistics m.updateStatsForAdd() // Fire event if m.config.EnableEvents { m.firePlayerLoginEvent(player) } return nil } // RemovePlayer removes a player from management func (m *Manager) RemovePlayer(playerID int32) error { m.playersLock.Lock() defer m.playersLock.Unlock() player, exists := m.players[playerID] if !exists { return fmt.Errorf("player with ID %d not found", playerID) } // Remove from maps delete(m.players, playerID) delete(m.playersByCharID, player.GetCharacterID()) delete(m.playersByName, player.GetName()) // Remove from zone map zoneID := player.GetZone() if zonePlayers, exists := m.playersByZone[zoneID]; exists { for i, p := range zonePlayers { if p == player { m.playersByZone[zoneID] = append(zonePlayers[:i], zonePlayers[i+1:]...) break } } // Clean up empty zone lists if len(m.playersByZone[zoneID]) == 0 { delete(m.playersByZone, zoneID) } } // Update statistics m.updateStatsForRemove() // Fire event if m.config.EnableEvents { m.firePlayerLogoutEvent(player) } // Save player data before removal if m.database != nil { m.database.SavePlayer(player) } return nil } // GetPlayer returns a player by spawn ID func (m *Manager) GetPlayer(playerID int32) *Player { m.playersLock.RLock() defer m.playersLock.RUnlock() return m.players[playerID] } // GetPlayerByName returns a player by name func (m *Manager) GetPlayerByName(name string) *Player { m.playersLock.RLock() defer m.playersLock.RUnlock() return m.playersByName[name] } // GetPlayerByCharacterID returns a player by character ID func (m *Manager) GetPlayerByCharacterID(characterID int32) *Player { m.playersLock.RLock() defer m.playersLock.RUnlock() return m.playersByCharID[characterID] } // GetAllPlayers returns all managed players func (m *Manager) GetAllPlayers() []*Player { m.playersLock.RLock() defer m.playersLock.RUnlock() players := make([]*Player, 0, len(m.players)) for _, player := range m.players { players = append(players, player) } return players } // GetPlayersInZone returns all players in a zone func (m *Manager) GetPlayersInZone(zoneID int32) []*Player { m.playersLock.RLock() defer m.playersLock.RUnlock() if zonePlayers, exists := m.playersByZone[zoneID]; exists { // Return a copy to avoid race conditions players := make([]*Player, len(zonePlayers)) copy(players, zonePlayers) return players } return []*Player{} } // SendToAll sends a message to all players func (m *Manager) SendToAll(message interface{}) error { if m.packetHandler == nil { return fmt.Errorf("no packet handler configured") } players := m.GetAllPlayers() return m.packetHandler.BroadcastPacket(players, message) } // SendToZone sends a message to all players in a zone func (m *Manager) SendToZone(zoneID int32, message interface{}) error { if m.packetHandler == nil { return fmt.Errorf("no packet handler configured") } players := m.GetPlayersInZone(zoneID) return m.packetHandler.BroadcastPacket(players, message) } // MovePlayerToZone moves a player to a different zone func (m *Manager) MovePlayerToZone(playerID, newZoneID int32) error { m.playersLock.Lock() defer m.playersLock.Unlock() player, exists := m.players[playerID] if !exists { return fmt.Errorf("player with ID %d not found", playerID) } oldZoneID := player.GetZone() if oldZoneID == newZoneID { return nil // Already in the zone } // Remove from old zone if zonePlayers, exists := m.playersByZone[oldZoneID]; exists { for i, p := range zonePlayers { if p == player { m.playersByZone[oldZoneID] = append(zonePlayers[:i], zonePlayers[i+1:]...) break } } if len(m.playersByZone[oldZoneID]) == 0 { delete(m.playersByZone, oldZoneID) } } // Add to new zone if m.playersByZone[newZoneID] == nil { m.playersByZone[newZoneID] = make([]*Player, 0) } m.playersByZone[newZoneID] = append(m.playersByZone[newZoneID], player) // Update player's zone player.SetZone(newZoneID) // Fire event if m.config.EnableEvents { m.firePlayerZoneChangeEvent(player, oldZoneID, newZoneID) } return nil } // GetPlayerCount returns the current number of players func (m *Manager) GetPlayerCount() int32 { m.playersLock.RLock() defer m.playersLock.RUnlock() return int32(len(m.players)) } // GetZonePlayerCount returns the number of players in a zone func (m *Manager) GetZonePlayerCount(zoneID int32) int32 { m.playersLock.RLock() defer m.playersLock.RUnlock() if zonePlayers, exists := m.playersByZone[zoneID]; exists { return int32(len(zonePlayers)) } return 0 } // GetPlayerStats returns current player statistics func (m *Manager) GetPlayerStats() PlayerStats { m.statsLock.RLock() defer m.statsLock.RUnlock() return m.stats } // AddEventHandler adds an event handler func (m *Manager) AddEventHandler(handler PlayerEventHandler) { m.eventLock.Lock() defer m.eventLock.Unlock() m.eventHandlers = append(m.eventHandlers, handler) } // AddValidator adds a validator func (m *Manager) AddValidator(validator PlayerValidator) { m.validators = append(m.validators, validator) } // SetDatabase sets the database interface func (m *Manager) SetDatabase(db PlayerDatabase) { m.database = db } // SetPacketHandler sets the packet handler func (m *Manager) SetPacketHandler(handler PlayerPacketHandler) { m.packetHandler = handler } // SetNotifier sets the notifier func (m *Manager) SetNotifier(notifier PlayerNotifier) { m.notifier = notifier } // SetStatistics sets the statistics tracker func (m *Manager) SetStatistics(stats PlayerStatistics) { m.statistics = stats } // ValidatePlayer validates a player using all validators func (m *Manager) ValidatePlayer(player *Player) error { if !m.config.EnableValidation { return nil } for _, validator := range m.validators { if err := validator.ValidateLogin(player); err != nil { return err } } return nil } // savePlayersLoop periodically saves all players func (m *Manager) savePlayersLoop() { defer m.wg.Done() ticker := time.NewTicker(m.config.SaveInterval) defer ticker.Stop() for { select { case <-ticker.C: m.saveAllPlayers() case <-m.shutdown: // Final save before shutdown m.saveAllPlayers() return } } } // updateStatsLoop periodically updates statistics func (m *Manager) updateStatsLoop() { defer m.wg.Done() ticker := time.NewTicker(m.config.StatsInterval) defer ticker.Stop() for { select { case <-ticker.C: m.updatePlayerStats() case <-m.shutdown: return } } } // processPlayersLoop processes player updates func (m *Manager) processPlayersLoop() { defer m.wg.Done() ticker := time.NewTicker(100 * time.Millisecond) // 10Hz defer ticker.Stop() for { select { case <-ticker.C: m.processAllPlayers() case <-m.shutdown: return } } } // saveAllPlayers saves all players to database func (m *Manager) saveAllPlayers() { if m.database == nil { return } players := m.GetAllPlayers() for _, player := range players { m.database.SavePlayer(player) } } // updatePlayerStats updates player statistics func (m *Manager) updatePlayerStats() { m.playersLock.RLock() defer m.playersLock.RUnlock() m.statsLock.Lock() defer m.statsLock.Unlock() m.stats.ActivePlayers = int64(len(m.players)) var totalLevel int64 var maxLevel int8 for _, player := range m.players { level := player.GetLevel() totalLevel += int64(level) if level > maxLevel { maxLevel = level } } if len(m.players) > 0 { m.stats.AverageLevel = float64(totalLevel) / float64(len(m.players)) } m.stats.MaxLevel = maxLevel } // processAllPlayers processes updates for all players func (m *Manager) processAllPlayers() { players := m.GetAllPlayers() for _, player := range players { // Process spawn state queue player.CheckSpawnStateQueue() // Process combat player.ProcessCombat() // Process range updates player.ProcessSpawnRangeUpdates() // TODO: Add other periodic processing } } // updateStatsForAdd updates stats when a player is added func (m *Manager) updateStatsForAdd() { m.statsLock.Lock() defer m.statsLock.Unlock() m.stats.TotalPlayers++ m.stats.PlayersLoggedIn++ } // updateStatsForRemove updates stats when a player is removed func (m *Manager) updateStatsForRemove() { m.statsLock.Lock() defer m.statsLock.Unlock() m.stats.PlayersLoggedOut++ } // Event firing methods func (m *Manager) firePlayerLoginEvent(player *Player) { m.eventLock.RLock() defer m.eventLock.RUnlock() for _, handler := range m.eventHandlers { handler.OnPlayerLogin(player) } if m.statistics != nil { m.statistics.RecordPlayerLogin(player) } } func (m *Manager) firePlayerLogoutEvent(player *Player) { m.eventLock.RLock() defer m.eventLock.RUnlock() for _, handler := range m.eventHandlers { handler.OnPlayerLogout(player) } if m.statistics != nil { m.statistics.RecordPlayerLogout(player) } } func (m *Manager) firePlayerZoneChangeEvent(player *Player, fromZoneID, toZoneID int32) { m.eventLock.RLock() defer m.eventLock.RUnlock() for _, handler := range m.eventHandlers { handler.OnPlayerZoneChange(player, fromZoneID, toZoneID) } } // FirePlayerLevelUpEvent fires a level up event func (m *Manager) FirePlayerLevelUpEvent(player *Player, newLevel int8) { m.eventLock.RLock() defer m.eventLock.RUnlock() for _, handler := range m.eventHandlers { handler.OnPlayerLevelUp(player, newLevel) } if m.notifier != nil { m.notifier.NotifyLevelUp(player, newLevel) } } // FirePlayerDeathEvent fires a death event func (m *Manager) FirePlayerDeathEvent(player *Player, killer entity.Entity) { m.eventLock.RLock() defer m.eventLock.RUnlock() for _, handler := range m.eventHandlers { handler.OnPlayerDeath(player, killer) } if m.statistics != nil { m.statistics.RecordPlayerDeath(player, killer) } } // FirePlayerResurrectEvent fires a resurrect event func (m *Manager) FirePlayerResurrectEvent(player *Player) { m.eventLock.RLock() defer m.eventLock.RUnlock() for _, handler := range m.eventHandlers { handler.OnPlayerResurrect(player) } }