From b22d0b1367a09df4f6bc2eccd5b22b82ce1afe3f Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 29 Jan 2024 23:16:06 +0100 Subject: [PATCH] Improved reconnect flow --- client/Main.gd | 26 +++++-- client/Main.tscn | 11 ++- client/network/Chat.gd | 2 +- client/network/Client.gd | 4 +- client/network/Login.gd | 6 +- client/network/Logout.gd | 12 +++ client/network/Ping.gd | 2 +- client/network/PlayerAdd.gd | 18 ++++- client/network/PlayerJump.gd | 2 +- client/network/PlayerMove.gd | 2 +- client/network/PlayerRemove.gd | 4 +- client/network/shared/NetworkNode.gd | 4 +- client/network/shared/PacketHandler.gd | 2 +- client/player/controller/Controller.gd | 5 ++ client/player/controller/PlayerController.gd | 5 +- client/player/controller/ProxyController.gd | 9 +-- client/ui/UI.gd | 4 + client/ui/UI.tscn | 26 ++++++- client/ui/connect/ConnectPanel.gd | 11 +++ client/world/PlayerManager.gd | 6 ++ server/game/Chat.go | 24 +++++- server/game/Game.go | 31 +++++--- server/game/Login.go | 13 +--- server/game/Player.go | 2 + server/game/PlayerManager.go | 80 ++++++++++---------- 25 files changed, 212 insertions(+), 99 deletions(-) create mode 100644 client/network/Logout.gd create mode 100644 client/player/controller/Controller.gd create mode 100644 client/ui/connect/ConnectPanel.gd diff --git a/client/Main.gd b/client/Main.gd index 55ec50c..10ccb7b 100644 --- a/client/Main.gd +++ b/client/Main.gd @@ -1,26 +1,40 @@ extends Node func _ready(): + pause(true) + Global.instance_id = OS.get_process_id() % 4 Global.username = "user%d" % Global.instance_id %Login.success.connect(on_login) + %Logout.success.connect(on_logout) %Login.send_login() - mute_audio() - func _input(event): if event.is_action_pressed("toggle_fullscreen"): toggle_fullscreen() get_viewport().set_input_as_handled() -func mute_audio(): - var master_sound = AudioServer.get_bus_index("Master") - AudioServer.set_bus_mute(master_sound, true) - func on_login(): + print("[%s] Login succeeded." % Global.username) + print("[%s] ID: %s" % [Global.username, Global.account_id]) + print("[%s] Auth token: %s" % [Global.username, Global.auth_token]) + DisplayServer.window_set_title("%s - %s" % [Global.username, Global.account_id]) DisplayServer.window_set_position(Vector2((Global.instance_id % 2) * 960, (Global.instance_id / 2 % 2) * 540)) + pause(false) + +func on_logout(): + print("[%s] Logout." % Global.username) + pause(true) + +func pause(enabled: bool): + get_tree().paused = enabled + mute_audio(enabled) + +func mute_audio(enabled: bool): + var master_sound = AudioServer.get_bus_index("Master") + AudioServer.set_bus_mute(master_sound, enabled) func toggle_fullscreen(): var mode = DisplayServer.window_get_mode() diff --git a/client/Main.tscn b/client/Main.tscn index dd32a2d..37dfde4 100644 --- a/client/Main.tscn +++ b/client/Main.tscn @@ -1,10 +1,11 @@ -[gd_scene load_steps=24 format=3 uid="uid://b40y7iuskv1ar"] +[gd_scene load_steps=25 format=3 uid="uid://b40y7iuskv1ar"] [ext_resource type="Script" path="res://Main.gd" id="1_cw3ws"] [ext_resource type="Script" path="res://network/Client.gd" id="2_8hxcx"] [ext_resource type="Script" path="res://network/Ping.gd" id="3_d6qf1"] [ext_resource type="Script" path="res://network/Login.gd" id="4_fsx7a"] [ext_resource type="Script" path="res://network/PlayerAdd.gd" id="5_376ik"] +[ext_resource type="Script" path="res://network/Logout.gd" id="5_au5w3"] [ext_resource type="PackedScene" uid="uid://2lcnu3dy54lx" path="res://player/Player.tscn" id="6_cdj8w"] [ext_resource type="Script" path="res://network/PlayerRemove.gd" id="7_2r42o"] [ext_resource type="Script" path="res://network/PlayerMove.gd" id="8_ke1yy"] @@ -40,6 +41,7 @@ script = ExtResource("1_cw3ws") [node name="Client" type="Node" parent="."] unique_name_in_owner = true +process_mode = 3 script = ExtResource("2_8hxcx") [node name="Ping" type="Node" parent="Client"] @@ -59,6 +61,11 @@ packet_type = 2 wait_time = 5.0 autostart = true +[node name="Logout" type="Node" parent="Client"] +unique_name_in_owner = true +script = ExtResource("5_au5w3") +packet_type = 3 + [node name="PlayerAdd" type="Node" parent="Client"] unique_name_in_owner = true script = ExtResource("5_376ik") @@ -71,7 +78,6 @@ packet_type = 11 [node name="PlayerMove" type="Node" parent="Client"] script = ExtResource("8_ke1yy") -delay = null packet_type = 12 [node name="PlayerJump" type="Node" parent="Client"] @@ -131,6 +137,7 @@ unique_name_in_owner = true script = ExtResource("16_dp6bj") [node name="UI" parent="." instance=ExtResource("17_43qhq")] +process_mode = 3 [node name="Viewport" type="SubViewportContainer" parent="."] texture_filter = 1 diff --git a/client/network/Chat.gd b/client/network/Chat.gd index 9e82ede..3c23c58 100644 --- a/client/network/Chat.gd +++ b/client/network/Chat.gd @@ -8,7 +8,7 @@ func send_message(message: String): buffer.put_data(message.to_utf8_buffer()) %Client.send(buffer.data_array) -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var buffer := StreamPeerBuffer.new() buffer.data_array = data diff --git a/client/network/Client.gd b/client/network/Client.gd index 994fa30..9c1a6c9 100644 --- a/client/network/Client.gd +++ b/client/network/Client.gd @@ -21,7 +21,7 @@ func _enter_tree(): func _process(_delta): while socket.get_available_packet_count() > 0: var packet := socket.get_packet() - handle_packet(packet, socket) + handle_packet(packet) func update_statistics(): download_changed.emit(download) @@ -31,4 +31,4 @@ func update_statistics(): func send(data: PackedByteArray): socket.put_packet(data) - upload += data.size() \ No newline at end of file + upload += data.size() diff --git a/client/network/Login.gd b/client/network/Login.gd index 14002e2..9bd5c43 100644 --- a/client/network/Login.gd +++ b/client/network/Login.gd @@ -3,7 +3,7 @@ extends PacketHandler signal success signal failure -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var buffer := StreamPeerBuffer.new() buffer.data_array = data @@ -15,10 +15,6 @@ func handle_packet(data: PackedByteArray, _peer: PacketPeer): Global.account_id = buffer.get_string() Global.auth_token = buffer.get_string() - - print("[%s] Login succeeded." % Global.username) - print("[%s] ID: %s" % [Global.username, Global.account_id]) - print("[%s] Auth token: %s" % [Global.username, Global.auth_token]) success.emit() diff --git a/client/network/Logout.gd b/client/network/Logout.gd new file mode 100644 index 0000000..c38208b --- /dev/null +++ b/client/network/Logout.gd @@ -0,0 +1,12 @@ +extends PacketHandler + +signal success + +func handle_packet(_data: PackedByteArray): + logout() + success.emit() + +func logout(): + Global.auth_token = "" + Global.account_id = "" + Global.player = null \ No newline at end of file diff --git a/client/network/Ping.gd b/client/network/Ping.gd index 39a7f79..64b33a8 100644 --- a/client/network/Ping.gd +++ b/client/network/Ping.gd @@ -10,7 +10,7 @@ var history: Array[float] = [] func _init(): history.resize(HISTORY_SIZE) -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var id := data[0] var ping := get_time() - history[id] changed.emit(ping) diff --git a/client/network/PlayerAdd.gd b/client/network/PlayerAdd.gd index 93de719..f214d7d 100644 --- a/client/network/PlayerAdd.gd +++ b/client/network/PlayerAdd.gd @@ -7,7 +7,7 @@ signal main_player_spawned(player: Player) func _ready(): assert(player_scene) -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var buffer := StreamPeerBuffer.new() buffer.data_array = data @@ -24,19 +24,29 @@ func handle_packet(data: PackedByteArray, _peer: PacketPeer): var player := spawn_player(player_id) player.position = server_position player.set_character_name(player_name) + Global.players.add(player) func spawn_player(id: String) -> Player: - var player = player_scene.instantiate() + var player: Player + + if Global.players.has(id): + player = Global.players.get_player(id) + + if id == Global.account_id: + Global.player = player + + return player + + player = player_scene.instantiate() player.id = id if id == Global.account_id: - Global.player = player player.controller = PlayerController.new() + Global.player = player main_player_spawned.emit(player) else: player.controller = ProxyController.new() player.controller.character = player player.add_child(player.controller) - %Players.add(player) return player diff --git a/client/network/PlayerJump.gd b/client/network/PlayerJump.gd index c1448f7..027f56e 100644 --- a/client/network/PlayerJump.gd +++ b/client/network/PlayerJump.gd @@ -11,7 +11,7 @@ func on_jump(): buffer.put_8(PacketHandler.Packet.PLAYER_JUMP) %Client.send(buffer.data_array) -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var player_id := data.get_string_from_ascii() var player := Global.players.get_player(player_id) player.jump() diff --git a/client/network/PlayerMove.gd b/client/network/PlayerMove.gd index ab628c0..710fcc0 100644 --- a/client/network/PlayerMove.gd +++ b/client/network/PlayerMove.gd @@ -5,7 +5,7 @@ extends PacketHandler var last_sent := Time.get_ticks_msec() var last_sent_position := Vector3.ZERO -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var buffer := StreamPeerBuffer.new() buffer.data_array = data diff --git a/client/network/PlayerRemove.gd b/client/network/PlayerRemove.gd index ddd2915..0b3ffd2 100644 --- a/client/network/PlayerRemove.gd +++ b/client/network/PlayerRemove.gd @@ -1,6 +1,6 @@ extends PacketHandler -func handle_packet(data: PackedByteArray, _peer: PacketPeer): +func handle_packet(data: PackedByteArray): var player_id := data.get_string_from_ascii() print("[%s] Remove player: %s" % [Global.username, player_id]) - %Players.remove(player_id) \ No newline at end of file + Global.players.remove(player_id) \ No newline at end of file diff --git a/client/network/shared/NetworkNode.gd b/client/network/shared/NetworkNode.gd index 581d393..0803d6c 100644 --- a/client/network/shared/NetworkNode.gd +++ b/client/network/shared/NetworkNode.gd @@ -13,7 +13,7 @@ func get_handler(index: int) -> PacketHandler: func set_handler(index: int, node: PacketHandler): handlers[index] = node -func handle_packet(packet: PackedByteArray, peer: PacketPeer): +func handle_packet(packet: PackedByteArray): var type := packet.decode_u8(0) var handler := get_handler(type) @@ -21,5 +21,5 @@ func handle_packet(packet: PackedByteArray, peer: PacketPeer): push_warning("Unknown packet type %d" % type) return - handler.handle_packet(packet.slice(1), peer) + handler.handle_packet(packet.slice(1)) download += packet.size() diff --git a/client/network/shared/PacketHandler.gd b/client/network/shared/PacketHandler.gd index 95efe59..f26a75c 100644 --- a/client/network/shared/PacketHandler.gd +++ b/client/network/shared/PacketHandler.gd @@ -16,5 +16,5 @@ enum Packet { @export var packet_type: Packet -func handle_packet(_data: PackedByteArray, _peer: PacketPeer): +func handle_packet(_data: PackedByteArray): pass \ No newline at end of file diff --git a/client/player/controller/Controller.gd b/client/player/controller/Controller.gd new file mode 100644 index 0000000..d39ae8d --- /dev/null +++ b/client/player/controller/Controller.gd @@ -0,0 +1,5 @@ +class_name Controller +extends Node + +## The character that we're controlling. +@export var character: Character \ No newline at end of file diff --git a/client/player/controller/PlayerController.gd b/client/player/controller/PlayerController.gd index 7ea2bb0..93cf924 100644 --- a/client/player/controller/PlayerController.gd +++ b/client/player/controller/PlayerController.gd @@ -1,8 +1,5 @@ class_name PlayerController -extends Node - -## The character that we're controlling. -@export var character: Character +extends Controller var move: Vector2 diff --git a/client/player/controller/ProxyController.gd b/client/player/controller/ProxyController.gd index 95f03a0..f7653bf 100644 --- a/client/player/controller/ProxyController.gd +++ b/client/player/controller/ProxyController.gd @@ -1,20 +1,19 @@ class_name ProxyController -extends Node - -## The character that we're controlling. -@export var character: Character +extends Controller +## The authoritative position on the server. var server_position: Vector3 func _ready(): + print(character.name, ".old: ", server_position) server_position = character.position + print(character.name, ".new: ", server_position) func _process(_delta): var move := server_position - character.position move.y = 0.0 if move.length_squared() < 0.01: - # character.position = server_position character.direction = Vector3.ZERO return diff --git a/client/ui/UI.gd b/client/ui/UI.gd index 5cf1b12..bcf98db 100644 --- a/client/ui/UI.gd +++ b/client/ui/UI.gd @@ -7,9 +7,13 @@ var ping_changed: Signal var download_changed: Signal var upload_changed: Signal var message_received: Signal +var login: Signal +var logout: Signal func _enter_tree(): ping_changed = %Ping.changed download_changed = %Client.download_changed upload_changed = %Client.upload_changed message_received = %Chat.message_received + login = %Login.success + logout = %Logout.success diff --git a/client/ui/UI.tscn b/client/ui/UI.tscn index 12b4068..9af9b5b 100644 --- a/client/ui/UI.tscn +++ b/client/ui/UI.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=11 format=3 uid="uid://dagn5bf7ou3sd"] +[gd_scene load_steps=12 format=3 uid="uid://dagn5bf7ou3sd"] [ext_resource type="PackedScene" uid="uid://cch67vqpsmtej" path="res://ui/debug/DebugLabel.tscn" id="1_7s8uu"] [ext_resource type="Script" path="res://ui/UI.gd" id="1_l5b6o"] @@ -10,6 +10,7 @@ [ext_resource type="Script" path="res://ui/chat/ChatInput.gd" id="6_cg2h5"] [ext_resource type="Script" path="res://ui/debug/UploadLabel.gd" id="7_cfnpx"] [ext_resource type="Script" path="res://ui/debug/DownloadLabel.gd" id="8_ogt38"] +[ext_resource type="Script" path="res://ui/connect/ConnectPanel.gd" id="11_cwl0t"] [node name="UI" type="Control"] layout_mode = 3 @@ -102,6 +103,29 @@ layout_mode = 2 alignment = 2 script = ExtResource("5_8lm6a") +[node name="ConnectPanel" type="Panel" parent="CanvasLayer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("11_cwl0t") + +[node name="ConnectLabel" type="Label" parent="CanvasLayer/ConnectPanel"] +layout_mode = 1 +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -47.5 +offset_top = -10.0 +offset_right = 47.5 +offset_bottom = 10.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Connecting..." + [connection signal="focus_entered" from="CanvasLayer/BottomLeft/Chat/ChatInput" to="CanvasLayer/BottomLeft/Chat/ChatInput" method="_on_focus_entered"] [connection signal="focus_exited" from="CanvasLayer/BottomLeft/Chat/ChatInput" to="CanvasLayer/BottomLeft/Chat/ChatInput" method="_on_focus_exited"] [connection signal="text_submitted" from="CanvasLayer/BottomLeft/Chat/ChatInput" to="CanvasLayer/BottomLeft/Chat/ChatInput" method="_on_text_submitted"] diff --git a/client/ui/connect/ConnectPanel.gd b/client/ui/connect/ConnectPanel.gd new file mode 100644 index 0000000..477d5e0 --- /dev/null +++ b/client/ui/connect/ConnectPanel.gd @@ -0,0 +1,11 @@ +extends Panel + +func _ready(): + owner.login.connect(on_login) + owner.logout.connect(on_logout) + +func on_login(): + hide() + +func on_logout(): + show() diff --git a/client/world/PlayerManager.gd b/client/world/PlayerManager.gd index dfe1dbd..10e984c 100644 --- a/client/world/PlayerManager.gd +++ b/client/world/PlayerManager.gd @@ -7,12 +7,18 @@ func _ready(): Global.players = self func add(player: Player): + if has(player.id): + return + add_child(player) players[player.id] = player func get_player(id: String) -> Player: return players[id] as Player +func has(id: String) -> bool: + return players.has(id) + func remove(id: String): if !players.has(id): return diff --git a/server/game/Chat.go b/server/game/Chat.go index 0e82329..f739a48 100644 --- a/server/game/Chat.go +++ b/server/game/Chat.go @@ -2,6 +2,7 @@ package game import ( "net" + "strings" ) // Chat is used for chat messages. @@ -12,9 +13,30 @@ func (game *Game) Chat(data []byte, address *net.UDPAddr) error { return ErrUnknownAddress } + message := string(data) + + if game.ChatCommand(player, message) { + return nil + } + newData := []byte{} newData = AppendString(newData, player.ID) - newData = AppendStringBytes(newData, data) + newData = AppendString(newData, message) game.Broadcast(Chat, newData) return nil } + +// ChatCommand executes chat commands and returns true if it was one. +func (game *Game) ChatCommand(player *Player, message string) bool { + if strings.HasPrefix(message, "/") { + switch message { + case "/logout": + game.server.Send(Logout, nil, player.address) + game.players.Remove(player) + } + + return true + } + + return false +} diff --git a/server/game/Game.go b/server/game/Game.go index 22cd79f..a683fa1 100644 --- a/server/game/Game.go +++ b/server/game/Game.go @@ -10,7 +10,7 @@ import ( // Game represents the entire state of the game server. type Game struct { server *Server - router Router + router *Router players *PlayerManager } @@ -21,19 +21,26 @@ func New() *Game { players: NewPlayerManager(), } - game.router.Get(Ping, game.Ping) - game.router.Get(Login, game.Login) - game.router.Get(Move, game.Move) - game.router.Get(Jump, game.Jump) - game.router.Get(Chat, game.Chat) + game.router = NewRouter(game) return game } +// NewRouter creates a new router. +func NewRouter(game *Game) *Router { + router := &Router{} + router.Get(Ping, game.Ping) + router.Get(Login, game.Login) + router.Get(Move, game.Move) + router.Get(Jump, game.Jump) + router.Get(Chat, game.Chat) + return router +} + // Run starts all game systems. func (game *Game) Run() { - physics := time.NewTicker(20 * time.Millisecond) - statistics := time.NewTicker(time.Second) - clean := time.NewTicker(time.Second) + physics := time.NewTicker(20 * time.Millisecond).C + statistics := time.NewTicker(time.Second).C + clean := time.NewTicker(time.Second).C close := make(chan os.Signal, 1) signal.Notify(close, os.Interrupt) @@ -44,16 +51,16 @@ func (game *Game) Run() { case p := <-game.server.incoming: game.router.handle(p) - case <-physics.C: + case <-physics: game.players.Each(func(c *Player) { c.Tick() }) - case <-statistics.C: + case <-statistics: fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount()) game.server.ResetPacketCount() - case <-clean.C: + case <-clean: game.players.Clean(5 * time.Second) case <-close: diff --git a/server/game/Login.go b/server/game/Login.go index 2a140d2..cf96196 100644 --- a/server/game/Login.go +++ b/server/game/Login.go @@ -50,21 +50,14 @@ func (game *Game) Login(data []byte, address *net.UDPAddr) error { player = game.players.ByAccount(account.ID) if player != nil { - game.reconnect(player, address) - } else { - game.connect(account, address) + game.server.Send(Login, []byte{Failure}, address) + return ErrAlreadyLoggedIn } + game.connect(account, address) return nil } -func (game *Game) reconnect(player *Player, address *net.UDPAddr) { - player.KeepAlive() - game.players.ChangeAddress(player, address) - game.sendLoginSuccess(player) - player.OnConnect() -} - func (game *Game) connect(account *Account, address *net.UDPAddr) { player := NewPlayer(address, account, game) player.authToken = createAuthToken() diff --git a/server/game/Player.go b/server/game/Player.go index 15a92aa..47a9713 100644 --- a/server/game/Player.go +++ b/server/game/Player.go @@ -48,6 +48,7 @@ func (player *Player) State() []byte { return state } +// OnConnect is executed when a connection has been established. func (player *Player) OnConnect() { fmt.Printf("%s connected.\n", player.Name) players := player.game.players @@ -62,6 +63,7 @@ func (player *Player) OnConnect() { }) } +// OnDisconnect is executed when the connection has been closed. func (player *Player) OnDisconnect() { fmt.Printf("%s disconnected.\n", player.Name) player.game.BroadcastOthers(PlayerRemove, []byte(player.ID), player) diff --git a/server/game/PlayerManager.go b/server/game/PlayerManager.go index 7ea785f..f5bc436 100644 --- a/server/game/PlayerManager.go +++ b/server/game/PlayerManager.go @@ -2,92 +2,96 @@ package game import ( "net" - "sync" - "sync/atomic" "time" ) // PlayerManager keeps tracks of all players. type PlayerManager struct { - players sync.Map - accounts sync.Map - count atomic.Int64 + players map[string]*Player + accounts map[string]*Player + count int } // NewPlayerManager creates a new player manager. func NewPlayerManager() *PlayerManager { - return &PlayerManager{} -} - -func (m *PlayerManager) Clean(timeout time.Duration) { - now := time.Now() - - m.players.Range(func(key, value interface{}) bool { - player := value.(*Player) - - if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) { - m.players.Delete(key) - m.accounts.Delete(player.ID) - m.count.Add(-1) - player.OnDisconnect() - } - - return true - }) + return &PlayerManager{ + players: map[string]*Player{}, + accounts: map[string]*Player{}, + } } // Add adds a new player. func (m *PlayerManager) Add(player *Player) { - m.players.Store(player.address.String(), player) - m.accounts.Store(player.ID, player) - m.count.Add(1) + m.players[player.address.String()] = player + m.accounts[player.ID] = player + m.count++ player.OnConnect() } // ChangeAddress changes the address of a player. func (m *PlayerManager) ChangeAddress(player *Player, address *net.UDPAddr) { - m.players.Delete(player.address.String()) + delete(m.players, player.address.String()) player.address = address - m.players.Store(player.address.String(), player) + m.players[player.address.String()] = player +} + +// Clean checks for players who haven't responded in the timeout duration and disconnects them. +func (m *PlayerManager) Clean(timeout time.Duration) { + now := time.Now() + + for _, player := range m.players { + if player.lastPacket.IsZero() || now.Before(player.lastPacket.Add(timeout)) { + continue + } + + m.Remove(player) + } +} + +// Remove removes a player. +func (m *PlayerManager) Remove(player *Player) { + delete(m.players, player.address.String()) + delete(m.accounts, player.ID) + m.count-- + player.OnDisconnect() } // Contains tells you whether the address is already a registered client. func (m *PlayerManager) Contains(address *net.UDPAddr) bool { - _, exists := m.players.Load(address.String()) + _, exists := m.players[address.String()] return exists } // Count returns the number of clients. func (m *PlayerManager) Count() int { - return int(m.count.Load()) + return m.count } // Each calls the callback function for each client. func (m *PlayerManager) Each(callback func(*Player)) { - m.players.Range(func(key, value any) bool { - callback(value.(*Player)) - return true - }) + for _, player := range m.players { + callback(player) + } } // ByAddress returns an existing client for the requested address. func (m *PlayerManager) ByAddress(address *net.UDPAddr) *Player { - obj, exists := m.players.Load(address.String()) + player, exists := m.players[address.String()] if !exists { return nil } - return obj.(*Player) + return player } // ByAccount returns the player with the given account ID. func (m *PlayerManager) ByAccount(id string) *Player { - obj, exists := m.accounts.Load(id) + player, exists := m.accounts[id] if !exists { return nil } - return obj.(*Player) + return player }