player.gd refactor progress
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
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 item = player.playerboard[i]
|
||||
if item == -1:
|
||||
continue
|
||||
|
||||
# Case 1: Item is not in goals at all → definitely junk
|
||||
if not 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] == item:
|
||||
current_count += 1
|
||||
|
||||
# If we already have all needed copies in central area, this is extra
|
||||
if current_count >= goal_counts.get(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 item = player.playerboard[i]
|
||||
if 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 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:
|
||||
main.set_action_state(main.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.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 = from_slot / 5
|
||||
var from_col = from_slot % 5
|
||||
var to_row = to_slot / 5
|
||||
var to_col = to_slot % 5
|
||||
|
||||
var row_diff = abs(from_row - to_row)
|
||||
var col_diff = abs(from_col - to_col)
|
||||
|
||||
return (row_diff == 1 and col_diff == 0) or (row_diff == 0 and col_diff == 1)
|
||||
|
||||
func 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()
|
||||
Reference in New Issue
Block a user