226 lines
4.7 KiB
Go
226 lines
4.7 KiB
Go
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)
|
|
}
|
|
} |