diff --git a/clients/godot/.gitignore b/clients/godot/.gitignore
new file mode 100644
index 0000000..2e58676
--- /dev/null
+++ b/clients/godot/.gitignore
@@ -0,0 +1,21 @@
+# Godot 4 specific
+.godot/
+*.import
+
+# Builds
+build/
+
+# User-specific
+*.user
+*.userprefs
+
+# OS files
+.DS_Store
+Thumbs.db
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
diff --git a/clients/godot/config/loader.gd.uid b/clients/godot/config/loader.gd.uid
new file mode 100644
index 0000000..29c41f9
--- /dev/null
+++ b/clients/godot/config/loader.gd.uid
@@ -0,0 +1 @@
+uid://jq432e34g4ie
diff --git a/clients/godot/entities/ents.gd b/clients/godot/entities/ents.gd
index 05d0fb1..5ddbdf8 100644
--- a/clients/godot/entities/ents.gd
+++ b/clients/godot/entities/ents.gd
@@ -271,7 +271,7 @@ func add_interaction_area(entity_node: Node3D, entity_type: int, data: Dictionar
func add_health_display(entity_node: Node3D, data: Dictionary):
# Create health bar UI
var health_bar = Sprite3D.new()
- health_bar.texture = preload("res://textures/health_bar.png")
+ health_bar.texture = preload("res://textures/health_bar.svg")
health_bar.pixel_size = 0.01
health_bar.billboard = BaseMaterial3D.BILLBOARD_ENABLED
health_bar.position = Vector3(0, 1.5, 0)
diff --git a/clients/godot/entities/ents.gd.uid b/clients/godot/entities/ents.gd.uid
new file mode 100644
index 0000000..f9f9c85
--- /dev/null
+++ b/clients/godot/entities/ents.gd.uid
@@ -0,0 +1 @@
+uid://bwq4tcxq2qbgo
diff --git a/clients/godot/export_presets.cfg b/clients/godot/export_presets.cfg
new file mode 100644
index 0000000..82902e3
--- /dev/null
+++ b/clients/godot/export_presets.cfg
@@ -0,0 +1,90 @@
+[preset.0]
+
+name="Linux/X11"
+platform="Linux"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter=""
+exclude_filter=""
+export_path="build/linux/gameserver_client.x86_64"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+
+[preset.0.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/s3tc_bptc=true
+texture_format/etc2_astc=false
+binary_format/architecture="x86_64"
+
+[preset.1]
+
+name="Windows"
+platform="Windows"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter=""
+exclude_filter=""
+export_path="build/windows/gameserver_client.exe"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+
+[preset.1.options]
+
+custom_template/debug=""
+custom_template/release=""
+debug/export_console_wrapper=1
+binary_format/embed_pck=false
+texture_format/s3tc_bptc=true
+texture_format/etc2_astc=false
+binary_format/architecture="x86_64"
+codesign/enable=false
+
+[preset.2]
+
+name="Web"
+platform="Web"
+runnable=true
+dedicated_server=false
+custom_features=""
+export_filter="all_resources"
+include_filter=""
+exclude_filter=""
+export_path="build/web/index.html"
+encryption_include_filters=""
+encryption_exclude_filters=""
+encrypt_pck=false
+encrypt_directory=false
+
+[preset.2.options]
+
+custom_template/debug=""
+custom_template/release=""
+variant/extensions_support=false
+vram_texture_compression/for_desktop=true
+vram_texture_compression/for_mobile=false
+html/export_icon=true
+html/custom_html_shell=""
+html/head_include=""
+html/canvas_resize_policy=2
+html/focus_canvas_on_start=true
+html/experimental_virtual_keyboard=false
+progressive_web_app/enabled=false
+progressive_web_app/offline_page=""
+progressive_web_app/display=1
+progressive_web_app/orientation=0
+progressive_web_app/icon_144x144=""
+progressive_web_app/icon_180x180=""
+progressive_web_app/icon_512x512=""
+progressive_web_app/background_color=Color(0, 0, 0, 1)
diff --git a/clients/godot/icon.svg b/clients/godot/icon.svg
new file mode 100644
index 0000000..b3ba8bf
--- /dev/null
+++ b/clients/godot/icon.svg
@@ -0,0 +1,4 @@
+
diff --git a/clients/godot/main.gd b/clients/godot/main.gd
index f14f1ef..a412719 100644
--- a/clients/godot/main.gd
+++ b/clients/godot/main.gd
@@ -29,6 +29,11 @@ var chunk_height: int = 64
var lod_distances: Array = [20.0, 40.0, 80.0]
var max_entities: int = 100
+func setup_input():
+ # Input is already configured via project settings.
+ # Additional runtime input setup can go here.
+ pass
+
func _ready():
load_settings()
setup_network_signals()
diff --git a/clients/godot/main.gd.uid b/clients/godot/main.gd.uid
new file mode 100644
index 0000000..f014eff
--- /dev/null
+++ b/clients/godot/main.gd.uid
@@ -0,0 +1 @@
+uid://brbguaumulrvw
diff --git a/clients/godot/main.tscn b/clients/godot/main.tscn
new file mode 100644
index 0000000..bfac8f6
--- /dev/null
+++ b/clients/godot/main.tscn
@@ -0,0 +1,36 @@
+[gd_scene load_steps=2 format=3 uid="uid://c1234"]
+
+[ext_resource type="Script" path="res://main.gd" id="1"]
+
+[node name="GameClient" type="Node3D"]
+script = ExtResource("1")
+
+[node name="World" type="Node3D" parent="."]
+
+[node name="Player" type="CharacterBody3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0)
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Player"]
+
+[node name="CameraPivot" type="Node3D" parent="Player"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0)
+
+[node name="Camera3D" type="Camera3D" parent="Player/CameraPivot"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 3)
+fov = 70.0
+
+[node name="UI" type="Control" parent="."]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+
+[node name="NetworkManager" type="Node" parent="."]
+
+[node name="ModelGenerator" type="Node" parent="."]
+
+[node name="EntityManager" type="Node" parent="."]
+
+[node name="ConfigLoader" type="Node" parent="."]
diff --git a/clients/godot/materials/mob.tres b/clients/godot/materials/mob.tres
new file mode 100644
index 0000000..31467b2
--- /dev/null
+++ b/clients/godot/materials/mob.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "Mob"
+albedo_color = Color(0.8, 0.2, 0.2, 1)
+roughness = 0.7
+metallic = 0.0
diff --git a/clients/godot/materials/npc.tres b/clients/godot/materials/npc.tres
new file mode 100644
index 0000000..91c2886
--- /dev/null
+++ b/clients/godot/materials/npc.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "NPC"
+albedo_color = Color(0.8, 0.6, 0.2, 1)
+roughness = 0.7
+metallic = 0.0
diff --git a/clients/godot/materials/player.tres b/clients/godot/materials/player.tres
new file mode 100644
index 0000000..a984163
--- /dev/null
+++ b/clients/godot/materials/player.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "Player"
+albedo_color = Color(0.3, 0.5, 0.8, 1)
+roughness = 0.7
+metallic = 0.0
diff --git a/clients/godot/materials/stone.tres b/clients/godot/materials/stone.tres
new file mode 100644
index 0000000..c443f6a
--- /dev/null
+++ b/clients/godot/materials/stone.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "Stone"
+albedo_color = Color(0.5, 0.5, 0.5, 1)
+roughness = 0.9
+metallic = 0.1
diff --git a/clients/godot/materials/terrain.tres b/clients/godot/materials/terrain.tres
new file mode 100644
index 0000000..d736353
--- /dev/null
+++ b/clients/godot/materials/terrain.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "Terrain"
+albedo_color = Color(0.4, 0.6, 0.3, 1)
+roughness = 0.9
+metallic = 0.0
diff --git a/clients/godot/materials/tree.tres b/clients/godot/materials/tree.tres
new file mode 100644
index 0000000..17f5528
--- /dev/null
+++ b/clients/godot/materials/tree.tres
@@ -0,0 +1,7 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "Tree"
+albedo_color = Color(0.3, 0.5, 0.2, 1)
+roughness = 0.8
+metallic = 0.0
diff --git a/clients/godot/materials/water.tres b/clients/godot/materials/water.tres
new file mode 100644
index 0000000..73dff6f
--- /dev/null
+++ b/clients/godot/materials/water.tres
@@ -0,0 +1,8 @@
+[gd_resource type="StandardMaterial3D" format=3]
+
+[resource]
+resource_name = "Water"
+transparency = 1
+albedo_color = Color(0.2, 0.4, 0.8, 0.7)
+metallic = 0.3
+roughness = 0.1
diff --git a/clients/godot/models/generator.gd b/clients/godot/models/generator.gd
index 1d3da92..84c96cb 100644
--- a/clients/godot/models/generator.gd
+++ b/clients/godot/models/generator.gd
@@ -218,7 +218,7 @@ func generate_river_model(data: Dictionary) -> MeshInstance3D:
mesh_instance.mesh = plane_mesh
var material = ShaderMaterial.new()
- var shader = preload("res://shaders/water.shader")
+ var shader = preload("res://shaders/water.gdshader")
material.shader = shader
if data.has("color"):
diff --git a/clients/godot/models/generator.gd.uid b/clients/godot/models/generator.gd.uid
new file mode 100644
index 0000000..0039c02
--- /dev/null
+++ b/clients/godot/models/generator.gd.uid
@@ -0,0 +1 @@
+uid://xqpklfwcaoh4
diff --git a/clients/godot/network/networks.gd b/clients/godot/network/networks.gd
index 2ee918e..6b285f0 100644
--- a/clients/godot/network/networks.gd
+++ b/clients/godot/network/networks.gd
@@ -1,50 +1,56 @@
extends Node
-class_name NetworkManager
-
# Protocol types
enum ProtocolType {
BINARY = 0,
WEBSOCKET = 1,
}
-# Message types matching server
+# Message types matching server BinaryProtocol.hpp exactly
enum MessageType {
MESSAGE_TYPE_INVALID = 0,
- MESSAGE_TYPE_HEARTBEAT = 1,
- MESSAGE_TYPE_PROTOCOL_NEGOTIATION = 2,
- MESSAGE_TYPE_AUTHENTICATION = 3,
- MESSAGE_TYPE_ERROR = 4,
- MESSAGE_TYPE_SUCCESS = 5,
- MESSAGE_TYPE_CHUNK_DATA = 100,
- MESSAGE_TYPE_CHUNK_REQUEST = 101,
- MESSAGE_TYPE_TERRAIN_HEIGHT = 102,
- MESSAGE_TYPE_BIOME_DATA = 103,
- MESSAGE_TYPE_ENTITY_SPAWN = 200,
- MESSAGE_TYPE_ENTITY_UPDATE = 201,
- MESSAGE_TYPE_ENTITY_DESPAWN = 202,
- MESSAGE_TYPE_ENTITY_BATCH_UPDATE = 203,
- MESSAGE_TYPE_PLAYER_POSITION = 300,
- MESSAGE_TYPE_PLAYER_VELOCITY = 301,
- MESSAGE_TYPE_PLAYER_ROTATION = 302,
- MESSAGE_TYPE_PLAYER_STATE = 303,
- MESSAGE_TYPE_PLAYER_POSITION_CORRECTION = 304,
- MESSAGE_TYPE_NPC_SPAWN = 400,
- MESSAGE_TYPE_NPC_UPDATE = 401,
- MESSAGE_TYPE_NPC_DESPAWN = 402,
- MESSAGE_TYPE_NPC_INTERACTION = 403,
- MESSAGE_TYPE_COMBAT_EVENT = 500,
- MESSAGE_TYPE_DAMAGE_EVENT = 501,
- MESSAGE_TYPE_HEALTH_UPDATE = 502,
- MESSAGE_TYPE_INVENTORY_UPDATE = 600,
- MESSAGE_TYPE_LOOT_SPAWN = 601,
- MESSAGE_TYPE_LOOT_PICKUP = 602,
- MESSAGE_TYPE_CHAT_MESSAGE = 700,
- MESSAGE_TYPE_SYSTEM_MESSAGE = 701,
- MESSAGE_TYPE_CUSTOM_EVENT = 1000
+ MESSAGE_TYPE_HEARTBEAT = 100,
+ MESSAGE_TYPE_PROTOCOL_NEGOTIATION = 102,
+ MESSAGE_TYPE_AUTHENTICATION = 103,
+ MESSAGE_TYPE_ERROR = 104,
+ MESSAGE_TYPE_SUCCESS = 105,
+ MESSAGE_TYPE_COLLISION_CHECK = 150,
+ MESSAGE_TYPE_CHUNK_PARAMS = 200,
+ MESSAGE_TYPE_CHUNK_DATA = 201,
+ MESSAGE_TYPE_BIOME_DATA = 202,
+ MESSAGE_TYPE_PLAYER_CONNECT = 300,
+ MESSAGE_TYPE_PLAYER_DISCONNECT = 301,
+ MESSAGE_TYPE_PLAYER_STATE = 302,
+ MESSAGE_TYPE_PLAYER_SPAWN = 303,
+ MESSAGE_TYPE_PLAYER_DESPAWN = 304,
+ MESSAGE_TYPE_PLAYER_UPDATE = 305,
+ MESSAGE_TYPE_PLAYER_VELOCITY = 306,
+ MESSAGE_TYPE_PLAYER_ROTATION = 307,
+ MESSAGE_TYPE_PLAYER_POSITION = 308,
+ MESSAGE_TYPE_PLAYER_POSITION_CORRECTION = 309,
+ MESSAGE_TYPE_PLAYERS_UPDATE = 350,
+ MESSAGE_TYPE_ENTITY_SPAWN = 400,
+ MESSAGE_TYPE_ENTITY_UPDATE = 401,
+ MESSAGE_TYPE_ENTITY_DESPAWN = 402,
+ MESSAGE_TYPE_ENTITY_BATCH_UPDATE = 403,
+ MESSAGE_TYPE_NPC_SPAWN = 500,
+ MESSAGE_TYPE_NPC_UPDATE = 501,
+ MESSAGE_TYPE_NPC_DESPAWN = 502,
+ MESSAGE_TYPE_NPC_INTERACTION = 503,
+ MESSAGE_TYPE_COMBAT_EVENT = 600,
+ MESSAGE_TYPE_DAMAGE_EVENT = 601,
+ MESSAGE_TYPE_HEALTH_UPDATE = 602,
+ MESSAGE_TYPE_LOOT_SPAWN = 700,
+ MESSAGE_TYPE_LOOT_PICKUP = 701,
+ MESSAGE_TYPE_INVENTORY_UPDATE = 702,
+ MESSAGE_TYPE_INVENTORY_MOVE = 703,
+ MESSAGE_TYPE_CHAT_MESSAGE = 800,
+ MESSAGE_TYPE_SYSTEM_MESSAGE = 801,
+ MESSAGE_TYPE_FAMILIAR_COMMAND = 900,
+ MESSAGE_TYPE_CUSTOM_EVENT = 1000,
}
-# Protocol flags
+# Protocol flags matching server
enum ProtocolFlags {
FLAG_COMPRESSED = 0x01,
FLAG_ENCRYPTED = 0x02,
@@ -78,6 +84,7 @@ var protocol_type: int = ProtocolType.BINARY
var use_ssl: bool = false
var reconnect_attempts: int = 3
var reconnect_delay: float = 2.0
+var ws_path: String = "/game"
# Network objects
var tcp_client: StreamPeerTCP
@@ -93,6 +100,8 @@ var sequence_number: int = 0
var pending_acks: Dictionary = {}
var last_heartbeat: float = 0.0
var heartbeat_interval: float = 5.0
+var protocol_negotiated: bool = false
+var recv_buffer: PackedByteArray = PackedByteArray()
# Quality monitoring
var network_quality: Dictionary = {
@@ -130,13 +139,15 @@ func _process(delta):
func connect_to_server(address: String, port: int, protocol: int = ProtocolType.BINARY):
if connection_state != ConnectionState.DISCONNECTED:
disconnect_from_server()
-
+
server_address = address
server_port = port
protocol_type = protocol
-
+ protocol_negotiated = false
+ recv_buffer = PackedByteArray()
+
set_connection_state(ConnectionState.CONNECTING)
-
+
match protocol_type:
ProtocolType.BINARY:
connect_binary()
@@ -151,7 +162,7 @@ func disconnect_from_server():
ProtocolType.WEBSOCKET:
if websocket:
websocket.close()
-
+
set_connection_state(ConnectionState.DISCONNECTED)
cleanup()
@@ -160,109 +171,222 @@ func send_message(message_type: int, data: PackedByteArray, reliable: bool = tru
outgoing_queue.append(message)
func send_json_message(message_type: int, json_data: Dictionary):
- var json_string = JSON.stringify(json_data)
- var data = json_string.to_utf8_buffer()
- send_message(message_type, data)
+ if protocol_type == ProtocolType.WEBSOCKET:
+ send_ws_json(json_data)
+ else:
+ var json_string = JSON.stringify(json_data)
+ var data = json_string.to_utf8_buffer()
+ send_message(message_type, data)
func send_binary_message(message_type: int, binary_data: PackedByteArray):
send_message(message_type, binary_data)
func authenticate(username: String, password: String):
- var auth_data = {
- "username": username,
- "password": password,
- "client_version": "1.0.0"
- }
- send_json_message(MessageType.MESSAGE_TYPE_AUTHENTICATION, auth_data)
+ if protocol_type == ProtocolType.WEBSOCKET:
+ send_ws_json({
+ "msg": "authentication",
+ "login": username,
+ "password": password,
+ })
+ else:
+ var body = PackedByteArray()
+ body.append_array(_write_uint64(0))
+ body.append_array(_write_string(username))
+ body.append_array(_write_string(password))
+ send_message(MessageType.MESSAGE_TYPE_AUTHENTICATION, body)
set_connection_state(ConnectionState.AUTHENTICATING)
func request_chunk(chunk_x: int, chunk_z: int):
- var request_data = {
- "chunk_x": chunk_x,
- "chunk_z": chunk_z
- }
- send_json_message(MessageType.MESSAGE_TYPE_CHUNK_REQUEST, request_data)
+ if protocol_type == ProtocolType.WEBSOCKET:
+ send_ws_json({
+ "msg": "get_chunk",
+ "x": chunk_x,
+ "z": chunk_z,
+ })
+ else:
+ var body = PackedByteArray()
+ body.append_array(_write_int32(chunk_x))
+ body.append_array(_write_int32(chunk_z))
+ send_message(MessageType.MESSAGE_TYPE_CHUNK_DATA, body)
func update_player_position(position: Vector3, rotation: Vector3, velocity: Vector3 = Vector3.ZERO):
- var pos_data = {
- "position": {
+ if protocol_type == ProtocolType.WEBSOCKET:
+ send_ws_json({
+ "msg": "player_position",
+ "player_id": player_id,
"x": position.x,
"y": position.y,
- "z": position.z
- },
- "rotation": {
- "x": rotation.x,
- "y": rotation.y,
- "z": rotation.z
- },
- "velocity": {
- "x": velocity.x,
- "y": velocity.y,
- "z": velocity.z
- },
- "timestamp": Time.get_ticks_msec(),
- "input_id": sequence_number
- }
- send_json_message(MessageType.MESSAGE_TYPE_PLAYER_POSITION, pos_data)
-
+ "z": position.z,
+ "vx": velocity.x,
+ "vy": velocity.y,
+ "vz": velocity.z,
+ "timestamp": Time.get_ticks_msec(),
+ })
+ else:
+ var body = PackedByteArray()
+ body.append_array(_write_uint64(player_id))
+ body.append_array(_write_vector3(position))
+ body.append_array(_write_vector3(velocity))
+ body.append_array(_write_uint64(Time.get_ticks_msec()))
+ send_message(MessageType.MESSAGE_TYPE_PLAYER_POSITION, body)
+
if prediction_enabled:
store_input(sequence_number, position, rotation, velocity)
sequence_number += 1
-func send_chat_message(message: String, channel: String = "global"):
- var chat_data = {
- "message": message,
- "channel": channel,
- "timestamp": Time.get_ticks_msec()
- }
- send_json_message(MessageType.MESSAGE_TYPE_CHAT_MESSAGE, chat_data)
+func send_chat_message(message: String, sender: String = "Player"):
+ if protocol_type == ProtocolType.WEBSOCKET:
+ send_ws_json({
+ "msg": "chat_message",
+ "sender": sender,
+ "message": message,
+ "timestamp": Time.get_ticks_msec(),
+ })
+ else:
+ var body = PackedByteArray()
+ body.append_array(_write_string(sender))
+ body.append_array(_write_string(message))
+ body.append_array(_write_uint64(Time.get_ticks_msec()))
+ send_message(MessageType.MESSAGE_TYPE_CHAT_MESSAGE, body)
func interact_with_entity(entity_id: int, interaction_type: String = "use"):
- var interaction_data = {
- "entity_id": entity_id,
- "interaction_type": interaction_type,
- "timestamp": Time.get_ticks_msec()
- }
- send_json_message(MessageType.MESSAGE_TYPE_NPC_INTERACTION, interaction_data)
+ if protocol_type == ProtocolType.WEBSOCKET:
+ send_ws_json({
+ "msg": "npc_interaction",
+ "npc_id": entity_id,
+ "npc_type": interaction_type,
+ })
+ else:
+ var body = PackedByteArray()
+ body.append_array(_write_uint64(entity_id))
+ body.append_array(_write_string(interaction_type))
+ send_message(MessageType.MESSAGE_TYPE_NPC_INTERACTION, body)
# Message handlers
func setup_message_handlers():
- # System messages
message_handlers[MessageType.MESSAGE_TYPE_HEARTBEAT] = _handle_heartbeat
message_handlers[MessageType.MESSAGE_TYPE_SUCCESS] = _handle_success
message_handlers[MessageType.MESSAGE_TYPE_ERROR] = _handle_error
-
- # World messages
message_handlers[MessageType.MESSAGE_TYPE_CHUNK_DATA] = _handle_chunk_data
- message_handlers[MessageType.MESSAGE_TYPE_TERRAIN_HEIGHT] = _handle_terrain_height
message_handlers[MessageType.MESSAGE_TYPE_BIOME_DATA] = _handle_biome_data
-
- # Entity messages
message_handlers[MessageType.MESSAGE_TYPE_ENTITY_SPAWN] = _handle_entity_spawn
message_handlers[MessageType.MESSAGE_TYPE_ENTITY_UPDATE] = _handle_entity_update
message_handlers[MessageType.MESSAGE_TYPE_ENTITY_DESPAWN] = _handle_entity_despawn
-
- # Player messages
message_handlers[MessageType.MESSAGE_TYPE_PLAYER_POSITION_CORRECTION] = _handle_position_correction
+ message_handlers[MessageType.MESSAGE_TYPE_PLAYER_STATE] = _handle_player_state
message_handlers[MessageType.MESSAGE_TYPE_HEALTH_UPDATE] = _handle_health_update
-
- # Inventory messages
message_handlers[MessageType.MESSAGE_TYPE_INVENTORY_UPDATE] = _handle_inventory_update
message_handlers[MessageType.MESSAGE_TYPE_LOOT_SPAWN] = _handle_loot_spawn
-
- # Chat messages
message_handlers[MessageType.MESSAGE_TYPE_CHAT_MESSAGE] = _handle_chat_message
message_handlers[MessageType.MESSAGE_TYPE_SYSTEM_MESSAGE] = _handle_system_message
-# Private methods
+# --- Binary protocol helpers (big-endian body fields, matching server BinaryReader) ---
+
+func _write_uint8(value: int) -> PackedByteArray:
+ return PackedByteArray([value & 0xFF])
+
+func _write_uint16(value: int) -> PackedByteArray:
+ return PackedByteArray([(value >> 8) & 0xFF, value & 0xFF])
+
+func _write_uint32(value: int) -> PackedByteArray:
+ return PackedByteArray([
+ (value >> 24) & 0xFF,
+ (value >> 16) & 0xFF,
+ (value >> 8) & 0xFF,
+ value & 0xFF
+ ])
+
+func _write_uint64(value: int) -> PackedByteArray:
+ return PackedByteArray([
+ (value >> 56) & 0xFF,
+ (value >> 48) & 0xFF,
+ (value >> 40) & 0xFF,
+ (value >> 32) & 0xFF,
+ (value >> 24) & 0xFF,
+ (value >> 16) & 0xFF,
+ (value >> 8) & 0xFF,
+ value & 0xFF
+ ])
+
+func _write_int32(value: int) -> PackedByteArray:
+ return _write_uint32(value)
+
+func _write_float(value: float) -> PackedByteArray:
+ var int_val = int(value)
+ return _write_uint32(int_val)
+
+func _write_vector3(v: Vector3) -> PackedByteArray:
+ var buf = PackedByteArray()
+ buf.append_array(_write_float(v.x))
+ buf.append_array(_write_float(v.y))
+ buf.append_array(_write_float(v.z))
+ return buf
+
+func _write_string(s: String) -> PackedByteArray:
+ var encoded = s.to_utf8_buffer()
+ var buf = PackedByteArray()
+ buf.append_array(_write_uint16(encoded.size()))
+ buf.append_array(encoded)
+ return buf
+
+func _read_uint8(data: PackedByteArray, offset: int) -> int:
+ return data[offset]
+
+func _read_uint16_be(data: PackedByteArray, offset: int) -> int:
+ return (data[offset] << 8) | data[offset + 1]
+
+func _read_uint32_be(data: PackedByteArray, offset: int) -> int:
+ return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]
+
+func _read_uint64_be(data: PackedByteArray, offset: int) -> int:
+ var val: int = 0
+ for i in range(8):
+ val = (val << 8) | data[offset + i]
+ return val
+
+func _read_int32_be(data: PackedByteArray, offset: int) -> int:
+ return _read_uint32_be(data, offset)
+
+func _read_float_be(data: PackedByteArray, offset: int) -> float:
+ return float(_read_uint32_be(data, offset))
+
+func _read_vector3_be(data: PackedByteArray, offset: int) -> Vector3:
+ return Vector3(
+ _read_float_be(data, offset),
+ _read_float_be(data, offset + 4),
+ _read_float_be(data, offset + 8)
+ )
+
+func _read_string_be(data: PackedByteArray, offset: int) -> String:
+ var length = _read_uint16_be(data, offset)
+ offset += 2
+ if offset + length > data.size():
+ return ""
+ return data.slice(offset, offset + length).get_string_from_utf8()
+
+# --- CRC32 (matching server polynomial 0xEDB88320) ---
+
+func _crc32(data: PackedByteArray) -> int:
+ var crc: int = 0xFFFFFFFF
+ for i in range(data.size()):
+ crc ^= data[i]
+ for _j in range(8):
+ if crc & 1:
+ crc = (crc >> 1) ^ 0xEDB88320
+ else:
+ crc = crc >> 1
+ return ~crc & 0xFFFFFFFF
+
+# --- Connection ---
+
func connect_binary():
tcp_client = StreamPeerTCP.new()
var error = tcp_client.connect_to_host(server_address, server_port)
-
+
if error == OK:
if use_ssl:
ssl_context = StreamPeerTLS.new()
- error = ssl_context.connect_to_stream(tcp_client)
+ error = ssl_context.connect_to_stream(tcp_client, server_address)
if error == OK:
set_connection_state(ConnectionState.HANDSHAKE)
else:
@@ -276,18 +400,20 @@ func connect_binary():
func connect_websocket():
websocket = WebSocketPeer.new()
- var url = "ws://%s:%d" % [server_address, server_port]
+ var url = "ws://%s:%d%s" % [server_address, server_port, ws_path]
if use_ssl:
- url = "wss://%s:%d" % [server_address, server_port]
-
+ url = "wss://%s:%d%s" % [server_address, server_port, ws_path]
+
var error = websocket.connect_to_url(url)
-
+
if error == OK:
set_connection_state(ConnectionState.HANDSHAKE)
else:
set_connection_state(ConnectionState.ERROR)
emit_signal("error_occurred", error, "WebSocket connection failed")
+# --- Network processing ---
+
func process_network():
match protocol_type:
ProtocolType.BINARY:
@@ -298,30 +424,65 @@ func process_network():
func process_binary():
if not tcp_client or tcp_client.get_status() != StreamPeerTCP.STATUS_CONNECTED:
return
-
- # Check for incoming data
- var available_bytes = tcp_client.get_available_bytes()
- if available_bytes > 0:
- var data = tcp_client.get_data(available_bytes)
- if data[0] == OK:
- parse_binary_data(data[1])
+
+ var available = tcp_client.get_available_bytes()
+ if available > 0:
+ var result = tcp_client.get_data(available)
+ if result[0] == OK:
+ var new_data = result[1]
+ recv_buffer.append_array(new_data)
+ process_recv_buffer()
+
+func process_recv_buffer():
+ while recv_buffer.size() >= 20:
+ var msg_type = _read_uint16_be(recv_buffer, 2)
+ var length = _read_uint32_be(recv_buffer, 12)
+
+ if length > 10 * 1024 * 1024:
+ recv_buffer = PackedByteArray()
+ return
+
+ var total = 20 + length
+ if recv_buffer.size() < total:
+ return
+
+ var message = {
+ "version": recv_buffer[0],
+ "flags": recv_buffer[1],
+ "message_type": msg_type,
+ "sequence": _read_uint32_be(recv_buffer, 4),
+ "timestamp": _read_uint32_be(recv_buffer, 8),
+ "length": length,
+ }
+
+ if length > 0:
+ message.data = recv_buffer.slice(20, total)
+ else:
+ message.data = PackedByteArray()
+
+ recv_buffer = recv_buffer.slice(total, recv_buffer.size())
+
+ if message.flags & ProtocolFlags.FLAG_RELIABLE:
+ handle_ack(message.sequence)
+
+ incoming_queue.append(message)
func process_websocket():
if not websocket:
return
-
+
websocket.poll()
-
+
var state = websocket.get_ready_state()
match state:
WebSocketPeer.STATE_OPEN:
if connection_state != ConnectionState.CONNECTED:
set_connection_state(ConnectionState.CONNECTED)
-
+
while websocket.get_available_packet_count():
var packet = websocket.get_packet()
parse_websocket_data(packet)
-
+
WebSocketPeer.STATE_CLOSED:
var code = websocket.get_close_code()
var reason = websocket.get_close_reason()
@@ -329,193 +490,122 @@ func process_websocket():
emit_signal("error_occurred", code, "WebSocket closed: %s" % reason)
func process_queues():
- # Process incoming messages
while incoming_queue.size() > 0:
var message = incoming_queue.pop_front()
handle_incoming_message(message)
-
- # Process outgoing messages
+
while outgoing_queue.size() > 0 and connection_state == ConnectionState.CONNECTED:
var message = outgoing_queue.pop_front()
send_network_message(message)
-func check_heartbeat(delta: float):
- last_heartbeat += delta
- if last_heartbeat >= heartbeat_interval:
- send_heartbeat()
- last_heartbeat = 0.0
-
-func send_heartbeat():
- var heartbeat_data = PackedByteArray()
- send_message(MessageType.MESSAGE_TYPE_HEARTBEAT, heartbeat_data)
-
-func create_message(message_type: int, data: PackedByteArray, reliable: bool, priority: int) -> Dictionary:
- var flags: int = 0
- if reliable:
- flags |= ProtocolFlags.FLAG_RELIABLE
-
- match priority:
- 1:
- flags |= ProtocolFlags.FLAG_PRIORITY_HIGH
- -1:
- flags |= ProtocolFlags.FLAG_PRIORITY_LOW
-
- var message = {
- "version": 1,
- "flags": flags,
- "message_type": message_type,
- "sequence": get_next_sequence(),
- "timestamp": Time.get_ticks_msec(),
- "data": data
- }
-
- if reliable:
- pending_acks[message.sequence] = {
- "message": message,
- "timestamp": Time.get_ticks_msec(),
- "retries": 0
- }
-
- return message
-
-func get_next_sequence() -> int:
- var seq = sequence_number
- sequence_number = (sequence_number + 1) % 65536
- return seq
+# --- Serialization ---
func send_network_message(message: Dictionary):
- var packet = serialize_message(message)
-
match protocol_type:
ProtocolType.BINARY:
+ var packet = serialize_binary_message(message)
if use_ssl and ssl_context:
ssl_context.put_data(packet)
elif tcp_client:
tcp_client.put_data(packet)
-
+
ProtocolType.WEBSOCKET:
- if websocket:
- websocket.send(packet)
+ pass
-func serialize_message(message: Dictionary) -> PackedByteArray:
+func send_ws_json(json_data: Dictionary):
+ if websocket:
+ websocket.send(JSON.stringify(json_data).to_utf8_buffer())
+
+func serialize_binary_message(message: Dictionary) -> PackedByteArray:
var buffer = PackedByteArray()
-
- # Header (28 bytes)
- buffer.append(message.version) # version
- buffer.append(message.flags) # flags
-
- # message_type (2 bytes)
- buffer.append((message.message_type >> 8) & 0xFF)
+
+ # Header: all little-endian matching server memcpy on x86
+ buffer.append(message.version & 0xFF)
+ buffer.append(message.flags & 0xFF)
+
+ # message_type (2 bytes, little-endian)
buffer.append(message.message_type & 0xFF)
-
- # sequence (4 bytes)
+ buffer.append((message.message_type >> 8) & 0xFF)
+
+ # sequence (4 bytes, little-endian)
for i in range(4):
buffer.append((message.sequence >> (i * 8)) & 0xFF)
-
- # timestamp (4 bytes)
+
+ # timestamp (4 bytes, little-endian)
var timestamp = message.timestamp
for i in range(4):
buffer.append((timestamp >> (i * 8)) & 0xFF)
-
- # length (4 bytes)
+
+ # length (4 bytes, little-endian)
var length = message.data.size()
for i in range(4):
buffer.append((length >> (i * 8)) & 0xFF)
-
- # checksum (4 bytes) - placeholder
+
+ # checksum (4 bytes, placeholder, little-endian)
for i in range(4):
buffer.append(0)
-
+
# Data
buffer.append_array(message.data)
-
- # Calculate and update checksum
- update_checksum(buffer)
-
+
+ # Calculate CRC32 checksum over body only (matching server)
+ var body_bytes = message.data
+ if body_bytes.size() > 0:
+ var checksum = _crc32(body_bytes)
+ for i in range(4):
+ buffer[20 + i] = (checksum >> (i * 8)) & 0xFF
+
return buffer
-func update_checksum(buffer: PackedByteArray):
- # Simple XOR checksum for now
- var checksum: int = 0
- for i in range(24, buffer.size()): # Skip header
- checksum ^= buffer[i]
-
- # Update checksum in buffer (positions 24-27)
- for i in range(4):
- buffer[24 + i] = (checksum >> (i * 8)) & 0xFF
+# --- Incoming parsing ---
+
+func parse_websocket_data(data: PackedByteArray):
+ var json_string = data.get_string_from_utf8()
+ if json_string == "":
+ return
+
+ var json = JSON.new()
+ var error = json.parse(json_string)
+ if error != OK:
+ return
-func parse_binary_data(data: PackedByteArray):
- if data.size() < 28:
+ var message_data = json.data
+ if not message_data is Dictionary:
return
-
+
var message = {
- "version": data[0],
- "flags": data[1],
- "message_type": (data[2] << 8) | data[3],
- "sequence": (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7],
- "timestamp": (data[8] << 24) | (data[9] << 16) | (data[10] << 8) | data[11],
- "length": (data[12] << 24) | (data[13] << 16) | (data[14] << 8) | data[15]
+ "message_type": 0,
+ "data": message_data,
+ "flags": 0,
+ "sequence": 0,
+ "timestamp": 0
}
-
- # Verify checksum
- if not verify_checksum(data):
- print("Checksum verification failed for message type: ", message.message_type)
- return
-
- # Extract data
- if message.length > 0 and data.size() >= 28 + message.length:
- message.data = data.slice(28, 28 + message.length)
-
incoming_queue.append(message)
-
- # Handle ACKs
- if message.flags & ProtocolFlags.FLAG_RELIABLE:
- handle_ack(message.sequence)
-func parse_websocket_data(data: PackedByteArray):
- # For WebSocket, assume JSON or custom binary format
- # Try to parse as JSON first
- var json_string = data.get_string_from_utf8()
- if json_string != "":
- var json = JSON.new()
- var error = json.parse(json_string)
- if error == OK:
- var message_data = json.data
- if message_data.has("type") and message_data.has("data"):
- var message = {
- "message_type": message_data.type,
- "data": message_data.data,
- "flags": 0,
- "sequence": message_data.get("sequence", 0),
- "timestamp": message_data.get("timestamp", 0)
- }
- incoming_queue.append(message)
- else:
- # Try to parse as binary protocol
- parse_binary_data(data)
+# --- Checksum ---
func verify_checksum(data: PackedByteArray) -> bool:
- # Simple XOR checksum verification
- var checksum: int = 0
- for i in range(24, data.size()):
- checksum ^= data[i]
-
- var stored_checksum: int = 0
+ if data.size() < 20:
+ return false
+ var length = _read_uint32_be(data, 12)
+ if data.size() < 20 + length:
+ return false
+ var stored = 0
for i in range(4):
- stored_checksum |= (data[24 + i] << (i * 8))
-
- return checksum == stored_checksum
+ stored |= (data[20 + i] << (i * 8))
+ var body = data.slice(20, 20 + length)
+ return _crc32(body) == stored
+
+# --- Message dispatch ---
func handle_incoming_message(message: Dictionary):
var message_type = message.message_type
-
+
if message_handlers.has(message_type):
message_handlers[message_type].call(message.data)
else:
if default_message_handler:
default_message_handler.call(message_type, message.data)
- else:
- print("Unhandled message type: ", message_type)
func handle_ack(sequence: int):
if pending_acks.has(sequence):
@@ -526,7 +616,7 @@ func set_connection_state(new_state: int):
if connection_state != new_state:
connection_state = new_state
emit_signal("connection_state_changed", new_state)
-
+
if new_state == ConnectionState.CONNECTED:
set_process(true)
elif new_state == ConnectionState.DISCONNECTED:
@@ -536,22 +626,69 @@ func cleanup():
tcp_client = null
websocket = null
ssl_context = null
+ recv_buffer = PackedByteArray()
incoming_queue.clear()
outgoing_queue.clear()
pending_acks.clear()
sequence_number = 0
player_id = 0
auth_token = ""
+ protocol_negotiated = false
func update_network_quality(success: bool):
- # Update network quality metrics
pass
-# Prediction system
+# --- Heartbeat ---
+
+func check_heartbeat(delta: float):
+ last_heartbeat += delta
+ if last_heartbeat >= heartbeat_interval:
+ send_heartbeat()
+ last_heartbeat = 0.0
+
+func send_heartbeat():
+ send_message(MessageType.MESSAGE_TYPE_HEARTBEAT, PackedByteArray())
+
+func create_message(message_type: int, data: PackedByteArray, reliable: bool, priority: int) -> Dictionary:
+ var flags: int = 0
+ if reliable:
+ flags |= ProtocolFlags.FLAG_RELIABLE
+
+ match priority:
+ 1:
+ flags |= ProtocolFlags.FLAG_PRIORITY_HIGH
+ -1:
+ flags |= ProtocolFlags.FLAG_PRIORITY_LOW
+
+ var message = {
+ "version": 1,
+ "flags": flags,
+ "message_type": message_type,
+ "sequence": get_next_sequence(),
+ "timestamp": Time.get_ticks_msec(),
+ "data": data
+ }
+
+ if reliable:
+ pending_acks[message.sequence] = {
+ "message": message,
+ "timestamp": Time.get_ticks_msec(),
+ "retries": 0
+ }
+
+ return message
+
+func get_next_sequence() -> int:
+ var seq = sequence_number
+ sequence_number = (sequence_number + 1) % 65536
+ return seq
+
+# --- Prediction system ---
+
func store_input(input_id: int, position: Vector3, rotation: Vector3, velocity: Vector3):
if not prediction_enabled:
return
-
+
var input = {
"input_id": input_id,
"position": position,
@@ -559,36 +696,29 @@ func store_input(input_id: int, position: Vector3, rotation: Vector3, velocity:
"velocity": velocity,
"timestamp": Time.get_ticks_msec()
}
-
+
input_buffer.append(input)
-
- # Keep buffer size reasonable
+
if input_buffer.size() > 100:
input_buffer.pop_front()
func update_prediction(delta: float):
if not prediction_enabled or input_buffer.size() == 0:
return
-
- # Apply prediction to local entities
- pass
func reconcile_with_server(server_state: Dictionary):
if not prediction_enabled:
return
-
- # Find the last confirmed input
+
var last_confirmed_input_id = server_state.get("last_input_id", 0)
-
- # Remove processed inputs from buffer
+
var i = 0
while i < input_buffer.size():
if input_buffer[i].input_id <= last_confirmed_input_id:
input_buffer.remove_at(i)
else:
i += 1
-
- # Apply correction if needed
+
var correction = server_state.get("correction", null)
if correction:
emit_signal("player_position_corrected", Vector3(
@@ -597,27 +727,26 @@ func reconcile_with_server(server_state: Dictionary):
correction.z
))
-# Message handlers implementation
-func _handle_heartbeat(data: PackedByteArray):
- # Update last heartbeat time
+# --- Message handlers ---
+
+func _handle_heartbeat(_data: PackedByteArray):
last_heartbeat = 0.0
update_network_quality(true)
func _handle_success(data: PackedByteArray):
+ if connection_state == ConnectionState.HANDSHAKE or connection_state == ConnectionState.CONNECTING:
+ protocol_negotiated = true
+ set_connection_state(ConnectionState.CONNECTED)
+ return
+
var json_string = data.get_string_from_utf8()
if json_string:
var json = JSON.new()
if json.parse(json_string) == OK:
var success_data = json.data
-
if connection_state == ConnectionState.AUTHENTICATING:
- if success_data.get("type") == "authentication":
- auth_token = success_data.get("auth_token", "")
- player_id = success_data.get("player_id", 0)
- session_id = success_data.get("session_id", 0)
- set_connection_state(ConnectionState.CONNECTED)
- emit_signal("authentication_result", true, success_data.get("message", "Authenticated"))
-
+ emit_signal("authentication_result", true, success_data.get("message", "Authenticated"))
+ set_connection_state(ConnectionState.CONNECTED)
emit_signal("message_received", MessageType.MESSAGE_TYPE_SUCCESS, success_data)
func _handle_error(data: PackedByteArray):
@@ -626,19 +755,14 @@ func _handle_error(data: PackedByteArray):
var json = JSON.new()
if json.parse(json_string) == OK:
var error_data = json.data
-
if connection_state == ConnectionState.AUTHENTICATING:
emit_signal("authentication_result", false, error_data.get("message", "Authentication failed"))
-
emit_signal("error_occurred", error_data.get("code", -1), error_data.get("message", "Unknown error"))
emit_signal("message_received", MessageType.MESSAGE_TYPE_ERROR, error_data)
func _handle_chunk_data(data: PackedByteArray):
emit_signal("message_received", MessageType.MESSAGE_TYPE_CHUNK_DATA, data)
-func _handle_terrain_height(data: PackedByteArray):
- emit_signal("message_received", MessageType.MESSAGE_TYPE_TERRAIN_HEIGHT, data)
-
func _handle_biome_data(data: PackedByteArray):
emit_signal("message_received", MessageType.MESSAGE_TYPE_BIOME_DATA, data)
@@ -652,22 +776,10 @@ func _handle_entity_despawn(data: PackedByteArray):
emit_signal("message_received", MessageType.MESSAGE_TYPE_ENTITY_DESPAWN, data)
func _handle_position_correction(data: PackedByteArray):
- var json_string = data.get_string_from_utf8()
- if json_string:
- var json = JSON.new()
- if json.parse(json_string) == OK:
- var correction_data = json.data
-
- # Apply prediction reconciliation
- if correction_data.has("server_state"):
- reconcile_with_server(correction_data.server_state)
-
- # Emit correction signal
- if correction_data.has("position"):
- var pos = correction_data.position
- emit_signal("player_position_corrected", Vector3(pos.x, pos.y, pos.z))
-
- emit_signal("message_received", MessageType.MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, correction_data)
+ emit_signal("message_received", MessageType.MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, data)
+
+func _handle_player_state(data: PackedByteArray):
+ emit_signal("message_received", MessageType.MESSAGE_TYPE_PLAYER_STATE, data)
func _handle_health_update(data: PackedByteArray):
emit_signal("message_received", MessageType.MESSAGE_TYPE_HEALTH_UPDATE, data)
@@ -679,7 +791,17 @@ func _handle_loot_spawn(data: PackedByteArray):
emit_signal("message_received", MessageType.MESSAGE_TYPE_LOOT_SPAWN, data)
func _handle_chat_message(data: PackedByteArray):
- emit_signal("message_received", MessageType.MESSAGE_TYPE_CHAT_MESSAGE, data)
+ var result = {"sender": "", "message": "", "timestamp": 0}
+ if data.size() > 0:
+ var offset = 0
+ result.sender = _read_string_be(data, offset)
+ offset += 2 + result.sender.to_utf8_buffer().size()
+ if offset < data.size():
+ result.message = _read_string_be(data, offset)
+ offset += 2 + result.message.to_utf8_buffer().size()
+ if offset + 8 <= data.size():
+ result.timestamp = _read_uint64_be(data, offset)
+ emit_signal("message_received", MessageType.MESSAGE_TYPE_CHAT_MESSAGE, result)
func _handle_system_message(data: PackedByteArray):
- emit_signal("message_received", MessageType.MESSAGE_TYPE_SYSTEM_MESSAGE, data)
\ No newline at end of file
+ emit_signal("message_received", MessageType.MESSAGE_TYPE_SYSTEM_MESSAGE, data)
diff --git a/clients/godot/network/networks.gd.uid b/clients/godot/network/networks.gd.uid
new file mode 100644
index 0000000..fc295c5
--- /dev/null
+++ b/clients/godot/network/networks.gd.uid
@@ -0,0 +1 @@
+uid://cvmgfnqjs26jl
diff --git a/clients/godot/player/controller.gd b/clients/godot/player/controller.gd
index 53f7b6d..be95b7c 100644
--- a/clients/godot/player/controller.gd
+++ b/clients/godot/player/controller.gd
@@ -29,7 +29,7 @@ var move_input: Vector2 = Vector2.ZERO
var look_input: Vector2 = Vector2.ZERO
# Network
-var network_manager: NetworkManager
+var network_manager
var last_sent_position: Vector3 = Vector3.ZERO
var position_update_rate: float = 0.1 # 10 times per second
var last_update_time: float = 0.0
@@ -68,7 +68,7 @@ func _input(event):
toggle_mouse_capture()
elif event.keycode == KEY_SHIFT:
is_sprinting = true
- elif event.keycode == KEY_CONTROL:
+ elif event.keycode == KEY_CTRL:
toggle_crouch()
func process_movement(delta):
@@ -222,7 +222,7 @@ func send_position_update():
)
last_sent_position = current_position
-func set_network_manager(manager: NetworkManager):
+func set_network_manager(manager):
network_manager = manager
func apply_position_correction(corrected_position: Vector3):
diff --git a/clients/godot/player/controller.gd.uid b/clients/godot/player/controller.gd.uid
new file mode 100644
index 0000000..a8eb174
--- /dev/null
+++ b/clients/godot/player/controller.gd.uid
@@ -0,0 +1 @@
+uid://cuc8f6a3di2d5
diff --git a/clients/godot/project.godot b/clients/godot/project.godot
new file mode 100644
index 0000000..ea75db4
--- /dev/null
+++ b/clients/godot/project.godot
@@ -0,0 +1,110 @@
+; 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
+
+[animation]
+
+compatibility/default_parent_skeleton_in_mesh_instance_3d=true
+
+[application]
+
+config/name="GameServer Client"
+config/description="MMORPG client connecting to gameserver via binary/WebSocket protocol"
+run/main_scene="res://main.tscn"
+config/features=PackedStringArray("4.6")
+config/icon="res://icon.svg"
+
+[autoload]
+
+NetworkManager="*res://network/networks.gd"
+
+[display]
+
+window/size/viewport_width=1280
+window/size/viewport_height=720
+window/stretch/mode="canvas_items"
+window/stretch/aspect="expand"
+
+[input]
+
+move_forward={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
+]
+}
+move_backward={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
+]
+}
+move_left={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
+]
+}
+move_right={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
+]
+}
+jump={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
+]
+}
+sprint={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+crouch={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":67,"key_label":0,"unicode":99,"location":0,"echo":false,"script":null)
+]
+}
+interact={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
+]
+}
+chat={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":84,"key_label":0,"unicode":116,"location":0,"echo":false,"script":null)
+]
+}
+toggle_debug={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194344,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+toggle_wireframe={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194345,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+toggle_prediction={
+"deadzone": 0.5,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194346,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
+]
+}
+
+[layer_names]
+
+3d_physics/layer_1="World"
+3d_physics/layer_2="Player"
+3d_physics/layer_3="Entity"
+3d_physics/layer_4="Interactable"
+
+[rendering]
+
+renderer/rendering_method="gl_compatibility"
+renderer/rendering_method.mobile="gl_compatibility"
+textures/default_filters/anisotropic_filtering_level=4
+anti_aliasing/quality/msaa_3d=2
+environment/defaults/default_clear_color=Color(0.3, 0.4, 0.5, 1)
diff --git a/clients/godot/shaders/water.gdshader b/clients/godot/shaders/water.gdshader
new file mode 100644
index 0000000..a9d8146
--- /dev/null
+++ b/clients/godot/shaders/water.gdshader
@@ -0,0 +1,13 @@
+shader_type canvas_item;
+
+uniform vec4 water_color : source_color = vec4(0.2, 0.4, 0.8, 0.7);
+uniform float wave_speed = 1.0;
+uniform float wave_amplitude = 0.02;
+uniform float wave_frequency = 2.0;
+
+void fragment() {
+ vec2 uv = UV;
+ float wave = sin(uv.x * wave_frequency + TIME * wave_speed) * wave_amplitude;
+ uv.y += wave;
+ COLOR = water_color;
+}
diff --git a/clients/godot/shaders/water.gdshader.uid b/clients/godot/shaders/water.gdshader.uid
new file mode 100644
index 0000000..24a6194
--- /dev/null
+++ b/clients/godot/shaders/water.gdshader.uid
@@ -0,0 +1 @@
+uid://cr4davplssiqu
diff --git a/clients/godot/textures/health_bar.svg b/clients/godot/textures/health_bar.svg
new file mode 100644
index 0000000..b6258cc
--- /dev/null
+++ b/clients/godot/textures/health_bar.svg
@@ -0,0 +1,4 @@
+
diff --git a/clients/godot/ui/uiface.gd b/clients/godot/ui/uiface.gd
index 58b1b04..07620d9 100644
--- a/clients/godot/ui/uiface.gd
+++ b/clients/godot/ui/uiface.gd
@@ -12,7 +12,7 @@ class_name UIManager
@onready var debug_info = $DebugInfo
# Game references
-var network_manager: NetworkManager
+var network_manager
var player_node: Node3D
# UI state
@@ -40,7 +40,7 @@ func _input(event):
elif event.keycode == KEY_TAB:
toggle_minimap()
-func setup_references(network: NetworkManager, player: Node3D):
+func setup_references(network, player: Node3D):
network_manager = network
player_node = player
diff --git a/clients/godot/ui/uiface.gd.uid b/clients/godot/ui/uiface.gd.uid
new file mode 100644
index 0000000..9c30fd2
--- /dev/null
+++ b/clients/godot/ui/uiface.gd.uid
@@ -0,0 +1 @@
+uid://b1387b4oh4cm
diff --git a/clients/godot/world/chunk.gd.uid b/clients/godot/world/chunk.gd.uid
new file mode 100644
index 0000000..1fc9d66
--- /dev/null
+++ b/clients/godot/world/chunk.gd.uid
@@ -0,0 +1 @@
+uid://cp83g6n426w1c
diff --git a/clients/godot/world/terrain.gd.uid b/clients/godot/world/terrain.gd.uid
new file mode 100644
index 0000000..d39dae6
--- /dev/null
+++ b/clients/godot/world/terrain.gd.uid
@@ -0,0 +1 @@
+uid://bc3l0s7jj3s7k
diff --git a/clients/godot/world/world.gd b/clients/godot/world/world.gd
index f7cddc5..0faccea 100644
--- a/clients/godot/world/world.gd
+++ b/clients/godot/world/world.gd
@@ -1,18 +1,15 @@
extends Node3D
-class_name ChunkLoader
-
-func load_chunk(chunk_x: int, chunk_z: int, chunk_size: int) -> Dictionary:
- # This would load chunk data from disk or network
- # For now, return empty data
- return {
- "chunk_x": chunk_x,
- "chunk_z": chunk_z,
- "heightmap": [],
- "biomes": []
- }
-
-class_name GameWorld
+class ChunkLoader:
+ func load_chunk(chunk_x: int, chunk_z: int, chunk_size: int) -> Dictionary:
+ # This would load chunk data from disk or network
+ # For now, return empty data
+ return {
+ "chunk_x": chunk_x,
+ "chunk_z": chunk_z,
+ "heightmap": [],
+ "biomes": []
+ }
# World parameters
@export var chunk_size: int = 16
diff --git a/clients/godot/world/world.gd.uid b/clients/godot/world/world.gd.uid
new file mode 100644
index 0000000..4c111eb
--- /dev/null
+++ b/clients/godot/world/world.gd.uid
@@ -0,0 +1 @@
+uid://d1wm6d3xe6pg7
diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp
index 4d02ef5..0e8c178 100644
--- a/include/process/ProcessPool.hpp
+++ b/include/process/ProcessPool.hpp
@@ -7,33 +7,13 @@
#include
#include
#include
-#include
-#include
#include
#include
-#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
#include
-using asio::ip::tcp;
-using asio::awaitable;
-using asio::co_spawn;
-using asio::detached;
-using asio::redirect_error;
-using asio::use_awaitable;
-
-#include
-
#include "logging/Logger.hpp"
#include "config/ConfigManager.hpp"
#include "network/BinaryProtocol.hpp"
diff --git a/src/network/ClientListener.cpp b/src/network/ClientListener.cpp
index b56a02a..a03d8ea 100644
--- a/src/network/ClientListener.cpp
+++ b/src/network/ClientListener.cpp
@@ -17,9 +17,7 @@ void ClientListener::Start() {
std::thread([this]() { manager_->Start(); }).detach();
channel_->Start([this](const IPCEnvelope& env) { onMasterMessage(env); });
ioThread_ = std::thread([this]() {
- Logger::Trace("ClientListener::Start: worker {} entering io_context::run()", workerId_);
io_.run();
- Logger::Trace("ClientListener::Start: worker {} exiting io_context::run()", workerId_);
});
}
@@ -41,7 +39,6 @@ void ClientListener::onMasterMessage(const IPCEnvelope& env) {
}
void ClientListener::sendToMaster(uint32_t correlationId, uint64_t sessionId, uint16_t messageType, const std::vector& body) {
- Logger::Trace("ClientListener::sendToMaster: corrId={}, session={}, type={}, bodySize={}", correlationId, sessionId, messageType, body.size());
IPCEnvelope env;
env.correlationId = correlationId;
env.sessionId = sessionId;
diff --git a/src/network/ConnectionManager.cpp b/src/network/ConnectionManager.cpp
index fff1a16..fb340b9 100644
--- a/src/network/ConnectionManager.cpp
+++ b/src/network/ConnectionManager.cpp
@@ -46,11 +46,7 @@ bool ConnectionManager::Start() {
}
void ConnectionManager::Shutdown() {
- if (!running_.exchange(false))
- {
- Logger::Trace("ConnectionManager::Shutdown already executed, wait finish...");
- return;
- }
+ if (!running_.exchange(false)) return;
acceptor_.close();
std::vector> sessions;
{
@@ -63,7 +59,6 @@ void ConnectionManager::Shutdown() {
ioContext_.stop();
ioContext_.poll();
for (auto& t : workerThreads_) if (t.joinable()) t.join();
- Logger::Trace("ConnectionManager::Shutdown complete");
}
void ConnectionManager::initSessionFactory()
@@ -234,7 +229,6 @@ void ConnectionManager::doAccept() {
void ConnectionManager::onClientMessage(uint64_t sessionId, uint16_t type, const std::vector& data) {
uint32_t corrId = nextCorrelationId_++;
pendingReplies_[corrId] = {sessionId, type};
- Logger::Trace("ConnectionManager::onClientMessage: sessionId={}, type={}, corrId={}, dataSize={}", sessionId, type, corrId, data.size());
masterSender_(corrId, sessionId, type, data);
}
@@ -436,11 +430,6 @@ nlohmann::json ConnectionManager::binaryToJson(uint16_t type, const std::vector<
}
void ConnectionManager::OnMasterPush(uint64_t sessionId, const std::vector& data) {
- Logger::Trace("ConnectionManager::OnMasterPush: sessionId={}, dataSize={}", sessionId, data.size());
- if (data.size() >= 4) {
- Logger::Trace("First 4 bytes: {:02x} {:02x} {:02x} {:02x}",
- data[0], data[1], data[2], data[3]);
- }
auto sendToSession = [&](const std::shared_ptr& session, const std::vector& payload) {
if (session->GetProtocolMode() == ProtocolMode::Binary) {
if (payload.size() >= 2) {
@@ -475,6 +464,5 @@ void ConnectionManager::OnMasterPush(uint64_t sessionId, const std::vector& data) {
void MasterRouter::WireCallbacks() {
gameLogic_.SetSendAuthenticationResponseCallback([this](uint64_t session_id, const std::string& message, uint64_t player_id) {
- Logger::Trace("MasterRouter auth response: session={}, player={}, message={}", session_id, player_id, message);
BinaryProtocol::BinaryWriter w;
w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION);
w.WriteUInt64(gameLogic_.GetCurrentTimestamp());
diff --git a/src/process/IPCChannel.cpp b/src/process/IPCChannel.cpp
index 10a3690..a1edb6e 100644
--- a/src/process/IPCChannel.cpp
+++ b/src/process/IPCChannel.cpp
@@ -68,13 +68,9 @@ void IPCChannel::doRead() {
if (stopped_) return;
auto self = shared_from_this();
auto lengthBuf = std::make_shared(0);
- Logger::Trace("IPCChannel::doRead: starting async_read on fd={}, stopped={}", stream_.native_handle(), stopped_.load());
asio::async_read(stream_, asio::buffer(lengthBuf.get(), sizeof(uint32_t)),
[self, lengthBuf](std::error_code ec, std::size_t) {
- if (ec || self->stopped_) {
- Logger::Trace("IPCChannel::doRead: async_read error ec={}, stopped={}", ec.value(), self->stopped_.load());
- return;
- }
+ if (ec || self->stopped_) return;
uint32_t msgLen = ntohl(*lengthBuf);
if (msgLen == 0 || msgLen > BinaryProtocol::MAX_MESSAGE_SIZE) {
self->doRead();
@@ -85,7 +81,6 @@ void IPCChannel::doRead() {
[self, msgBuffer](std::error_code ec, std::size_t) {
if (ec || self->stopped_) return;
IPCEnvelope env = decodeFrame(msgBuffer->data(), msgBuffer->size());
- Logger::Trace("IPCChannel::doRead: received envelope, corrId={}, session={}, type={}, payloadSize={}", env.correlationId, env.sessionId, env.messageType, env.payload.size());
if (self->onMessage_) {
self->onMessage_(env);
}
@@ -112,7 +107,6 @@ void IPCChannel::doWrite() {
Logger::Error("IPCChannel::doWrite failed: {}", ec.message());
return;
}
- Logger::Trace("IPCChannel::doWrite: wrote {} bytes", data.size());
self->doWrite();
});
}
@@ -128,7 +122,6 @@ void IPCChannel::SendAsync(const IPCEnvelope& envelope) {
return;
}
}
- Logger::Trace("IPCChannel::SendAsync: posting doWrite, frameSize={}, fd={}", frame.size(), stream_.native_handle());
asio::post(io_, [self = shared_from_this()]() {
self->doWrite();
});
diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp
index 698db80..e57a8de 100644
--- a/src/process/ProcessPool.cpp
+++ b/src/process/ProcessPool.cpp
@@ -66,11 +66,8 @@ void ProcessPool::Run() { io_.run(); }
void ProcessPool::Shutdown() {
if (!running_.exchange(false)) return;
- Logger::Trace("ProcessPool::Shutdown: running...");
for (auto& w : workers_) w->Shutdown();
- Logger::Trace("ProcessPool::Shutdown: workers shutdown finished");
io_.stop();
- Logger::Trace("ProcessPool::Shutdown: io_.stop finished");
}
void ProcessPool::SetWorker(std::function func) {
@@ -109,7 +106,6 @@ void ProcessPool::doSpawnWorkers() {
if (std::string(err.what()) == "worker_function") {
int fd = worker->GetMasterFd();
worker_(globalId, groups_[gi], fd);
- Logger::Trace("ProcessPool::doSpawnWorkers: worker {} exiting cleanly", globalId);
_exit(0);
} else {
Logger::Error("ProcessPool::doSpawnWorkers: worker start failed: {}", err.what());