extends Node3D # Managers var movement_manager var race_manager var input_manager var playerboard_manager var action_manager var special_tiles_manager # Special effect states var is_frozen: bool = false var is_invisible: bool = false var original_movement_range: int = 1 @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: get: return movement_manager.is_moving if movement_manager else false set(value): if movement_manager: movement_manager.is_moving = value var _verify_timer: float = 0.0 var can_finish: bool: get: return race_manager.can_finish if race_manager else false set(value): if race_manager: race_manager.can_finish = value @export var cell_size: Vector3 = Vector3(2, 2, 2) @export var cell_offset: Vector3 = Vector3(0, 0, 0) @export var goals: Array[int]: get: return race_manager.goals if race_manager else Array([], TYPE_INT, "", null) as Array[int] set(value): if race_manager: race_manager.goals = value @export var playerboard: Array[int]: get: return race_manager.playerboard if race_manager else Array([], TYPE_INT, "", null) as Array[int] set(value): if race_manager: race_manager.playerboard = value # Modifier for Turn based var has_performed_action: bool = false var _is_processing_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 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: Array: get: return race_manager.finish_locations if race_manager else [] var spawn_point_selected = false # Action for hilighter var highlighted_spawn_points = [] @export var movement_range: int = 1: set(value): movement_range = value if movement_manager: movement_manager.movement_range = value @export var use_diagonal_movement: bool = false: set(value): use_diagonal_movement = value if enhanced_gridmap: enhanced_gridmap.set_diagonal_movement(value) if movement_manager: movement_manager.use_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 # Delegated to RaceManager 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 # Ensure proper initialization order enhanced_gridmap = get_node(enhanced_gridmap_path) if main_scene: enhanced_gridmap = main_scene.get_node("EnhancedGridMap") _init_managers() # 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 action_manager.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) func _init_managers(): movement_manager = load("res://scripts/managers/player_movement_manager.gd").new() movement_manager.name = "MovementManager" add_child(movement_manager) movement_manager.initialize(self, enhanced_gridmap) race_manager = load("res://scripts/managers/player_race_manager.gd").new() race_manager.name = "RaceManager" add_child(race_manager) race_manager.initialize(self, enhanced_gridmap) input_manager = load("res://scripts/managers/player_input_manager.gd").new() input_manager.name = "InputManager" add_child(input_manager) input_manager.initialize(self, movement_manager, race_manager) playerboard_manager = load("res://scripts/managers/playerboard_manager.gd").new() playerboard_manager.name = "PlayerboardManager" add_child(playerboard_manager) playerboard_manager.initialize(self, enhanced_gridmap) action_manager = load("res://scripts/managers/player_action_manager.gd").new() action_manager.name = "ActionManager" add_child(action_manager) action_manager.initialize(self, enhanced_gridmap) special_tiles_manager = load("res://scripts/managers/special_tiles_manager.gd").new() special_tiles_manager.name = "SpecialTilesManager" add_child(special_tiles_manager) special_tiles_manager.initialize(self, enhanced_gridmap) # Add function to check if position is at finish line func is_at_finish_line() -> bool: return race_manager.is_at_finish_line() # Helper function to check if a 3x3 section matches the goals pattern # Delegated to RaceManager # Generate full 3x3 goals for second lap # Delegated to RaceManager # Modify finish_race to handle lap completion @rpc("any_peer", "reliable") func finish_race(): race_manager.finish_race() # Add function to check 3x3 pattern matching anywhere in 5x5 playerboard func check_pattern_match() -> bool: return race_manager.check_pattern_match() ## 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(): race_manager.start_new_lap() # Function to find valid position in finish line # Delegated to RaceManager # Add function to check if player can reach finish func update_finish_availability(): race_manager.update_finish_availability() func unhighlight_finish_line(): race_manager.unhighlight_finish_line() # Add functions to handle finish line visualization func highlight_finish_line(): race_manager.highlight_finish_line() 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 action_manager.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") # Delegate rotation to movement manager if movement_manager: movement_manager._process(delta) @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 var last_sent_position: Vector3 func _physics_process(delta): if is_multiplayer_authority(): if global_position.distance_squared_to(last_sent_position) > 0.001: rpc("remote_set_position", global_position) last_sent_position = global_position # Add continuous finish line check (uses lap-aware finish locations) var current_finish = race_manager.get_current_finish_locations() if race_manager else [] if race_manager and current_position in current_finish and can_finish and not is_player_moving: finish_race() # This handles lap increment and calls start_new_lap properly # -------------------------------------------------------------------- # Input # -------------------------------------------------------------------- func _unhandled_input(event): if input_manager: input_manager.handle_unhandled_input(event) func _on_slot_gui_input(event, slot_index, slot_ui) -> int: if input_manager: return input_manager.handle_slot_gui_input(event, slot_index, slot_ui) return -1 func handle_grid_click(grid_position: Vector2i): if input_manager: input_manager.handle_grid_click(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: return movement_manager.is_within_movement_range(target_position) # ----------------------------------------------------------------- # Movement # ----------------------------------------------------------------- func simple_move_to(grid_position: Vector2i): movement_manager.simple_move_to(grid_position) func move_player_to_clicked_position(grid_position: Vector2i): movement_manager.move_to_clicked_position(grid_position) @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.25) 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 (uses lap-aware finish locations) var current_finish_locs = race_manager.get_current_finish_locations() if race_manager else finish_locations if current_position in current_finish_locs 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.ui_manager.current_action_state == main.ui_manager.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.ui_manager.current_action_state = main.ui_manager.ActionState.NONE if TurnManager.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(): if race_manager: race_manager.current_lap = 0 race_manager.race_position = 0 race_manager.can_finish = false race_manager.goals = race_manager.first_lap_goals.duplicate() race_manager.playerboard.fill(-1) if is_multiplayer_authority(): rpc("sync_goals", race_manager.goals) rpc("sync_playerboard", race_manager.playerboard) # Add a static reset for new games static func reset_race_stats(): var race_mgr = load("res://scripts/managers/player_race_manager.gd") if race_mgr: race_mgr.lap1_finishers = 0 race_mgr.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): # Send message to the main scene's message bar instead of player bubble var main = get_tree().get_root().get_node_or_null("Main") if main and main.has_method("add_message_to_bar"): var player_name = $Name.text.split("\n")[0] if $Name else str(name) main.add_message_to_bar(player_name, message) 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: return playerboard_manager.bot_try_grab_item() # ----------------------------------------------------------------- # 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: return playerboard_manager.grab_item(grid_position) # ----------------------------------------------------------------- # Execute Grab # ----------------------------------------------------------------- func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int): return playerboard_manager._execute_grab(grid_pos, cell, item_id) # ----------------------------------------------------------------- # 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 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: return playerboard_manager.auto_put_item() # ----------------------------------------------------------------- # Force ActionState : None # ----------------------------------------------------------------- @rpc("any_peer", "call_local", "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 and main.ui_manager: main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE action_manager.clear_highlights() action_manager.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): 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): playerboard_manager.handle_put_slot_selected(slot_index) func arrange_playerboard_item(slot_index: int): 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) 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): action_manager.highlight_cells_if_authorized(cells_to_highlight) # Update highlight_movement_range to respect the expanded obstacle blocking func highlight_movement_range(): movement_manager.highlight_movement_range() # Helper function to check if a cell can be reached given the blocked cells # Helper function to check if a direction is diagonal func highlight_adjacent_cells(): movement_manager.highlight_adjacent_cells() func highlight_empty_adjacent_cells(): action_manager.highlight_empty_adjacent_cells() @rpc("any_peer", "call_local") func sync_action_points(points: int): action_manager.sync_action_points(points) func highlight_random_valid_cells(): action_manager.highlight_random_valid_cells() func highlight_occupied_playerboard_slots(): action_manager.highlight_occupied_playerboard_slots() func clear_highlights(): action_manager.clear_highlights() func clear_playerboard_highlights(): 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): playerboard_manager.select_playerboard_slot(slot_index) func deselect_playerboard_slot(): playerboard_manager.deselect_playerboard_slot() func target_playerboard_slot(slot_index: int): playerboard_manager.target_playerboard_slot(slot_index) func untarget_playerboard_slot(): playerboard_manager.untarget_playerboard_slot() func can_move_to_target_playerboard_slot() -> bool: return playerboard_manager.can_move_to_target_playerboard_slot() func _update_playerboard_slot_visual(slot_index: int): playerboard_manager._update_playerboard_slot_visual(slot_index) func _highlight_adjacent_playerboard_slots(): playerboard_manager._highlight_adjacent_playerboard_slots() @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 # Also update race_manager's goals directly if race_manager: race_manager.goals = new_goals.duplicate() # Re-check finish availability with new goals race_manager.update_finish_availability() # Update the AllPlayerGoals UI var main = get_tree().get_root().get_node_or_null("Main") if main and main.has_method("_update_goals_ui_for_player"): var player_id = get_multiplayer_authority() main._update_goals_ui_for_player(player_id, new_goals) @rpc("any_peer", "call_local") func sync_second_lap_goals(new_goals: Array): if race_manager: race_manager.second_lap_goals = new_goals.duplicate() @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) action_manager.after_action_completed() func _after_action_completed(): action_manager.after_action_completed() func is_finish_position(pos: Vector2i) -> bool: var current_finish = race_manager.get_current_finish_locations() if race_manager else finish_locations return pos in current_finish func consume_action_points(points: int): 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): 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): 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): playerboard_manager.bot_arrange_item(from_slot, to_slot) 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(): action_manager.highlight_valid_obstacle_cells() @rpc("any_peer", "call_local", "reliable") func complete_race(final_position: int): if race_manager: race_manager.on_race_completed(final_position)