## Client Main — Client entry point for testing. ## ## Connects to the server. Receives broadcast_spawn_player / ## broadcast_despawn_player RPCs from the server and creates ## visual player nodes locally so each client sees every other player. ## ## Phase 0: simple position replication via RPC + box mesh player nodes. extends Node3D # --------------------------------------------------------------------------- # Exports # --------------------------------------------------------------------------- @export var server_host: String = "68.202.6.107" @export var server_port: int = 34197 @export var player_scene: PackedScene = preload("res://scenes/player.tscn") # --------------------------------------------------------------------------- # State # --------------------------------------------------------------------------- # remote_players[peer_id] = Node3D — visual representation of other players var remote_players: Dictionary = {} var connected: bool = false # --------------------------------------------------------------------------- # Lifecycle # --------------------------------------------------------------------------- func _ready() -> void: # Connect to server after a short delay await get_tree().create_timer(0.5).timeout _connect_to_server() func _connect_to_server() -> void: if OS.has_environment("SERVER_HOST"): server_host = OS.get_environment("SERVER_HOST") if OS.has_environment("SERVER_PORT"): server_port = int(OS.get_environment("SERVER_PORT")) print("[ClientMain] Connecting to %s:%d ..." % [server_host, server_port]) var err: Error = NetworkManager.join_server(server_host, server_port) if err != OK: push_error("[ClientMain] Connection failed: %s" % error_string(err)) await get_tree().create_timer(2.0).timeout _connect_to_server() return # Setup camera and lights _setup_scene() connected = true # Connect replication signals from NetworkManager # These fire when the server broadcasts spawn_player / despawn_player NetworkManager.remote_player_spawned.connect(_on_remote_player_spawned) NetworkManager.remote_player_despawned.connect(_on_remote_player_despawned) 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) light.look_at(Vector3.ZERO) add_child(light) # --------------------------------------------------------------------------- # 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. return if peer_id in remote_players: push_warning("[ClientMain] Remote player %d already exists, skipping" % peer_id) return # Create a remote player node for visualization var player: Node3D if player_scene: player = player_scene.instantiate() else: player = Node3D.new() player.set_script(preload("res://scripts/network/player.gd")) player.name = "RemotePlayer_%d" % peer_id player.set_multiplayer_authority(peer_id) player.position = pos add_child(player, true) remote_players[peer_id] = player print("[ClientMain] Spawned remote player %d at (%.1f, %.1f)" % [peer_id, pos.x, pos.z]) func _on_remote_player_despawned(peer_id: int) -> void: if peer_id == multiplayer.get_unique_id(): return if peer_id in remote_players: remote_players[peer_id].queue_free() remote_players.erase(peer_id) print("[ClientMain] Despawned remote player %d" % peer_id) func _exit_tree() -> void: if connected: NetworkManager.stop() # Clean up any remaining remote players for p in remote_players.values(): p.queue_free() remote_players.clear()