## ServerMain - Dedicated server root node. ## Accepts ENet connections, spawns Player nodes for each peer, ## and runs the simulation tick loop at 20 Hz. extends Node ## Maximum number of concurrent players. const MAX_PLAYERS := 16 ## Server tick rate (Hz). const TICK_RATE := 20 ## Tick duration in seconds. const TICK_DURATION := 1.0 / TICK_RATE ## ENet server port. const SERVER_PORT := 7777 ## Network peer used for ENet communication. var network_peer: ENetMultiplayerPeer ## Map of peer_id -> Player node. var players: Dictionary = {} ## Current tick counter (server-authoritative). var tick: int = 0 ## Whether the server is running. var is_running: bool = false ## Accumulated real time for fixed timestep simulation. var accumulator: float = 0.0 ## Player scene resource (loaded once). var _player_scene: PackedScene func _ready() -> void: # Preload the player scene once. _player_scene = preload("res://scenes/player.tscn") # Create and assign the ENet server peer. network_peer = _create_server_peer() if network_peer: multiplayer.multiplayer_peer = network_peer is_running = true print("[Server] Started on port %d" % SERVER_PORT) else: printerr("[Server] Failed to create ENet peer.") get_tree().quit(1) # --------------------------------------------------------------------------- # Server creation # --------------------------------------------------------------------------- func _create_server_peer() -> ENetMultiplayerPeer: var peer := ENetMultiplayerPeer.new() var error := peer.create_server(SERVER_PORT, MAX_PLAYERS) if error != OK: printerr("[Server] create_server error: %d" % error) return null return peer # --------------------------------------------------------------------------- # Fixed-timestep simulation loop # --------------------------------------------------------------------------- func _process(delta: float) -> void: if not is_running: return # Fixed timestep accumulator. accumulator += delta while accumulator >= TICK_DURATION: accumulator -= TICK_DURATION _tick() func _tick() -> void: """Main server simulation tick (called at TICK_RATE).""" tick += 1 # Process player inputs and update state. for peer_id in players: var player: Node3D = players[peer_id] if player and player.has_method("process_server_input"): player.process_server_input(tick) # Broadcast snapshot to all clients. _broadcast_snapshot() func _broadcast_snapshot() -> void: """Broadcast the current authoritative state to all connected clients.""" var snapshot_data := { "tick": tick, "players": {}, } for peer_id in players: var player: Node3D = players[peer_id] if player and player.has_method("get_server_state"): snapshot_data.players[peer_id] = player.get_server_state() # Send snapshot to all peers except the server itself (peer_id == 1). var peer_ids: Array = multiplayer.get_peers() for pid in peer_ids: if pid == 1: # 1 is the server itself continue rpc_id(pid, "_receive_snapshot", snapshot_data) # Remote: clients receive snapshots. @rpc("any_peer", "call_remote", "reliable") func _receive_snapshot(snapshot_data: Dictionary) -> void: pass # Handled by client scene. # --------------------------------------------------------------------------- # Connection lifecycle # --------------------------------------------------------------------------- func _on_peer_connected(peer_id: int) -> void: print("[Server] Peer %d connected." % peer_id) if players.size() >= MAX_PLAYERS: print("[Server] Max players reached. Disconnecting peer %d." % peer_id) rpc_id(peer_id, "_kick", "Server is full.") return # Spawn a Player node for this peer. var player: Node3D = _player_scene.instantiate() player.name = "Player_%d" % peer_id player.peer_id = peer_id add_child(player) players[peer_id] = player print("[Server] Spawned player node for peer %d." % peer_id) func _on_peer_disconnected(peer_id: int) -> void: print("[Server] Peer %d disconnected." % peer_id) if players.has(peer_id): var player: Node3D = players[peer_id] if player.is_inside_tree(): player.queue_free() players.erase(peer_id) # --------------------------------------------------------------------------- # RPCs # --------------------------------------------------------------------------- @rpc("any_peer", "call_remote", "reliable") func _kick(reason: String) -> void: print("[Server] Kicking caller: %s" % reason) rpc_id(multiplayer.get_remote_sender_id(), "_disconnect", reason) @rpc("call_remote", "reliable") func _disconnect(reason: String) -> void: print("[Server] Disconnecting: %s" % reason) get_tree().quit(0) # --------------------------------------------------------------------------- # Signal connections # --------------------------------------------------------------------------- func _enter_tree() -> void: multiplayer.peer_connected.connect(_on_peer_connected) multiplayer.peer_disconnected.connect(_on_peer_disconnected)