From e192802f0141e3a55f067b6e8ab4410539ef76ad Mon Sep 17 00:00:00 2001 From: Twirpytherobot Date: Wed, 19 Nov 2025 23:12:12 +0000 Subject: [PATCH] Finally fixed the sync issue! Late joining now syncs properly!!!!!! --- level/scripts/level.gd | 152 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 137 insertions(+), 15 deletions(-) diff --git a/level/scripts/level.gd b/level/scripts/level.gd index 267a95d..ccf3130 100644 --- a/level/scripts/level.gd +++ b/level/scripts/level.gd @@ -13,6 +13,8 @@ extends Node3D var _weapon_spawn_counter: int = 0 # Track active weapons for late-join sync (server-side only) var _active_weapons: Dictionary = {} # weapon_id -> WorldWeapon reference +# Track if we've already initialized to prevent double-spawning +var _multiplayer_initialized: bool = false # multiplayer chat @onready var message: LineEdit = $MultiplayerChat/VBoxContainer/HBoxContainer/Message @@ -23,6 +25,8 @@ var _active_weapons: Dictionary = {} # weapon_id -> WorldWeapon reference var chat_visible = false func _ready(): + print("[Level] _ready() called. Peer ID: ", multiplayer.get_unique_id(), " Is server: ", multiplayer.is_server()) + multiplayer_chat.hide() menu.show() multiplayer_chat.set_process_input(true) @@ -39,33 +43,71 @@ func _ready(): add_child(weapons_container) print("Created WeaponsContainer") - # Clients: Remove manually placed weapons (server will sync them via RPC) - if not multiplayer.is_server(): - _cleanup_manual_weapons_on_client() + # Don't initialize weapons in _ready() - wait for Host/Join to be pressed + # This is handled in initialize_multiplayer() which is called after the + # multiplayer peer is properly set up + print("[Level] _ready() complete - waiting for Host/Join") + +func _on_connected_to_server(): + print("[Level] Connected to server! Cleaning up manual weapons") + _cleanup_manual_weapons_on_client() + +## Called by Network when multiplayer peer is set up +func initialize_multiplayer(): + print("[Level] initialize_multiplayer called. is_server: ", multiplayer.is_server()) + + # Prevent double initialization + if _multiplayer_initialized: + print("[Level] Already initialized, skipping") return - Network.connect("player_connected", Callable(self, "_on_player_connected")) - multiplayer.peer_disconnected.connect(_remove_player) + _multiplayer_initialized = true + + if multiplayer.is_server(): + print("[Level] Running server initialization") + Network.connect("player_connected", Callable(self, "_on_player_connected")) + multiplayer.peer_disconnected.connect(_remove_player) + + # Initialize any manually placed weapons in the scene + _initialize_manual_weapons() - # Initialize any manually placed weapons in the scene - _initialize_manual_weapons() + # Spawn initial weapons when server starts + _spawn_initial_weapons() - # Spawn initial weapons when server starts - _spawn_initial_weapons() + # Spawn the host player (peer ID 1) + print("[Level] Spawning host player") + var host_info = Network.players.get(1, {"nick": "Host", "skin": "blue"}) + _add_player(1, host_info) + else: + # Client initialization - clean up manual weapons immediately + print("[Level] Running client initialization - cleaning up manual weapons") + _cleanup_manual_weapons_on_client() func _cleanup_manual_weapons_on_client(): """Remove manually placed weapons on clients (server will sync them via RPC)""" + print("[Client ", multiplayer.get_unique_id(), "] _cleanup_manual_weapons_on_client called") + if not weapons_container: + print("[Client] No weapons_container found!") return + print("[Client] WeaponsContainer has ", weapons_container.get_child_count(), " children") + var weapons_to_remove = [] for child in weapons_container.get_children(): - if child is WorldWeapon and child.weapon_id == -1: - weapons_to_remove.append(child) + print("[Client] Checking child: ", child.name, " (type: ", child.get_class(), ")") + if child is WorldWeapon: + print("[Client] - Is WorldWeapon with weapon_id: ", child.weapon_id) + if child.weapon_id == -1: + weapons_to_remove.append(child) + print("[Client] - Marked for removal") + print("[Client] Found ", weapons_to_remove.size(), " weapons to remove") for weapon in weapons_to_remove: print("[Client] Removing manually placed weapon: ", weapon.name) - weapon.queue_free() + # Use immediate removal to prevent RPC errors + weapons_container.remove_child(weapon) + weapon.free() func _initialize_manual_weapons(): """Initialize any WorldWeapon nodes manually placed in the level scene""" @@ -144,16 +186,29 @@ func _spawn_initial_weapons(): ) func _on_player_connected(peer_id, player_info): + print("[Server] _on_player_connected called for peer ", peer_id, " with info: ", player_info) _add_player(peer_id, player_info) + # Sync existing players to the newly joined player + if multiplayer.is_server() and peer_id != 1: + print("[Server] Syncing existing players to newly connected peer: ", peer_id) + # Wait a frame to ensure new player is fully initialized + await get_tree().process_frame + for existing_player in players_container.get_children(): + var existing_id = int(existing_player.name) + if existing_id != peer_id: # Don't sync the player to themselves + print("[Server] Syncing existing player ", existing_id, " to peer ", peer_id) + rpc_id(peer_id, "_spawn_player_local", existing_id, existing_player.position) + # Sync existing weapons to the newly joined player (but not to server itself) if multiplayer.is_server() and peer_id != 1: print("[Server] Syncing weapons to newly connected peer: ", peer_id) + print("[Server] Active weapons in _active_weapons: ", _active_weapons.keys()) print("[Server] Active weapons count: ", _active_weapons.size()) for weapon_id in _active_weapons.keys(): var weapon = _active_weapons[weapon_id] if is_instance_valid(weapon) and weapon.weapon_data: - print("[Server] Sending weapon ", weapon_id, " to peer ", peer_id) + print("[Server] Sending weapon ", weapon_id, " (", weapon.weapon_data.weapon_name, ") at position ", weapon.global_position, " to peer ", peer_id) # Send current position and zero velocity for syncing rpc_id(peer_id, "_client_spawn_weapon", weapon.weapon_data.resource_path, @@ -161,24 +216,83 @@ func _on_player_connected(peer_id, player_info): Vector3.ZERO, weapon_id ) + else: + print("[Server] Skipping invalid weapon ", weapon_id) + + # Sync equipped weapons for all existing players to the newly joined player + print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id) + for player_node in players_container.get_children(): + var player = player_node as Character + if player and is_instance_valid(player): + # Skip the newly joined player (they don't have weapons yet) + if int(player.name) == peer_id: + continue + + # Sync main hand weapon + if player.equipped_weapon and player.equipped_weapon.weapon_data: + print("[Server] Syncing main hand weapon for player ", player.name, " to peer ", peer_id) + player.rpc_id(peer_id, "equip_weapon_from_world", + player.equipped_weapon.weapon_data.resource_path) + + # Sync off-hand weapon + if player.equipped_offhand and player.equipped_offhand.weapon_data: + print("[Server] Syncing off-hand weapon for player ", player.name, " to peer ", peer_id) + player.rpc_id(peer_id, "equip_weapon_from_world", + player.equipped_offhand.weapon_data.resource_path) func _on_host_pressed(): + print("[Level] Host button pressed") menu.hide() + print("[Level] Calling Network.start_host()") Network.start_host(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower()) + print("[Level] Waiting one frame...") + await get_tree().process_frame + print("[Level] Calling initialize_multiplayer()") + initialize_multiplayer() func _on_join_pressed(): + print("[Level] Join button pressed") menu.hide() + print("[Level] Calling Network.join_game()") Network.join_game(nick_input.text.strip_edges(), skin_input.text.strip_edges().to_lower(), address_input.text.strip_edges()) + print("[Level] Waiting one frame...") + await get_tree().process_frame + print("[Level] Calling initialize_multiplayer()") + initialize_multiplayer() func _add_player(id: int, player_info : Dictionary): + print("[Level] _add_player called for peer ", id, " with info: ", player_info) + + # Server spawns player and replicates to all clients via RPC + if multiplayer.is_server(): + if players_container.has_node(str(id)): + print("[Level] Player ", id, " already exists, skipping") + return + + var spawn_pos = get_spawn_point() + print("[Level] Server spawning player ", id, " at ", spawn_pos) + + # Spawn on server and all clients (call_local does both) + rpc("_spawn_player_local", id, spawn_pos) + +@rpc("any_peer", "call_local", "reliable") +func _spawn_player_local(id: int, spawn_pos: Vector3): if players_container.has_node(str(id)): + print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " already exists, skipping") return + + print("[Peer ", multiplayer.get_unique_id(), "] Creating player instance for peer ", id) var player = player_scene.instantiate() player.name = str(id) - player.position = get_spawn_point() + player.position = spawn_pos + print("[Peer ", multiplayer.get_unique_id(), "] Adding player to PlayersContainer") players_container.add_child(player, true) + print("[Peer ", multiplayer.get_unique_id(), "] Player ", id, " spawned at ", player.position) + + # Get player info from Network + var player_info = Network.players.get(id, {"nick": "Player", "skin": "blue"}) - var nick = Network.players[id]["nick"] + var nick = player_info["nick"] # Access nickname directly via node path since @onready hasn't loaded yet var nickname_label = player.get_node_or_null("PlayerNick/Nickname") if nickname_label: @@ -359,6 +473,14 @@ func remove_world_weapon(weapon_id: int): print("[ERROR] remove_world_weapon called on client!") return + # Immediately remove from active weapons to prevent late-join sync issues + if _active_weapons.has(weapon_id): + _active_weapons.erase(weapon_id) + print("[Server] Removed weapon ", weapon_id, " from _active_weapons. Remaining: ", _active_weapons.size()) + print("[Server] Remaining weapon IDs: ", _active_weapons.keys()) + else: + print("[Server] WARNING: Weapon ", weapon_id, " not found in _active_weapons!") + # Broadcast removal to all clients print("[Server] Broadcasting removal RPC to all clients") rpc("_remove_weapon_on_clients", weapon_id)