diff --git a/scenes/main.gd b/scenes/main.gd index a0034b0..f1f2590 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -86,8 +86,11 @@ func _process(delta): func _on_host_pressed(): $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Server (Creating Match...)" $Menu.visible = false - NakamaManager.connect_to_nakama_async() - await NakamaManager.connected_to_nakama + var success = await NakamaManager.connect_to_nakama_async() + if not success: + $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Connection Failed" + $Menu.visible = true + return NakamaManager.host_game() func _on_join_pressed(): @@ -98,8 +101,11 @@ func _on_join_pressed(): $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Client (Joining...)" $Menu.visible = false - NakamaManager.connect_to_nakama_async() - await NakamaManager.connected_to_nakama + var success = await NakamaManager.connect_to_nakama_async() + if not success: + $NetworkPanel/NetworkInfo/NetworkSideDisplay.text = "Connection Failed" + $Menu.visible = true + return NakamaManager.join_game(match_id) func _on_match_joined(match_id: String): diff --git a/scenes/player.gd b/scenes/player.gd index 1276fbe..15f8b96 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -43,12 +43,12 @@ var action_points: int = 2 var rotation_speed: float = 10.0 var spawn_locations = [ - Vector2i(0, 0), # (0,1,0) - Vector2i(0, 1), # (0,1,1) - Vector2i(0, 2), # (0,1,2) - Vector2i(0, 3), # (0,1,3) - Vector2i(0, 4), # (0,1,4) - Vector2i(0, 5), # (0,1,5) + Vector2i(0, 0), # (0,1,0) + Vector2i(0, 1), # (0,1,1) + Vector2i(0, 2), # (0,1,2) + Vector2i(0, 3), # (0,1,3) + Vector2i(0, 4), # (0,1,4) + Vector2i(0, 5), # (0,1,5) Vector2i(0, 6), Vector2i(0, 7), Vector2i(0, 8), @@ -67,9 +67,7 @@ var spawn_point_selected = false # Action for hilighter var highlighted_spawn_points = [] -var highlighted_cells = [] -var _is_processing_action = false -var _is_highlighting = false + @export var movement_range: int = 1: set(value): @@ -126,7 +124,7 @@ func _ready(): set_physics_process(false) # Disable visual highlights for bots - highlighted_cells.clear() + action_manager.highlighted_cells.clear() if behavior_tree: behavior_tree.enabled = is_multiplayer_authority() @@ -315,7 +313,7 @@ func sync_bot_status(is_bot_status: bool): set_process_unhandled_input(false) # Clear any existing highlights - highlighted_cells.clear() + action_manager.highlighted_cells.clear() #clear_highlights() #clear_playerboard_highlights() @@ -329,7 +327,6 @@ func sync_bot_status(is_bot_status: bool): 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()) + ")" @@ -415,7 +412,7 @@ func highlight_available_spawn_points(): func get_occupied_positions() -> Array: var occupied = [] for player in get_tree().get_nodes_in_group("Players"): - if player.spawn_point_selected: # Only count players who have selected a spawn point + if player.spawn_point_selected: # Only count players who have selected a spawn point occupied.append(player.current_position) return occupied @@ -482,9 +479,9 @@ func find_random_valid_position_in_range() -> Vector2i: var valid_positions = [] - for x in range(max(0, current_position.x - movement_range), + for x in range(max(0, current_position.x - movement_range), min(enhanced_gridmap.columns, current_position.x + movement_range + 1)): - for z in range(max(0, current_position.y - movement_range), + for z in range(max(0, current_position.y - movement_range), min(enhanced_gridmap.rows, current_position.y + movement_range + 1)): var pos = Vector2i(x, z) if pos != current_position and is_within_movement_range(pos): @@ -656,12 +653,12 @@ func display_message(message): $Bubble.hide() $Bubble/Message.hide() -func initialize_random_goals(_size:int, min_value:int, max_value:int, null_count:float) -> Array[int]: +func initialize_random_goals(_size: int, min_value: int, max_value: int, null_count: float) -> Array[int]: goals.clear() var rng = RandomNumberGenerator.new() rng.randomize() - var result : Array[int] = [] + var result: Array[int] = [] var null_val = 0 var max_nulls = 3 @@ -685,42 +682,7 @@ func append_random_goals(): rpc("sync_goals", goals) func bot_try_grab_item() -> bool: - if not enhanced_gridmap or action_points <= 0: - return false - - # First check current position - var current_cell = Vector3i(current_position.x, 1, current_position.y) - var item = enhanced_gridmap.get_cell_item(current_cell) - - if item != -1: - var empty_slot = playerboard.find(-1) - if empty_slot != -1: - if is_multiplayer_authority(): - playerboard[empty_slot] = item - rpc("sync_grid_item", current_cell.x, current_cell.y, current_cell.z, -1) - rpc("sync_playerboard", playerboard) - has_performed_action = true - action_points -= 1 - return true - - # Check adjacent cells if nothing at current position - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - for neighbor in neighbors: - if neighbor.is_walkable: - var cell = Vector3i(neighbor.position.x, 1, neighbor.position.y) - item = enhanced_gridmap.get_cell_item(cell) - if item != -1: - var empty_slot = playerboard.find(-1) - if empty_slot != -1: - if is_multiplayer_authority(): - playerboard[empty_slot] = item - rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) - rpc("sync_playerboard", playerboard) - has_performed_action = true - action_points -= 1 - return true - - return false + return playerboard_manager.bot_try_grab_item() # ----------------------------------------------------------------- # OLD GRAB Func @@ -819,107 +781,15 @@ func bot_try_grab_item() -> bool: # #return true # ----------------------------------------------------------------- -func grab_item(grid_position: Vector2i = current_position) -> bool: - if not enhanced_gridmap or action_points <= 0: - return false - - var cell = Vector3i(grid_position.x, 1, grid_position.y) - var item = enhanced_gridmap.get_cell_item(cell) - - # Validate adjacency (unless it's current position) - if grid_position != current_position: - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - var is_adjacent = false - for neighbor in neighbors: - if neighbor.position == grid_position: - is_adjacent = true - break - if not is_adjacent: - return false - - if item == -1: - return false - - # === AUTO-ARRANGE LOGIC (Client-side pre-check) === - var target_slot = find_best_goal_slot_for_item(item) - if target_slot == -1: - print("Player: No valid slot found for item.") - return false # no space - - if not is_multiplayer_authority(): - return false - - # === Branching Logic: Host vs Client === - if multiplayer.is_server(): - # HOST/SERVER: Call the logic directly - _execute_grab(grid_position, cell, item) - else: - # CLIENT: Send RPC request to server (peer 1) - rpc_id(1, "request_server_grab", grid_position, cell.x, cell.y, cell.z, item) - - return true # Request was sent or processed +func grab_item(grid_position: Vector2i = current_position) -> bool: + return playerboard_manager.grab_item(grid_position) # ----------------------------------------------------------------- # Execute Grab # ----------------------------------------------------------------- func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int): - var main = get_tree().get_root().get_node_or_null("Main") - if not main: - push_error("Server: Main node not found.") - return false - - var server_gridmap = main.get_node("EnhancedGridMap") - if not server_gridmap: - push_error("Server: EnhancedGridMap not found.") - return false - - # 1. Server-side Validation - var server_item = server_gridmap.get_cell_item(cell) - - # Check if item is still there - if server_item != item_id: - print("Server: Item mismatch or already taken. Server has ", server_item) - return false - - # Check action points - if action_points <= 0: - print("Server: Player has no action points.") - return false - - # Check adjacency - if grid_pos != current_position: - var neighbors = server_gridmap.get_neighbors(current_position, 0) - if not neighbors.any(func(n): return n.position == grid_pos): - print("Server: Player is not adjacent to item.") - return false - - # 2. Server-side Auto-Arrange - var target_slot = find_best_goal_slot_for_item(item_id) - if target_slot == -1: - print("Server: Player has no valid slot for item.") - return false - - # 3. Server Executes the Action - - # 3a. Update gridmap (using Main's RPC, which has authority) - main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) - - # 3b. Update playerboard state (on this server-side instance) - playerboard[target_slot] = item_id - - # 3c. Broadcast the new playerboard state to all clients - rpc("sync_playerboard", playerboard) - - # 3d. Consume action points - has_performed_action = true - consume_action_points(1) - - # 3e. Reset the UI for the player who acted - # This will RPC to the client, or run locally for the host - rpc("force_action_state_none") - - return true + return playerboard_manager._execute_grab(grid_pos, cell, item_id) # ----------------------------------------------------------------- # This function runs on the server when requested by a client @@ -927,111 +797,27 @@ func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int): @rpc("any_peer", "reliable") func request_server_grab(grid_pos: Vector2i, x: int, y: int, z: int, item_id: int): # 1. Only the server (peer 1) should process this - if not multiplayer.is_server(): + if not multiplayer.is_server(): return # 2. Security check: Did this request come from the actual owner of this node? - if multiplayer.get_remote_sender_id() != get_multiplayer_authority(): + if multiplayer.get_remote_sender_id() != get_multiplayer_authority(): push_error("Security: Non-authority tried to grab item!") return # 3. Call the execution logic - _execute_grab(grid_pos, Vector3i(x, y, z), item_id) + playerboard_manager._execute_grab(grid_pos, Vector3i(x, y, z), item_id) +# ----------------------------------------------------------------- +# Auto-put: no manual selection needed +# Automatically puts a goal-matching tile into an adjacent (or current) empty grid cell +# ----------------------------------------------------------------- # ----------------------------------------------------------------- # Auto-put: no manual selection needed # Automatically puts a goal-matching tile into an adjacent (or current) empty grid cell # ----------------------------------------------------------------- func auto_put_item() -> bool: - if not enhanced_gridmap or action_points <= 0 or is_bot or is_in_group("Bots"): - return false - - # Step 1: Find empty adjacent (or current) grid cells - var valid_put_positions = [] - var current_cell_3d = Vector3i(current_position.x, 1, current_position.y) - if enhanced_gridmap.get_cell_item(current_cell_3d) == -1: - valid_put_positions.append(current_position) - - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - for neighbor in neighbors: - if neighbor.is_walkable: - var pos = neighbor.position - var cell_3d = Vector3i(pos.x, 1, pos.y) - if enhanced_gridmap.get_cell_item(cell_3d) == -1 and not is_position_occupied(pos): - valid_put_positions.append(pos) - - if valid_put_positions.is_empty(): - return false - - # Step 2: Find a tile that should NOT be on the board - var put_slot = -1 - - # Count how many times each goal item appears in central 3x3 - var goal_counts = {} - for i in range(3): - for j in range(3): - var g = goals[i * 3 + j] - if g != -1: - goal_counts[g] = goal_counts.get(g, 0) + 1 - - # Now scan playerboard - for i in range(playerboard.size()): - var item = playerboard[i] - if item == -1: - continue - - # Case 1: Item is not in goals at all → definitely junk - if not item in goals: - put_slot = i - break - - # Case 2: Item is in goals, but we already have enough in correct spots - var current_count = 0 - for r in range(1, 4): # central rows 1-3 (5x5 board) - for c in range(1, 4): # central cols 1-3 - var idx = r * 5 + c - if playerboard[idx] == item: - current_count += 1 - - # If we already have all needed copies in central area, this is extra - if current_count >= goal_counts.get(item, 0): - put_slot = i - break - - # If no junk found, fall back to any non-goal-matching tile outside center - if put_slot == -1: - for i in range(playerboard.size()): - var item = playerboard[i] - if item == -1: - continue - var row = i / 5 - var col = i % 5 - # If it's outside the central 3x3, it shouldn't be there - if row < 1 or row > 3 or col < 1 or col > 3: - if not item in goals or playerboard[i] != goals[(row - 1) * 3 + (col - 1)]: - put_slot = i - break - - if put_slot == -1: - return false # Nothing suitable to put - - # Step 3: Perform the put - var target_pos = valid_put_positions[0] - var item = playerboard[put_slot] - var cell = Vector3i(target_pos.x, 1, target_pos.y) - - if is_multiplayer_authority(): - rpc("sync_grid_item", cell.x, cell.y, cell.z, item) - playerboard[put_slot] = -1 - rpc("sync_playerboard", playerboard) - has_performed_action = true - consume_action_points(1) - - var main = get_tree().get_root().get_node_or_null("Main") - if main: - main.set_action_state(main.ActionState.NONE) - - return true + return playerboard_manager.auto_put_item() # ----------------------------------------------------------------- # Force ActionState : None @@ -1042,9 +828,9 @@ func force_action_state_none(): # This is called by the server on the client to reset the UI var main = get_tree().get_root().get_node_or_null("Main") if main: - main.set_action_state(main.ActionState.NONE) - clear_highlights() - clear_playerboard_highlights() + main.set_action_state(main.ActionState.NONE) + action_manager.clear_highlights() + action_manager.clear_playerboard_highlights() # ----------------------------------------------------------------- @@ -1062,7 +848,7 @@ func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int # Server-side validation var cell = Vector3i(x, y, z) if enhanced_gridmap.get_cell_item(cell) != -1: - return # Cell not empty + return # Cell not empty # Check if position is adjacent or current position if grid_position != current_position: @@ -1073,7 +859,7 @@ func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int is_adjacent = true break if not is_adjacent: - return # Not adjacent + return # Not adjacent # Verify player has the item if playerboard[slot_index] != item: @@ -1126,267 +912,21 @@ func notify_spawn_selected(spawn_pos: Vector2i): #highlighted_cells.append(i) func handle_playerboard_slot_selected(slot_index: int): - var main = get_tree().get_root().get_node_or_null("Main") - if not main: - return - - if main.ui_manager.current_action_state == main.ui_manager.ActionState.PUTTING: - if playerboard[slot_index] != -1: # If slot has an item - selected_playerboard_slot = slot_index - clear_highlights() - highlight_empty_adjacent_cells() # Highlight valid put locations - elif main.ui_manager.current_action_state == main.ui_manager.ActionState.GRABBING: - if slot_index in highlighted_cells and playerboard[slot_index] == -1: - var cell = Vector3i(selected_gridmap_position.x, 1, selected_gridmap_position.y) - var item = enhanced_gridmap.get_cell_item(cell) - - if item != -1: - if is_multiplayer_authority(): - playerboard[slot_index] = item - rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) - rpc("sync_playerboard", playerboard) - - has_performed_action = true - consume_action_points(1) - if not is_bot == true: - clear_highlights() - clear_playerboard_highlights() - selected_gridmap_position = Vector2i(-1, -1) - main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE - _after_action_completed() + playerboard_manager.handle_playerboard_slot_selected(slot_index) # We also need to add handle_put_slot_selected: func handle_put_slot_selected(slot_index: int): - var main = get_tree().get_root().get_node_or_null("Main") - if not main or main.ui_manager.current_action_state != main.ui_manager.ActionState.PUTTING: - return - - print("PUT slot selected: ", slot_index, ", item: ", playerboard[slot_index]) - - if slot_index in highlighted_cells and playerboard[slot_index] in goals: - selected_playerboard_slot = slot_index - clear_highlights() - if not is_bot == true: - highlight_empty_adjacent_cells() - - if playerboard[slot_index] != -1: # If slot has an item - selected_playerboard_slot = slot_index - - # Visual feedback of selection - clear_highlights() - - # Highlight valid put locations - highlight_empty_adjacent_cells() - - # Print debug info - print("Selected slot ", slot_index, " for PUT with item ", playerboard[slot_index]) - print("Highlighted cells: ", highlighted_cells) + playerboard_manager.handle_put_slot_selected(slot_index) func arrange_playerboard_item(slot_index: int): - if action_points < 2 or playerboard[slot_index] == -1: - return - - var selected_item = playerboard[slot_index] - var adjacent_slots = get_adjacent_playerboard_slots(slot_index) - - var main = get_tree().get_root().get_node_or_null("Main") - if not main or not main.ui_manager.playerboard_ui: - return - - # Store the selected slot - selected_playerboard_slot = slot_index - - # Highlight selected slot - var selected_slot_ui = main.ui_manager.playerboard_ui.get_child(slot_index) - if selected_slot_ui.get_child_count() > 1: - selected_slot_ui.get_child(1).show() - - # Highlight valid adjacent slots - for adj_slot in adjacent_slots: - if playerboard[adj_slot] == -1: # Only highlight empty adjacent slots - var adj_slot_ui = main.ui_manager.playerboard_ui.get_child(adj_slot) - if adj_slot_ui.get_child_count() > 2: - adj_slot_ui.get_child(2).show() - highlighted_cells.append(adj_slot) - - # Connect to slot click signals - for i in range(playerboard.size()): - var slot = main.ui_manager.playerboard_ui.get_child(i) - if not slot.gui_input.is_connected(_on_slot_clicked): - slot.gui_input.connect(_on_slot_clicked.bind(i)) + playerboard_manager.arrange_playerboard_item(slot_index) func _on_slot_clicked(event: InputEvent, slot_index: int): if not event is InputEventMouseButton or is_bot == true or not event.pressed or event.button_index != MOUSE_BUTTON_LEFT: return + playerboard_manager.handle_slot_clicked(slot_index) - var main = get_tree().get_root().get_node_or_null("Main") - if not main or main.ui_manager.current_action_state != main.ui_manager.ActionState.ARRANGING: - return - - if selected_playerboard_slot == -1 or slot_index == selected_playerboard_slot: - return - - var adjacent_slots = get_adjacent_playerboard_slots(selected_playerboard_slot) - if slot_index in adjacent_slots and playerboard[slot_index] == -1: - # Move item to empty target slot - var selected_item = playerboard[selected_playerboard_slot] - playerboard[slot_index] = selected_item - playerboard[selected_playerboard_slot] = -1 - - if is_multiplayer_authority(): - rpc("sync_playerboard", playerboard) - consume_action_points(2) - has_performed_action = true - - # Clear highlights - clear_highlights() - clear_playerboard_highlights() - - # Reset selection - selected_playerboard_slot = -1 - - # Update the visual representation - main.ui_manager.update_playerboard_ui() - main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE - -func is_valid_arrangement_slot(from_slot: int, to_slot: int) -> bool: - var from_row = from_slot / 5 - var from_col = from_slot % 5 - var to_row = to_slot / 5 - var to_col = to_slot % 5 - - var row_diff = abs(from_row - to_row) - var col_diff = abs(from_col - to_col) - - return (row_diff == 1 and col_diff == 0) or (row_diff == 0 and col_diff == 1) - -# Returns { slot_index: int, grid_position: Vector2i } or null if no valid put -func find_best_put_candidate() -> Dictionary: - # Convert goals to 2D (3x3) - var goals_2d = [] - for i in range(3): - var row = [] - for j in range(3): - row.append(goals[i * 3 + j]) - goals_2d.append(row) - - # Convert playerboard to 2D (5x5) - var board_2d = [] - for i in range(5): - var row = [] - for j in range(5): - row.append(playerboard[i * 5 + j]) - board_2d.append(row) - - # Step 1: Find misplaced or extra goal-matching items - var candidate_items = [] - for board_i in range(5): - for board_j in range(5): - var item = board_2d[board_i][board_j] - if item == -1: - continue - var board_idx = board_i * 5 + board_j - - # Is this item part of the goals? - if item not in goals: - continue - - # Is it already in the correct central position? - var is_in_correct_central_spot = false - if board_i in [1,2,3] and board_j in [1,2,3]: - var goal_i = board_i - 1 - var goal_j = board_j - 1 - if goals_2d[goal_i][goal_j] == item: - is_in_correct_central_spot = true - - if not is_in_correct_central_spot: - candidate_items.append({ - "slot": board_idx, - "item": item - }) - - # Step 2: Find valid adjacent empty grid cells - var valid_cells = [] - # Check current position - var current_cell_3d = Vector3i(current_position.x, 1, current_position.y) - if enhanced_gridmap.get_cell_item(current_cell_3d) == -1: - valid_cells.append(current_position) - # Check neighbors - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - for neighbor in neighbors: - if neighbor.is_walkable: - var pos = neighbor.position - var cell_3d = Vector3i(pos.x, 1, pos.y) - if enhanced_gridmap.get_cell_item(cell_3d) == -1 and not is_position_occupied(pos): - valid_cells.append(pos) - - if valid_cells.is_empty() or candidate_items.is_empty(): - return {} - - # Step 3: Prefer to put an item that *completes* a missing goal - for goal_i in range(3): - for goal_j in range(3): - var needed_item = goals_2d[goal_i][goal_j] - if needed_item == -1: - continue - # Check if central spot is empty - var board_i = goal_i + 1 - var board_j = goal_j + 1 - var central_slot = board_i * 5 + board_j - if playerboard[central_slot] == -1: - # Look for this item in candidate_items - for cand in candidate_items: - if cand.item == needed_item: - if not valid_cells.is_empty(): - return { - "slot_index": cand.slot, - "grid_position": valid_cells[0] # pick first valid cell - } - - # Fallback: just put any candidate item - return { - "slot_index": candidate_items[0].slot, - "grid_position": valid_cells[0] - } - -# Finds the best slot in the playerboard for a given item based on goals -func find_best_goal_slot_for_item(item: int) -> int: - if item == -1: - return -1 - - # Convert goals to 2D (3x3) - var goals_2d = [] - for i in range(3): - var row = [] - for j in range(3): - row.append(goals[i * 3 + j]) - goals_2d.append(row) - - # Search for where this item should go in the central 3x3 (mapped to 5x5 board) - for i in range(3): - for j in range(3): - if goals_2d[i][j] == item: - var board_row = i + 1 # offset to center in 5x5 - var board_col = j + 1 - var slot_index = board_row * 5 + board_col - if playerboard[slot_index] == -1: # only if empty - return slot_index - - # No ideal slot? Return any empty slot - return playerboard.find(-1) - -func get_adjacent_playerboard_slots(slot_index) -> Array: - var adjacent = [] - var row = slot_index / 5 - var col = slot_index % 5 - - if row > 0: adjacent.append(slot_index - 5) - if row < 4: adjacent.append(slot_index + 5) - if col > 0: adjacent.append(slot_index - 1) - if col < 4: adjacent.append(slot_index + 1) - - return adjacent func has_item_at_current_position() -> bool: var current_cell = Vector3i(current_position.x, 1, current_position.y) @@ -1399,16 +939,7 @@ func playerboard_is_full() -> bool: return playerboard.find(-1) == -1 func highlight_cells_if_authorized(cells_to_highlight: Array): - if not is_multiplayer_authority() or is_bot or is_in_group("Bots"): - return - - clear_highlights() - for cell in cells_to_highlight: - highlighted_cells.append(cell) - enhanced_gridmap.set_cell_item( - Vector3i(cell.x, 0, cell.y), - enhanced_gridmap.hover_item - ) + action_manager.highlight_cells_if_authorized(cells_to_highlight) # Update highlight_movement_range to respect the expanded obstacle blocking func highlight_movement_range(): @@ -1424,201 +955,48 @@ func highlight_adjacent_cells(): movement_manager.highlight_adjacent_cells() func highlight_empty_adjacent_cells(): - if is_bot == true or is_in_group("Bots"): - return - - # Debug print - print("Highlighting empty adjacent cells. Current position: ", current_position) - - # Clear previous highlights - clear_highlights() - - # Highlight current position if empty - var current_cell = Vector3i(current_position.x, 1, current_position.y) - if enhanced_gridmap.get_cell_item(current_cell) == -1: - highlighted_cells.append(current_position) - enhanced_gridmap.set_cell_item(Vector3i(current_position.x, 0, current_position.y), - enhanced_gridmap.hover_item) - print("Highlighted current position: ", current_position) - - # Highlight empty adjacent cells - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - for neighbor in neighbors: - if neighbor.is_walkable: - var cell_pos = neighbor.position - var cell = Vector3i(cell_pos.x, 1, cell_pos.y) - if enhanced_gridmap.get_cell_item(cell) == -1: # Check if cell is empty - highlighted_cells.append(cell_pos) - enhanced_gridmap.set_cell_item(Vector3i(cell_pos.x, 0, cell_pos.y), - enhanced_gridmap.hover_item) - print("Highlighted adjacent cell: ", cell_pos) + action_manager.highlight_empty_adjacent_cells() @rpc("any_peer", "call_local") func sync_action_points(points: int): - action_points = points + action_manager.sync_action_points(points) func highlight_random_valid_cells(): - if is_bot == true or is_in_group("Bots") or not is_multiplayer_authority(): - return - - clear_highlights() - - # First check the current position - var current_cell = Vector3i(current_position.x, 1, current_position.y) - var current_item = enhanced_gridmap.get_cell_item(current_cell) - if current_item != -1: - highlighted_cells.append(current_position) - enhanced_gridmap.set_cell_item(Vector3i(current_position.x, 0, current_position.y), - enhanced_gridmap.hover_item) - - # Then check all adjacent cells for items - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - for neighbor in neighbors: - if neighbor.is_walkable: - var cell_pos = neighbor.position - var cell = Vector3i(cell_pos.x, 1, cell_pos.y) - if enhanced_gridmap.get_cell_item(cell) != -1: # Only highlight cells with items - highlighted_cells.append(cell_pos) - enhanced_gridmap.set_cell_item(Vector3i(cell_pos.x, 0, cell_pos.y), - enhanced_gridmap.hover_item) + action_manager.highlight_random_valid_cells() func highlight_occupied_playerboard_slots(): - if is_bot == true or is_in_group("Bots") or not is_multiplayer_authority(): - return - - var main = get_tree().get_root().get_node_or_null("Main") - if not main or not main.ui_manager.playerboard_ui: - return - - # First reset all slots to normal - for i in range(playerboard.size()): - var slot = main.ui_manager.playerboard_ui.get_child(i) - for child in slot.get_children(): - child.hide() - - # Highlight occupied slots that match goals - for i in range(playerboard.size()): - if playerboard[i] in goals: - var slot = main.ui_manager.playerboard_ui.get_child(i) - if slot.get_child_count() > 0: - slot.get_child(0).show() # Show highlight for matching items - highlighted_cells.append(i) # Add to highlighted cells for tracking - - # Update the UI to reflect changes - main.ui_manager.update_playerboard_ui() + action_manager.highlight_occupied_playerboard_slots() func clear_highlights(): - # Never allow bots to clear highlights for human players - if is_bot or is_in_group("Bots"): - return - - if not enhanced_gridmap or not is_multiplayer_authority(): - return - - # Store the current action state before clearing - var main = get_tree().get_root().get_node_or_null("Main") - var current_state = main.ui_manager.current_action_state if main else null - - for cell in highlighted_cells: - if cell is Vector2i: - enhanced_gridmap.set_cell_item(Vector3i(cell.x, 0, cell.y), enhanced_gridmap.normal_items[0]) - - highlighted_cells.clear() - - if main and main.ui_manager.playerboard_ui: - for i in range(main.ui_manager.playerboard_ui.get_child_count()): - var slot = main.ui_manager.playerboard_ui.get_child(i) - for child in slot.get_children(): - child.hide() - - # Restore highlights based on current action state - if main and current_state == main.ui_manager.ActionState.MOVING and is_my_turn and current_state != main.ui_manager.ActionState.PLACING_OBSTACLE: - highlight_movement_range() + action_manager.clear_highlights() func clear_playerboard_highlights(): - # Never allow bots to clear highlights for human players - if is_bot or is_in_group("Bots"): - return - - if not is_multiplayer_authority(): - return - - var main = get_tree().get_root().get_node_or_null("Main") - if main and main.ui_manager.playerboard_ui: - for i in range(main.ui_manager.playerboard_ui.get_child_count()): - var slot = main.ui_manager.playerboard_ui.get_child(i) - if slot.get_child_count() > 0: slot.get_child(0).hide() - if slot.get_child_count() > 1: slot.get_child(1).hide() - if slot.get_child_count() > 2: slot.get_child(2).hide() - - highlighted_cells.clear() + action_manager.clear_playerboard_highlights() func rotate_towards_target(target_pos: Vector2i): movement_manager.rotate_towards_target(target_pos) # We also need to add these supporting functions: func select_playerboard_slot(slot_index: int): - selected_playerboard_slot = slot_index - _update_playerboard_slot_visual(slot_index) - _highlight_adjacent_playerboard_slots() + playerboard_manager.select_playerboard_slot(slot_index) func deselect_playerboard_slot(): - var old_selected = selected_playerboard_slot - selected_playerboard_slot = -1 - if old_selected != -1: - _update_playerboard_slot_visual(old_selected) - untarget_playerboard_slot() - _highlight_adjacent_playerboard_slots() + playerboard_manager.deselect_playerboard_slot() func target_playerboard_slot(slot_index: int): - if targeted_playerboard_slot != -1: - untarget_playerboard_slot() - targeted_playerboard_slot = slot_index - _update_playerboard_slot_visual(slot_index) + playerboard_manager.target_playerboard_slot(slot_index) func untarget_playerboard_slot(): - if targeted_playerboard_slot != -1: - var old_targeted = targeted_playerboard_slot - targeted_playerboard_slot = -1 - _update_playerboard_slot_visual(old_targeted) + playerboard_manager.untarget_playerboard_slot() func can_move_to_target_playerboard_slot() -> bool: - if selected_playerboard_slot == -1 or targeted_playerboard_slot == -1 or selected_playerboard_slot == targeted_playerboard_slot: - return false - - var adjacent_slots = get_adjacent_playerboard_slots(selected_playerboard_slot) - return adjacent_slots.has(targeted_playerboard_slot) + return playerboard_manager.can_move_to_target_playerboard_slot() func _update_playerboard_slot_visual(slot_index: int): - var main = get_tree().get_root().get_node_or_null("Main") - if not main or not main.playerboard_ui: - return - - var slot = main.playerboard_ui.get_child(slot_index) - if slot: - if slot.get_child_count() > 0: - slot.get_child(0).visible = slot_index == selected_playerboard_slot - if slot.get_child_count() > 1: - slot.get_child(1).visible = slot_index == targeted_playerboard_slot - if slot.get_child_count() > 2: - slot.get_child(2).visible = selected_playerboard_slot != -1 and get_adjacent_playerboard_slots(selected_playerboard_slot).has(slot_index) + playerboard_manager._update_playerboard_slot_visual(slot_index) func _highlight_adjacent_playerboard_slots(): - var main = get_tree().get_root().get_node_or_null("Main") - if not main or not main.playerboard_ui: - return - - for i in range(25): - var slot = main.playerboard_ui.get_child(i) - if slot.get_child_count() > 2: - slot.get_child(2).hide() - - if selected_playerboard_slot != -1: - var adjacent_slots = get_adjacent_playerboard_slots(selected_playerboard_slot) - for adj_slot in adjacent_slots: - var slot = main.playerboard_ui.get_child(adj_slot) - if slot.get_child_count() > 2: - slot.get_child(2).show() + playerboard_manager._highlight_adjacent_playerboard_slots() @rpc("any_peer", "call_local", "reliable") func sync_rotation(new_rotation: float): @@ -1645,7 +1023,7 @@ func sync_grid_item(x: int, y: int, z: int, item: int): @rpc("any_peer", "call_local") func sync_goals(new_goals: Array): - goals = new_goals.duplicate() # Make sure to duplicate the array + goals = new_goals.duplicate() # Make sure to duplicate the array @rpc("any_peer", "call_local") func sync_second_lap_goals(new_goals: Array): @@ -1658,112 +1036,28 @@ func sync_playerboard(new_playerboard: Array): var main = get_tree().get_root().get_node_or_null("Main") if main: main.rpc("sync_playerboard", get_multiplayer_authority(), playerboard) - _after_action_completed() + action_manager.after_action_completed() func _after_action_completed(): - # Guard against recursive calls - if _is_processing_action: - return - _is_processing_action = true - - if is_multiplayer_authority(): - update_finish_availability() - - # Clear the highlights after placing the tiles. (Quickfix for Clientside) - clear_highlights() - - if multiplayer.get_unique_id() == get_multiplayer_authority(): - var main = get_tree().get_root().get_node_or_null("Main") - if main: - # Add this condition for bots - if not TurnManager.turn_based_mode and (action_points <= 0 or is_bot): - action_points = 20 # For bots in non-turn-based mode, this will keep refreshing - has_performed_action = false - has_moved_this_turn = false - - main.ui_manager.update_button_states() - main.ui_manager.update_playerboard_ui() - - # Add this line to sync all boards - main.update_all_players_boards() - - # Add sync for playerboard - if is_multiplayer_authority(): - main.rpc("sync_playerboard", get_multiplayer_authority(), playerboard) - - _is_processing_action = false + action_manager.after_action_completed() func is_finish_position(pos: Vector2i) -> bool: return pos in finish_locations func consume_action_points(points: int): - if not is_instance_valid(self) or not is_multiplayer_authority(): - return - - var main = get_tree().get_root().get_node_or_null("Main") - if not main: - return - - # Don't consume points for bots in non-turn-based mode - if is_bot == true and not TurnManager.turn_based_mode: - _after_action_completed() - return - - action_points -= points - - if action_points <= 0: - if TurnManager.turn_based_mode: - main.request_next_turn() - else: - action_points = 2 - has_performed_action = false - has_moved_this_turn = false - rpc("display_message", "Action Points Reset!") - - _after_action_completed() + action_manager.consume_action_points(points) @rpc("any_peer", "call_local") func bot_grab_item(pos: Vector2i, slot: int, x: int, y: int, z: int): - if not (is_bot or is_in_group("Bots")): - return - - var cell = Vector3i(x, y, z) - var item = enhanced_gridmap.get_cell_item(cell) - - if item != -1: - playerboard[slot] = item - enhanced_gridmap.set_cell_item(cell, -1) - has_performed_action = true - action_points -= 1 - _after_action_completed() + playerboard_manager.bot_grab_item(pos, slot, x, y, z) @rpc("any_peer", "call_local") func bot_put_item(pos: Vector2i, slot: int, x: int, y: int, z: int): - if not (is_bot or is_in_group("Bots")): - return - - var cell = Vector3i(x, y, z) - var item = playerboard[slot] - - if enhanced_gridmap.get_cell_item(cell) == -1: - enhanced_gridmap.set_cell_item(cell, item) - playerboard[slot] = -1 - has_performed_action = true - action_points -= 1 - _after_action_completed() + playerboard_manager.bot_put_item(pos, slot, x, y, z) @rpc("any_peer", "call_local") func bot_arrange_item(from_slot: int, to_slot: int): - if not (is_bot or is_in_group("Bots")) or action_points < 2: - return - - if playerboard[from_slot] != -1 and playerboard[to_slot] == -1: - var temp = playerboard[from_slot] - playerboard[from_slot] = -1 - playerboard[to_slot] = temp - has_performed_action = true - action_points -= 2 - _after_action_completed() + playerboard_manager.bot_arrange_item(from_slot, to_slot) func update_visual_position(): # Ensure proper grid-aligned positioning @@ -1786,33 +1080,4 @@ func sync_position(pos: Vector2i): ) + cell_offset func highlight_valid_obstacle_cells(): - if not is_multiplayer_authority() or is_bot or is_in_group("Bots"): - return - - clear_highlights() - - var cells_to_highlight = [] - - # Highlight all empty cells on the grid except those occupied by players or obstacles - for x in range(enhanced_gridmap.columns): - for z in range(enhanced_gridmap.rows): - var pos = Vector2i(x, z) - var cell = Vector3i(x, 3, z) # Check floor 3 for occupancy - var occupied_by_player = false - var occupied_by_obstacle = false - - # Check if cell is occupied by any player - for player in get_tree().get_nodes_in_group("Players"): - if player.current_position == pos: - occupied_by_player = true - break - - # Check if cell is occupied by an obstacle - if enhanced_gridmap.get_cell_item(cell) in enhanced_gridmap.obstacle_items: - occupied_by_obstacle = true - - # Only add to highlights if not occupied by player or obstacle - if not occupied_by_player and not occupied_by_obstacle: - cells_to_highlight.append(pos) - - highlight_cells_if_authorized(cells_to_highlight) + action_manager.highlight_valid_obstacle_cells() diff --git a/scripts/managers/player_action_manager.gd b/scripts/managers/player_action_manager.gd index 84e92bb..6a631bf 100644 --- a/scripts/managers/player_action_manager.gd +++ b/scripts/managers/player_action_manager.gd @@ -4,6 +4,7 @@ extends Node var player: Node3D var enhanced_gridmap: Node +var highlighted_cells = [] func initialize(p_player: Node3D, p_gridmap: Node): player = p_player @@ -56,7 +57,7 @@ func after_action_completed(): if main: # Add this condition for bots if not TurnManager.turn_based_mode and (player.action_points <= 0 or player.is_bot): - player.action_points = 20 # For bots in non-turn-based mode, this will keep refreshing + player.action_points = 20 # For bots in non-turn-based mode, this will keep refreshing player.has_performed_action = false player.has_moved_this_turn = false @@ -82,9 +83,9 @@ func highlight_cells_if_authorized(cells_to_highlight: Array): clear_highlights() for cell in cells_to_highlight: - player.highlighted_cells.append(cell) + highlighted_cells.append(cell) enhanced_gridmap.set_cell_item( - Vector3i(cell.x, 0, cell.y), + Vector3i(cell.x, 0, cell.y), enhanced_gridmap.hover_item ) @@ -101,8 +102,8 @@ func highlight_empty_adjacent_cells(): # Highlight current position if empty var current_cell = Vector3i(player.current_position.x, 1, player.current_position.y) if enhanced_gridmap.get_cell_item(current_cell) == -1: - player.highlighted_cells.append(player.current_position) - enhanced_gridmap.set_cell_item(Vector3i(player.current_position.x, 0, player.current_position.y), + highlighted_cells.append(player.current_position) + enhanced_gridmap.set_cell_item(Vector3i(player.current_position.x, 0, player.current_position.y), enhanced_gridmap.hover_item) print("Highlighted current position: ", player.current_position) @@ -112,9 +113,9 @@ func highlight_empty_adjacent_cells(): if neighbor.is_walkable: var cell_pos = neighbor.position var cell = Vector3i(cell_pos.x, 1, cell_pos.y) - if enhanced_gridmap.get_cell_item(cell) == -1: # Check if cell is empty - player.highlighted_cells.append(cell_pos) - enhanced_gridmap.set_cell_item(Vector3i(cell_pos.x, 0, cell_pos.y), + if enhanced_gridmap.get_cell_item(cell) == -1: # Check if cell is empty + highlighted_cells.append(cell_pos) + enhanced_gridmap.set_cell_item(Vector3i(cell_pos.x, 0, cell_pos.y), enhanced_gridmap.hover_item) print("Highlighted adjacent cell: ", cell_pos) @@ -128,8 +129,8 @@ func highlight_random_valid_cells(): var current_cell = Vector3i(player.current_position.x, 1, player.current_position.y) var current_item = enhanced_gridmap.get_cell_item(current_cell) if current_item != -1: - player.highlighted_cells.append(player.current_position) - enhanced_gridmap.set_cell_item(Vector3i(player.current_position.x, 0, player.current_position.y), + highlighted_cells.append(player.current_position) + enhanced_gridmap.set_cell_item(Vector3i(player.current_position.x, 0, player.current_position.y), enhanced_gridmap.hover_item) # Then check all adjacent cells for items @@ -138,9 +139,9 @@ func highlight_random_valid_cells(): if neighbor.is_walkable: var cell_pos = neighbor.position var cell = Vector3i(cell_pos.x, 1, cell_pos.y) - if enhanced_gridmap.get_cell_item(cell) != -1: # Only highlight cells with items - player.highlighted_cells.append(cell_pos) - enhanced_gridmap.set_cell_item(Vector3i(cell_pos.x, 0, cell_pos.y), + if enhanced_gridmap.get_cell_item(cell) != -1: # Only highlight cells with items + highlighted_cells.append(cell_pos) + enhanced_gridmap.set_cell_item(Vector3i(cell_pos.x, 0, cell_pos.y), enhanced_gridmap.hover_item) func highlight_occupied_playerboard_slots(): @@ -162,8 +163,8 @@ func highlight_occupied_playerboard_slots(): if player.playerboard[i] in player.goals: var slot = main.ui_manager.playerboard_ui.get_child(i) if slot.get_child_count() > 0: - slot.get_child(0).show() # Show highlight for matching items - player.highlighted_cells.append(i) # Add to highlighted cells for tracking + slot.get_child(0).show() # Show highlight for matching items + highlighted_cells.append(i) # Add to highlighted cells for tracking # Update the UI to reflect changes main.ui_manager.update_playerboard_ui() @@ -180,7 +181,7 @@ func highlight_valid_obstacle_cells(): for x in range(enhanced_gridmap.columns): for z in range(enhanced_gridmap.rows): var pos = Vector2i(x, z) - var cell = Vector3i(x, 3, z) # Check floor 3 for occupancy + var cell = Vector3i(x, 3, z) # Check floor 3 for occupancy var occupied_by_player = false var occupied_by_obstacle = false @@ -212,11 +213,11 @@ func clear_highlights(): var main = player.get_tree().get_root().get_node_or_null("Main") var current_state = main.ui_manager.current_action_state if main else null - for cell in player.highlighted_cells: + for cell in highlighted_cells: if cell is Vector2i: enhanced_gridmap.set_cell_item(Vector3i(cell.x, 0, cell.y), enhanced_gridmap.normal_items[0]) - player.highlighted_cells.clear() + highlighted_cells.clear() if main and main.ui_manager.playerboard_ui: for i in range(main.ui_manager.playerboard_ui.get_child_count()): @@ -244,4 +245,4 @@ func clear_playerboard_highlights(): if slot.get_child_count() > 1: slot.get_child(1).hide() if slot.get_child_count() > 2: slot.get_child(2).hide() - player.highlighted_cells.clear() + highlighted_cells.clear() diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index cb1262b..1155af7 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -43,7 +43,7 @@ func grab_item(grid_position: Vector2i) -> bool: var target_slot = find_best_goal_slot_for_item(item) if target_slot == -1: print("Player: No valid slot found for item.") - return false # no space + return false # no space if not player.is_multiplayer_authority(): return false @@ -191,44 +191,44 @@ func auto_put_item() -> bool: # Now scan playerboard for i in range(player.playerboard.size()): - var item = player.playerboard[i] - if item == -1: + var current_item = player.playerboard[i] + if current_item == -1: continue # Case 1: Item is not in goals at all → definitely junk - if not item in player.goals: + if not current_item in player.goals: put_slot = i break # Case 2: Item is in goals, but we already have enough in correct spots var current_count = 0 - for r in range(1, 4): # central rows 1-3 (5x5 board) - for c in range(1, 4): # central cols 1-3 + for r in range(1, 4): # central rows 1-3 (5x5 board) + for c in range(1, 4): # central cols 1-3 var idx = r * 5 + c - if player.playerboard[idx] == item: + if player.playerboard[idx] == current_item: current_count += 1 # If we already have all needed copies in central area, this is extra - if current_count >= goal_counts.get(item, 0): + if current_count >= goal_counts.get(current_item, 0): put_slot = i break # If no junk found, fall back to any non-goal-matching tile outside center if put_slot == -1: for i in range(player.playerboard.size()): - var item = player.playerboard[i] - if item == -1: + var board_item = player.playerboard[i] + if board_item == -1: continue var row = i / 5 var col = i % 5 # If it's outside the central 3x3, it shouldn't be there if row < 1 or row > 3 or col < 1 or col > 3: - if not item in player.goals or player.playerboard[i] != player.goals[(row - 1) * 3 + (col - 1)]: + if not board_item in player.goals or player.playerboard[i] != player.goals[(row - 1) * 3 + (col - 1)]: put_slot = i break if put_slot == -1: - return false # Nothing suitable to put + return false # Nothing suitable to put # Step 3: Perform the put var target_pos = valid_put_positions[0] @@ -256,7 +256,7 @@ func arrange_playerboard_item(slot_index: int): if player.action_points < 2 or player.playerboard[slot_index] == -1: return - var selected_item = player.playerboard[slot_index] + #var selected_item = player.playerboard[slot_index] var adjacent_slots = get_adjacent_playerboard_slots(slot_index) var main = player.get_tree().get_root().get_node_or_null("Main") @@ -273,11 +273,11 @@ func arrange_playerboard_item(slot_index: int): # Highlight valid adjacent slots for adj_slot in adjacent_slots: - if player.playerboard[adj_slot] == -1: # Only highlight empty adjacent slots + if player.playerboard[adj_slot] == -1: # Only highlight empty adjacent slots var adj_slot_ui = main.ui_manager.playerboard_ui.get_child(adj_slot) if adj_slot_ui.get_child_count() > 2: adj_slot_ui.get_child(2).show() - player.highlighted_cells.append(adj_slot) + player.action_manager.highlighted_cells.append(adj_slot) # Connect to slot click signals for i in range(player.playerboard.size()): @@ -336,10 +336,10 @@ func find_best_goal_slot_for_item(item: int) -> int: for i in range(3): for j in range(3): if goals_2d[i][j] == item: - var board_row = i + 1 # offset to center in 5x5 + var board_row = i + 1 # offset to center in 5x5 var board_col = j + 1 var slot_index = board_row * 5 + board_col - if player.playerboard[slot_index] == -1: # only if empty + if player.playerboard[slot_index] == -1: # only if empty return slot_index # No ideal slot? Return any empty slot @@ -377,7 +377,7 @@ func find_best_put_candidate() -> Dictionary: # Is it already in the correct central position? var is_in_correct_central_spot = false - if board_i in [1,2,3] and board_j in [1,2,3]: + if board_i in [1, 2, 3] and board_j in [1, 2, 3]: var goal_i = board_i - 1 var goal_j = board_j - 1 if goals_2d[goal_i][goal_j] == item: @@ -424,7 +424,7 @@ func find_best_put_candidate() -> Dictionary: if not valid_cells.is_empty(): return { "slot_index": cand.slot, - "grid_position": valid_cells[0] # pick first valid cell + "grid_position": valid_cells[0] # pick first valid cell } # Fallback: just put any candidate item @@ -446,9 +446,9 @@ func get_adjacent_playerboard_slots(slot_index) -> Array: return adjacent func is_valid_arrangement_slot(from_slot: int, to_slot: int) -> bool: - var from_row = from_slot / 5 + var from_row = int(from_slot / 5) var from_col = from_slot % 5 - var to_row = to_slot / 5 + var to_row = int(to_slot / 5) var to_col = to_slot % 5 var row_diff = abs(from_row - to_row) diff --git a/scripts/nakama_manager.gd b/scripts/nakama_manager.gd index bd2d658..2c70612 100644 --- a/scripts/nakama_manager.gd +++ b/scripts/nakama_manager.gd @@ -33,12 +33,17 @@ func _process(_delta): if socket: pass -func connect_to_nakama_async(email: String = "", password: String = "") -> void: +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()) + device_id = device_id + str(randi()) session = await client.authenticate_device_async(device_id) else: session = await client.authenticate_email_async(email, password) @@ -46,7 +51,7 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> void: if session.is_exception(): printerr("Auth Error: ", session.get_exception().message) emit_signal("connection_failed", session.get_exception().message) - return + return false # 2. Connect Socket socket = Nakama.create_socket_from(client) @@ -55,7 +60,7 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> void: if socket_result.is_exception(): printerr("Socket Error: ", socket_result.get_exception().message) emit_signal("connection_failed", socket_result.get_exception().message) - return + return false # 3. Initialize Multiplayer Bridge # This links Nakama's socket to Godot's High-Level Multiplayer API @@ -71,6 +76,7 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> void: print("Connected to Nakama and Bridge Initialized.") emit_signal("connected_to_nakama") + return true # --- Match Management ---