1131 lines
36 KiB
GDScript
1131 lines
36 KiB
GDScript
extends Node3D
|
|
|
|
# Managers
|
|
var movement_manager
|
|
var race_manager
|
|
var input_manager
|
|
var playerboard_manager
|
|
var action_manager
|
|
var special_tiles_manager
|
|
var powerup_manager
|
|
|
|
# Score tracking
|
|
var score: int = 0
|
|
|
|
# Special effect states
|
|
var is_frozen: bool = false
|
|
var is_invisible: bool = false
|
|
var original_movement_range: int = 1
|
|
|
|
@export var is_bot: bool = false
|
|
|
|
@export var enhanced_gridmap_path: NodePath = "/root/Main/EnhancedGridMap"
|
|
var enhanced_gridmap: EnhancedGridMap
|
|
@export var current_position: Vector2i
|
|
var is_player_moving: bool = false:
|
|
get: return movement_manager.is_moving if movement_manager else false
|
|
set(value): if movement_manager: movement_manager.is_moving = value
|
|
|
|
var _verify_timer: float = 0.0
|
|
var can_finish: bool:
|
|
get: return race_manager.can_finish if race_manager else false
|
|
set(value): if race_manager: race_manager.can_finish = value
|
|
|
|
@export var cell_size: Vector3 = Vector3(2, 2, 2)
|
|
@export var cell_offset: Vector3 = Vector3(0, 0, 0)
|
|
|
|
@export var goals: Array[int]:
|
|
get: return race_manager.goals if race_manager else Array([], TYPE_INT, "", null) as Array[int]
|
|
set(value): if race_manager: race_manager.goals = value
|
|
|
|
@export var playerboard: Array[int]:
|
|
get: return race_manager.playerboard if race_manager else Array([], TYPE_INT, "", null) as Array[int]
|
|
set(value): if race_manager: race_manager.playerboard = value
|
|
|
|
# Modifier for Turn based
|
|
var has_performed_action: bool = false
|
|
var _is_processing_action: bool = false
|
|
var selected_gridmap_position = Vector2i(-1, -1)
|
|
var selected_playerboard_slot = -1
|
|
var targeted_playerboard_slot = -1
|
|
var action_points: int = 2
|
|
|
|
# Modifier for player models
|
|
var rotation_speed: float = 10.0
|
|
|
|
var spawn_locations = [
|
|
Vector2i(0, 0), # (0,1,0)
|
|
Vector2i(0, 1), # (0,1,1)
|
|
Vector2i(0, 2), # (0,1,2)
|
|
Vector2i(0, 3), # (0,1,3)
|
|
Vector2i(0, 4), # (0,1,4)
|
|
Vector2i(0, 5), # (0,1,5)
|
|
Vector2i(0, 6),
|
|
Vector2i(0, 7),
|
|
Vector2i(0, 8),
|
|
Vector2i(0, 9),
|
|
Vector2i(0, 10),
|
|
Vector2i(0, 11),
|
|
Vector2i(0, 12),
|
|
Vector2i(0, 13)
|
|
]
|
|
|
|
# Add these as class variables at the top of the file
|
|
var finish_locations: Array:
|
|
get: return race_manager.finish_locations if race_manager else []
|
|
|
|
var spawn_point_selected = false
|
|
|
|
# Action for hilighter
|
|
var highlighted_spawn_points = []
|
|
|
|
|
|
@export var movement_range: int = 1:
|
|
set(value):
|
|
movement_range = value
|
|
if movement_manager: movement_manager.movement_range = value
|
|
|
|
@export var use_diagonal_movement: bool = false:
|
|
set(value):
|
|
use_diagonal_movement = value
|
|
if enhanced_gridmap:
|
|
enhanced_gridmap.set_diagonal_movement(value)
|
|
if movement_manager: movement_manager.use_diagonal_movement = value
|
|
|
|
@export var is_my_turn: bool = false:
|
|
set(value):
|
|
is_my_turn = value
|
|
if is_my_turn and is_multiplayer_authority():
|
|
rpc("display_message", "It's your turn!")
|
|
|
|
@export var has_moved_this_turn = false
|
|
|
|
# Track, Lap, Position
|
|
# Delegated to RaceManager
|
|
|
|
func _ready():
|
|
# Ensure name is set first
|
|
name = str(get_multiplayer_authority())
|
|
$Name.text = str(name)
|
|
|
|
# Wait briefly to ensure proper scene setup
|
|
await get_tree().create_timer(0.1).timeout
|
|
|
|
# More robust way to get the main scene
|
|
var main_scene = get_tree().get_root().get_node_or_null("Main")
|
|
if not main_scene:
|
|
push_error("Main scene not found")
|
|
return
|
|
|
|
# Ensure proper initialization order
|
|
enhanced_gridmap = get_node(enhanced_gridmap_path)
|
|
if main_scene:
|
|
enhanced_gridmap = main_scene.get_node("EnhancedGridMap")
|
|
|
|
_init_managers()
|
|
|
|
# Early setup for bots
|
|
if is_bot == true or is_in_group("Bots"):
|
|
# Initialize behavior tree for bots
|
|
var behavior_tree = $BehaviorTree
|
|
# Disable all input processing for bots immediately
|
|
set_process_input(false)
|
|
set_process_unhandled_input(false)
|
|
set_process(false)
|
|
set_physics_process(false)
|
|
|
|
# Disable visual highlights for bots
|
|
action_manager.highlighted_cells.clear()
|
|
|
|
if behavior_tree:
|
|
behavior_tree.enabled = is_multiplayer_authority()
|
|
behavior_tree.actor = self
|
|
|
|
rpc("sync_bot_status", true)
|
|
|
|
## Initialize bot-specific components
|
|
#if enhanced_gridmap:
|
|
#current_position = find_valid_starting_position()
|
|
#update_player_position(current_position)
|
|
|
|
# Spawn point handler
|
|
if enhanced_gridmap:
|
|
current_position = _find_random_spawn_position()
|
|
update_player_position(current_position)
|
|
spawn_point_selected = true
|
|
# Notify others about bot spawn position
|
|
rpc("notify_spawn_selected", current_position)
|
|
|
|
else:
|
|
# Human player initialization
|
|
if enhanced_gridmap:
|
|
enhanced_gridmap.initialize_astar()
|
|
enhanced_gridmap.set_diagonal_movement(use_diagonal_movement)
|
|
|
|
# Request current spawn positions before highlighting
|
|
request_spawn_positions_update()
|
|
|
|
highlight_available_spawn_points()
|
|
# Remove this line as goals are now managed by the host
|
|
#append_random_goals()
|
|
|
|
playerboard.resize(25)
|
|
playerboard.fill(-1)
|
|
return
|
|
|
|
# Disable Beehave tree if this is not a bot
|
|
if not is_bot and has_node("BehaviorTree"):
|
|
$BehaviorTree.enabled = false
|
|
|
|
# Rest of initialization (only for human players)
|
|
if enhanced_gridmap:
|
|
enhanced_gridmap.initialize_astar()
|
|
enhanced_gridmap.set_diagonal_movement(use_diagonal_movement)
|
|
current_position = find_valid_starting_position()
|
|
update_player_position(current_position)
|
|
|
|
#append_random_goals()
|
|
playerboard.resize(25)
|
|
playerboard.fill(-1)
|
|
|
|
# Ensure proper initial positioning
|
|
global_position = Vector3(
|
|
current_position.x * cell_size.x + cell_size.x * 0.5,
|
|
1.0,
|
|
current_position.y * cell_size.z + cell_size.z * 0.5
|
|
)
|
|
if is_multiplayer_authority():
|
|
rpc("sync_position", current_position)
|
|
|
|
func _init_managers():
|
|
movement_manager = load("res://scripts/managers/player_movement_manager.gd").new()
|
|
movement_manager.name = "MovementManager"
|
|
add_child(movement_manager)
|
|
movement_manager.initialize(self, enhanced_gridmap)
|
|
|
|
race_manager = load("res://scripts/managers/player_race_manager.gd").new()
|
|
race_manager.name = "RaceManager"
|
|
add_child(race_manager)
|
|
race_manager.initialize(self, enhanced_gridmap)
|
|
|
|
input_manager = load("res://scripts/managers/player_input_manager.gd").new()
|
|
input_manager.name = "InputManager"
|
|
add_child(input_manager)
|
|
input_manager.initialize(self, movement_manager, race_manager)
|
|
|
|
playerboard_manager = load("res://scripts/managers/playerboard_manager.gd").new()
|
|
playerboard_manager.name = "PlayerboardManager"
|
|
add_child(playerboard_manager)
|
|
playerboard_manager.initialize(self, enhanced_gridmap)
|
|
|
|
action_manager = load("res://scripts/managers/player_action_manager.gd").new()
|
|
action_manager.name = "ActionManager"
|
|
add_child(action_manager)
|
|
action_manager.initialize(self, enhanced_gridmap)
|
|
|
|
special_tiles_manager = load("res://scripts/managers/special_tiles_manager.gd").new()
|
|
special_tiles_manager.name = "SpecialTilesManager"
|
|
add_child(special_tiles_manager)
|
|
special_tiles_manager.initialize(self, enhanced_gridmap)
|
|
|
|
powerup_manager = load("res://scripts/managers/powerup_manager.gd").new()
|
|
powerup_manager.name = "PowerUpManager"
|
|
add_child(powerup_manager)
|
|
powerup_manager.initialize(self, enhanced_gridmap)
|
|
|
|
# Add function to check if position is at finish line
|
|
func is_at_finish_line() -> bool:
|
|
return race_manager.is_at_finish_line()
|
|
|
|
# Helper function to check if a 3x3 section matches the goals pattern
|
|
# Delegated to RaceManager
|
|
|
|
# Generate full 3x3 goals for second lap
|
|
# Delegated to RaceManager
|
|
|
|
# Modify finish_race to handle lap completion
|
|
@rpc("any_peer", "reliable")
|
|
func finish_race():
|
|
race_manager.finish_race()
|
|
|
|
# Add function to check 3x3 pattern matching anywhere in 5x5 playerboard
|
|
func check_pattern_match() -> bool:
|
|
return race_manager.check_pattern_match()
|
|
|
|
## Add function to handle new lap
|
|
#@rpc("any_peer", "reliable")
|
|
#func start_new_lap():
|
|
## Reset position to start
|
|
#current_position = find_valid_starting_position()
|
|
#update_player_position(current_position)
|
|
#
|
|
## Reset playerboard but keep the goals
|
|
#playerboard.fill(-1)
|
|
#
|
|
## Reset can_finish flag
|
|
#can_finish = false
|
|
#
|
|
## Sync with other clients
|
|
#if is_multiplayer_authority():
|
|
#rpc("sync_position", current_position)
|
|
#rpc("sync_playerboard", playerboard)
|
|
|
|
# Modify start_new_lap to handle different lap goals and starting positions
|
|
@rpc("any_peer", "reliable")
|
|
func start_new_lap():
|
|
race_manager.start_new_lap()
|
|
|
|
# Function to find valid position in finish line
|
|
# Delegated to RaceManager
|
|
|
|
# Add function to check if player can reach finish
|
|
func update_finish_availability():
|
|
race_manager.update_finish_availability()
|
|
|
|
func unhighlight_finish_line():
|
|
race_manager.unhighlight_finish_line()
|
|
|
|
# Add functions to handle finish line visualization
|
|
func highlight_finish_line():
|
|
race_manager.highlight_finish_line()
|
|
|
|
func request_spawn_positions_update():
|
|
if multiplayer.is_server():
|
|
# Server can directly highlight available positions
|
|
highlight_available_spawn_points()
|
|
else:
|
|
# Clients request an update from the server
|
|
rpc_id(1, "server_update_spawn_positions")
|
|
|
|
# Add server-side spawn position update handler
|
|
@rpc("any_peer", "reliable")
|
|
func server_update_spawn_positions():
|
|
if not multiplayer.is_server():
|
|
return
|
|
|
|
var sender_id = multiplayer.get_remote_sender_id()
|
|
var occupied = get_occupied_positions()
|
|
|
|
# Send the occupied positions back to the requesting client
|
|
rpc_id(sender_id, "receive_spawn_positions_update", occupied)
|
|
|
|
# Add client-side spawn position update receiver
|
|
@rpc("authority", "reliable")
|
|
func receive_spawn_positions_update(occupied_positions: Array):
|
|
# Update local highlight state based on received occupied positions
|
|
for pos in highlighted_spawn_points:
|
|
if pos in occupied_positions:
|
|
highlighted_spawn_points.erase(pos)
|
|
if enhanced_gridmap:
|
|
enhanced_gridmap.set_cell_item(
|
|
Vector3i(pos.x, 0, pos.y),
|
|
enhanced_gridmap.normal_items[0]
|
|
)
|
|
|
|
# Now highlight available positions
|
|
highlight_available_spawn_points()
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_bot_status(is_bot_status: bool):
|
|
is_bot = is_bot_status
|
|
if is_bot:
|
|
add_to_group("Bots", true)
|
|
set_process_input(false)
|
|
set_process_unhandled_input(false)
|
|
|
|
# Clear any existing highlights
|
|
action_manager.highlighted_cells.clear()
|
|
#clear_highlights()
|
|
#clear_playerboard_highlights()
|
|
|
|
var behavior_tree = get_node_or_null("BehaviorTree")
|
|
if behavior_tree:
|
|
behavior_tree.enabled = is_multiplayer_authority()
|
|
behavior_tree.actor = self
|
|
if not is_multiplayer_authority():
|
|
behavior_tree.set_physics_process(false)
|
|
behavior_tree.set_process(false)
|
|
|
|
func _process(delta):
|
|
if is_multiplayer_authority():
|
|
# Visual debugging - show connection status in name label
|
|
$Name.text = str(name) + "\n(Auth: " + str(get_multiplayer_authority()) + ")"
|
|
|
|
# Periodically verify our existence to others
|
|
_verify_timer += delta
|
|
if _verify_timer >= 3.0:
|
|
_verify_timer = 0.0
|
|
rpc("ping_existence")
|
|
|
|
# Delegate rotation to movement manager
|
|
if movement_manager:
|
|
movement_manager._process(delta)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func ping_existence():
|
|
# This just lets other clients know this player exists
|
|
# They can check if they have this node
|
|
pass
|
|
|
|
var last_sent_position: Vector3
|
|
|
|
func _physics_process(delta):
|
|
if is_multiplayer_authority():
|
|
if global_position.distance_squared_to(last_sent_position) > 0.001:
|
|
rpc("remote_set_position", global_position)
|
|
last_sent_position = global_position
|
|
|
|
# NOTE: Finish line checking removed - game uses cycle-based goals system now
|
|
|
|
# --------------------------------------------------------------------
|
|
# Input
|
|
# --------------------------------------------------------------------
|
|
|
|
func _unhandled_input(event):
|
|
# Handle power-up usage
|
|
if event.is_action_pressed("use_powerup") and is_multiplayer_authority():
|
|
if powerup_manager and powerup_manager.can_use_special():
|
|
powerup_manager.use_special_effect()
|
|
return
|
|
|
|
if input_manager:
|
|
input_manager.handle_unhandled_input(event)
|
|
|
|
func _on_slot_gui_input(event, slot_index, slot_ui) -> int:
|
|
if input_manager:
|
|
return input_manager.handle_slot_gui_input(event, slot_index, slot_ui)
|
|
return -1
|
|
|
|
func handle_grid_click(grid_position: Vector2i):
|
|
if input_manager:
|
|
input_manager.handle_grid_click(grid_position)
|
|
|
|
# Modify is_position_occupied to check for selected spawn points
|
|
func is_position_occupied(pos: Vector2i) -> bool:
|
|
for player in get_tree().get_nodes_in_group("Players"):
|
|
if player != self and player.spawn_point_selected and player.current_position == pos:
|
|
return true
|
|
return false
|
|
|
|
func find_valid_starting_position() -> Vector2i:
|
|
if is_bot:
|
|
return _find_random_spawn_position()
|
|
else:
|
|
highlight_available_spawn_points()
|
|
# Return temporary position, will be updated when player selects spawn point
|
|
return Vector2i(-1, -1)
|
|
|
|
func highlight_available_spawn_points():
|
|
if not is_multiplayer_authority() or is_bot or spawn_point_selected:
|
|
return
|
|
|
|
# Clear any existing highlights
|
|
clear_highlights()
|
|
highlighted_spawn_points.clear()
|
|
|
|
# Get all currently occupied positions
|
|
var occupied_positions = get_occupied_positions()
|
|
|
|
# Check each spawn location
|
|
for spawn_pos in spawn_locations:
|
|
if not is_position_occupied(spawn_pos):
|
|
highlighted_spawn_points.append(spawn_pos)
|
|
if enhanced_gridmap:
|
|
# Highlight the cell at y=0 (ground level)
|
|
enhanced_gridmap.set_cell_item(
|
|
Vector3i(spawn_pos.x, 0, spawn_pos.y),
|
|
enhanced_gridmap.hover_item
|
|
)
|
|
|
|
# Add function to get all occupied positions
|
|
func get_occupied_positions() -> Array:
|
|
var occupied = []
|
|
for player in get_tree().get_nodes_in_group("Players"):
|
|
if player.spawn_point_selected: # Only count players who have selected a spawn point
|
|
occupied.append(player.current_position)
|
|
return occupied
|
|
|
|
# Modify the select_spawn_point function to notify other clients
|
|
func select_spawn_point(spawn_pos: Vector2i) -> bool:
|
|
if not is_multiplayer_authority() or is_bot or spawn_point_selected:
|
|
return false
|
|
|
|
if spawn_pos in highlighted_spawn_points and not is_position_occupied(spawn_pos):
|
|
current_position = spawn_pos
|
|
spawn_point_selected = true
|
|
|
|
# Update position in the world
|
|
position = grid_to_world(spawn_pos)
|
|
|
|
# Notify all clients about the spawn selection
|
|
rpc("notify_spawn_selected", spawn_pos)
|
|
|
|
# Clear highlights locally
|
|
clear_spawn_highlights()
|
|
|
|
# Sync position with other clients
|
|
if is_multiplayer_authority():
|
|
rpc("sync_position", current_position)
|
|
|
|
return true
|
|
return false
|
|
|
|
func clear_spawn_highlights():
|
|
# Clear the highlighted spawn points array
|
|
for spawn_pos in highlighted_spawn_points:
|
|
if enhanced_gridmap:
|
|
# Reset the cell to its original state
|
|
var cell_item = enhanced_gridmap.get_cell_item(Vector3i(spawn_pos.x, 1, spawn_pos.y))
|
|
enhanced_gridmap.set_cell_item(
|
|
Vector3i(spawn_pos.x, 0, spawn_pos.y),
|
|
enhanced_gridmap.normal_items[0] if cell_item != -1 else -1
|
|
)
|
|
|
|
# Clear the array
|
|
highlighted_spawn_points.clear()
|
|
|
|
# Force an update of the grid visualization
|
|
if enhanced_gridmap:
|
|
enhanced_gridmap._update_cell_option_buttons()
|
|
|
|
func _find_random_spawn_position() -> Vector2i:
|
|
var available_positions = []
|
|
|
|
for spawn_pos in spawn_locations:
|
|
if not is_position_occupied(spawn_pos):
|
|
available_positions.append(spawn_pos)
|
|
|
|
if available_positions.size() > 0:
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
return available_positions[rng.randi() % available_positions.size()]
|
|
|
|
return Vector2i.ZERO
|
|
|
|
func find_random_valid_position_in_range() -> Vector2i:
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
|
|
var valid_positions = []
|
|
|
|
for x in range(max(0, current_position.x - movement_range),
|
|
min(enhanced_gridmap.columns, current_position.x + movement_range + 1)):
|
|
for z in range(max(0, current_position.y - movement_range),
|
|
min(enhanced_gridmap.rows, current_position.y + movement_range + 1)):
|
|
var pos = Vector2i(x, z)
|
|
if pos != current_position and is_within_movement_range(pos):
|
|
var cell_item = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z))
|
|
if cell_item != -1 and not (cell_item in enhanced_gridmap.non_walkable_items) and not is_position_occupied(pos):
|
|
valid_positions.append(pos)
|
|
|
|
if valid_positions.size() > 0:
|
|
return valid_positions[rng.randi() % valid_positions.size()]
|
|
return current_position
|
|
|
|
func raycast_to_grid(from: Vector3, to: Vector3) -> Vector2i:
|
|
var plane = Plane(Vector3.UP, cell_offset.y)
|
|
var intersection = plane.intersects_ray(from, to - from)
|
|
|
|
if intersection:
|
|
var adjusted_intersection = intersection - cell_offset
|
|
var grid_position = Vector2i(
|
|
floor(adjusted_intersection.x / cell_size.x),
|
|
floor(adjusted_intersection.z / cell_size.z)
|
|
)
|
|
|
|
if grid_position.x >= 0 and grid_position.x < enhanced_gridmap.columns and \
|
|
grid_position.y >= 0 and grid_position.y < enhanced_gridmap.rows:
|
|
return grid_position
|
|
|
|
return Vector2i(-1, -1)
|
|
|
|
func is_within_movement_range(target_position: Vector2i) -> bool:
|
|
return movement_manager.is_within_movement_range(target_position)
|
|
|
|
# -----------------------------------------------------------------
|
|
# Movement
|
|
# -----------------------------------------------------------------
|
|
|
|
func simple_move_to(grid_position: Vector2i):
|
|
movement_manager.simple_move_to(grid_position)
|
|
|
|
func move_player_to_clicked_position(grid_position: Vector2i):
|
|
movement_manager.move_to_clicked_position(grid_position)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func start_movement_along_path(path: Array, clear_visual: bool = true):
|
|
is_player_moving = true
|
|
var tween = create_tween()
|
|
tween.set_trans(Tween.TRANS_CUBIC)
|
|
tween.set_ease(Tween.EASE_IN_OUT)
|
|
|
|
for point in path:
|
|
tween.tween_property(self, "position", grid_to_world(Vector2i(point.x, point.y)), 0.25)
|
|
|
|
tween.tween_callback(func():
|
|
current_position = Vector2i(path[-1].x, path[-1].y)
|
|
is_player_moving = false
|
|
|
|
# Check if we've reached the finish line (uses lap-aware finish locations)
|
|
var current_finish_locs = race_manager.get_current_finish_locations() if race_manager else finish_locations
|
|
if current_position in current_finish_locs and can_finish:
|
|
finish_race()
|
|
|
|
var main = get_tree().get_root().get_node_or_null("Main")
|
|
|
|
# Only clear visuals if this is a human player
|
|
if not (is_bot or is_in_group("Bots")):
|
|
if clear_visual:
|
|
enhanced_gridmap.clear_path_visualization()
|
|
|
|
# Restore movement range highlights if it was the player's turn
|
|
if main and main.ui_manager.current_action_state == main.ui_manager.ActionState.MOVING and is_my_turn:
|
|
highlight_movement_range()
|
|
|
|
has_moved_this_turn = path.size() <= movement_range
|
|
|
|
if main:
|
|
if not (is_bot or is_in_group("Bots")):
|
|
main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE
|
|
|
|
if TurnManager.turn_based_mode:
|
|
end_turn()
|
|
_after_action_completed()
|
|
)
|
|
|
|
#func trigger_finish_line():
|
|
#if not is_multiplayer_authority():
|
|
#return
|
|
#
|
|
#if current_lap == 0: # First lap
|
|
#lap1_finishers += 1
|
|
#race_position = lap1_finishers
|
|
#
|
|
## Display first lap completion message
|
|
#var message = "Finish 1st lap on " + get_ordinal_string(race_position)
|
|
#rpc("display_message", message)
|
|
#print("DEBUG: Triggered first lap finish. Position: ", race_position)
|
|
#
|
|
## Start second lap
|
|
#current_lap += 1
|
|
#rpc("start_new_lap")
|
|
#
|
|
#elif current_lap == 1: # Second lap
|
|
#lap2_finishers += 1
|
|
#race_position = lap2_finishers
|
|
#
|
|
## Display second lap completion message
|
|
#var message = "Finish 2nd lap on " + get_ordinal_string(race_position)
|
|
#rpc("display_message", message)
|
|
#print("DEBUG: Triggered second lap finish. Position: ", race_position)
|
|
#
|
|
##func debug_finish_state():
|
|
##print("DEBUG: Current Position: ", current_position)
|
|
##print("DEBUG: Can Finish: ", can_finish)
|
|
##print("DEBUG: Current Lap: ", current_lap)
|
|
##print("DEBUG: Pattern Match: ", check_pattern_match())
|
|
##print("DEBUG: Is at finish: ", current_position in finish_locations)
|
|
|
|
func update_player_position(grid_position: Vector2i):
|
|
position = grid_to_world(grid_position)
|
|
|
|
func grid_to_world(grid_position: Vector2i) -> Vector3:
|
|
var world_position = Vector3(
|
|
grid_position.x * cell_size.x + cell_size.x * 0.5,
|
|
cell_size.y,
|
|
grid_position.y * cell_size.z + cell_size.z * 0.5
|
|
) + cell_offset
|
|
return world_position
|
|
|
|
func start_turn():
|
|
action_points = 2
|
|
has_moved_this_turn = false
|
|
has_performed_action = false
|
|
is_my_turn = true
|
|
if is_multiplayer_authority():
|
|
rpc("display_message", "It's your turn!")
|
|
_after_action_completed()
|
|
|
|
func end_turn():
|
|
is_my_turn = false
|
|
has_moved_this_turn = false
|
|
if is_multiplayer_authority():
|
|
get_tree().get_root().get_node_or_null("Main").request_next_turn()
|
|
|
|
func reset_race():
|
|
if race_manager:
|
|
race_manager.current_lap = 0
|
|
race_manager.race_position = 0
|
|
race_manager.can_finish = false
|
|
race_manager.goals = race_manager.first_lap_goals.duplicate()
|
|
race_manager.playerboard.fill(-1)
|
|
if is_multiplayer_authority():
|
|
rpc("sync_goals", race_manager.goals)
|
|
rpc("sync_playerboard", race_manager.playerboard)
|
|
|
|
# Add a static reset for new games
|
|
static func reset_race_stats():
|
|
var race_mgr = load("res://scripts/managers/player_race_manager.gd")
|
|
if race_mgr:
|
|
race_mgr.lap1_finishers = 0
|
|
race_mgr.lap2_finishers = 0
|
|
|
|
@rpc("any_peer", "call_local", "unreliable")
|
|
func remote_set_position(authority_position):
|
|
global_position = authority_position
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func display_message(message):
|
|
# Send message to the main scene's message bar instead of player bubble
|
|
var main = get_tree().get_root().get_node_or_null("Main")
|
|
if main and main.has_method("add_message_to_bar"):
|
|
var player_name = $Name.text.split("\n")[0] if $Name else str(name)
|
|
main.add_message_to_bar(player_name, message)
|
|
|
|
func initialize_random_goals(_size: int, min_value: int, max_value: int, null_count: float) -> Array[int]:
|
|
goals.clear()
|
|
var rng = RandomNumberGenerator.new()
|
|
rng.randomize()
|
|
|
|
var result: Array[int] = []
|
|
var null_val = 0
|
|
var max_nulls = 3
|
|
|
|
const SPECIAL_VALUES = {1: 7, 2: 8, 3: 9, 4: 10}
|
|
|
|
for i in range(_size):
|
|
if null_val < max_nulls and rng.randf() < null_count:
|
|
result.append(-1)
|
|
null_val += 1
|
|
else:
|
|
var val = rng.randi_range(min_value, max_value)
|
|
result.append(val if not val in SPECIAL_VALUES else SPECIAL_VALUES[val])
|
|
|
|
return result
|
|
|
|
# Remove this since goals are now set by main.gd
|
|
func append_random_goals():
|
|
goals.append_array(initialize_random_goals(9, 7, 10, 1.0))
|
|
|
|
if is_multiplayer_authority():
|
|
rpc("sync_goals", goals)
|
|
|
|
func bot_try_grab_item() -> bool:
|
|
return playerboard_manager.bot_try_grab_item()
|
|
|
|
# -----------------------------------------------------------------
|
|
# OLD GRAB Func
|
|
# -----------------------------------------------------------------
|
|
#func grab_item(grid_position: Vector2i = current_position) -> bool:
|
|
#if is_bot:
|
|
#return bot_try_grab_item()
|
|
#
|
|
#if not enhanced_gridmap or action_points <= 0:
|
|
#return false
|
|
#
|
|
#var cell = Vector3i(grid_position.x, 1, grid_position.y)
|
|
#var item = enhanced_gridmap.get_cell_item(cell)
|
|
#
|
|
#if grid_position != current_position:
|
|
#var neighbors = enhanced_gridmap.get_neighbors(current_position, 0)
|
|
#var is_adjacent = false
|
|
#for neighbor in neighbors:
|
|
#if neighbor.position == grid_position:
|
|
#is_adjacent = true
|
|
#break
|
|
#if not is_adjacent:
|
|
#return false
|
|
#
|
|
#if item == -1:
|
|
#return false
|
|
#
|
|
## Bot-specific grab logic moved to bot_grab_item RPC
|
|
#if is_in_group("Bots") or is_bot:
|
|
#var empty_slot = playerboard.find(-1)
|
|
#if empty_slot == -1:
|
|
#return false
|
|
#
|
|
#if is_multiplayer_authority():
|
|
#rpc("bot_grab_item", grid_position, empty_slot, cell.x, cell.y, cell.z)
|
|
#return true
|
|
#
|
|
#var main = get_tree().get_root().get_node_or_null("Main")
|
|
#if main:
|
|
#selected_gridmap_position = grid_position
|
|
#clear_highlights()
|
|
#clear_playerboard_highlights()
|
|
#
|
|
#for i in range(playerboard.size()):
|
|
#if playerboard[i] == -1:
|
|
#var slot = main.playerboard_ui.get_child(i)
|
|
#if slot.get_child_count() > 0:
|
|
#slot.get_child(0).show()
|
|
#highlighted_cells.append(i)
|
|
#return true
|
|
#
|
|
#return false
|
|
# -----------------------------------------------------------------
|
|
#func grab_item(grid_position: Vector2i = current_position) -> bool:
|
|
#if not enhanced_gridmap or action_points <= 0:
|
|
#return false
|
|
#
|
|
#var cell = Vector3i(grid_position.x, 1, grid_position.y)
|
|
#var item = enhanced_gridmap.get_cell_item(cell)
|
|
#
|
|
## Validate adjacency (unless it's current position)
|
|
#if grid_position != current_position:
|
|
#var neighbors = enhanced_gridmap.get_neighbors(current_position, 0)
|
|
#var is_adjacent = false
|
|
#for neighbor in neighbors:
|
|
#if neighbor.position == grid_position:
|
|
#is_adjacent = true
|
|
#break
|
|
#if not is_adjacent:
|
|
#return false
|
|
#
|
|
#if item == -1:
|
|
#return false
|
|
#
|
|
## === AUTO-ARRANGE LOGIC ===
|
|
#var target_slot = find_best_goal_slot_for_item(item)
|
|
#if target_slot == -1:
|
|
#return false # no space
|
|
#
|
|
## Perform the grab and auto-place
|
|
#if is_multiplayer_authority():
|
|
## Update gridmap: remove item
|
|
#rpc("sync_grid_item", cell.x, cell.y, cell.z, -1)
|
|
## Update playerboard
|
|
#playerboard[target_slot] = item
|
|
#rpc("sync_playerboard", playerboard)
|
|
## Consume action
|
|
#has_performed_action = true
|
|
#consume_action_points(1)
|
|
#
|
|
## Optional: visual feedback
|
|
#var main = get_tree().get_root().get_node_or_null("Main")
|
|
#if main:
|
|
#main.update_playerboard_ui()
|
|
#main.set_action_state(main.ActionState.NONE)
|
|
#
|
|
#return true
|
|
# -----------------------------------------------------------------
|
|
func grab_item(grid_position: Vector2i = current_position) -> bool:
|
|
return playerboard_manager.grab_item(grid_position)
|
|
|
|
# -----------------------------------------------------------------
|
|
# Execute Grab
|
|
# -----------------------------------------------------------------
|
|
|
|
func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
|
return playerboard_manager._execute_grab(grid_pos, cell, item_id)
|
|
|
|
# -----------------------------------------------------------------
|
|
# This function runs on the server when requested by a client
|
|
# -----------------------------------------------------------------
|
|
@rpc("any_peer", "reliable")
|
|
func request_server_grab(grid_pos: Vector2i, x: int, y: int, z: int, item_id: int):
|
|
# 1. Only the server (peer 1) should process this
|
|
if not multiplayer.is_server():
|
|
return
|
|
|
|
# 2. Security check: Did this request come from the actual owner of this node?
|
|
if multiplayer.get_remote_sender_id() != get_multiplayer_authority():
|
|
push_error("Security: Non-authority tried to grab item!")
|
|
return
|
|
|
|
# 3. Call the execution logic
|
|
playerboard_manager._execute_grab(grid_pos, Vector3i(x, y, z), item_id)
|
|
|
|
# -----------------------------------------------------------------
|
|
# Auto-put: no manual selection needed
|
|
# Automatically puts a goal-matching tile into an adjacent (or current) empty grid cell
|
|
# -----------------------------------------------------------------
|
|
# -----------------------------------------------------------------
|
|
# Auto-put: no manual selection needed
|
|
# Automatically puts a goal-matching tile into an adjacent (or current) empty grid cell
|
|
# -----------------------------------------------------------------
|
|
func auto_put_item() -> bool:
|
|
return playerboard_manager.auto_put_item()
|
|
|
|
# -----------------------------------------------------------------
|
|
# Force ActionState : None
|
|
# -----------------------------------------------------------------
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func force_action_state_none():
|
|
# This is called by the server on the client to reset the UI
|
|
var main = get_tree().get_root().get_node_or_null("Main")
|
|
if main and main.ui_manager:
|
|
main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE
|
|
action_manager.clear_highlights()
|
|
action_manager.clear_playerboard_highlights()
|
|
|
|
# -----------------------------------------------------------------
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int, z: int, item: int):
|
|
# This RPC should only be processed by the server
|
|
if not multiplayer.is_server():
|
|
return
|
|
|
|
# Verify that this request came from the authority of this player
|
|
if multiplayer.get_remote_sender_id() != get_multiplayer_authority():
|
|
push_error("Security: Non-authority tried to put item!")
|
|
return
|
|
|
|
# Server-side validation
|
|
var cell = Vector3i(x, y, z)
|
|
if enhanced_gridmap.get_cell_item(cell) != -1:
|
|
return # Cell not empty
|
|
|
|
# Check if position is adjacent or current position
|
|
if grid_position != current_position:
|
|
var is_adjacent = false
|
|
var neighbors = enhanced_gridmap.get_neighbors(current_position, 0)
|
|
for neighbor in neighbors:
|
|
if neighbor.position == grid_position:
|
|
is_adjacent = true
|
|
break
|
|
if not is_adjacent:
|
|
return # Not adjacent
|
|
|
|
# Verify player has the item
|
|
if playerboard[slot_index] != item:
|
|
push_error("Item mismatch! Player doesn't have claimed item")
|
|
return
|
|
|
|
# Perform the put operation as the server
|
|
rpc("sync_grid_item", x, y, z, item)
|
|
playerboard[slot_index] = -1
|
|
rpc("sync_playerboard", playerboard)
|
|
|
|
# Update player state
|
|
has_performed_action = true
|
|
action_points -= 1
|
|
selected_playerboard_slot = -1
|
|
|
|
# Notify about action completion
|
|
_after_action_completed()
|
|
|
|
# Add new RPC function to notify others about spawn selection
|
|
@rpc("any_peer", "reliable")
|
|
func notify_spawn_selected(spawn_pos: Vector2i):
|
|
# Update local highlight state for all clients
|
|
if spawn_pos in highlighted_spawn_points:
|
|
highlighted_spawn_points.erase(spawn_pos)
|
|
|
|
# Clear highlight for the selected position
|
|
if enhanced_gridmap:
|
|
enhanced_gridmap.set_cell_item(
|
|
Vector3i(spawn_pos.x, 0, spawn_pos.y),
|
|
enhanced_gridmap.normal_items[0]
|
|
)
|
|
|
|
# Disabled, auto put activated
|
|
#func handle_put_action():
|
|
#var main = get_tree().get_root().get_node_or_null("Main")
|
|
#if not main or action_points < 1:
|
|
#return
|
|
#
|
|
#if not is_bot == true:
|
|
#clear_highlights()
|
|
#clear_playerboard_highlights()
|
|
#
|
|
## Highlight non-empty slots in playerboard
|
|
#for i in range(playerboard.size()):
|
|
#if playerboard[i] != -1: # Highlight occupied slots
|
|
#var slot = main.playerboard_ui.get_child(i)
|
|
#if slot.get_child_count() > 0:
|
|
#slot.get_child(0).show() # Show highlight for occupied slots
|
|
#highlighted_cells.append(i)
|
|
|
|
func handle_playerboard_slot_selected(slot_index: int):
|
|
playerboard_manager.handle_playerboard_slot_selected(slot_index)
|
|
|
|
# We also need to add handle_put_slot_selected:
|
|
func handle_put_slot_selected(slot_index: int):
|
|
playerboard_manager.handle_put_slot_selected(slot_index)
|
|
|
|
|
|
func arrange_playerboard_item(slot_index: int):
|
|
playerboard_manager.arrange_playerboard_item(slot_index)
|
|
|
|
func _on_slot_clicked(event: InputEvent, slot_index: int):
|
|
if not event is InputEventMouseButton or is_bot == true or not event.pressed or event.button_index != MOUSE_BUTTON_LEFT:
|
|
return
|
|
playerboard_manager.handle_slot_clicked(slot_index)
|
|
|
|
|
|
func has_item_at_current_position() -> bool:
|
|
var current_cell = Vector3i(current_position.x, 1, current_position.y)
|
|
return enhanced_gridmap.get_cell_item(current_cell) != -1
|
|
|
|
func has_items_in_playerboard() -> bool:
|
|
return playerboard.any(func(item): return item != -1)
|
|
|
|
func playerboard_is_full() -> bool:
|
|
return playerboard.find(-1) == -1
|
|
|
|
func highlight_cells_if_authorized(cells_to_highlight: Array):
|
|
action_manager.highlight_cells_if_authorized(cells_to_highlight)
|
|
|
|
# Update highlight_movement_range to respect the expanded obstacle blocking
|
|
func highlight_movement_range():
|
|
movement_manager.highlight_movement_range()
|
|
|
|
# Helper function to check if a cell can be reached given the blocked cells
|
|
|
|
|
|
# Helper function to check if a direction is diagonal
|
|
|
|
|
|
func highlight_adjacent_cells():
|
|
movement_manager.highlight_adjacent_cells()
|
|
|
|
func highlight_empty_adjacent_cells():
|
|
action_manager.highlight_empty_adjacent_cells()
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_action_points(points: int):
|
|
action_manager.sync_action_points(points)
|
|
|
|
func highlight_random_valid_cells():
|
|
action_manager.highlight_random_valid_cells()
|
|
|
|
func highlight_occupied_playerboard_slots():
|
|
action_manager.highlight_occupied_playerboard_slots()
|
|
|
|
func clear_highlights():
|
|
action_manager.clear_highlights()
|
|
|
|
func clear_playerboard_highlights():
|
|
action_manager.clear_playerboard_highlights()
|
|
|
|
func rotate_towards_target(target_pos: Vector2i):
|
|
movement_manager.rotate_towards_target(target_pos)
|
|
|
|
# We also need to add these supporting functions:
|
|
func select_playerboard_slot(slot_index: int):
|
|
playerboard_manager.select_playerboard_slot(slot_index)
|
|
|
|
func deselect_playerboard_slot():
|
|
playerboard_manager.deselect_playerboard_slot()
|
|
|
|
func target_playerboard_slot(slot_index: int):
|
|
playerboard_manager.target_playerboard_slot(slot_index)
|
|
|
|
func untarget_playerboard_slot():
|
|
playerboard_manager.untarget_playerboard_slot()
|
|
|
|
func can_move_to_target_playerboard_slot() -> bool:
|
|
return playerboard_manager.can_move_to_target_playerboard_slot()
|
|
|
|
func _update_playerboard_slot_visual(slot_index: int):
|
|
playerboard_manager._update_playerboard_slot_visual(slot_index)
|
|
|
|
func _highlight_adjacent_playerboard_slots():
|
|
playerboard_manager._highlight_adjacent_playerboard_slots()
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func sync_rotation(new_rotation: float):
|
|
if not is_multiplayer_authority():
|
|
rotation.y = new_rotation
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func sync_grid_item(x: int, y: int, z: int, item: int):
|
|
if enhanced_gridmap:
|
|
var cell = Vector3i(x, y, z)
|
|
|
|
# Log the change for debugging
|
|
print("Setting grid item at ", cell, " to ", item, " (called by ", multiplayer.get_remote_sender_id(), ")")
|
|
|
|
# Make sure we set the cell reliably
|
|
enhanced_gridmap.set_cell_item(cell, item)
|
|
|
|
# Double-check the cell was set
|
|
var check_value = enhanced_gridmap.get_cell_item(cell)
|
|
if check_value != item:
|
|
push_warning("Cell item didn't update correctly! Expected " + str(item) + " but got " + str(check_value))
|
|
# Try once more
|
|
enhanced_gridmap.set_cell_item(cell, item)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_goals(new_goals: Array):
|
|
goals = new_goals.duplicate() # Make sure to duplicate the array
|
|
|
|
# Also update race_manager's goals directly
|
|
if race_manager:
|
|
race_manager.goals = new_goals.duplicate()
|
|
# Re-check finish availability with new goals
|
|
race_manager.update_finish_availability()
|
|
|
|
# Update the AllPlayerGoals UI
|
|
var main = get_tree().get_root().get_node_or_null("Main")
|
|
if main and main.has_method("_update_goals_ui_for_player"):
|
|
var player_id = get_multiplayer_authority()
|
|
main._update_goals_ui_for_player(player_id, new_goals)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_second_lap_goals(new_goals: Array):
|
|
if race_manager:
|
|
race_manager.second_lap_goals = new_goals.duplicate()
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_playerboard(new_playerboard: Array):
|
|
playerboard = new_playerboard.duplicate()
|
|
var main = get_tree().get_root().get_node_or_null("Main")
|
|
if main:
|
|
main.rpc("sync_playerboard", get_multiplayer_authority(), playerboard)
|
|
action_manager.after_action_completed()
|
|
|
|
func _after_action_completed():
|
|
action_manager.after_action_completed()
|
|
|
|
func is_finish_position(pos: Vector2i) -> bool:
|
|
var current_finish = race_manager.get_current_finish_locations() if race_manager else finish_locations
|
|
return pos in current_finish
|
|
|
|
func consume_action_points(points: int):
|
|
action_manager.consume_action_points(points)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func bot_grab_item(pos: Vector2i, slot: int, x: int, y: int, z: int):
|
|
playerboard_manager.bot_grab_item(pos, slot, x, y, z)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func bot_put_item(pos: Vector2i, slot: int, x: int, y: int, z: int):
|
|
playerboard_manager.bot_put_item(pos, slot, x, y, z)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func bot_arrange_item(from_slot: int, to_slot: int):
|
|
playerboard_manager.bot_arrange_item(from_slot, to_slot)
|
|
|
|
func update_visual_position():
|
|
# Ensure proper grid-aligned positioning
|
|
global_position = Vector3(
|
|
current_position.x * cell_size.x + cell_size.x * 0.5,
|
|
1.0,
|
|
current_position.y * cell_size.z + cell_size.z * 0.5
|
|
)
|
|
if is_multiplayer_authority():
|
|
rpc("sync_position", current_position)
|
|
|
|
@rpc("any_peer", "call_local")
|
|
func sync_position(pos: Vector2i):
|
|
current_position = pos
|
|
# Always update the visual position after position sync
|
|
global_position = Vector3(
|
|
current_position.x * cell_size.x + cell_size.x * 0.5,
|
|
cell_size.y,
|
|
current_position.y * cell_size.z + cell_size.z * 0.5
|
|
) + cell_offset
|
|
|
|
func highlight_valid_obstacle_cells():
|
|
action_manager.highlight_valid_obstacle_cells()
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func complete_race(final_position: int):
|
|
if race_manager:
|
|
race_manager.on_race_completed(final_position)
|