532 lines
17 KiB
GDScript
532 lines
17 KiB
GDScript
extends Node
|
|
|
|
# PlayerboardManager - Handles all playerboard operations including grab, put, arrange
|
|
|
|
var player: Node3D
|
|
var enhanced_gridmap: Node
|
|
|
|
# Playerboard state
|
|
var selected_gridmap_position = Vector2i(-1, -1)
|
|
var selected_playerboard_slot = -1
|
|
var targeted_playerboard_slot = -1
|
|
|
|
func initialize(p_player: Node3D, p_gridmap: Node):
|
|
player = p_player
|
|
enhanced_gridmap = p_gridmap
|
|
|
|
# =============================================================================
|
|
# GRAB Operations
|
|
# =============================================================================
|
|
|
|
func grab_item(grid_position: Vector2i) -> bool:
|
|
if not enhanced_gridmap or player.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 != player.current_position:
|
|
var neighbors = enhanced_gridmap.get_neighbors(player.current_position, 0)
|
|
var is_adjacent = false
|
|
for neighbor in neighbors:
|
|
if neighbor.position == grid_position:
|
|
is_adjacent = true
|
|
break
|
|
if not is_adjacent:
|
|
return false
|
|
|
|
if item == -1:
|
|
return false
|
|
|
|
# === AUTO-ARRANGE LOGIC (Client-side pre-check) ===
|
|
var target_slot = find_best_goal_slot_for_item(item)
|
|
if target_slot == -1:
|
|
print("Player: No valid slot found for item.")
|
|
return false # no space
|
|
|
|
if not player.is_multiplayer_authority():
|
|
return false
|
|
|
|
# === Branching Logic: Host vs Client ===
|
|
if multiplayer.is_server():
|
|
# HOST/SERVER: Call the logic directly
|
|
_execute_grab(grid_position, cell, item)
|
|
else:
|
|
# CLIENT: Send RPC request to server (peer 1)
|
|
player.rpc_id(1, "request_server_grab", grid_position, cell.x, cell.y, cell.z, item)
|
|
|
|
return true # Request was sent or processed
|
|
|
|
func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
if not main:
|
|
push_error("Server: Main node not found.")
|
|
return false
|
|
|
|
var server_gridmap = main.get_node("EnhancedGridMap")
|
|
if not server_gridmap:
|
|
push_error("Server: EnhancedGridMap not found.")
|
|
return false
|
|
|
|
# 1. Server-side Validation
|
|
var server_item = server_gridmap.get_cell_item(cell)
|
|
|
|
# Check if item is still there
|
|
if server_item != item_id:
|
|
print("Server: Item mismatch or already taken. Server has ", server_item)
|
|
return false
|
|
|
|
# Check action points
|
|
if player.action_points <= 0:
|
|
print("Server: Player has no action points.")
|
|
return false
|
|
|
|
# Check adjacency
|
|
if grid_pos != player.current_position:
|
|
var neighbors = server_gridmap.get_neighbors(player.current_position, 0)
|
|
if not neighbors.any(func(n): return n.position == grid_pos):
|
|
print("Server: Player is not adjacent to item.")
|
|
return false
|
|
|
|
# 2. Server-side Auto-Arrange
|
|
var target_slot = find_best_goal_slot_for_item(item_id)
|
|
if target_slot == -1:
|
|
print("Server: Player has no valid slot for item.")
|
|
return false
|
|
|
|
# 3. Server Executes the Action
|
|
|
|
# 3a. Update gridmap (using Main's RPC, which has authority)
|
|
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1)
|
|
|
|
# 3b. Update playerboard state (on this server-side instance)
|
|
player.playerboard[target_slot] = item_id
|
|
|
|
# 3c. Broadcast the new playerboard state to all clients
|
|
player.rpc("sync_playerboard", player.playerboard)
|
|
|
|
# 3d. Consume action points
|
|
player.has_performed_action = true
|
|
player.consume_action_points(1)
|
|
|
|
# 3e. Reset the UI for the player who acted
|
|
player.rpc("force_action_state_none")
|
|
|
|
return true
|
|
|
|
func bot_try_grab_item() -> bool:
|
|
if not enhanced_gridmap or player.action_points <= 0:
|
|
return false
|
|
|
|
# First check current position
|
|
var current_cell = Vector3i(player.current_position.x, 1, player.current_position.y)
|
|
var item = enhanced_gridmap.get_cell_item(current_cell)
|
|
|
|
if item != -1:
|
|
var empty_slot = player.playerboard.find(-1)
|
|
if empty_slot != -1:
|
|
if player.is_multiplayer_authority():
|
|
player.playerboard[empty_slot] = item
|
|
player.rpc("sync_grid_item", current_cell.x, current_cell.y, current_cell.z, -1)
|
|
player.rpc("sync_playerboard", player.playerboard)
|
|
player.has_performed_action = true
|
|
player.action_points -= 1
|
|
return true
|
|
|
|
# Check adjacent cells if nothing at current position
|
|
var neighbors = enhanced_gridmap.get_neighbors(player.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 = player.playerboard.find(-1)
|
|
if empty_slot != -1:
|
|
if player.is_multiplayer_authority():
|
|
player.playerboard[empty_slot] = item
|
|
player.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1)
|
|
player.rpc("sync_playerboard", player.playerboard)
|
|
player.has_performed_action = true
|
|
player.action_points -= 1
|
|
return true
|
|
|
|
return false
|
|
|
|
# =============================================================================
|
|
# PUT Operations
|
|
# =============================================================================
|
|
|
|
func auto_put_item() -> bool:
|
|
if not enhanced_gridmap or player.action_points <= 0 or player.is_bot or player.is_in_group("Bots"):
|
|
return false
|
|
|
|
# Step 1: Find empty adjacent (or current) grid cells
|
|
var valid_put_positions = []
|
|
var current_cell_3d = Vector3i(player.current_position.x, 1, player.current_position.y)
|
|
if enhanced_gridmap.get_cell_item(current_cell_3d) == -1:
|
|
valid_put_positions.append(player.current_position)
|
|
|
|
var neighbors = enhanced_gridmap.get_neighbors(player.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 player.is_position_occupied(pos):
|
|
valid_put_positions.append(pos)
|
|
|
|
if valid_put_positions.is_empty():
|
|
return false
|
|
|
|
# Step 2: Find a tile that should NOT be on the board
|
|
var put_slot = -1
|
|
|
|
# Count how many times each goal item appears in central 3x3
|
|
var goal_counts = {}
|
|
for i in range(3):
|
|
for j in range(3):
|
|
var g = player.goals[i * 3 + j]
|
|
if g != -1:
|
|
goal_counts[g] = goal_counts.get(g, 0) + 1
|
|
|
|
# Now scan playerboard
|
|
for i in range(player.playerboard.size()):
|
|
var current_item = player.playerboard[i]
|
|
if current_item == -1:
|
|
continue
|
|
|
|
# Case 1: Item is not in goals at all → definitely junk
|
|
if not current_item in player.goals:
|
|
put_slot = i
|
|
break
|
|
|
|
# Case 2: Item is in goals, but we already have enough in correct spots
|
|
var current_count = 0
|
|
for r in range(1, 4): # central rows 1-3 (5x5 board)
|
|
for c in range(1, 4): # central cols 1-3
|
|
var idx = r * 5 + c
|
|
if player.playerboard[idx] == current_item:
|
|
current_count += 1
|
|
|
|
# If we already have all needed copies in central area, this is extra
|
|
if current_count >= goal_counts.get(current_item, 0):
|
|
put_slot = i
|
|
break
|
|
|
|
# If no junk found, fall back to any non-goal-matching tile outside center
|
|
if put_slot == -1:
|
|
for i in range(player.playerboard.size()):
|
|
var board_item = player.playerboard[i]
|
|
if board_item == -1:
|
|
continue
|
|
var row = i / 5
|
|
var col = i % 5
|
|
# If it's outside the central 3x3, it shouldn't be there
|
|
if row < 1 or row > 3 or col < 1 or col > 3:
|
|
if not board_item in player.goals or player.playerboard[i] != player.goals[(row - 1) * 3 + (col - 1)]:
|
|
put_slot = i
|
|
break
|
|
|
|
if put_slot == -1:
|
|
return false # Nothing suitable to put
|
|
|
|
# Step 3: Perform the put
|
|
var target_pos = valid_put_positions[0]
|
|
var item = player.playerboard[put_slot]
|
|
var cell = Vector3i(target_pos.x, 1, target_pos.y)
|
|
|
|
if player.is_multiplayer_authority():
|
|
player.rpc("sync_grid_item", cell.x, cell.y, cell.z, item)
|
|
player.playerboard[put_slot] = -1
|
|
player.rpc("sync_playerboard", player.playerboard)
|
|
player.has_performed_action = true
|
|
player.consume_action_points(1)
|
|
|
|
var main = player.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
|
|
|
|
return true
|
|
|
|
# =============================================================================
|
|
# ARRANGE Operations
|
|
# =============================================================================
|
|
|
|
func arrange_playerboard_item(slot_index: int):
|
|
if player.action_points < 2 or player.playerboard[slot_index] == -1:
|
|
return
|
|
|
|
#var selected_item = player.playerboard[slot_index]
|
|
var adjacent_slots = get_adjacent_playerboard_slots(slot_index)
|
|
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
if not main or not main.ui_manager.playerboard_ui:
|
|
return
|
|
|
|
# Store the selected slot
|
|
selected_playerboard_slot = slot_index
|
|
|
|
# Highlight selected slot
|
|
var selected_slot_ui = main.ui_manager.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 player.playerboard[adj_slot] == -1: # Only highlight empty adjacent slots
|
|
var adj_slot_ui = main.ui_manager.playerboard_ui.get_child(adj_slot)
|
|
if adj_slot_ui.get_child_count() > 2:
|
|
adj_slot_ui.get_child(2).show()
|
|
player.action_manager.highlighted_cells.append(adj_slot)
|
|
|
|
# Connect to slot click signals
|
|
for i in range(player.playerboard.size()):
|
|
var slot = main.ui_manager.playerboard_ui.get_child(i)
|
|
if not slot.gui_input.is_connected(player._on_slot_clicked):
|
|
slot.gui_input.connect(player._on_slot_clicked.bind(i))
|
|
|
|
func handle_slot_clicked(slot_index: int):
|
|
var main = player.get_tree().get_root().get_node_or_null("Main")
|
|
if not main or main.ui_manager.current_action_state != main.ui_manager.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 player.playerboard[slot_index] == -1:
|
|
# Move item to empty target slot
|
|
var selected_item = player.playerboard[selected_playerboard_slot]
|
|
player.playerboard[slot_index] = selected_item
|
|
player.playerboard[selected_playerboard_slot] = -1
|
|
|
|
if player.is_multiplayer_authority():
|
|
player.rpc("sync_playerboard", player.playerboard)
|
|
player.consume_action_points(2)
|
|
player.has_performed_action = true
|
|
|
|
# Clear highlights
|
|
player.clear_highlights()
|
|
player.clear_playerboard_highlights()
|
|
|
|
# Reset selection
|
|
selected_playerboard_slot = -1
|
|
|
|
# Update the visual representation
|
|
main.ui_manager.update_playerboard_ui()
|
|
main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE
|
|
|
|
# =============================================================================
|
|
# Helper Functions
|
|
# =============================================================================
|
|
|
|
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(player.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 player.playerboard[slot_index] == -1: # only if empty
|
|
return slot_index
|
|
|
|
# No ideal slot? Return any empty slot
|
|
return player.playerboard.find(-1)
|
|
|
|
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(player.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(player.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 player.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(player.current_position.x, 1, player.current_position.y)
|
|
if enhanced_gridmap.get_cell_item(current_cell_3d) == -1:
|
|
valid_cells.append(player.current_position)
|
|
# Check neighbors
|
|
var neighbors = enhanced_gridmap.get_neighbors(player.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 player.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 player.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]
|
|
}
|
|
|
|
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 is_valid_arrangement_slot(from_slot: int, to_slot: int) -> bool:
|
|
var from_row = int(from_slot / 5)
|
|
var from_col = from_slot % 5
|
|
var to_row = int(to_slot / 5)
|
|
var to_col = to_slot % 5
|
|
|
|
var row_diff = abs(from_row - to_row)
|
|
var col_diff = abs(from_col - to_col)
|
|
|
|
return (row_diff == 1 and col_diff == 0) or (row_diff == 0 and col_diff == 1)
|
|
|
|
func has_item_at_current_position() -> bool:
|
|
var current_cell = Vector3i(player.current_position.x, 1, player.current_position.y)
|
|
return enhanced_gridmap.get_cell_item(current_cell) != -1
|
|
|
|
func has_items_in_playerboard() -> bool:
|
|
return player.playerboard.any(func(item): return item != -1)
|
|
|
|
func playerboard_is_full() -> bool:
|
|
return player.playerboard.find(-1) == -1
|
|
|
|
# Slot selection management
|
|
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 = player.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 = player.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()
|