eq2go/internal/player/manager.go

615 lines
14 KiB
Go

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 any) 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 any) 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)
}
}