package login import ( "sync" "time" "eq2emu/internal/udp" ) // ClientList manages connected login clients type ClientList struct { clients map[*udp.Connection]*Client byAccountID map[int32]*Client mu sync.RWMutex } // NewClientList creates a new client list func NewClientList() *ClientList { return &ClientList{ clients: make(map[*udp.Connection]*Client), byAccountID: make(map[int32]*Client), } } // Add adds a client to the list func (cl *ClientList) Add(client *Client) { if client == nil || client.GetConnection() == nil { return } cl.mu.Lock() defer cl.mu.Unlock() // Remove any existing client for this connection if existing, exists := cl.clients[client.GetConnection()]; exists { existing.Disconnect("Replaced by new connection") if existing.GetAccountID() > 0 { delete(cl.byAccountID, existing.GetAccountID()) } } cl.clients[client.GetConnection()] = client } // Remove removes a client from the list func (cl *ClientList) Remove(client *Client) { if client == nil { return } cl.mu.Lock() defer cl.mu.Unlock() conn := client.GetConnection() if conn != nil { delete(cl.clients, conn) } if client.GetAccountID() > 0 { delete(cl.byAccountID, client.GetAccountID()) } } // GetByConnection returns a client by connection func (cl *ClientList) GetByConnection(conn *udp.Connection) *Client { if conn == nil { return nil } cl.mu.RLock() defer cl.mu.RUnlock() return cl.clients[conn] } // GetByAccountID returns a client by account ID func (cl *ClientList) GetByAccountID(accountID int32) *Client { if accountID <= 0 { return nil } cl.mu.RLock() defer cl.mu.RUnlock() return cl.byAccountID[accountID] } // UpdateAccountMapping updates the account ID mapping for a client func (cl *ClientList) UpdateAccountMapping(client *Client) { if client == nil { return } cl.mu.Lock() defer cl.mu.Unlock() accountID := client.GetAccountID() if accountID > 0 { // Remove any existing mapping for this account if existing, exists := cl.byAccountID[accountID]; exists && existing != client { existing.Disconnect("Account logged in elsewhere") delete(cl.clients, existing.GetConnection()) } cl.byAccountID[accountID] = client } } // Process processes all clients func (cl *ClientList) Process() { cl.mu.Lock() defer cl.mu.Unlock() // Check for timed out clients timeout := 5 * time.Minute var toRemove []*Client for _, client := range cl.clients { if client.IsTimedOut(timeout) { toRemove = append(toRemove, client) } } // Remove timed out clients for _, client := range toRemove { client.Disconnect("Connection timeout") delete(cl.clients, client.GetConnection()) if client.GetAccountID() > 0 { delete(cl.byAccountID, client.GetAccountID()) } } } // Count returns the number of connected clients func (cl *ClientList) Count() int { cl.mu.RLock() defer cl.mu.RUnlock() return len(cl.clients) } // GetClients returns a slice of all clients (snapshot) func (cl *ClientList) GetClients() []*Client { cl.mu.RLock() defer cl.mu.RUnlock() clients := make([]*Client, 0, len(cl.clients)) for _, client := range cl.clients { clients = append(clients, client) } return clients } // GetClientsByState returns clients in a specific state func (cl *ClientList) GetClientsByState(state ClientState) []*Client { cl.mu.RLock() defer cl.mu.RUnlock() var clients []*Client for _, client := range cl.clients { if client.GetState() == state { clients = append(clients, client) } } return clients } // DisconnectAll disconnects all clients func (cl *ClientList) DisconnectAll(reason string) { cl.mu.Lock() defer cl.mu.Unlock() for _, client := range cl.clients { client.Disconnect(reason) } // Clear maps cl.clients = make(map[*udp.Connection]*Client) cl.byAccountID = make(map[int32]*Client) } // DisconnectByAccountID disconnects a client by account ID func (cl *ClientList) DisconnectByAccountID(accountID int32, reason string) bool { client := cl.GetByAccountID(accountID) if client == nil { return false } client.Disconnect(reason) cl.Remove(client) return true } // GetStats returns client list statistics func (cl *ClientList) GetStats() ClientListStats { cl.mu.RLock() defer cl.mu.RUnlock() stats := ClientListStats{ Total: len(cl.clients), States: make(map[ClientState]int), } for _, client := range cl.clients { state := client.GetState() stats.States[state]++ } return stats } // ClientListStats represents statistics about the client list type ClientListStats struct { Total int `json:"total"` States map[ClientState]int `json:"states"` } // ForEach executes a function for each client (thread-safe) func (cl *ClientList) ForEach(fn func(*Client)) { clients := cl.GetClients() for _, client := range clients { fn(client) } }