## 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 func _ready() -> void: # Set this node as the multiplayer authority. multiplayer.multiplayer_peer = _create_server_peer() multiplayer.server = self if 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) 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 ENetMultiplayerPeer() return peer func _process(delta: float) -> void: if not is_running: return # Handle disconnect events. if multiplayer.peer_disconnected.is_connected(_on_peer_disconnected): pass # already connected # 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: Node = 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: Node = 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. var peer_ids: Array = multiplayer.get_peers() for pid in peer_ids: 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 := preload("res://scripts/player.tscn").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: Node = players[peer_id] if player.is_inside_tree(): player.queue_free() players.erase(peer_id) # --- RPCs --- @rpc("any_peer", "call_remote", "reliable") func _kick(peer_id: int, reason: String) -> void: print("[Server] Kicking peer %d: %s" % [peer_id, reason]) rpc_id(peer_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)