diff --git a/scripts/network/client_main.gd b/scripts/network/client_main.gd index e0ffaef..5ff92c9 100644 --- a/scripts/network/client_main.gd +++ b/scripts/network/client_main.gd @@ -22,6 +22,10 @@ extends Node3D var remote_players: Dictionary = {} var connected: bool = false +# Our local player node (created when server spawns us) +var _local_player: Node = null +var _camera: Camera3D = null + # --------------------------------------------------------------------------- # Lifecycle # --------------------------------------------------------------------------- @@ -45,8 +49,17 @@ func _connect_to_server() -> void: _connect_to_server() return - # Setup camera and lights - _setup_scene() + # Setup camera (fixed overhead until a player is spawned) + _camera = _make_camera() + add_child(_camera) + + # Ambient light so we can see the box meshes + var light := DirectionalLight3D.new() + light.light_energy = 1.0 + light.shadow_enabled = true + light.position = Vector3(10, 20, 10) + add_child(light) + light.look_at(Vector3.ZERO) connected = true @@ -57,31 +70,41 @@ func _connect_to_server() -> void: print("[ClientMain] Connected to server. Peer ID: %d" % multiplayer.get_unique_id()) -func _setup_scene() -> void: - # Simple 3/4 top-down camera for Phase 0 testing - var camera := Camera3D.new() - camera.current = true - camera.position = Vector3(0, 16, 12) - camera.rotation_degrees.x = -55 - add_child(camera) - - # Ambient light so we can see the box meshes - var light := DirectionalLight3D.new() - light.light_energy = 1.0 - light.shadow_enabled = true - light.position = Vector3(10, 20, 10) - add_child(light) - light.look_at(Vector3.ZERO) +func _make_camera() -> Camera3D: + var cam := Camera3D.new() + cam.current = true + cam.position = Vector3(0, 16, 12) + cam.rotation_degrees.x = -55 + return cam + +func _process(_delta: float) -> void: + # Make camera follow local player if we have one + if _local_player and _camera: + _camera.position = _local_player.position + Vector3(0, 16, 12) + # Always look down at the player + var look_target := _local_player.position + look_target.y = _local_player.position.y + 2.0 + _camera.look_at(look_target) # --------------------------------------------------------------------------- # Player replication handlers (called when server broadcasts via RPC) # --------------------------------------------------------------------------- func _on_remote_player_spawned(peer_id: int, pos: Vector3) -> void: if peer_id == multiplayer.get_unique_id(): - # This is OUR player — the server has authority over it, - # but we don't need to create a duplicate. Our local player - # node is managed by the server's authority system. - # The player.gd script will handle input on this peer. + # THIS IS OUR PLAYER — create a local player node and attach camera. + # The player.gd script handles WASD input and sends position to server. + if _local_player: + push_warning("[ClientMain] Local player already exists, respawning") + _local_player.queue_free() + + _local_player = player_scene.instantiate() + _local_player.name = "LocalPlayer" + _local_player.position = pos + # Set authority so player.gd detects is_local = true and enables input + _local_player.set_multiplayer_authority(peer_id) + add_child(_local_player, true) + + print("[ClientMain] Spawned LOCAL player at (%.1f, %.1f)" % [pos.x, pos.z]) return if peer_id in remote_players: @@ -107,6 +130,11 @@ func _on_remote_player_spawned(peer_id: int, pos: Vector3) -> void: func _on_remote_player_despawned(peer_id: int) -> void: if peer_id == multiplayer.get_unique_id(): + # Our player was despawned + if _local_player: + _local_player.queue_free() + _local_player = null + print("[ClientMain] Local player despawned") return if peer_id in remote_players: @@ -118,7 +146,9 @@ func _exit_tree() -> void: if connected: NetworkManager.stop() - # Clean up any remaining remote players + # Clean up any remaining players + if _local_player: + _local_player.queue_free() for p in remote_players.values(): p.queue_free() remote_players.clear()