From 2738895efea796fe67e311e35b4af64072114cc7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Wed, 24 Jan 2024 20:57:31 +0100 Subject: [PATCH] Improved network communication --- client/{world => }/Game.gd | 0 client/{world => }/Global.gd | 0 client/{world => camera}/Camera.gd | 0 .../{world => camera}/CameraAttributes.tres | 0 .../character/{ => health}/HealthComponent.gd | 0 .../{ => health}/HealthComponent.tscn | 2 +- client/enemy/{ => slime}/Slime.blend.import | 6 +- client/enemy/{ => slime}/Slime.tscn | 4 +- client/network/PlayerState.gd | 28 ++++ client/network/Spawn.gd | 14 -- client/{ => network}/shared/Account.gd | 0 client/{ => network}/shared/NetworkNode.gd | 0 client/{ => network}/shared/Packet.gd | 4 +- client/{ => network}/shared/PacketHandler.gd | 0 client/player/Player.tscn | 4 +- .../{ => controller}/PlayerController.gd | 0 client/project.godot | 2 +- client/world/Client.tscn | 43 +++-- client/world/World.tscn | 28 ---- server/Client.gd | 15 -- server/Database.gd | 12 -- server/Server.gd | 98 ------------ server/Server.tscn | 27 ---- server/core/Handler.go | 6 + server/core/Server.go | 103 ++++++++++++ server/game/Client.go | 31 ++++ server/game/Login.go | 72 +++++++++ server/game/Manager.go | 68 ++++++++ server/game/Ping.go | 18 +++ server/game/Player.go | 6 + server/game/Vector3.go | 7 + {tools/server => server}/go.mod | 0 server/grid/Cell.gd | 3 - server/grid/Grid.gd | 31 ---- server/handler/Login.gd | 65 -------- server/handler/Ping.gd | 13 -- server/main.go | 32 ++++ server/packet/packet.go | 8 + server/player/Player.gd | 24 --- server/player/Player.tscn | 14 -- server/project.godot | 38 ----- {tools/stresstest => stresstest}/go.mod | 0 {tools/stresstest => stresstest}/main.go | 7 +- tools/server/core/Client.go | 17 -- tools/server/core/Server.go | 149 ------------------ tools/server/login.go | 20 --- tools/server/main.go | 14 -- tools/server/packet/types.go | 8 - tools/server/ping.go | 11 -- 49 files changed, 427 insertions(+), 625 deletions(-) rename client/{world => }/Game.gd (100%) rename client/{world => }/Global.gd (100%) rename client/{world => camera}/Camera.gd (100%) rename client/{world => camera}/CameraAttributes.tres (100%) rename client/character/{ => health}/HealthComponent.gd (100%) rename client/character/{ => health}/HealthComponent.tscn (59%) rename client/enemy/{ => slime}/Slime.blend.import (84%) rename client/enemy/{ => slime}/Slime.tscn (94%) create mode 100644 client/network/PlayerState.gd delete mode 100644 client/network/Spawn.gd rename client/{ => network}/shared/Account.gd (100%) rename client/{ => network}/shared/NetworkNode.gd (100%) rename client/{ => network}/shared/Packet.gd (62%) rename client/{ => network}/shared/PacketHandler.gd (100%) rename client/player/{ => controller}/PlayerController.gd (100%) delete mode 100644 client/world/World.tscn delete mode 100644 server/Client.gd delete mode 100644 server/Database.gd delete mode 100644 server/Server.gd delete mode 100644 server/Server.tscn create mode 100644 server/core/Handler.go create mode 100644 server/core/Server.go create mode 100644 server/game/Client.go create mode 100644 server/game/Login.go create mode 100644 server/game/Manager.go create mode 100644 server/game/Ping.go create mode 100644 server/game/Player.go create mode 100644 server/game/Vector3.go rename {tools/server => server}/go.mod (100%) delete mode 100644 server/grid/Cell.gd delete mode 100644 server/grid/Grid.gd delete mode 100644 server/handler/Login.gd delete mode 100644 server/handler/Ping.gd create mode 100644 server/main.go create mode 100644 server/packet/packet.go delete mode 100644 server/player/Player.gd delete mode 100644 server/player/Player.tscn delete mode 100644 server/project.godot rename {tools/stresstest => stresstest}/go.mod (100%) rename {tools/stresstest => stresstest}/main.go (88%) delete mode 100644 tools/server/core/Client.go delete mode 100644 tools/server/core/Server.go delete mode 100644 tools/server/login.go delete mode 100644 tools/server/main.go delete mode 100644 tools/server/packet/types.go delete mode 100644 tools/server/ping.go diff --git a/client/world/Game.gd b/client/Game.gd similarity index 100% rename from client/world/Game.gd rename to client/Game.gd diff --git a/client/world/Global.gd b/client/Global.gd similarity index 100% rename from client/world/Global.gd rename to client/Global.gd diff --git a/client/world/Camera.gd b/client/camera/Camera.gd similarity index 100% rename from client/world/Camera.gd rename to client/camera/Camera.gd diff --git a/client/world/CameraAttributes.tres b/client/camera/CameraAttributes.tres similarity index 100% rename from client/world/CameraAttributes.tres rename to client/camera/CameraAttributes.tres diff --git a/client/character/HealthComponent.gd b/client/character/health/HealthComponent.gd similarity index 100% rename from client/character/HealthComponent.gd rename to client/character/health/HealthComponent.gd diff --git a/client/character/HealthComponent.tscn b/client/character/health/HealthComponent.tscn similarity index 59% rename from client/character/HealthComponent.tscn rename to client/character/health/HealthComponent.tscn index c1cd8f4..d1fb4ab 100644 --- a/client/character/HealthComponent.tscn +++ b/client/character/health/HealthComponent.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://2bbycjulf00g"] -[ext_resource type="Script" path="res://character/HealthComponent.gd" id="1_403dm"] +[ext_resource type="Script" path="res://character/health/HealthComponent.gd" id="1_403dm"] [node name="HealthComponent" type="Node"] script = ExtResource("1_403dm") diff --git a/client/enemy/Slime.blend.import b/client/enemy/slime/Slime.blend.import similarity index 84% rename from client/enemy/Slime.blend.import rename to client/enemy/slime/Slime.blend.import index 5eab4ae..e56ce91 100644 --- a/client/enemy/Slime.blend.import +++ b/client/enemy/slime/Slime.blend.import @@ -4,12 +4,12 @@ importer="scene" importer_version=1 type="PackedScene" uid="uid://b358op5h1y83m" -path="res://.godot/imported/Slime.blend-8da460d18e3c1f7085d49df94a74fec6.scn" +path="res://.godot/imported/Slime.blend-ea5047bc644fde7498b554f0822da73a.scn" [deps] -source_file="res://enemy/Slime.blend" -dest_files=["res://.godot/imported/Slime.blend-8da460d18e3c1f7085d49df94a74fec6.scn"] +source_file="res://enemy/slime/Slime.blend" +dest_files=["res://.godot/imported/Slime.blend-ea5047bc644fde7498b554f0822da73a.scn"] [params] diff --git a/client/enemy/Slime.tscn b/client/enemy/slime/Slime.tscn similarity index 94% rename from client/enemy/Slime.tscn rename to client/enemy/slime/Slime.tscn index 40cfb94..7f0f104 100644 --- a/client/enemy/Slime.tscn +++ b/client/enemy/slime/Slime.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=8 format=3 uid="uid://cb2t7bvvf3gwh"] -[ext_resource type="PackedScene" uid="uid://b358op5h1y83m" path="res://enemy/Slime.blend" id="1_1h1hj"] +[ext_resource type="PackedScene" uid="uid://b358op5h1y83m" path="res://enemy/slime/Slime.blend" id="1_1h1hj"] [ext_resource type="Script" path="res://enemy/Enemy.gd" id="1_r5888"] -[ext_resource type="PackedScene" uid="uid://2bbycjulf00g" path="res://character/HealthComponent.tscn" id="2_fsqxc"] +[ext_resource type="PackedScene" uid="uid://2bbycjulf00g" path="res://character/health/HealthComponent.tscn" id="2_fsqxc"] [sub_resource type="BoxShape3D" id="BoxShape3D_x1ppt"] size = Vector3(0.7, 0.6, 0.7) diff --git a/client/network/PlayerState.gd b/client/network/PlayerState.gd new file mode 100644 index 0000000..3d6c363 --- /dev/null +++ b/client/network/PlayerState.gd @@ -0,0 +1,28 @@ +extends PacketHandler + +@export var player_scene: PackedScene + +func _ready(): + assert(player_scene) + %Client.set_handler(Packet.PLAYER_STATE, self) + +func handle_packet(data: PackedByteArray, _peer: PacketPeer): + var player_name := data.get_string_from_utf8() + print(player_name) + + var position_data := data.slice(player_name.length() + 1) + var server_position := Vector3() + server_position.x = position_data.decode_float(0) + server_position.y = position_data.decode_float(4) + server_position.z = position_data.decode_float(8) + print(server_position) + + var player := spawn_player() + player.name = player_name + player.position = server_position + Global.player = player + +func spawn_player() -> Player: + var player = player_scene.instantiate() + %Players.add_child(player) + return player diff --git a/client/network/Spawn.gd b/client/network/Spawn.gd deleted file mode 100644 index 8ce6bed..0000000 --- a/client/network/Spawn.gd +++ /dev/null @@ -1,14 +0,0 @@ -extends PacketHandler - -@export var player_scene: PackedScene - -func _ready(): - %Client.set_handler(Packet.SPAWN, self) - -func handle_packet(_data: PackedByteArray, _peer: PacketPeer): - Global.player = spawn_player() - -func spawn_player() -> Player: - var player = player_scene.instantiate() - %Players.add_child(player) - return player diff --git a/client/shared/Account.gd b/client/network/shared/Account.gd similarity index 100% rename from client/shared/Account.gd rename to client/network/shared/Account.gd diff --git a/client/shared/NetworkNode.gd b/client/network/shared/NetworkNode.gd similarity index 100% rename from client/shared/NetworkNode.gd rename to client/network/shared/NetworkNode.gd diff --git a/client/shared/Packet.gd b/client/network/shared/Packet.gd similarity index 62% rename from client/shared/Packet.gd rename to client/network/shared/Packet.gd index 3cc6eeb..004a4ee 100644 --- a/client/shared/Packet.gd +++ b/client/network/shared/Packet.gd @@ -4,6 +4,6 @@ enum { PING = 1, LOGIN = 2, LOGOUT = 3, - STATE = 10, - MOVE = 11, + PLAYER_STATE = 10, + PLAYER_MOVE = 11, } diff --git a/client/shared/PacketHandler.gd b/client/network/shared/PacketHandler.gd similarity index 100% rename from client/shared/PacketHandler.gd rename to client/network/shared/PacketHandler.gd diff --git a/client/player/Player.tscn b/client/player/Player.tscn index 0115a7c..fb7a007 100644 --- a/client/player/Player.tscn +++ b/client/player/Player.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=6 format=3 uid="uid://2lcnu3dy54lx"] [ext_resource type="Script" path="res://player/Player.gd" id="1_8gebs"] -[ext_resource type="PackedScene" uid="uid://2bbycjulf00g" path="res://character/HealthComponent.tscn" id="2_np5ag"] -[ext_resource type="Script" path="res://player/PlayerController.gd" id="3_oox5k"] +[ext_resource type="PackedScene" uid="uid://2bbycjulf00g" path="res://character/health/HealthComponent.tscn" id="2_np5ag"] +[ext_resource type="Script" path="res://player/controller/PlayerController.gd" id="3_oox5k"] [sub_resource type="PrismMesh" id="PrismMesh_y7abh"] size = Vector3(0.5, 1.6, 0.5) diff --git a/client/player/PlayerController.gd b/client/player/controller/PlayerController.gd similarity index 100% rename from client/player/PlayerController.gd rename to client/player/controller/PlayerController.gd diff --git a/client/project.godot b/client/project.godot index f9faaff..4dc6b78 100644 --- a/client/project.godot +++ b/client/project.godot @@ -17,7 +17,7 @@ config/icon="res://ui/icon.svg" [autoload] -Global="*res://world/Global.gd" +Global="*res://Global.gd" [display] diff --git a/client/world/Client.tscn b/client/world/Client.tscn index 69107d6..a58d00c 100644 --- a/client/world/Client.tscn +++ b/client/world/Client.tscn @@ -1,21 +1,23 @@ -[gd_scene load_steps=19 format=3 uid="uid://b40y7iuskv1ar"] +[gd_scene load_steps=21 format=3 uid="uid://b40y7iuskv1ar"] -[ext_resource type="Script" path="res://world/Game.gd" id="1_pux6q"] +[ext_resource type="Script" path="res://Game.gd" id="1_pux6q"] [ext_resource type="Script" path="res://network/Client.gd" id="2_1ofik"] [ext_resource type="Script" path="res://network/Ping.gd" id="3_4h5la"] [ext_resource type="Script" path="res://network/Login.gd" id="4_k8n1i"] -[ext_resource type="Script" path="res://network/Spawn.gd" id="5_7n5ie"] [ext_resource type="PackedScene" uid="uid://2lcnu3dy54lx" path="res://player/Player.tscn" id="5_22pku"] -[ext_resource type="PackedScene" uid="uid://mgyg01l21t0j" path="res://world/World.tscn" id="7_fb4vs"] +[ext_resource type="Script" path="res://network/PlayerState.gd" id="5_wttxq"] [ext_resource type="Environment" uid="uid://dixa0yso2s1u3" path="res://world/Environment.tres" id="8_5uta8"] [ext_resource type="PackedScene" uid="uid://cch67vqpsmtej" path="res://ui/debug/DebugLabel.tscn" id="8_jr2jn"] [ext_resource type="Script" path="res://ui/debug/FPSLabel.gd" id="9_0imyg"] -[ext_resource type="CameraAttributesPractical" uid="uid://b835orxyqq6w5" path="res://world/CameraAttributes.tres" id="9_w4cdu"] +[ext_resource type="CameraAttributesPractical" uid="uid://b835orxyqq6w5" path="res://camera/CameraAttributes.tres" id="9_w4cdu"] +[ext_resource type="PackedScene" uid="uid://tgmbtt7u172g" path="res://world/Arena.blend" id="10_cje7b"] [ext_resource type="Script" path="res://world/Sun.gd" id="10_mlmor"] [ext_resource type="Script" path="res://ui/debug/PingLabel.gd" id="10_pu6sd"] [ext_resource type="Script" path="res://ui/debug/PositionLabel.gd" id="11_k0017"] +[ext_resource type="PackedScene" uid="uid://hnn0n1xc2qt7" path="res://world/Tree.blend" id="11_wlyv1"] +[ext_resource type="PackedScene" uid="uid://cb2t7bvvf3gwh" path="res://enemy/slime/Slime.tscn" id="12_6yrwn"] [ext_resource type="Script" path="res://ui/debug/VelocityLabel.gd" id="12_dgr41"] -[ext_resource type="Script" path="res://world/Camera.gd" id="13_y4waa"] +[ext_resource type="Script" path="res://camera/Camera.gd" id="13_y4waa"] [ext_resource type="Material" uid="uid://ddy5gkw0k16dq" path="res://shader/OutlineMaterial.tres" id="14_hobco"] [sub_resource type="QuadMesh" id="QuadMesh_7yiqd"] @@ -41,17 +43,17 @@ autostart = true script = ExtResource("4_k8n1i") [node name="Timer" type="Timer" parent="Client/Login"] -wait_time = 10.0 +wait_time = 5.0 autostart = true -[node name="Spawn" type="Node" parent="Client"] -script = ExtResource("5_7n5ie") +[node name="PlayerState" type="Node" parent="Client"] +script = ExtResource("5_wttxq") player_scene = ExtResource("5_22pku") [node name="Players" type="Node3D" parent="."] unique_name_in_owner = true -[node name="World" parent="." instance=ExtResource("7_fb4vs")] +[node name="World" type="Node3D" parent="."] [node name="Sun" type="DirectionalLight3D" parent="World"] transform = Transform3D(0.984808, 0.122788, -0.122788, 0, 0.707107, 0.707107, 0.173648, -0.696364, 0.696364, 0, 10, 0) @@ -63,6 +65,27 @@ script = ExtResource("10_mlmor") environment = ExtResource("8_5uta8") camera_attributes = ExtResource("9_w4cdu") +[node name="Arena" parent="World" instance=ExtResource("10_cje7b")] + +[node name="Trees" type="Node3D" parent="World"] + +[node name="Tree" parent="World/Trees" instance=ExtResource("11_wlyv1")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, -4.64839) + +[node name="Tree2" parent="World/Trees" instance=ExtResource("11_wlyv1")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, 5.35161) + +[node name="Enemies" type="Node3D" parent="World"] + +[node name="Slime" parent="World/Enemies" instance=ExtResource("12_6yrwn")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.53558, 1.28057, -3.79687) + +[node name="Slime2" parent="World/Enemies" instance=ExtResource("12_6yrwn")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.00829, 1.28057, -1.95247) + +[node name="Slime3" parent="World/Enemies" instance=ExtResource("12_6yrwn")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.53558, 1.28057, -0.306177) + [node name="UI" type="Control" parent="."] layout_mode = 3 anchors_preset = 0 diff --git a/client/world/World.tscn b/client/world/World.tscn deleted file mode 100644 index bf15c68..0000000 --- a/client/world/World.tscn +++ /dev/null @@ -1,28 +0,0 @@ -[gd_scene load_steps=4 format=3 uid="uid://mgyg01l21t0j"] - -[ext_resource type="PackedScene" uid="uid://tgmbtt7u172g" path="res://world/Arena.blend" id="1_1sh4g"] -[ext_resource type="PackedScene" uid="uid://hnn0n1xc2qt7" path="res://world/Tree.blend" id="2_lunhk"] -[ext_resource type="PackedScene" uid="uid://cb2t7bvvf3gwh" path="res://enemy/Slime.tscn" id="3_uuodv"] - -[node name="World" type="Node3D"] - -[node name="Arena" parent="." instance=ExtResource("1_1sh4g")] - -[node name="Trees" type="Node3D" parent="."] - -[node name="Tree" parent="Trees" instance=ExtResource("2_lunhk")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, -4.64839) - -[node name="Tree2" parent="Trees" instance=ExtResource("2_lunhk")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.11323, 0, 5.35161) - -[node name="Enemies" type="Node3D" parent="."] - -[node name="Slime" parent="Enemies" instance=ExtResource("3_uuodv")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.53558, 1.28057, -3.79687) - -[node name="Slime2" parent="Enemies" instance=ExtResource("3_uuodv")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.00829, 1.28057, -1.95247) - -[node name="Slime3" parent="Enemies" instance=ExtResource("3_uuodv")] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.53558, 1.28057, -0.306177) diff --git a/server/Client.gd b/server/Client.gd deleted file mode 100644 index 37d33b6..0000000 --- a/server/Client.gd +++ /dev/null @@ -1,15 +0,0 @@ -class_name Client - -var peer: PacketPeerUDP -var address: String -var last_packet: int -var auth_token: String -var account: Account -var player: Player - -func _init(p: PacketPeerUDP, a: String): - peer = p - address = a - -func is_logged_in() -> bool: - return auth_token != "" \ No newline at end of file diff --git a/server/Database.gd b/server/Database.gd deleted file mode 100644 index 82e19b6..0000000 --- a/server/Database.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends Node - -var accounts := { - "user1": Account.new("user1", "password"), - "user2": Account.new("user2", "password"), -} - -func get_account(username: String): - if !accounts.has(username): - return null - - return accounts[username] \ No newline at end of file diff --git a/server/Server.gd b/server/Server.gd deleted file mode 100644 index a370729..0000000 --- a/server/Server.gd +++ /dev/null @@ -1,98 +0,0 @@ -class_name Server -extends NetworkNode - -## Port number. -@export var port := 4242 - -## Timeout in milliseconds. -@export var timeout := 5000 - -## Maximum number of pending connections. -@export var max_pending_connections := 4096 - -var server := UDPServer.new() -var clients := {} -var packet_count := 0 -var now := 0 - -func _init(): - super._init() - server.set_max_pending_connections(max_pending_connections) - server.listen(port) - Performance.add_custom_monitor("Server/Clients", get_client_count) - Performance.add_custom_monitor("Server/Packets", get_packet_count) - -func _process(_delta): - server.poll() - -func _physics_process(_delta): - now = Time.get_ticks_msec() - - # Accept new connections - while server.is_connection_available(): - var peer := server.take_connection() - peer_to_client(peer) - - # Process packets from clients - for address in clients: - var client = clients[address] - var peer = client.peer - - while peer.get_available_packet_count() > 0: - client.last_packet = now - var packet = peer.get_packet() - handle_packet(packet, peer) - packet_count += 1 - - # Disconnect - for address in clients.keys(): - var client = clients[address] - var last_packet_time = client.last_packet - - if now - last_packet_time > timeout: - peer_disconnected(client) - clients.erase(address) - -func broadcast(packet: PackedByteArray): - for address in clients: - clients[address].peer.put_packet(packet) - -func broadcast_others(packet: PackedByteArray, exclude: Client): - for address in clients: - var client = clients[address] - - if client == exclude: - continue - - client.peer.put_packet(packet) - -func get_client_count() -> int: - return clients.size() - -func get_packet_count() -> int: - var tmp := packet_count - packet_count = 0 - return tmp - -func peer_address(peer: PacketPeerUDP): - return "%s:%d" % [peer.get_packet_ip(), peer.get_packet_port()] - -func peer_connected(c: Client): - print("[%s] Connected." % c.address) - -func peer_disconnected(c: Client): - print("[%s] Disconnected." % c.address) - - if c.player: - c.player.queue_free() - -func peer_to_client(peer: PacketPeerUDP) -> Client: - var address = peer_address(peer) - - if !clients.has(address): - var client = Client.new(peer, address) - clients[address] = client - peer_connected(client) - return client - - return clients[address] diff --git a/server/Server.tscn b/server/Server.tscn deleted file mode 100644 index f42d153..0000000 --- a/server/Server.tscn +++ /dev/null @@ -1,27 +0,0 @@ -[gd_scene load_steps=6 format=3 uid="uid://b0e7n717sqeo6"] - -[ext_resource type="Script" path="res://Database.gd" id="1_58s18"] -[ext_resource type="Script" path="res://Server.gd" id="1_gwiwx"] -[ext_resource type="Script" path="res://handler/Ping.gd" id="3_l2i5g"] -[ext_resource type="Script" path="res://handler/Login.gd" id="4_7aotg"] -[ext_resource type="PackedScene" uid="uid://148x2hp1hjq0" path="res://player/Player.tscn" id="5_26wo2"] - -[node name="Main" type="Node"] - -[node name="Server" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("1_gwiwx") - -[node name="Database" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("1_58s18") - -[node name="Ping" type="Node" parent="."] -script = ExtResource("3_l2i5g") - -[node name="Login" type="Node" parent="."] -script = ExtResource("4_7aotg") -player_scene = ExtResource("5_26wo2") - -[node name="Players" type="Node3D" parent="."] -unique_name_in_owner = true diff --git a/server/core/Handler.go b/server/core/Handler.go new file mode 100644 index 0000000..fe0b335 --- /dev/null +++ b/server/core/Handler.go @@ -0,0 +1,6 @@ +package core + +import "net" + +// Handler is a byte code specific packet handler. +type Handler func([]byte, *net.UDPAddr, *Server) error diff --git a/server/core/Server.go b/server/core/Server.go new file mode 100644 index 0000000..224223c --- /dev/null +++ b/server/core/Server.go @@ -0,0 +1,103 @@ +package core + +import ( + "fmt" + "net" +) + +// Server represents a UDP server. +type Server struct { + handlers [256]Handler + socket *net.UDPConn + packetCount int +} + +// New creates a new server. +func New() *Server { + return &Server{} +} + +// SetHandler sets the handler for the given byte code. +func (s *Server) SetHandler(code byte, handler Handler) { + s.handlers[code] = handler +} + +// Run starts the server on the given port. +func (s *Server) Run(port int) { + s.socket = listen(port) + defer s.socket.Close() + + s.read() +} + +// PacketCount returns the number of processed packets. +func (s *Server) PacketCount() int { + return s.packetCount +} + +// ResetPacketCount resets the number of processed packets to zero. +func (s *Server) ResetPacketCount() { + s.packetCount = 0 +} + +// Send sends the data prefixed with the byte code to the client. +func (s *Server) Send(code byte, data []byte, address *net.UDPAddr) error { + data = append([]byte{code}, data...) + _, err := s.socket.WriteToUDP(data, address) + return err +} + +// listen creates a socket for the server and starts listening for incoming packets. +func listen(port int) *net.UDPConn { + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port)) + + if err != nil { + panic(err) + } + + connection, err := net.ListenUDP("udp", addr) + + if err != nil { + panic(err) + } + + return connection +} + +// read is a blocking call which will read incoming packets and handle them. +func (s *Server) read() { + buffer := make([]byte, 4096) + + for { + n, addr, err := s.socket.ReadFromUDP(buffer) + + if err != nil { + fmt.Println("Error reading from UDP connection:", err) + continue + } + + if n == 0 { + continue + } + + s.handle(buffer[:n], addr) + } +} + +// handle deals with an incoming packet. +func (s *Server) handle(data []byte, addr *net.UDPAddr) { + handler := s.handlers[data[0]] + + if handler == nil { + fmt.Printf("No callback registered for packet type %d\n", data[0]) + return + } + + err := handler(data[1:], addr, s) + + if err != nil { + fmt.Println(err) + } + + s.packetCount++ +} diff --git a/server/game/Client.go b/server/game/Client.go new file mode 100644 index 0000000..a15267a --- /dev/null +++ b/server/game/Client.go @@ -0,0 +1,31 @@ +package game + +import ( + "net" + "time" +) + +// Client represents a logged in client. +type Client struct { + address *net.UDPAddr + lastPacket time.Time + authToken string + player Player +} + +// NewClient creates a new client. +func NewClient(address *net.UDPAddr) *Client { + return &Client{ + address: address, + } +} + +// String shows the client address. +func (c *Client) String() string { + return c.address.String() +} + +// KeepAlive sets the last packet time to now. +func (c *Client) KeepAlive() { + c.lastPacket = time.Now() +} diff --git a/server/game/Login.go b/server/game/Login.go new file mode 100644 index 0000000..bf9e993 --- /dev/null +++ b/server/game/Login.go @@ -0,0 +1,72 @@ +package game + +import ( + "crypto/rand" + "encoding/base64" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math" + "net" + "server/core" + "server/packet" +) + +var ( + Clients = NewManager() + LoginSuccess = []byte{0} + LoginFailure = []byte{1} +) + +// Login checks the account credentials and gives a network peer access to an account. +func Login(data []byte, address *net.UDPAddr, server *core.Server) error { + var loginRequest [2]string + err := json.Unmarshal(data, &loginRequest) + + if err != nil { + server.Send(packet.Login, LoginFailure, address) + return err + } + + username := loginRequest[0] + password := loginRequest[1] + + if password != "password" { + server.Send(packet.Login, LoginFailure, address) + return errors.New("login failure") + } + + randomBytes := make([]byte, 32) + _, err = rand.Read(randomBytes) + + if err != nil { + server.Send(packet.Login, LoginFailure, address) + return err + } + + client := Clients.Get(address) + client.KeepAlive() + client.authToken = base64.StdEncoding.EncodeToString(randomBytes) + client.player.Name = username + client.player.Position.X = 5.0 + playerState := []byte(client.player.Name + "\u0000") + playerState = appendVector3(playerState, client.player.Position) + + server.Send(packet.Login, append(LoginSuccess, []byte(client.authToken)...), address) + server.Send(packet.PlayerState, playerState, address) + + fmt.Printf("%s logged in.\n", username) + return nil +} + +func appendVector3(data []byte, vector Vector3) []byte { + bits := math.Float32bits(vector.X) + data = binary.LittleEndian.AppendUint32(data, bits) + + bits = math.Float32bits(vector.Y) + data = binary.LittleEndian.AppendUint32(data, bits) + + bits = math.Float32bits(vector.Z) + return binary.LittleEndian.AppendUint32(data, bits) +} diff --git a/server/game/Manager.go b/server/game/Manager.go new file mode 100644 index 0000000..79956e8 --- /dev/null +++ b/server/game/Manager.go @@ -0,0 +1,68 @@ +package game + +import ( + "net" + "sync" + "sync/atomic" + "time" +) + +// Manager keeps tracks of all player connections. +type Manager struct { + clients sync.Map + count atomic.Int64 +} + +// NewManager creates a new manager. +func NewManager() *Manager { + manager := &Manager{} + timeout := 5 * time.Second + interval := time.Second + + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for range ticker.C { + now := time.Now() + + manager.clients.Range(func(key, value interface{}) bool { + item := value.(*Client) + + if !item.lastPacket.IsZero() && now.After(item.lastPacket.Add(timeout)) { + manager.clients.Delete(key) + manager.count.Add(-1) + } + + return true + }) + } + }() + + return manager +} + +// Get either returns a new or existing client for the requested address. +func (m *Manager) Get(addr *net.UDPAddr) *Client { + obj, exists := m.clients.Load(addr.String()) + + if exists { + return obj.(*Client) + } + + client := NewClient(addr) + m.clients.Store(addr.String(), client) + m.count.Add(1) + return client +} + +// Contains tells you whether the address is already a registered client. +func (m *Manager) Contains(addr *net.UDPAddr) bool { + _, exists := m.clients.Load(addr.String()) + return exists +} + +// Count returns the number of clients. +func (m *Manager) Count() int { + return int(m.count.Load()) +} diff --git a/server/game/Ping.go b/server/game/Ping.go new file mode 100644 index 0000000..a0946c1 --- /dev/null +++ b/server/game/Ping.go @@ -0,0 +1,18 @@ +package game + +import ( + "net" + "server/core" + "server/packet" +) + +// Ping is used as a heartbeat and latency check. +func Ping(data []byte, address *net.UDPAddr, server *core.Server) error { + server.Send(packet.Ping, data, address) + + if Clients.Contains(address) { + Clients.Get(address).KeepAlive() + } + + return nil +} diff --git a/server/game/Player.go b/server/game/Player.go new file mode 100644 index 0000000..ea1f930 --- /dev/null +++ b/server/game/Player.go @@ -0,0 +1,6 @@ +package game + +type Player struct { + Name string `json:"name"` + Position Vector3 `json:"position"` +} diff --git a/server/game/Vector3.go b/server/game/Vector3.go new file mode 100644 index 0000000..fec244b --- /dev/null +++ b/server/game/Vector3.go @@ -0,0 +1,7 @@ +package game + +type Vector3 struct { + X float32 `json:"x"` + Y float32 `json:"y"` + Z float32 `json:"z"` +} diff --git a/tools/server/go.mod b/server/go.mod similarity index 100% rename from tools/server/go.mod rename to server/go.mod diff --git a/server/grid/Cell.gd b/server/grid/Cell.gd deleted file mode 100644 index c5bbbb6..0000000 --- a/server/grid/Cell.gd +++ /dev/null @@ -1,3 +0,0 @@ -class_name Cell - -var player_list := [] as Array[Player] \ No newline at end of file diff --git a/server/grid/Grid.gd b/server/grid/Grid.gd deleted file mode 100644 index aaaad23..0000000 --- a/server/grid/Grid.gd +++ /dev/null @@ -1,31 +0,0 @@ -extends Node - -## Width of the grid in cells. -const width := 100 - -## Height of the grid in cells. -const height := 100 - -## The size of a single cell. -const cell_size := 10.0 - -var cells: Array[Cell] - -func _init(): - cells.resize(width * height) - - for i in cells.size(): - cells[i] = Cell.new() - -func notify_cell_changed(player: Player, old_pos: Vector2i, new_pos: Vector2i): - print(player.name, " cell changed! ", old_pos, " ", new_pos) - add_player(player, new_pos) - remove_player(player, old_pos) - -func add_player(player: Player, coords: Vector2i): - var cell := cells[coords.x + coords.y * height] - cell.player_list.append(player) - -func remove_player(player: Player, coords: Vector2i): - var cell := cells[coords.x + coords.y * height] - cell.player_list.erase(player) \ No newline at end of file diff --git a/server/handler/Login.gd b/server/handler/Login.gd deleted file mode 100644 index 39fdb72..0000000 --- a/server/handler/Login.gd +++ /dev/null @@ -1,65 +0,0 @@ -extends PacketHandler - -enum { - SUCCESS = 0, - FAIL = 1, -} - -## Player scene instantiated for each client. -@export var player_scene: PackedScene - -func _ready(): - %Server.set_handler(Packet.LOGIN, self) - -func handle_packet(data: PackedByteArray, peer: PacketPeer): - var client = %Server.peer_to_client(peer) - - if client.is_logged_in(): - return - - var data_string = data.get_string_from_utf8() - var login_request = JSON.parse_string(data_string) - - if login_request.size() < 2: - login_fail(peer) - return - - var username = login_request[0] - var password = login_request[1] - var account = %Database.get_account(username) - - if account == null || account.password != password: - login_fail(peer) - return - - client.auth_token = generate_auth_token() - client.account = account - spawn_player(client) - login_success(peer, client.auth_token) - -func spawn_player(client: Client): - var player := player_scene.instantiate() - player.client = client - player.server = %Server - player.name = client.address - client.player = player - %Players.add_child(player) - -func generate_auth_token() -> String: - var crypto = Crypto.new() - var buffer = crypto.generate_random_bytes(32) - return Marshalls.raw_to_base64(buffer) - -func login_success(peer: PacketPeer, auth_token: String): - var buffer := StreamPeerBuffer.new() - buffer.put_8(Packet.LOGIN) - buffer.put_8(SUCCESS) - buffer.put_data(auth_token.to_ascii_buffer()) - peer.put_packet(buffer.data_array) - -func login_fail(peer: PacketPeer): - var buffer := StreamPeerBuffer.new() - buffer.put_8(Packet.LOGIN) - buffer.put_8(FAIL) - peer.put_packet(buffer.data_array) - diff --git a/server/handler/Ping.gd b/server/handler/Ping.gd deleted file mode 100644 index b52a0fe..0000000 --- a/server/handler/Ping.gd +++ /dev/null @@ -1,13 +0,0 @@ -extends PacketHandler - -func _ready(): - %Server.set_handler(Packet.PING, self) - -func handle_packet(data: PackedByteArray, peer: PacketPeer): - var buffer := StreamPeerBuffer.new() - buffer.put_8(Packet.PING) - - if data.size() > 0: - buffer.put_8(data[0]) - - peer.put_packet(buffer.data_array) \ No newline at end of file diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..8ab29e8 --- /dev/null +++ b/server/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "server/core" + "server/game" + "server/packet" + "time" +) + +func main() { + // Init server + server := core.New() + server.SetHandler(packet.Ping, game.Ping) + server.SetHandler(packet.Login, game.Login) + + // Show statistics + last := time.Now() + + go func() { + for { + if time.Now().After(last.Add(time.Second)) { + fmt.Printf("%d packets per second, %d clients\n", server.PacketCount(), game.Clients.Count()) + last = time.Now() + server.ResetPacketCount() + } + } + }() + + // Start listening + server.Run(4242) +} diff --git a/server/packet/packet.go b/server/packet/packet.go new file mode 100644 index 0000000..b1585bc --- /dev/null +++ b/server/packet/packet.go @@ -0,0 +1,8 @@ +package packet + +const ( + Ping = 1 + Login = 2 + Logout = 3 + PlayerState = 10 +) diff --git a/server/player/Player.gd b/server/player/Player.gd deleted file mode 100644 index fe57d38..0000000 --- a/server/player/Player.gd +++ /dev/null @@ -1,24 +0,0 @@ -class_name Player -extends CharacterBody3D - -var client: Client -var server: Server -var cell: Vector2i - -func _ready(): - print("Server player spawned") - Grid.notify_cell_changed(self, cell, cell) - # var buffer := StreamPeerBuffer.new() - # buffer.put_8(Packet.STATE) - # server.broadcast(buffer.data_array) - -func _physics_process(_delta): - move_and_slide() - update_grid() - -func update_grid(): - var new_cell := Vector2i(int(position.x / Grid.cell_size), int(position.z / Grid.cell_size)) - - if new_cell != cell: - Grid.notify_cell_changed(self, cell, new_cell) - cell = new_cell \ No newline at end of file diff --git a/server/player/Player.tscn b/server/player/Player.tscn deleted file mode 100644 index ec87b75..0000000 --- a/server/player/Player.tscn +++ /dev/null @@ -1,14 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://148x2hp1hjq0"] - -[ext_resource type="Script" path="res://player/Player.gd" id="1_46xlc"] - -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_y8kaq"] -radius = 0.25 -height = 1.6 - -[node name="Player" type="CharacterBody3D"] -script = ExtResource("1_46xlc") - -[node name="Collision" type="CollisionShape3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8, 0) -shape = SubResource("CapsuleShape3D_y8kaq") diff --git a/server/project.godot b/server/project.godot deleted file mode 100644 index 6482ce4..0000000 --- a/server/project.godot +++ /dev/null @@ -1,38 +0,0 @@ -; Engine configuration file. -; It's best edited using the editor UI and not directly, -; since the parameters that go here are not all obvious. -; -; Format: -; [section] ; section goes between [] -; param=value ; assign values to parameters - -config_version=5 - -[application] - -config/name="Server" -run/main_scene="res://Server.tscn" -config/features=PackedStringArray("4.2", "Forward Plus") -run/max_fps=100 -run/low_processor_mode_sleep_usec=1 - -[audio] - -driver/driver="Dummy" - -[autoload] - -Grid="*res://grid/Grid.gd" - -[debug] - -settings/stdout/verbose_stdout=true - -[editor] - -run/main_run_args="--headless --text-driver Dummy" - -[physics] - -3d/run_on_separate_thread=true -common/physics_ticks_per_second=100 diff --git a/tools/stresstest/go.mod b/stresstest/go.mod similarity index 100% rename from tools/stresstest/go.mod rename to stresstest/go.mod diff --git a/tools/stresstest/main.go b/stresstest/main.go similarity index 88% rename from tools/stresstest/main.go rename to stresstest/main.go index 8493e9c..da932c4 100644 --- a/tools/stresstest/main.go +++ b/stresstest/main.go @@ -26,16 +26,19 @@ func udpClient(wg *sync.WaitGroup) { defer wg.Done() clientAddr, err := net.ResolveUDPAddr("udp", serverAddress) + if err != nil { fmt.Println("Error resolving UDP address:", err) return } conn, err := net.DialUDP("udp", nil, clientAddr) + if err != nil { fmt.Println("Error connecting to UDP server:", err) return } + defer conn.Close() for { @@ -51,14 +54,12 @@ func udpClient(wg *sync.WaitGroup) { } func main() { - var wg sync.WaitGroup + wg := sync.WaitGroup{} - // Start multiple UDP clients in separate goroutines for i := 0; i < *numClients; i++ { wg.Add(1) go udpClient(&wg) } - // Signal all client goroutines to stop wg.Wait() } diff --git a/tools/server/core/Client.go b/tools/server/core/Client.go deleted file mode 100644 index 7dbc6b6..0000000 --- a/tools/server/core/Client.go +++ /dev/null @@ -1,17 +0,0 @@ -package core - -import ( - "net" - "time" -) - -// Client represents a UDP client. -type Client struct { - address *net.UDPAddr - lastPacket time.Time -} - -// String shows the client address. -func (c *Client) String() string { - return c.address.String() -} diff --git a/tools/server/core/Server.go b/tools/server/core/Server.go deleted file mode 100644 index a265f58..0000000 --- a/tools/server/core/Server.go +++ /dev/null @@ -1,149 +0,0 @@ -package core - -import ( - "fmt" - "net" - "sync" - "sync/atomic" - "time" -) - -// Handler is a byte code specific packet handler. -type Handler func([]byte, *Client) - -// Server represents a UDP server. -type Server struct { - socket *net.UDPConn - handlers [256]Handler - clients sync.Map - count atomic.Int64 -} - -// New creates a new server. -func New() *Server { - timeout := 3 * time.Second - interval := time.Second - server := &Server{} - - go func() { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for range ticker.C { - now := time.Now() - - server.clients.Range(func(key, value interface{}) bool { - item := value.(*Client) - - if !item.lastPacket.IsZero() && now.After(item.lastPacket.Add(timeout)) { - server.clients.Delete(key) - server.count.Add(-1) - } - - return true - }) - } - }() - - return server -} - -// AddHandler adds the handler for the given byte code. -func (s *Server) AddHandler(code byte, handler Handler) { - s.handlers[code] = handler -} - -// Count returns the number of connected clients. -func (s *Server) Count() int { - return int(s.count.Load()) -} - -// Send sends the data prefixed with the byte code to the client. -func (s *Server) Send(code byte, data []byte, client *Client) { - data = append([]byte{code}, data...) - _, err := s.socket.WriteToUDP(data, client.address) - - if err != nil { - fmt.Println("Error sending response:", err) - } -} - -// Run starts the server on the given port. -func (s *Server) Run(port int) { - s.socket = listen(port) - defer s.socket.Close() - - s.read() -} - -// listen creates a socket for the server and starts listening for incoming packets. -func listen(port int) *net.UDPConn { - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port)) - - if err != nil { - panic(err) - } - - connection, err := net.ListenUDP("udp", addr) - - if err != nil { - panic(err) - } - - return connection -} - -// read is a blocking call which will read incoming packets and handle them. -func (s *Server) read() { - buffer := make([]byte, 4096) - - for { - n, addr, err := s.socket.ReadFromUDP(buffer) - - if err != nil { - fmt.Println("Error reading from UDP connection:", err) - continue - } - - if n == 0 { - continue - } - - s.handle(buffer[:n], addr) - } -} -var packets = 0 -// handle deals with an incoming packet. -func (s *Server) handle(data []byte, addr *net.UDPAddr) { - c := s.getClient(addr) - c.lastPacket = time.Now() - //fmt.Printf("Received %d bytes from %s: %s\n", len(data), c, string(data)) - - handler := s.handlers[data[0]] - - if handler == nil { - fmt.Printf("No callback registered for packet type %d\n", data[0]) - return - } - - handler(data[1:], c) - packets++ - fmt.Println(packets) -} - -// getClient either returns a new or existing client for the requested address. -func (s *Server) getClient(addr *net.UDPAddr) *Client { - obj, exists := s.clients.Load(addr.String()) - - if exists { - return obj.(*Client) - } - - client := &Client{ - address: addr, - } - - s.clients.Store(addr.String(), client) - s.count.Add(1) - return client -} diff --git a/tools/server/login.go b/tools/server/login.go deleted file mode 100644 index 0a2808c..0000000 --- a/tools/server/login.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "server/core" - "server/packet" -) - -// login checks the account credentials and gives a network peer access to an account. -func login(data []byte, client *core.Client) { - if !bytes.Equal(data, []byte("password")) { - fmt.Println("login failure") - server.Send(packet.LOGIN, []byte{1}, client) - return - } - - fmt.Println("login success") - server.Send(packet.LOGIN, []byte{0}, client) -} diff --git a/tools/server/main.go b/tools/server/main.go deleted file mode 100644 index bb8e65b..0000000 --- a/tools/server/main.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "server/core" - "server/packet" -) - -var server = core.New() - -func main() { - server.AddHandler(packet.PING, ping) - server.AddHandler(packet.LOGIN, login) - server.Run(4242) -} diff --git a/tools/server/packet/types.go b/tools/server/packet/types.go deleted file mode 100644 index f722be9..0000000 --- a/tools/server/packet/types.go +++ /dev/null @@ -1,8 +0,0 @@ -package packet - -const ( - PING = 1 - LOGIN = 2 - LOGOUT = 3 - MOVE = 10 -) diff --git a/tools/server/ping.go b/tools/server/ping.go deleted file mode 100644 index 0363abc..0000000 --- a/tools/server/ping.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "server/core" - "server/packet" -) - -// ping is used as a heartbeat and latency check. -func ping(data []byte, client *core.Client) { - server.Send(packet.PING, data, client) -}