f9f7d34f80
Added a new example scene and player script for EnhancedGridMap. Updated mesh library, materials, and tile assets for improved visuals and structure. Modified main scene and logic to support new gridmap configuration and auto item handling. Adjusted project settings for resolution and main scene path.
2040 lines
64 KiB
GDScript
2040 lines
64 KiB
GDScript
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)
|
|
]
|
|
|
|
# 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)
|
|
]
|
|
|
|
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
|
|
|
|
#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
|
|
|
|
# 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 = []
|
|
# 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_put_positions.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_put_positions.append(pos)
|
|
|
|
if valid_put_positions.is_empty():
|
|
return false
|
|
|
|
# Step 2: Find a playerboard tile that matches any goal
|
|
var put_slot = -1
|
|
for i in range(playerboard.size()):
|
|
if playerboard[i] in goals:
|
|
put_slot = i
|
|
break
|
|
|
|
if put_slot == -1:
|
|
return false
|
|
|
|
# Step 3: Perform the put
|
|
var target_pos = valid_put_positions[0] # pick first valid
|
|
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
|
|
|
|
# Disabled logic, due the auto put
|
|
#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()
|
|
|
|
# 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)
|