extends Node # Standard Nakama Configuration var nakama_server_key = "defaultkey" var nakama_host = "localhost" var nakama_port = 7350 var nakama_scheme = "http" # Core Nakama Variables var client: NakamaClient var session: NakamaSession var socket: NakamaSocket var bridge: NakamaMultiplayerBridge # Signals signal connected_to_nakama signal connection_failed(error_message) signal match_joined(match_id) signal match_join_error(error_message) # Current Match Info var current_match_id: String = "" func _ready(): # Initialize the Nakama Client _init_client() # Ensure we process network events set_process(true) func _init_client(): client = Nakama.create_client(nakama_server_key, nakama_host, nakama_port, nakama_scheme) func set_server(host: String, port: int = 7350): nakama_host = host nakama_port = port _init_client() print("[NakamaManager] Server updated to: ", nakama_host, ":", nakama_port) func _process(_delta): # If using the standard socket adapter, it needs polling in some versions if socket: pass func connect_to_nakama_async(email: String = "", password: String = "") -> bool: if is_connected_to_nakama(): print("Already connected to Nakama.") emit_signal("connected_to_nakama") return true # 1. Authenticate if email == "": var device_id = OS.get_unique_id() # For testing, append a random number to device ID to simulate unique users on one machine device_id = device_id + str(randi()) session = await client.authenticate_device_async(device_id) else: session = await client.authenticate_email_async(email, password) if session.is_exception(): printerr("Auth Error: ", session.get_exception().message) emit_signal("connection_failed", session.get_exception().message) return false # 2. Connect Socket socket = Nakama.create_socket_from(client) var socket_result = await socket.connect_async(session) if socket_result.is_exception(): printerr("Socket Error: ", socket_result.get_exception().message) emit_signal("connection_failed", socket_result.get_exception().message) return false # 3. Initialize Multiplayer Bridge # This links Nakama's socket to Godot's High-Level Multiplayer API bridge = NakamaMultiplayerBridge.new(socket) # Connect bridge signals bridge.match_joined.connect(_on_bridge_match_joined) bridge.match_join_error.connect(_on_bridge_match_join_error) # CRITICAL: Set Godot's multiplayer peer to the Nakama bridge # This allows @rpc functions to work over Nakama multiplayer.set_multiplayer_peer(bridge.multiplayer_peer) print("Connected to Nakama and Bridge Initialized.") emit_signal("connected_to_nakama") return true # --- Match Management --- func host_game(): if not bridge: printerr("Cannot host: Bridge not initialized") return print("Hosting match via Nakama Bridge...") var result = await bridge.create_match() if result and result.is_exception(): emit_signal("match_join_error", result.get_exception().message) func join_game(match_id: String): if not bridge: printerr("Cannot join: Bridge not initialized") return print("Joining match: ", match_id) var result = await bridge.join_match(match_id) if result and result.is_exception(): emit_signal("match_join_error", result.get_exception().message) # --- Callbacks --- func _on_bridge_match_joined() -> void: current_match_id = bridge.match_id print("Successfully joined match: ", current_match_id) emit_signal("match_joined", current_match_id) func _on_bridge_match_join_error(error) -> void: printerr("Bridge failed to join match: ", error.message) emit_signal("match_join_error", error.message) func is_connected_to_nakama() -> bool: return socket != null and socket.is_connected_to_host() # --- Match Listing --- func list_matches_async() -> Array: """Query available matches from Nakama server.""" if not client: push_error("Cannot list matches: Client not initialized") return [] if not session or session.is_expired(): push_error("Cannot list matches: No valid session") return [] print("Querying matches from Nakama server...") # Query matches - min 0, max 8 players, limit 20, authoritative=false for relayed matches var result = await client.list_matches_async(session, 0, 8, 20, false, "", "") if result.is_exception(): printerr("Failed to list matches: ", result.get_exception().message) return [] var rooms: Array = [] if result.matches: print("Found %d matches" % result.matches.size()) for match_data in result.matches: print(" Match: ", match_data.match_id, " - Size: ", match_data.size) # Use first 8 chars of match ID as room identifier since Nakama doesn't store custom names var short_id = match_data.match_id.substr(0, 8) if match_data.match_id.length() > 8 else match_data.match_id rooms.append({ "match_id": match_data.match_id, "room_name": short_id, "host_name": "Host", "player_count": match_data.size if match_data.size else 1, "max_players": 4 }) else: print("No matches found") return rooms func _exit_tree(): if socket: socket.close()