Split client and server
This commit is contained in:
15
server/Client.gd
Normal file
15
server/Client.gd
Normal file
@ -0,0 +1,15 @@
|
||||
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 != ""
|
12
server/Database.gd
Normal file
12
server/Database.gd
Normal file
@ -0,0 +1,12 @@
|
||||
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]
|
98
server/Server.gd
Normal file
98
server/Server.gd
Normal file
@ -0,0 +1,98 @@
|
||||
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]
|
27
server/Server.tscn
Normal file
27
server/Server.tscn
Normal file
@ -0,0 +1,27 @@
|
||||
[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
|
3
server/grid/Cell.gd
Normal file
3
server/grid/Cell.gd
Normal file
@ -0,0 +1,3 @@
|
||||
class_name Cell
|
||||
|
||||
var player_list := [] as Array[Player]
|
31
server/grid/Grid.gd
Normal file
31
server/grid/Grid.gd
Normal file
@ -0,0 +1,31 @@
|
||||
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)
|
65
server/handler/Login.gd
Normal file
65
server/handler/Login.gd
Normal file
@ -0,0 +1,65 @@
|
||||
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)
|
||||
|
13
server/handler/Ping.gd
Normal file
13
server/handler/Ping.gd
Normal file
@ -0,0 +1,13 @@
|
||||
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)
|
24
server/player/Player.gd
Normal file
24
server/player/Player.gd
Normal file
@ -0,0 +1,24 @@
|
||||
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
|
14
server/player/Player.tscn
Normal file
14
server/player/Player.tscn
Normal file
@ -0,0 +1,14 @@
|
||||
[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")
|
38
server/project.godot
Normal file
38
server/project.godot
Normal file
@ -0,0 +1,38 @@
|
||||
; 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
|
Reference in New Issue
Block a user