diff --git a/splunk/game-logic/game_manager.gd b/splunk/game-logic/game_manager.gd index 97093c7..06ac956 100644 --- a/splunk/game-logic/game_manager.gd +++ b/splunk/game-logic/game_manager.gd @@ -1,9 +1,16 @@ extends Node +var players: Array[int] = [] + +var player_spawner: MultiplayerSpawner + @onready var debug_ui: PackedScene = preload("res://ui/multiplayer-debug-ui/multiplayer-debug-ui.tscn") var debug_ui_instance + func _ready() -> void: + get_tree().root.get_node("Lobby/PlayerSpawner") as MultiplayerSpawner + debug_ui_instance = debug_ui.instantiate() get_tree().root.add_child.call_deferred(debug_ui_instance) debug_ui_instance.hide() @@ -12,3 +19,12 @@ func _ready() -> void: func _input(event: InputEvent) -> void: if event.is_action_pressed("toggle_watch"): debug_ui_instance.visible = !debug_ui_instance.visible + + +@rpc("any_peer", "call_local", "reliable") +func request_server_to_spawn_player(peer_id: int) -> void: + var data: Dictionary = {} + + data["peer_id"] = peer_id + + player_spawner.spawn(data) diff --git a/splunk/networking/player_spawner.gd b/splunk/networking/player_spawner.gd new file mode 100644 index 0000000..84a2e7f --- /dev/null +++ b/splunk/networking/player_spawner.gd @@ -0,0 +1,42 @@ +extends MultiplayerSpawner + +@onready var player_scene: PackedScene = load("res://player/Player.tscn") + + +func _ready() -> void: + spawn_function = spawn_player + + +func spawn_player(data: Dictionary) -> Player: + var new_player = player_scene.instantiate() as Player + + var peer_id = str(data["peer_id"]) + + new_player.set_multiplayer_authority(peer_id, true) + new_player.set_player_name = peer_id + new_player.position = Vector3.UP + + GameManager.players.append(peer_id) + + return new_player + + + #var steam_id_of_player = peer_to_steam_id_map.get(peer_id) + #if steam_id_of_player == null: + #print("!!! [%s] CRITICAL: Cannot spawn player for Peer %s, not in map." % [multiplayer.get_unique_id(), peer_id]) + #return + # + #if players.has(peer_id): return +# + #var player_name = Steam.getFriendPersonaName(steam_id_of_player) + #print("-> [%s] Spawning character for Peer %s (Name: %s)." % [multiplayer.get_unique_id(), peer_id, player_name]) + # + #var new_player = player_scene.instantiate() as Player + #new_player.name = str(peer_id) + # + #players[peer_id] = new_player + #add_child(new_player) + #new_player.position = Vector3.UP + #new_player.set_player_name(player_name) + #new_player.set_multiplayer_authority(peer_id, true) + #new_player.setup_player() diff --git a/splunk/networking/player_spawner.gd.uid b/splunk/networking/player_spawner.gd.uid new file mode 100644 index 0000000..b495f1f --- /dev/null +++ b/splunk/networking/player_spawner.gd.uid @@ -0,0 +1 @@ +uid://bbhx0fwgcgjr4 diff --git a/splunk/networking/steam-manager.gd b/splunk/networking/steam-manager.gd index 7bc0f45..34e177f 100644 --- a/splunk/networking/steam-manager.gd +++ b/splunk/networking/steam-manager.gd @@ -2,18 +2,14 @@ extends Node @onready var player_scene: PackedScene = preload("res://player/Player.tscn") -# -- Game State Variables -- -var players: Dictionary = {} # Key: peer_id, Value: player_node -var peer_to_steam_id_map: Dictionary = {} # Key: peer_id, Value: steam_id +var players: Array[int] = [] -# -- Steam Variables -- var steam_id: int = 0 var steam_username: String = "" var lobby_id: int = 0 var steam_initialized: bool = false -var steam_app_id: int = 480 # Spacewar App ID for testing +var steam_app_id: int = 480 -# -- Godot Variables -- var peer: SteamMultiplayerPeer @@ -26,13 +22,11 @@ func _ready() -> void: print("!!! Steam did not initialize. Multiplayer will be disabled.") return - # Connect Steam lobby signals Steam.lobby_created.connect(_on_lobby_created) Steam.lobby_joined.connect(_on_lobby_joined) Steam.lobby_chat_update.connect(_on_lobby_chat_update) Steam.p2p_session_request.connect(_on_p2p_session_request) - # Connect Godot multiplayer signals multiplayer.peer_connected.connect(_on_peer_connected) multiplayer.peer_disconnected.connect(_on_peer_disconnected) multiplayer.connected_to_server.connect(_on_connected_to_server) @@ -41,19 +35,20 @@ func _ready() -> void: check_command_line() multiplayer.server_relay = true + + func _process(delta): if not steam_initialized: return Steam.run_callbacks() -# ----------------------------------------------------------------------------- -# CORE LOGIC: STEP 1 - HOST CREATION -# ----------------------------------------------------------------------------- + func create_lobby(): print("[HOST] 1. Attempting to create lobby...") Steam.createLobby(Steam.LOBBY_TYPE_FRIENDS_ONLY, 4) + func _on_lobby_created(connect: int, this_lobby_id: int): if connect != 1: print("[HOST] !!! Lobby creation failed.") @@ -68,21 +63,15 @@ func _on_lobby_created(connect: int, this_lobby_id: int): setup_multiplayer_peer(true) print("[HOST] 3. Multiplayer host started (My Peer ID is always 1).") - # Register self - peer_to_steam_id_map[1] = steam_id - print("[HOST] 4. Host (Peer 1) registered in the ID map.") - - # Spawn self - spawn_player.rpc(1) + #players.append(peer.get) + GameManager.request_server_to_spawn_player.rpc_id(1, 1) -# ----------------------------------------------------------------------------- -# CORE LOGIC: STEP 2 - CLIENT CONNECTION -# ----------------------------------------------------------------------------- func join_lobby(this_lobby_id: int): print("[CLIENT] 1. Attempting to join lobby (ID: %s)..." % this_lobby_id) Steam.joinLobby(this_lobby_id) + func _on_lobby_joined(this_lobby_id: int, _p, _l, response: int): if response != Steam.CHAT_ROOM_ENTER_RESPONSE_SUCCESS: print("[CLIENT] !!! Failed to join lobby: %s" % get_join_fail_reason(response)) @@ -95,76 +84,18 @@ func _on_lobby_joined(this_lobby_id: int, _p, _l, response: int): print("[CLIENT] 3. I am a client, creating multiplayer peer to connect to host.") setup_multiplayer_peer(false) + func _on_connected_to_server(): print("[CLIENT] 4. Successfully connected to host's multiplayer peer.") print("[CLIENT] 5. Sending my info to the server for registration...") - register_player_on_server.rpc_id(1, steam_id) + GameManager.request_server_to_spawn_player.rpc_id(1, multiplayer.get_unique_id()) -# ----------------------------------------------------------------------------- -# CORE LOGIC: STEP 3 - SERVER REGISTRATION & SPAWNING -# ----------------------------------------------------------------------------- -@rpc("any_peer") -func register_player_on_server(new_player_steam_id: int): - if not multiplayer.is_server(): return - - var new_player_peer_id = multiplayer.get_remote_sender_id() - print("[SERVER] 6. Received registration request from Peer %s." % new_player_peer_id) - - peer_to_steam_id_map[new_player_peer_id] = new_player_steam_id - print("[SERVER] 7. Peer %s (Steam ID: %s) added to ID map." % [new_player_peer_id, new_player_steam_id]) - - # --- NEW STEP --- - # Sync the completed map to the new client BEFORE spawning players for them - print("[SERVER] 8. Sending complete ID map to new client (Peer %s)." % new_player_peer_id) - sync_id_map.rpc_id(new_player_peer_id, peer_to_steam_id_map) - - # Tell the new client about all players already in the game - print("[SERVER] 9. Telling Peer %s to spawn existing players..." % new_player_peer_id) - for existing_peer_id in players: - spawn_player.rpc_id(new_player_peer_id, existing_peer_id) - - # Tell EVERYONE to spawn the new player - print("[SERVER] 10. Telling ALL peers to spawn the new player (Peer %s)." % new_player_peer_id) - spawn_player.rpc(new_player_peer_id) - -# --- NEW FUNCTION --- -# This RPC is sent by the server to a new client to give them the ID map -@rpc("authority") -func sync_id_map(map_from_server: Dictionary): - print("-> [%s] Received ID map from server." % multiplayer.get_unique_id()) - peer_to_steam_id_map = map_from_server - - -@rpc("any_peer", "call_local") -func spawn_player(peer_id: int): - var steam_id_of_player = peer_to_steam_id_map.get(peer_id) - if steam_id_of_player == null: - print("!!! [%s] CRITICAL: Cannot spawn player for Peer %s, not in map." % [multiplayer.get_unique_id(), peer_id]) - return - - if players.has(peer_id): return - - var player_name = Steam.getFriendPersonaName(steam_id_of_player) - print("-> [%s] Spawning character for Peer %s (Name: %s)." % [multiplayer.get_unique_id(), peer_id, player_name]) - - var new_player = player_scene.instantiate() as Player - new_player.name = str(peer_id) - - players[peer_id] = new_player - add_child(new_player) - new_player.position = Vector3.UP - new_player.set_player_name(player_name) - new_player.set_multiplayer_authority(peer_id, true) - new_player.setup_player() - -# ----------------------------------------------------------------------------- -# UTILITY AND CALLBACKS (Unchanged) -# ----------------------------------------------------------------------------- func _on_p2p_session_request(steam_id_remote: int) -> void: print("[P2P] ==> Session request from: %s. Accepting." % steam_id_remote) Steam.acceptP2PSessionWithUser(steam_id_remote) + func init_steam() -> bool: var response: Dictionary = Steam.steamInitEx() if response['status'] > 0: @@ -175,11 +106,13 @@ func init_steam() -> bool: print("Steam initialized successfully for %s (ID: %s)." % [steam_username, steam_id]) return true + func setup_multiplayer_peer(is_host: bool = false) -> void: if multiplayer.multiplayer_peer and multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_DISCONNECTED: multiplayer.multiplayer_peer.close() peer = SteamMultiplayerPeer.new() + if is_host: peer.create_host(0) else: @@ -188,20 +121,22 @@ func setup_multiplayer_peer(is_host: bool = false) -> void: multiplayer.multiplayer_peer = peer + func _on_peer_connected(id: int): print("[INFO] Peer %s has established a multiplayer session." % id) + #players.append() func _on_peer_disconnected(id: int): print("[INFO] Peer %s has disconnected." % id) if players.has(id): - players[id].queue_free() + #players[id].queue_free() players.erase(id) - if peer_to_steam_id_map.has(id): - peer_to_steam_id_map.erase(id) - + + func _on_connection_failed() -> void: print("[CLIENT] !!! Connection to the host failed.") + func _on_lobby_chat_update(_l_id, user_changed_id: int, _u_m_c_id, chat_state: int): var state_string = "UNKNOWN" match chat_state: @@ -210,6 +145,7 @@ func _on_lobby_chat_update(_l_id, user_changed_id: int, _u_m_c_id, chat_state: i Steam.CHAT_MEMBER_STATE_CHANGE_DISCONNECTED: state_string = "DISCONNECTED" print("[LOBBY INFO] User %s has %s the lobby." % [user_changed_id, state_string]) + func check_command_line() -> void: var args: Array = OS.get_cmdline_args() if args.size() > 1 and args[0] == "+connect_lobby": @@ -217,6 +153,7 @@ func check_command_line() -> void: print("[CMD] Command line join request for lobby ID: %s" % args[1]) join_lobby(int(args[1])) + func get_join_fail_reason(response: int) -> String: match response: 1: return "Doesnt Exist" diff --git a/splunk/player/Player.tscn b/splunk/player/Player.tscn index 277f1a6..b4ed1ee 100644 --- a/splunk/player/Player.tscn +++ b/splunk/player/Player.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=6 format=3 uid="uid://csmfxg011xisf"] +[gd_scene load_steps=7 format=3 uid="uid://csmfxg011xisf"] [ext_resource type="Script" uid="uid://dopyfulbw2mx5" path="res://player/player.gd" id="1_ulp21"] @@ -13,6 +13,14 @@ size = Vector3(0.5, 0.5, 0.5) [sub_resource type="SphereMesh" id="SphereMesh_wnvi2"] +[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_ulp21"] +properties/0/path = NodePath(".:position") +properties/0/spawn = true +properties/0/replication_mode = 2 +properties/1/path = NodePath(".:rotation") +properties/1/spawn = true +properties/1/replication_mode = 2 + [node name="Player" type="CharacterBody3D"] collision_layer = 2 collision_mask = 3 @@ -55,3 +63,6 @@ skeleton = NodePath("../..") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.887858, 0) billboard = 1 text = "Username" + +[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="."] +replication_config = SubResource("SceneReplicationConfig_ulp21") diff --git a/splunk/player/player.gd b/splunk/player/player.gd index 95d8dee..f8df821 100644 --- a/splunk/player/player.gd +++ b/splunk/player/player.gd @@ -8,23 +8,30 @@ class_name Player var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") var camera_node: Camera3D -func setup_player(): + + + + +func _ready(): if is_multiplayer_authority(): print("-> [%s] Authority granted. Setting up camera and input." % name) camera_node = $Camera3D camera_node.make_current() Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) $Mesh.hide() + + if multiplayer.is_server(): + set_player_name(SteamManager.steam_username) -@rpc("any_peer", "call_local") func set_player_name(player_name: String): + name = player_name $Label3D.text = player_name -@rpc("any_peer", "call_local", "unreliable") -func update_remote_transform(new_transform: Transform3D): - global_transform = new_transform +#@rpc("any_peer", "call_local", "unreliable") +#func update_remote_transform(new_transform: Transform3D): + #global_transform = new_transform func _physics_process(delta): @@ -54,23 +61,22 @@ func _physics_process(delta): velocity.z = lerp(velocity.z, direction.z * speed, delta * 5.0) move_and_slide() - update_remote_transform.rpc(global_transform) + #update_remote_transform.rpc(global_transform) func _input(event): - if !is_multiplayer_authority(): - return - if event is InputEventMouseMotion: - # Rotate the CharacterBody3D around the Y-axis for horizontal look - rotate_y(-event.relative.x * mouse_sensitivity) + if is_multiplayer_authority(): + if event is InputEventMouseMotion: + # Rotate the CharacterBody3D around the Y-axis for horizontal look + rotate_y(-event.relative.x * mouse_sensitivity) - # Rotate the Camera3D around its local X-axis for vertical look - var change = -event.relative.y * mouse_sensitivity - var new_x_rotation = camera_node.rotation.x + change - camera_node.rotation.x = clamp(new_x_rotation, deg_to_rad(-90), deg_to_rad(90)) + # Rotate the Camera3D around its local X-axis for vertical look + var change = -event.relative.y * mouse_sensitivity + var new_x_rotation = camera_node.rotation.x + change + camera_node.rotation.x = clamp(new_x_rotation, deg_to_rad(-90), deg_to_rad(90)) - if event.is_action_pressed("ui_cancel"): # Typically Escape key - if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - else: - Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) + if event.is_action_pressed("ui_cancel"): # Typically Escape key + if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED: + Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) + else: + Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)