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 @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] 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 var target_rotation: float = 0.0 var rotation_speed: float = 10.0 @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 var highlighted_cells = [] 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") # 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) # 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) @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) 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 # 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) func is_position_occupied(pos: Vector2i) -> bool: for player in get_tree().get_nodes_in_group("Players"): if player != self and player.current_position == pos: return true return false func find_valid_starting_position() -> Vector2i: var rng = RandomNumberGenerator.new() rng.randomize() var max_attempts = 100 var attempts = 0 while attempts < max_attempts: var x = rng.randi_range(0, enhanced_gridmap.columns - 1) var y = rng.randi_range(0, enhanced_gridmap.rows - 1) var pos = Vector2i(x, y) if not is_position_occupied(pos): return pos attempts += 1 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 # #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 # #rotate_towards_target(grid_position) # #var path = enhanced_gridmap.find_path(Vector2(current_position), Vector2(grid_position)) #if path.size() <= 1: #return # #var valid_path = true #for point in path.slice(1): #if is_position_occupied(Vector2i(point.x, point.y)): #valid_path = false #break # #if valid_path: #path.pop_front() ## Pass clear_visual=false for bots to preserve player highlights #rpc("start_movement_along_path", path, not (is_bot or is_in_group("Bots"))) #action_points -= 1 # ## Only clear highlights if not a bot #if not (is_bot or is_in_group("Bots")): #clear_highlights() #else: #print("Path is blocked by other players") func move_player_to_clicked_position(grid_position: Vector2i): if not is_multiplayer_authority() or is_player_moving or action_points <= 0: 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() @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 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 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() @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 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 put_item(grid_position: Vector2i = current_position) -> bool: if not enhanced_gridmap or action_points <= 0 or selected_playerboard_slot == -1: return false var cell = Vector3i(grid_position.x, 1, grid_position.y) if enhanced_gridmap.get_cell_item(cell) != -1: return false # 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 false # Get the item to place first var item = playerboard[selected_playerboard_slot] # For clients, we need to RPC to the server first, then let the server RPC back if is_multiplayer_authority() and not multiplayer.is_server(): # Client requests server to perform the put operation rpc_id(1, "request_server_put", grid_position, selected_playerboard_slot, cell.x, cell.y, cell.z, item) # We'll return true and let the server handle the actual operation # The server will RPC back to update our state if successful return true elif is_multiplayer_authority() and multiplayer.is_server(): # Server directly implements the change rpc("sync_grid_item", cell.x, cell.y, cell.z, item) playerboard[selected_playerboard_slot] = -1 rpc("sync_playerboard", playerboard) has_performed_action = true consume_action_points(1) if not is_bot == true: clear_highlights() clear_playerboard_highlights() selected_playerboard_slot = -1 var main = get_tree().get_root().get_node_or_null("Main") if main: main.set_action_state(main.ActionState.NONE) _after_action_completed() return true return false @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() 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) 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 ) # Modify highlight functions to use the new authorized highlight method func highlight_movement_range(): if not is_multiplayer_authority() or is_bot or is_in_group("Bots"): return var cells_to_highlight = [] # For each position within movement range for x in range(max(0, current_position.x - movement_range), min(enhanced_gridmap.columns, current_position.x + movement_range + 1)): for z in range(max(0, current_position.y - movement_range), min(enhanced_gridmap.rows, current_position.y + movement_range + 1)): var test_pos = Vector2i(x, z) # Skip the current position if test_pos == current_position: continue # Check if within movement range if is_within_movement_range(test_pos): var cell_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) # Basic walkability check if cell_item != -1 and not (cell_item in enhanced_gridmap.non_walkable_items) and not is_position_occupied(test_pos): # Check if there's a direct path without obstacles var path_blocked = false # For orthogonal movement if test_pos.x == current_position.x or test_pos.y == current_position.y: path_blocked = enhanced_gridmap.is_blocked_by_obstacle(current_position, test_pos, 3) else: # For diagonal movement, check if both orthogonal paths are blocked var mid1 = Vector2i(test_pos.x, current_position.y) var mid2 = Vector2i(current_position.x, test_pos.y) var path1_blocked = enhanced_gridmap.is_blocked_by_obstacle(current_position, mid1, 3) var path2_blocked = enhanced_gridmap.is_blocked_by_obstacle(current_position, mid2, 3) # Only completely block if both paths are blocked path_blocked = path1_blocked and path2_blocked if not path_blocked: cells_to_highlight.append(test_pos) highlight_cells_if_authorized(cells_to_highlight) 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 # #var valid_cells = [] #for x in range(enhanced_gridmap.columns): #for z in range(enhanced_gridmap.rows): #var cell_pos = Vector2i(x, z) #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): #valid_cells.append(cell_pos) # #var rng = RandomNumberGenerator.new() #rng.randomize() #for _i in range(min(5, valid_cells.size())): #var index = rng.randi() % valid_cells.size() #var cell = valid_cells[index] #highlighted_cells.append(cell) #enhanced_gridmap.set_cell_item(Vector3i(cell.x, 0, cell.y), enhanced_gridmap.hover_item) #valid_cells.remove_at(index) 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(): # 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) 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_end_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)