diff --git a/scenes/main.gd b/scenes/main.gd index f662968..82d2758 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -40,6 +40,7 @@ const ADDRESS = "127.0.0.1" @export var enable_bots: bool = true # Add this line var connected_peer_ids = [] +var _connection_check_timer: float = 0.0 var local_player_character : CharacterBody3D var player_scene = preload("res://scenes/player.tscn") var current_turn_index = 0 @@ -94,6 +95,28 @@ func _process(delta): rpc("sync_turn_index", current_turn_index) # Sync all players' goals to the new peer update_all_players_goals() + + # Also periodically verify client connections + _connection_check_timer += delta + if _connection_check_timer >= 5.0: + _connection_check_timer = 0.0 + verify_all_connections() + +func verify_all_connections(): + if multiplayer.is_server(): + for peer_id in players: + if peer_id != 1: # Skip server + # Ping each client + rpc_id(peer_id, "connection_verify", players) + +@rpc +func connection_verify(expected_players: Array): + # Client checks if it has all expected players + for peer_id in expected_players: + if peer_id != multiplayer.get_unique_id() and not has_node(str(peer_id)): + # Missing a player - request it + rpc_id(1, "request_specific_player_data", peer_id) + print("Requesting missing player: ", peer_id) func setup_action_buttons(): move_button.pressed.connect(func(): set_action_state(ActionState.MOVING)) @@ -319,49 +342,251 @@ func _on_join_pressed(): multiplayer_peer.create_client(ADDRESS, PORT) multiplayer.multiplayer_peer = multiplayer_peer $NetworkInfo/UniquePeerID.text = str(multiplayer.get_unique_id()) + # After connection is established + await get_tree().create_timer(2.0).timeout + rpc_id(1, "request_full_player_sync", multiplayer.get_unique_id()) + +#func _on_peer_connected(new_peer_id): + #if multiplayer.is_server(): + ## Increase delay to ensure scene is ready + #await get_tree().create_timer(1.5).timeout + # + ## First sync game state + #rpc_id(new_peer_id, "sync_game_state", players, bots, game_started, turn_based_mode) + #rpc_id(new_peer_id, "sync_preset_goals", preset_goals) + # + ## Wait a bit for the client to process state + #await get_tree().create_timer(0.5).timeout + # + ## Then sync all existing players in order + #var sorted_players = players.duplicate() + #sorted_players.sort() + #for peer_id in sorted_players: + #if peer_id != new_peer_id: + ## First ensure player exists + #var player = get_node_or_null(str(peer_id)) + #if player: + ## Sync player's full state + #var player_data = { + #"position": player.current_position, + #"goals": player.goals, + #"playerboard": player.playerboard + #} + #rpc_id(new_peer_id, "sync_existing_player", peer_id, player_data) + #await get_tree().create_timer(0.1).timeout # Small delay between players + # + ## Finally add the new player + #await get_tree().create_timer(0.5).timeout + #add_player_character(new_peer_id) + #rpc("add_newly_connected_player_character", new_peer_id) + # + ## Replace bot if needed + #if bots.size() > 0: + #replace_bot_with_player(new_peer_id) + # + ## Final sync of all goals + #await get_tree().create_timer(0.5).timeout + #rpc("force_update_all_goals") func _on_peer_connected(new_peer_id): if multiplayer.is_server(): - # Increase delay to ensure scene is ready + # Create a more robust state sync process await get_tree().create_timer(1.5).timeout - # First sync game state - rpc_id(new_peer_id, "sync_game_state", players, bots, game_started, turn_based_mode) - rpc_id(new_peer_id, "sync_preset_goals", preset_goals) + # First sync complete game state + var complete_state = { + "players": players, + "bots": bots, + "game_started": game_started, + "turn_based": turn_based_mode, + "preset_goals": preset_goals, + "player_states": {} + } - # Wait a bit for the client to process state - await get_tree().create_timer(0.5).timeout + # Gather all existing player states + for peer_id in players: + var player = get_node_or_null(str(peer_id)) + if player: + complete_state["player_states"][peer_id] = { + "position": player.current_position, + "goals": player.goals, + "playerboard": player.playerboard, + "is_bot": player.is_bot || player.is_in_group("Bots") + } - # Then sync all existing players in order - var sorted_players = players.duplicate() - sorted_players.sort() - for peer_id in sorted_players: - if peer_id != new_peer_id: - # First ensure player exists - var player = get_node_or_null(str(peer_id)) - if player: - # Sync player's full state - var player_data = { - "position": player.current_position, - "goals": player.goals, - "playerboard": player.playerboard - } - rpc_id(new_peer_id, "sync_existing_player", peer_id, player_data) - await get_tree().create_timer(0.1).timeout # Small delay between players + # Send complete state in one RPC + rpc_id(new_peer_id, "receive_complete_game_state", complete_state) # Finally add the new player await get_tree().create_timer(0.5).timeout add_player_character(new_peer_id) rpc("add_newly_connected_player_character", new_peer_id) - # Replace bot if needed - if bots.size() > 0: - replace_bot_with_player(new_peer_id) + # Make sure all clients know about all players + rpc("sync_complete_player_list", players) + ## Replace bot if needed + #if bots.size() > 0: + #replace_bot_with_player(new_peer_id) + # # Final sync of all goals await get_tree().create_timer(0.5).timeout rpc("force_update_all_goals") +@rpc("reliable") +func sync_complete_player_list(player_list: Array): + # Ensure we have all players in our list + players = player_list.duplicate() + + # Check which players we don't have nodes for + for peer_id in players: + if not has_node(str(peer_id)) and peer_id != multiplayer.get_unique_id(): + # Request this specific player's data from server + rpc_id(1, "request_specific_player_data", peer_id) + +@rpc("any_peer") +func request_specific_player_data(requested_peer_id: int): + if multiplayer.is_server(): + var player = get_node_or_null(str(requested_peer_id)) + if player: + var player_data = { + "peer_id": requested_peer_id, + "position": player.current_position, + "goals": player.goals, + "playerboard": player.playerboard, + "is_bot": player.is_bot || player.is_in_group("Bots") + } + + # Send to the requesting client only + rpc_id(multiplayer.get_remote_sender_id(), "create_specific_player", player_data) + +@rpc("any_peer") +func request_full_player_sync(requesting_peer_id): + if multiplayer.is_server(): + print("Full sync requested by: ", requesting_peer_id) + + # Send the complete list of players + rpc_id(requesting_peer_id, "sync_complete_player_list", players) + + # Send each player's data + for peer_id in players: + var player = get_node_or_null(str(peer_id)) + if player: + var player_data = { + "peer_id": peer_id, + "position": player.current_position, + "goals": player.goals, + "playerboard": player.playerboard, + "is_bot": player.is_bot || player.is_in_group("Bots") + } + rpc_id(requesting_peer_id, "create_specific_player", player_data) + + # Allow a short delay between player creations + await get_tree().create_timer(0.1).timeout + +@rpc("reliable") +func create_specific_player(data: Dictionary): + var peer_id = data["peer_id"] + + # Don't create if already exists + if has_node(str(peer_id)): + return + + # Create the player + var player_character = player_scene.instantiate() + player_character.set_multiplayer_authority(peer_id) + player_character.name = str(peer_id) + + # Set properties before adding to tree + player_character.current_position = data["position"] + + # Add to scene + add_child(player_character) + + # Apply properties after adding + player_character.add_to_group("Players", true) + if data["is_bot"]: + player_character.add_to_group("Bots", true) + player_character.is_bot = true + player_character.rpc("sync_bot_status", true) + + # Apply data + player_character.goals = data["goals"].duplicate() + player_character.playerboard = data["playerboard"].duplicate() + + # Force position sync + player_character.global_position = Vector3( + data["position"].x * 2 + 1, + 1.0, + data["position"].y * 2 + 1 + ) + player_character.rpc("sync_position", data["position"]) + + # Update UI + update_all_players_goals() + update_all_players_boards() + +@rpc("reliable") +func force_update_all_goals(): + # This is called but might be getting lost in the sequence + # Make sure it's called after all players are created + await get_tree().create_timer(0.2).timeout + update_all_players_goals() + update_all_players_boards() + +# Set +@rpc("reliable") +func receive_complete_game_state(state): + # Apply complete game state + players = state["players"] + bots = state["bots"] + game_started = state["game_started"] + turn_based_mode = state["turn_based"] + preset_goals = state["preset_goals"] + + # Process each player state in a consistent order + var sorted_peers = state["player_states"].keys() + sorted_peers.sort() + + for peer_id in sorted_peers: + var player_data = state["player_states"][peer_id] + + # Create player if doesn't exist + if not has_node(str(peer_id)): + var player_character = player_scene.instantiate() + player_character.set_multiplayer_authority(peer_id) + player_character.name = str(peer_id) + + # Set basic properties before adding to scene tree + player_character.current_position = player_data["position"] + + # Add to scene + add_child(player_character) + + # Apply state after adding to tree + player_character.add_to_group("Players", true) + if player_data["is_bot"]: + player_character.add_to_group("Bots", true) + player_character.is_bot = true + player_character.rpc("sync_bot_status", true) + + player_character.goals = player_data["goals"].duplicate() + player_character.playerboard = player_data["playerboard"].duplicate() + + # Ensure proper grid-aligned positioning + player_character.global_position = Vector3( + player_data["position"].x * 2 + 1, + 1.0, + player_data["position"].y * 2 + 1 + ) + + # Force position sync + player_character.rpc("sync_position", player_data["position"]) + + # Force UI updates + update_all_players_goals() + update_all_players_boards() + @rpc("reliable") func sync_existing_player(peer_id: int, player_data: Dictionary): # Create player if doesn't exist @@ -404,10 +629,13 @@ func _on_peer_disconnected(peer_id): @rpc("any_peer", "call_local") func add_player_character(peer_id): - # Check if player already exists + # First check if this player already exists if has_node(str(peer_id)): + print("Player already exists: ", peer_id) return - + + print("Adding player: ", peer_id) + connected_peer_ids.append(peer_id) var player_character = player_scene.instantiate() player_character.set_multiplayer_authority(peer_id) @@ -428,7 +656,14 @@ func add_player_character(peer_id): add_child(player_character) player_character.add_to_group("Players", true) - # Force position sync after adding to tree + # Wait for the node to be properly added to the scene + await get_tree().process_frame + + # Ensure the player list is updated + if not peer_id in players: + players.append(peer_id) + + # Finish setup and sync position if multiplayer.is_server(): await get_tree().create_timer(0.1).timeout player_character.rpc("sync_position", player_character.current_position) @@ -560,6 +795,26 @@ func replace_bot_with_player(player_id): rpc("remove_bot_keep_board", bot_id) rpc("sync_players", players) +@rpc("call_local") +func remove_bot_keep_board(bot_id): + # This RPC is called but not implemented in your code + var bot_node = get_node_or_null(str(bot_id)) + if bot_node: + # Don't immediately queue_free - this can cause timing issues + # Instead, mark for removal and remove after a short delay + bot_node.visible = false # Hide immediately + bot_node.set_process(false) + bot_node.set_physics_process(false) + + # Disable all input and behavior + var behavior_tree = bot_node.get_node_or_null("BehaviorTree") + if behavior_tree: + behavior_tree.enabled = false + + # Remove after a short delay + await get_tree().create_timer(0.5).timeout + if is_instance_valid(bot_node) and bot_node.get_parent() == self: + bot_node.queue_free() @rpc("call_local") func remove_bot(bot_id): diff --git a/scenes/player.gd b/scenes/player.gd index 916e500..b664382 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -6,6 +6,7 @@ extends Node3D var enhanced_gridmap: EnhancedGridMap @export var current_position: Vector2i var is_player_moving: bool = false +var _verify_timer: float = 0.0 @export var cell_size: Vector3 = Vector3(2, 2, 2) @export var cell_offset: Vector3 = Vector3(0, 0, 0) @@ -58,11 +59,10 @@ func _ready(): if main_scene: enhanced_gridmap = main_scene.get_node("EnhancedGridMap") - # Initialize behavior tree for bots - var behavior_tree = $BehaviorTree - # Early setup for bots if is_bot == true or is_in_group("Bots"): + # Initialize behavior tree for bots + var behavior_tree = $BehaviorTree # Disable all input processing for bots immediately set_process_input(false) set_process_unhandled_input(false) @@ -135,6 +135,24 @@ func sync_bot_status(is_bot_status: bool): behavior_tree.set_physics_process(false) behavior_tree.set_process(false) +func _process(delta): + if is_multiplayer_authority(): + + # Visual debugging - show connection status in name label + $Name.text = str(name) + "\n(Auth: " + str(get_multiplayer_authority()) + ")" + + # Periodically verify our existence to others + _verify_timer += delta + if _verify_timer >= 3.0: + _verify_timer = 0.0 + rpc("ping_existence") + +@rpc("any_peer", "call_local") +func ping_existence(): + # This just lets other clients know this player exists + # They can check if they have this node + pass + func _physics_process(_delta): if is_multiplayer_authority(): rpc("remote_set_position", global_position) @@ -1065,11 +1083,11 @@ func update_visual_position(): rpc("sync_position", current_position) @rpc("any_peer", "call_local") -func sync_position(pos: Vector2): +func sync_position(pos: Vector2i): current_position = pos - # Ensure proper grid-aligned positioning + # Always update the visual position after position sync global_position = Vector3( current_position.x * cell_size.x + cell_size.x * 0.5, - 1.0, + cell_size.y, current_position.y * cell_size.z + cell_size.z * 0.5 - ) + ) + cell_offset