Improved network communication
This commit is contained in:
parent
26c52a00b3
commit
2738895efe
@ -1,6 +1,6 @@
|
|||||||
[gd_scene load_steps=2 format=3 uid="uid://2bbycjulf00g"]
|
[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"]
|
[node name="HealthComponent" type="Node"]
|
||||||
script = ExtResource("1_403dm")
|
script = ExtResource("1_403dm")
|
@ -4,12 +4,12 @@ importer="scene"
|
|||||||
importer_version=1
|
importer_version=1
|
||||||
type="PackedScene"
|
type="PackedScene"
|
||||||
uid="uid://b358op5h1y83m"
|
uid="uid://b358op5h1y83m"
|
||||||
path="res://.godot/imported/Slime.blend-8da460d18e3c1f7085d49df94a74fec6.scn"
|
path="res://.godot/imported/Slime.blend-ea5047bc644fde7498b554f0822da73a.scn"
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://enemy/Slime.blend"
|
source_file="res://enemy/slime/Slime.blend"
|
||||||
dest_files=["res://.godot/imported/Slime.blend-8da460d18e3c1f7085d49df94a74fec6.scn"]
|
dest_files=["res://.godot/imported/Slime.blend-ea5047bc644fde7498b554f0822da73a.scn"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
[gd_scene load_steps=8 format=3 uid="uid://cb2t7bvvf3gwh"]
|
[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="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"]
|
[sub_resource type="BoxShape3D" id="BoxShape3D_x1ppt"]
|
||||||
size = Vector3(0.7, 0.6, 0.7)
|
size = Vector3(0.7, 0.6, 0.7)
|
28
client/network/PlayerState.gd
Normal file
28
client/network/PlayerState.gd
Normal file
@ -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
|
@ -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
|
|
@ -4,6 +4,6 @@ enum {
|
|||||||
PING = 1,
|
PING = 1,
|
||||||
LOGIN = 2,
|
LOGIN = 2,
|
||||||
LOGOUT = 3,
|
LOGOUT = 3,
|
||||||
STATE = 10,
|
PLAYER_STATE = 10,
|
||||||
MOVE = 11,
|
PLAYER_MOVE = 11,
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
[gd_scene load_steps=6 format=3 uid="uid://2lcnu3dy54lx"]
|
[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="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="PackedScene" uid="uid://2bbycjulf00g" path="res://character/health/HealthComponent.tscn" id="2_np5ag"]
|
||||||
[ext_resource type="Script" path="res://player/PlayerController.gd" id="3_oox5k"]
|
[ext_resource type="Script" path="res://player/controller/PlayerController.gd" id="3_oox5k"]
|
||||||
|
|
||||||
[sub_resource type="PrismMesh" id="PrismMesh_y7abh"]
|
[sub_resource type="PrismMesh" id="PrismMesh_y7abh"]
|
||||||
size = Vector3(0.5, 1.6, 0.5)
|
size = Vector3(0.5, 1.6, 0.5)
|
||||||
|
@ -17,7 +17,7 @@ config/icon="res://ui/icon.svg"
|
|||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
Global="*res://world/Global.gd"
|
Global="*res://Global.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
|
@ -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/Client.gd" id="2_1ofik"]
|
||||||
[ext_resource type="Script" path="res://network/Ping.gd" id="3_4h5la"]
|
[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/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://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="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="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="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://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/PingLabel.gd" id="10_pu6sd"]
|
||||||
[ext_resource type="Script" path="res://ui/debug/PositionLabel.gd" id="11_k0017"]
|
[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://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"]
|
[ext_resource type="Material" uid="uid://ddy5gkw0k16dq" path="res://shader/OutlineMaterial.tres" id="14_hobco"]
|
||||||
|
|
||||||
[sub_resource type="QuadMesh" id="QuadMesh_7yiqd"]
|
[sub_resource type="QuadMesh" id="QuadMesh_7yiqd"]
|
||||||
@ -41,17 +43,17 @@ autostart = true
|
|||||||
script = ExtResource("4_k8n1i")
|
script = ExtResource("4_k8n1i")
|
||||||
|
|
||||||
[node name="Timer" type="Timer" parent="Client/Login"]
|
[node name="Timer" type="Timer" parent="Client/Login"]
|
||||||
wait_time = 10.0
|
wait_time = 5.0
|
||||||
autostart = true
|
autostart = true
|
||||||
|
|
||||||
[node name="Spawn" type="Node" parent="Client"]
|
[node name="PlayerState" type="Node" parent="Client"]
|
||||||
script = ExtResource("5_7n5ie")
|
script = ExtResource("5_wttxq")
|
||||||
player_scene = ExtResource("5_22pku")
|
player_scene = ExtResource("5_22pku")
|
||||||
|
|
||||||
[node name="Players" type="Node3D" parent="."]
|
[node name="Players" type="Node3D" parent="."]
|
||||||
unique_name_in_owner = true
|
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"]
|
[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)
|
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")
|
environment = ExtResource("8_5uta8")
|
||||||
camera_attributes = ExtResource("9_w4cdu")
|
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="."]
|
[node name="UI" type="Control" parent="."]
|
||||||
layout_mode = 3
|
layout_mode = 3
|
||||||
anchors_preset = 0
|
anchors_preset = 0
|
||||||
|
@ -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)
|
|
@ -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 != ""
|
|
@ -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]
|
|
@ -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]
|
|
@ -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
|
|
6
server/core/Handler.go
Normal file
6
server/core/Handler.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
// Handler is a byte code specific packet handler.
|
||||||
|
type Handler func([]byte, *net.UDPAddr, *Server) error
|
103
server/core/Server.go
Normal file
103
server/core/Server.go
Normal file
@ -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++
|
||||||
|
}
|
31
server/game/Client.go
Normal file
31
server/game/Client.go
Normal file
@ -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()
|
||||||
|
}
|
72
server/game/Login.go
Normal file
72
server/game/Login.go
Normal file
@ -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)
|
||||||
|
}
|
68
server/game/Manager.go
Normal file
68
server/game/Manager.go
Normal file
@ -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())
|
||||||
|
}
|
18
server/game/Ping.go
Normal file
18
server/game/Ping.go
Normal file
@ -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
|
||||||
|
}
|
6
server/game/Player.go
Normal file
6
server/game/Player.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Position Vector3 `json:"position"`
|
||||||
|
}
|
7
server/game/Vector3.go
Normal file
7
server/game/Vector3.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
type Vector3 struct {
|
||||||
|
X float32 `json:"x"`
|
||||||
|
Y float32 `json:"y"`
|
||||||
|
Z float32 `json:"z"`
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
class_name Cell
|
|
||||||
|
|
||||||
var player_list := [] as Array[Player]
|
|
@ -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)
|
|
@ -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)
|
|
||||||
|
|
@ -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)
|
|
32
server/main.go
Normal file
32
server/main.go
Normal file
@ -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)
|
||||||
|
}
|
8
server/packet/packet.go
Normal file
8
server/packet/packet.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package packet
|
||||||
|
|
||||||
|
const (
|
||||||
|
Ping = 1
|
||||||
|
Login = 2
|
||||||
|
Logout = 3
|
||||||
|
PlayerState = 10
|
||||||
|
)
|
@ -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
|
|
@ -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")
|
|
@ -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
|
|
@ -26,16 +26,19 @@ func udpClient(wg *sync.WaitGroup) {
|
|||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
clientAddr, err := net.ResolveUDPAddr("udp", serverAddress)
|
clientAddr, err := net.ResolveUDPAddr("udp", serverAddress)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error resolving UDP address:", err)
|
fmt.Println("Error resolving UDP address:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := net.DialUDP("udp", nil, clientAddr)
|
conn, err := net.DialUDP("udp", nil, clientAddr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error connecting to UDP server:", err)
|
fmt.Println("Error connecting to UDP server:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -51,14 +54,12 @@ func udpClient(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var wg sync.WaitGroup
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
// Start multiple UDP clients in separate goroutines
|
|
||||||
for i := 0; i < *numClients; i++ {
|
for i := 0; i < *numClients; i++ {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go udpClient(&wg)
|
go udpClient(&wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal all client goroutines to stop
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
@ -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()
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package packet
|
|
||||||
|
|
||||||
const (
|
|
||||||
PING = 1
|
|
||||||
LOGIN = 2
|
|
||||||
LOGOUT = 3
|
|
||||||
MOVE = 10
|
|
||||||
)
|
|
@ -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)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user