extends Node3D @export var is_bot: bool = false @export var enhanced_gridmap_path: NodePath = "/root/Main/EnhancedGridMap" var enhanced_gridmap: EnhancedGridMap @export var current_position: Vector2i var is_player_moving: bool = false var _verify_timer: float = 0.0 var can_finish @export var cell_size: Vector3 = Vector3(2, 2, 2) @export var cell_offset: Vector3 = Vector3(0, 0, 0) @export var goals: Array[int] = [0,0,0,0,0,0,0,0,0] @export var playerboard: Array[int] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] # Modifier for Turn based var has_performed_action: bool = false var selected_gridmap_position = Vector2i(-1, -1) var selected_playerboard_slot = -1 var targeted_playerboard_slot = -1 var action_points: int = 2 # Modifier for player models var target_rotation: float = 0.0 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, 6), Vector2i(0, 7), Vector2i(0, 8), Vector2i(0, 9), Vector2i(0, 10), Vector2i(0, 11), Vector2i(0, 12), Vector2i(0, 13) ] # Add these as class variables at the top of the file var finish_locations = [ Vector2i(13, 0), # (13,0,0) Vector2i(13, 1), # (13,0,1) Vector2i(13, 2), # (13,0,2) Vector2i(13, 3), # (13,0,3) Vector2i(13, 4), # (13,0,4) Vector2i(13, 5), # (13,0,5) Vector2i(13, 6), Vector2i(13, 7), Vector2i(13, 8), Vector2i(13, 9), Vector2i(13, 10), Vector2i(13, 11), Vector2i(13, 12), Vector2i(13, 13) ] 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 @export var use_diagonal_movement: bool = false: set(value): use_diagonal_movement = value if enhanced_gridmap: enhanced_gridmap.set_diagonal_movement(value) @export var is_my_turn: bool = false: set(value): is_my_turn = value if is_my_turn and is_multiplayer_authority(): rpc("display_message", "It's your turn!") @export var has_moved_this_turn = false # Track, Lap, Position var current_lap: int = 0 var first_lap_goals: Array[int] = [] # Current goals (partial 3x3) var second_lap_goals: Array[int] = [] # Full 3x3 goals var race_position: int = 0 # Track finish position static var lap1_finishers: int = 0 # Static to track across all players static var lap2_finishers: int = 0 # Function to get ordinal string (1st, 2nd, 3rd, 4th) func get_ordinal_string(number: int) -> String: match number: 1: return "1st" 2: return "2nd" 3: return "3rd" 4: return "4th" _: return str(number) + "th" func _ready(): # Ensure name is set first name = str(get_multiplayer_authority()) $Name.text = str(name) # Wait briefly to ensure proper scene setup await get_tree().create_timer(0.1).timeout # More robust way to get the main scene var main_scene = get_tree().get_root().get_node_or_null("Main") if not main_scene: push_error("Main scene not found") return # Modifier to handle the lap if is_multiplayer_authority(): # Generate the first_lap_goals first_lap_goals = goals.duplicate() # Generate the second_lap_goals generate_second_lap_goals() # Ensure proper initialization order enhanced_gridmap = get_node(enhanced_gridmap_path) if main_scene: enhanced_gridmap = main_scene.get_node("EnhancedGridMap") # 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) set_process(false) set_physics_process(false) # Disable visual highlights for bots highlighted_cells.clear() if behavior_tree: behavior_tree.enabled = is_multiplayer_authority() behavior_tree.actor = self rpc("sync_bot_status", true) ## Initialize bot-specific components #if enhanced_gridmap: #current_position = find_valid_starting_position() #update_player_position(current_position) # Spawn point handler if enhanced_gridmap: current_position = _find_random_spawn_position() update_player_position(current_position) spawn_point_selected = true # Notify others about bot spawn position rpc("notify_spawn_selected", current_position) else: # Human player initialization if enhanced_gridmap: enhanced_gridmap.initialize_astar() enhanced_gridmap.set_diagonal_movement(use_diagonal_movement) # Request current spawn positions before highlighting request_spawn_positions_update() highlight_available_spawn_points() # Remove this line as goals are now managed by the host #append_random_goals() playerboard.resize(25) playerboard.fill(-1) return # Disable Beehave tree if this is not a bot if not is_bot and has_node("BehaviorTree"): $BehaviorTree.enabled = false # Rest of initialization (only for human players) if enhanced_gridmap: enhanced_gridmap.initialize_astar() enhanced_gridmap.set_diagonal_movement(use_diagonal_movement) current_position = find_valid_starting_position() update_player_position(current_position) #append_random_goals() playerboard.resize(25) playerboard.fill(-1) # Ensure proper initial positioning global_position = Vector3( current_position.x * cell_size.x + cell_size.x * 0.5, 1.0, current_position.y * cell_size.z + cell_size.z * 0.5 ) if is_multiplayer_authority(): rpc("sync_position", current_position) # Add function to check if position is at finish line func is_at_finish_line() -> bool: return current_position in finish_locations # Helper function to check if a 3x3 section matches the goals pattern func check_3x3_section(board: Array, goals: Array, start_row: int, start_col: int) -> bool: # First check if any required positions are empty (-1) for i in range(3): for j in range(3): if goals[i][j] != -1: # Only check positions that are required by the goals var board_value = board[start_row + i][start_col + j] if board_value == -1: # If required position is empty return false # Then check if the pattern matches for i in range(3): for j in range(3): if goals[i][j] != -1: # Only check positions that are required by the goals if board[start_row + i][start_col + j] != goals[i][j]: return false return true # Generate full 3x3 goals for second lap func generate_second_lap_goals(): second_lap_goals.clear() # Generate a complete 3x3 pattern (no empty spaces) for i in range(9): var val = (randi() % 4) + 7 # Values between 7 and 10 second_lap_goals.append(val) if is_multiplayer_authority(): rpc("sync_second_lap_goals", second_lap_goals) @rpc("any_peer", "reliable") func sync_second_lap_goals(new_goals: Array): second_lap_goals = new_goals # Modify finish_race to handle lap completion @rpc("any_peer", "reliable") func finish_race(): if current_lap == 0: # Finishing first lap lap1_finishers += 1 race_position = lap1_finishers # Display first lap completion message var message = "Finish 1st lap on " + get_ordinal_string(race_position) if is_multiplayer_authority(): rpc("display_message", message) # Start second lap current_lap += 1 rpc("start_new_lap") elif current_lap == 1: # Finishing second lap lap2_finishers += 1 race_position = lap2_finishers # Display second lap completion message var message = "Finish 2nd lap on " + get_ordinal_string(race_position) if is_multiplayer_authority(): rpc("display_message", message) # Add function to check 3x3 pattern matching anywhere in 5x5 playerboard func check_pattern_match() -> bool: # Early return if playerboard or goals are not properly sized if playerboard.size() != 25 or goals.size() != 9: return false var current_goals = goals # Convert 1D arrays to 2D for easier pattern matching var board_2d = [] var goals_2d = [] # Convert playerboard to 2D (5x5) for i in range(5): var row = [] for j in range(5): row.append(playerboard[i * 5 + j]) board_2d.append(row) # Convert goals to 2D (3x3) for i in range(3): var row = [] for j in range(3): row.append(goals[i * 3 + j]) goals_2d.append(row) # Check each possible 3x3 section in the 5x5 board for start_row in range(3): # 5-3+1 possible starting rows for start_col in range(3): # 5-3+1 possible starting columns if check_3x3_section(board_2d, goals_2d, start_row, start_col): return true return false ## Add function to handle new lap #@rpc("any_peer", "reliable") #func start_new_lap(): ## Reset position to start #current_position = find_valid_starting_position() #update_player_position(current_position) # ## Reset playerboard but keep the goals #playerboard.fill(-1) # ## Reset can_finish flag #can_finish = false # ## Sync with other clients #if is_multiplayer_authority(): #rpc("sync_position", current_position) #rpc("sync_playerboard", playerboard) # Modify start_new_lap to handle different lap goals and starting positions @rpc("any_peer", "reliable") func start_new_lap(): if current_lap == 1: # Moving to second lap # Start from first lap finish line var valid_finish_pos = find_valid_position_in_finish_line() if valid_finish_pos != Vector2i(-1, -1): current_position = valid_finish_pos update_player_position(current_position) # Set new goals (full 3x3) goals = second_lap_goals.duplicate() # Reset playerboard #playerboard.fill(-1) # Reset can_finish flag can_finish = false # Sync with other clients if is_multiplayer_authority(): rpc("sync_position", current_position) rpc("sync_playerboard", playerboard) rpc("sync_goals", goals) # Function to find valid position in finish line func find_valid_position_in_finish_line() -> Vector2i: for pos in finish_locations: if not is_position_occupied(pos): return pos return Vector2i(-1, -1) # Add function to check if player can reach finish func update_finish_availability(): can_finish = check_pattern_match() # Update visual feedback if needed if is_multiplayer_authority(): if can_finish: highlight_finish_line() else: unhighlight_finish_line() func unhighlight_finish_line(): if not is_multiplayer_authority() or is_bot: return for finish_pos in finish_locations: if enhanced_gridmap: enhanced_gridmap.set_cell_item( Vector3i(finish_pos.x, 0, finish_pos.y), enhanced_gridmap.normal_items[0] ) # Add functions to handle finish line visualization func highlight_finish_line(): if not is_multiplayer_authority() or is_bot: return for finish_pos in finish_locations: if enhanced_gridmap: enhanced_gridmap.set_cell_item( Vector3i(finish_pos.x, 0, finish_pos.y), enhanced_gridmap.hover_item ) func request_spawn_positions_update(): if multiplayer.is_server(): # Server can directly highlight available positions highlight_available_spawn_points() else: # Clients request an update from the server rpc_id(1, "server_update_spawn_positions") # Add server-side spawn position update handler @rpc("any_peer", "reliable") func server_update_spawn_positions(): if not multiplayer.is_server(): return var sender_id = multiplayer.get_remote_sender_id() var occupied = get_occupied_positions() # Send the occupied positions back to the requesting client rpc_id(sender_id, "receive_spawn_positions_update", occupied) # Add client-side spawn position update receiver @rpc("authority", "reliable") func receive_spawn_positions_update(occupied_positions: Array): # Update local highlight state based on received occupied positions for pos in highlighted_spawn_points: if pos in occupied_positions: highlighted_spawn_points.erase(pos) if enhanced_gridmap: enhanced_gridmap.set_cell_item( Vector3i(pos.x, 0, pos.y), enhanced_gridmap.normal_items[0] ) # Now highlight available positions highlight_available_spawn_points() @rpc("any_peer", "call_local") func sync_bot_status(is_bot_status: bool): is_bot = is_bot_status if is_bot: add_to_group("Bots", true) set_process_input(false) set_process_unhandled_input(false) # Clear any existing highlights highlighted_cells.clear() #clear_highlights() #clear_playerboard_highlights() var behavior_tree = get_node_or_null("BehaviorTree") if behavior_tree: behavior_tree.enabled = is_multiplayer_authority() behavior_tree.actor = self if not is_multiplayer_authority(): 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) # Add continuous finish line check if current_position in finish_locations and can_finish and not is_player_moving: start_new_lap() func _unhandled_input(event): # Early return if not authorized human player if not is_multiplayer_authority() or is_bot or is_in_group("Bots"): set_process_unhandled_input(false) return # Handle spawn point selection if not yet selected if not spawn_point_selected and highlighted_spawn_points.size() > 0: if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: var camera = get_viewport().get_camera_3d() var from = camera.project_ray_origin(event.position) var to = from + camera.project_ray_normal(event.position) * 1000 var click_position = raycast_to_grid(from, to) if click_position in highlighted_spawn_points: if select_spawn_point(click_position): return # Spawn point selected successfully # Use get_node_or_null for safer node access var main = get_tree().get_root().get_node_or_null("Main") if not main: return if not is_multiplayer_authority() or (main.turn_based_mode and (not is_my_turn or is_player_moving)): return if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: if is_bot == true or is_in_group("Bots"): set_process_unhandled_input(false) set_process_input(false) return var camera = get_viewport().get_camera_3d() var from = camera.project_ray_origin(event.position) var to = from + camera.project_ray_normal(event.position) * 1000 var click_position = raycast_to_grid(from, to) if click_position != Vector2i(-1, -1): handle_grid_click(click_position) func _on_slot_gui_input(event, slot_index, slot_ui) -> int: if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT: var main = get_tree().get_root().get_node_or_null("Main") if main.current_action_state == main.ActionState.ARRANGING: if selected_playerboard_slot == -1: select_playerboard_slot(slot_index) return slot_index else: if selected_playerboard_slot == slot_index: deselect_playerboard_slot() return slot_index elif can_move_to_target_playerboard_slot(): target_playerboard_slot(slot_index) main.emit_signal("can_move_item", true) return slot_index else: return -1 return -1 func handle_grid_click(grid_position: Vector2i): if is_bot == true or is_in_group("Bots"): return var main = get_tree().get_root().get_node_or_null("Main") if not main: push_error("Main node not found") return match main.current_action_state: main.ActionState.MOVING: if grid_position in highlighted_cells: move_player_to_clicked_position(grid_position) main.ActionState.GRABBING: if grid_position in highlighted_cells or grid_position == current_position: grab_item(grid_position) #main.ActionState.PUTTING: #if grid_position in highlighted_cells and selected_playerboard_slot != -1: #put_item(grid_position) main.ActionState.RANDOMIZING: if grid_position in highlighted_cells: main.randomize_item_at_position(grid_position) main.ActionState.PLACING_OBSTACLE: if grid_position in highlighted_cells: main.place_obstacle(grid_position) # Modify is_position_occupied to check for selected spawn points func is_position_occupied(pos: Vector2i) -> bool: for player in get_tree().get_nodes_in_group("Players"): if player != self and player.spawn_point_selected and player.current_position == pos: return true return false func find_valid_starting_position() -> Vector2i: if is_bot: return _find_random_spawn_position() else: highlight_available_spawn_points() # Return temporary position, will be updated when player selects spawn point return Vector2i(-1, -1) func highlight_available_spawn_points(): if not is_multiplayer_authority() or is_bot or spawn_point_selected: return # Clear any existing highlights clear_highlights() highlighted_spawn_points.clear() # Get all currently occupied positions var occupied_positions = get_occupied_positions() # Check each spawn location for spawn_pos in spawn_locations: if not is_position_occupied(spawn_pos): highlighted_spawn_points.append(spawn_pos) if enhanced_gridmap: # Highlight the cell at y=0 (ground level) enhanced_gridmap.set_cell_item( Vector3i(spawn_pos.x, 0, spawn_pos.y), enhanced_gridmap.hover_item ) # Add function to get all occupied positions 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 occupied.append(player.current_position) return occupied # Modify the select_spawn_point function to notify other clients func select_spawn_point(spawn_pos: Vector2i) -> bool: if not is_multiplayer_authority() or is_bot or spawn_point_selected: return false if spawn_pos in highlighted_spawn_points and not is_position_occupied(spawn_pos): current_position = spawn_pos spawn_point_selected = true # Update position in the world position = grid_to_world(spawn_pos) # Notify all clients about the spawn selection rpc("notify_spawn_selected", spawn_pos) # Clear highlights locally clear_spawn_highlights() # Sync position with other clients if is_multiplayer_authority(): rpc("sync_position", current_position) return true return false func clear_spawn_highlights(): # Clear the highlighted spawn points array for spawn_pos in highlighted_spawn_points: if enhanced_gridmap: # Reset the cell to its original state var cell_item = enhanced_gridmap.get_cell_item(Vector3i(spawn_pos.x, 1, spawn_pos.y)) enhanced_gridmap.set_cell_item( Vector3i(spawn_pos.x, 0, spawn_pos.y), enhanced_gridmap.normal_items[0] if cell_item != -1 else -1 ) # Clear the array highlighted_spawn_points.clear() # Force an update of the grid visualization if enhanced_gridmap: enhanced_gridmap._update_cell_option_buttons() func _find_random_spawn_position() -> Vector2i: var available_positions = [] for spawn_pos in spawn_locations: if not is_position_occupied(spawn_pos): available_positions.append(spawn_pos) if available_positions.size() > 0: var rng = RandomNumberGenerator.new() rng.randomize() return available_positions[rng.randi() % available_positions.size()] return Vector2i.ZERO func find_random_valid_position_in_range() -> Vector2i: var rng = RandomNumberGenerator.new() rng.randomize() var valid_positions = [] 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), 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): var cell_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) if cell_item != -1 and not (cell_item in enhanced_gridmap.non_walkable_items) and not is_position_occupied(pos): valid_positions.append(pos) if valid_positions.size() > 0: return valid_positions[rng.randi() % valid_positions.size()] return current_position func raycast_to_grid(from: Vector3, to: Vector3) -> Vector2i: var plane = Plane(Vector3.UP, cell_offset.y) var intersection = plane.intersects_ray(from, to - from) if intersection: var adjusted_intersection = intersection - cell_offset var grid_position = Vector2i( floor(adjusted_intersection.x / cell_size.x), floor(adjusted_intersection.z / cell_size.z) ) if grid_position.x >= 0 and grid_position.x < enhanced_gridmap.columns and \ grid_position.y >= 0 and grid_position.y < enhanced_gridmap.rows: return grid_position return Vector2i(-1, -1) func is_within_movement_range(target_position: Vector2i) -> bool: var distance: int if use_diagonal_movement: distance = max(abs(target_position.x - current_position.x), abs(target_position.y - current_position.y)) else: distance = abs(target_position.x - current_position.x) + abs(target_position.y - current_position.y) return distance <= movement_range func move_player_to_clicked_position(grid_position: Vector2i): if not is_multiplayer_authority() or is_player_moving or action_points <= 0: return # Check if trying to move to finish line if grid_position in finish_locations: if not can_finish: can_finish = check_pattern_match() if not can_finish: return # Cannot move to finish line if pattern doesn't match var is_valid_finish = false # Make scenario for match checking laps, for handle lap count if current_lap == 0: # first lap is_valid_finish = grid_position in finish_locations else: # second lap is_valid_finish = grid_position in spawn_locations if not is_within_movement_range(grid_position): return var main = get_tree().get_root().get_node_or_null("Main") if not main or main.current_action_state != main.ActionState.MOVING or not grid_position in highlighted_cells: return if not is_within_movement_range(grid_position): return var cell_item = enhanced_gridmap.get_cell_item(Vector3i(grid_position.x, 0, grid_position.y)) if cell_item in enhanced_gridmap.non_walkable_items or is_position_occupied(grid_position): return # Check if direct movement is blocked by an obstacle if enhanced_gridmap.is_blocked_by_obstacle(current_position, grid_position, 3): # Do not allow movement if blocked (this should not happen if highlight logic is correct) print("Movement blocked by obstacle") return rotate_towards_target(grid_position) # Create a direct path rather than using A* for obstacle avoidance # This ensures the player can only move to directly accessible positions var path = [Vector2(current_position.x, current_position.y), Vector2(grid_position.x, grid_position.y)] path.pop_front() rpc("start_movement_along_path", path, not (is_bot or is_in_group("Bots"))) action_points -= 1 # Clear highlights after moving if not (is_bot or is_in_group("Bots")): clear_highlights() # Handle finish line crossing if is_valid_finish and can_finish: rpc("finish_race") @rpc("any_peer", "call_local") func start_movement_along_path(path: Array, clear_visual: bool = true): is_player_moving = true var tween = create_tween() tween.set_trans(Tween.TRANS_CUBIC) tween.set_ease(Tween.EASE_IN_OUT) for point in path: tween.tween_property(self, "position", grid_to_world(Vector2i(point.x, point.y)), 0.5) tween.tween_callback(func(): current_position = Vector2i(path[-1].x, path[-1].y) is_player_moving = false # Check if we've reached the finish line if current_position in finish_locations and can_finish: finish_race() var main = get_tree().get_root().get_node_or_null("Main") # Only clear visuals if this is a human player if not (is_bot or is_in_group("Bots")): if clear_visual: enhanced_gridmap.clear_path_visualization() # Restore movement range highlights if it was the player's turn if main and main.current_action_state == main.ActionState.MOVING and is_my_turn: highlight_movement_range() has_moved_this_turn = path.size() <= movement_range if main: if not (is_bot or is_in_group("Bots")): main.set_action_state(main.ActionState.NONE) if main and main.turn_based_mode: end_turn() _after_action_completed() ) #func trigger_finish_line(): #if not is_multiplayer_authority(): #return # #if current_lap == 0: # First lap #lap1_finishers += 1 #race_position = lap1_finishers # ## Display first lap completion message #var message = "Finish 1st lap on " + get_ordinal_string(race_position) #rpc("display_message", message) #print("DEBUG: Triggered first lap finish. Position: ", race_position) # ## Start second lap #current_lap += 1 #rpc("start_new_lap") # #elif current_lap == 1: # Second lap #lap2_finishers += 1 #race_position = lap2_finishers # ## Display second lap completion message #var message = "Finish 2nd lap on " + get_ordinal_string(race_position) #rpc("display_message", message) #print("DEBUG: Triggered second lap finish. Position: ", race_position) # ##func debug_finish_state(): ##print("DEBUG: Current Position: ", current_position) ##print("DEBUG: Can Finish: ", can_finish) ##print("DEBUG: Current Lap: ", current_lap) ##print("DEBUG: Pattern Match: ", check_pattern_match()) ##print("DEBUG: Is at finish: ", current_position in finish_locations) func update_player_position(grid_position: Vector2i): position = grid_to_world(grid_position) func grid_to_world(grid_position: Vector2i) -> Vector3: var world_position = Vector3( grid_position.x * cell_size.x + cell_size.x * 0.5, cell_size.y, grid_position.y * cell_size.z + cell_size.z * 0.5 ) + cell_offset return world_position func start_turn(): action_points = 2 has_moved_this_turn = false has_performed_action = false is_my_turn = true if is_multiplayer_authority(): rpc("display_message", "It's your turn!") _after_action_completed() func end_turn(): is_my_turn = false has_moved_this_turn = false if is_multiplayer_authority(): get_tree().get_root().get_node_or_null("Main").request_next_turn() func reset_race(): current_lap = 0 race_position = 0 can_finish = false goals = first_lap_goals.duplicate() playerboard.fill(-1) if is_multiplayer_authority(): rpc("sync_goals", goals) rpc("sync_playerboard", playerboard) # Add a static reset for new games static func reset_race_stats(): lap1_finishers = 0 lap2_finishers = 0 @rpc("any_peer", "call_local", "unreliable") func remote_set_position(authority_position): global_position = authority_position @rpc("any_peer", "call_local") func display_message(message): $Bubble.show() $Bubble/Message.show() $Bubble/Message.text = str(message) await get_tree().create_timer(3).timeout $Bubble.hide() $Bubble/Message.hide() 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 null_val = 0 var max_nulls = 3 const SPECIAL_VALUES = {1: 7, 2: 8, 3: 9, 4: 10} for i in range(_size): if null_val < max_nulls and rng.randf() < null_count: result.append(-1) null_val += 1 else: var val = rng.randi_range(min_value, max_value) result.append(val if not val in SPECIAL_VALUES else SPECIAL_VALUES[val]) return result # Remove this since goals are now set by main.gd func append_random_goals(): goals.append_array(initialize_random_goals(9, 7, 10, 1.0)) if is_multiplayer_authority(): 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 # ----------------------------------------------------------------- # OLD GRAB Func # ----------------------------------------------------------------- #func grab_item(grid_position: Vector2i = current_position) -> bool: #if is_bot: #return bot_try_grab_item() # #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) # #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 # ## Bot-specific grab logic moved to bot_grab_item RPC #if is_in_group("Bots") or is_bot: #var empty_slot = playerboard.find(-1) #if empty_slot == -1: #return false # #if is_multiplayer_authority(): #rpc("bot_grab_item", grid_position, empty_slot, cell.x, cell.y, cell.z) #return true # #var main = get_tree().get_root().get_node_or_null("Main") #if main: #selected_gridmap_position = grid_position #clear_highlights() #clear_playerboard_highlights() # #for i in range(playerboard.size()): #if playerboard[i] == -1: #var slot = main.playerboard_ui.get_child(i) #if slot.get_child_count() > 0: #slot.get_child(0).show() #highlighted_cells.append(i) #return true # #return false # ----------------------------------------------------------------- #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 === #var target_slot = find_best_goal_slot_for_item(item) #if target_slot == -1: #return false # no space # ## Perform the grab and auto-place #if is_multiplayer_authority(): ## Update gridmap: remove item #rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) ## Update playerboard #playerboard[target_slot] = item #rpc("sync_playerboard", playerboard) ## Consume action #has_performed_action = true #consume_action_points(1) # ## Optional: visual feedback #var main = get_tree().get_root().get_node_or_null("Main") #if main: #main.update_playerboard_ui() #main.set_action_state(main.ActionState.NONE) # #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 # ----------------------------------------------------------------- # 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 # ----------------------------------------------------------------- # This function runs on the server when requested by a client # ----------------------------------------------------------------- @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(): return # 2. Security check: Did this request come from the actual owner of this node? 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) # ----------------------------------------------------------------- # 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 # ----------------------------------------------------------------- # Force ActionState : None # ----------------------------------------------------------------- @rpc("authority", "reliable") 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() # ----------------------------------------------------------------- @rpc("any_peer", "reliable") func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int, z: int, item: int): # This RPC should only be processed by the server if not multiplayer.is_server(): return # Verify that this request came from the authority of this player if multiplayer.get_remote_sender_id() != get_multiplayer_authority(): push_error("Security: Non-authority tried to put item!") return # Server-side validation var cell = Vector3i(x, y, z) if enhanced_gridmap.get_cell_item(cell) != -1: return # Cell not empty # Check if position is adjacent or current position if grid_position != current_position: var is_adjacent = false var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) for neighbor in neighbors: if neighbor.position == grid_position: is_adjacent = true break if not is_adjacent: return # Not adjacent # Verify player has the item if playerboard[slot_index] != item: push_error("Item mismatch! Player doesn't have claimed item") return # Perform the put operation as the server rpc("sync_grid_item", x, y, z, item) playerboard[slot_index] = -1 rpc("sync_playerboard", playerboard) # Update player state has_performed_action = true action_points -= 1 selected_playerboard_slot = -1 # Notify about action completion _after_action_completed() # Add new RPC function to notify others about spawn selection @rpc("any_peer", "reliable") func notify_spawn_selected(spawn_pos: Vector2i): # Update local highlight state for all clients if spawn_pos in highlighted_spawn_points: highlighted_spawn_points.erase(spawn_pos) # Clear highlight for the selected position if enhanced_gridmap: enhanced_gridmap.set_cell_item( Vector3i(spawn_pos.x, 0, spawn_pos.y), enhanced_gridmap.normal_items[0] ) # Disabled, auto put activated #func handle_put_action(): #var main = get_tree().get_root().get_node_or_null("Main") #if not main or action_points < 1: #return # #if not is_bot == true: #clear_highlights() #clear_playerboard_highlights() # ## Highlight non-empty slots in playerboard #for i in range(playerboard.size()): #if playerboard[i] != -1: # Highlight occupied slots #var slot = main.playerboard_ui.get_child(i) #if slot.get_child_count() > 0: #slot.get_child(0).show() # Show highlight for occupied slots #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.current_action_state == main.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.current_action_state == main.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.set_action_state(main.ActionState.NONE) _after_action_completed() # 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.current_action_state != main.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) 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.playerboard_ui: return # Store the selected slot selected_playerboard_slot = slot_index # Highlight selected slot var selected_slot_ui = main.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.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.playerboard_ui.get_child(i) if not slot.gui_input.is_connected(_on_slot_clicked): slot.gui_input.connect(_on_slot_clicked.bind(i)) 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 var main = get_tree().get_root().get_node_or_null("Main") if not main or main.current_action_state != main.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.update_playerboard_ui() main.set_action_state(main.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) return enhanced_gridmap.get_cell_item(current_cell) != -1 func has_items_in_playerboard() -> bool: return playerboard.any(func(item): return item != -1) 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 ) # Update highlight_movement_range to respect the expanded obstacle blocking func highlight_movement_range(): if not is_multiplayer_authority() or is_bot or is_in_group("Bots"): return # Prevent recursive highlighting if _is_highlighting: return _is_highlighting = true clear_highlights() var cells_to_highlight = [] # First, identify all cells that are blocked by obstacles var blocked_cells = [] # Check all cells for obstacles and get their blocked cells for x in range(enhanced_gridmap.columns): for z in range(enhanced_gridmap.rows): var cell_pos = Vector2i(x, z) var cell_pos3d = Vector3i(x, 3, z) if enhanced_gridmap.has_obstacle_at(cell_pos3d): var orientation = enhanced_gridmap.get_obstacle_orientation(cell_pos3d) blocked_cells.append_array(enhanced_gridmap.get_cells_blocked_by_obstacle(cell_pos, orientation, 3)) # Now highlight all cells within movement range that aren't blocked 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), min(enhanced_gridmap.rows, current_position.y + movement_range + 1)): var test_pos = Vector2i(x, z) # Skip current position if test_pos == current_position: continue # Check if within movement range if is_within_movement_range(test_pos): # Skip if blocked by obstacle if test_pos in blocked_cells: continue # Check basic walkability var cell_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items or is_position_occupied(test_pos): continue # Check if there's a valid path to this cell if can_reach_cell(test_pos, blocked_cells): cells_to_highlight.append(test_pos) # At the end of the function: highlight_cells_if_authorized(cells_to_highlight) _is_highlighting = false # Helper function to check if a cell can be reached given the blocked cells func can_reach_cell(target_pos: Vector2i, blocked_cells: Array) -> bool: # Simple BFS to find if there's a path var queue = [current_position] var visited = {current_position: true} var steps = {current_position: 0} while not queue.is_empty(): var current = queue.pop_front() # If we've found the target, check if it's within movement range if current == target_pos: return steps[current] <= movement_range # If we've used all movement, don't explore further if steps[current] >= movement_range: continue # Try all adjacent cells var directions = [ Vector2i(0, -1), # North Vector2i(1, 0), # East Vector2i(0, 1), # South Vector2i(-1, 0), # West ] # Add diagonal directions if enabled if enhanced_gridmap.diagonal_movement: directions.append(Vector2i(-1, -1)) # Northwest directions.append(Vector2i(1, -1)) # Northeast directions.append(Vector2i(-1, 1)) # Southwest directions.append(Vector2i(1, 1)) # Southeast for dir in directions: var next_pos = current + dir # Skip if already visited, blocked, or not valid if visited.has(next_pos) or next_pos in blocked_cells: continue if not enhanced_gridmap.is_position_valid(next_pos) or not enhanced_gridmap.is_cell_walkable(next_pos, 0): continue if is_position_occupied(next_pos) and next_pos != target_pos: continue # Check if movement between cells is blocked by an obstacle if not is_diagonal_direction(dir) and enhanced_gridmap.is_movement_blocked(current, next_pos, 3): continue # For diagonal movement, check if both orthogonal paths are blocked if is_diagonal_direction(dir): var mid1 = Vector2i(next_pos.x, current.y) var mid2 = Vector2i(current.x, next_pos.y) var path1_blocked = mid1 in blocked_cells or enhanced_gridmap.is_movement_blocked(current, mid1, 3) var path2_blocked = mid2 in blocked_cells or enhanced_gridmap.is_movement_blocked(current, mid2, 3) if path1_blocked and path2_blocked: continue # Add to queue queue.append(next_pos) visited[next_pos] = true steps[next_pos] = steps[current] + 1 return false # Helper function to check if a direction is diagonal func is_diagonal_direction(direction: Vector2i) -> bool: return direction.x != 0 and direction.y != 0 func highlight_adjacent_cells(): if not is_multiplayer_authority() or is_bot or is_in_group("Bots"): return var cells_to_highlight = [] # Add current position if item exists var current_cell = Vector3i(current_position.x, 1, current_position.y) if enhanced_gridmap.get_cell_item(current_cell) != -1: cells_to_highlight.append(current_position) # Add valid neighbors var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) for neighbor in neighbors: if neighbor.is_walkable: var cell_pos = neighbor.position if enhanced_gridmap.get_cell_item(Vector3i(cell_pos.x, 1, cell_pos.y)) != -1: cells_to_highlight.append(cell_pos) highlight_cells_if_authorized(cells_to_highlight) 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) @rpc("any_peer", "call_local") func sync_action_points(points: int): 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) 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.playerboard_ui: return # First reset all slots to normal for i in range(playerboard.size()): var slot = main.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.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.update_playerboard_ui() 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.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.playerboard_ui: for i in range(main.playerboard_ui.get_child_count()): var slot = main.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.ActionState.MOVING and is_my_turn and current_state != main.ActionState.PLACING_OBSTACLE: highlight_movement_range() 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.playerboard_ui: for i in range(main.playerboard_ui.get_child_count()): var slot = main.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() func rotate_towards_target(target_pos: Vector2i): var direction = Vector2(target_pos.x - current_position.x, target_pos.y - current_position.y).normalized() target_rotation = atan2(direction.x, direction.y) if is_multiplayer_authority(): rpc("sync_rotation", target_rotation) var tween = create_tween() tween.tween_property(self, "rotation:y", target_rotation, 0.2) # 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() 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() 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) 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) 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) 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) 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() @rpc("any_peer", "call_local", "reliable") func sync_rotation(new_rotation: float): if not is_multiplayer_authority(): rotation.y = new_rotation @rpc("any_peer", "call_local", "reliable") func sync_grid_item(x: int, y: int, z: int, item: int): if enhanced_gridmap: var cell = Vector3i(x, y, z) # Log the change for debugging print("Setting grid item at ", cell, " to ", item, " (called by ", multiplayer.get_remote_sender_id(), ")") # Make sure we set the cell reliably enhanced_gridmap.set_cell_item(cell, item) # Double-check the cell was set var check_value = enhanced_gridmap.get_cell_item(cell) if check_value != item: push_warning("Cell item didn't update correctly! Expected " + str(item) + " but got " + str(check_value)) # Try once more enhanced_gridmap.set_cell_item(cell, item) @rpc("any_peer", "call_local") func sync_goals(new_goals: Array): goals = new_goals.duplicate() # Make sure to duplicate the array @rpc("any_peer", "call_local") func sync_playerboard(new_playerboard: Array): playerboard = new_playerboard.duplicate() var main = get_tree().get_root().get_node_or_null("Main") if main: main.rpc("sync_playerboard", get_multiplayer_authority(), playerboard) _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 main.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.update_button_states() main.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 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 main.turn_based_mode: _after_action_completed() return action_points -= points if action_points <= 0: if main.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() @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() @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() @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() func update_visual_position(): # Ensure proper grid-aligned positioning global_position = Vector3( current_position.x * cell_size.x + cell_size.x * 0.5, 1.0, current_position.y * cell_size.z + cell_size.z * 0.5 ) if is_multiplayer_authority(): rpc("sync_position", current_position) @rpc("any_peer", "call_local") func sync_position(pos: Vector2i): current_position = pos # Always update the visual position after position sync global_position = Vector3( current_position.x * cell_size.x + cell_size.x * 0.5, cell_size.y, current_position.y * cell_size.z + cell_size.z * 0.5 ) + 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)