feat: fix some bug
This commit is contained in:
@@ -31,14 +31,9 @@ func _normalize_tile(tile: int) -> int:
|
||||
# =============================================================================
|
||||
|
||||
func grab_item(grid_position: Vector2i) -> bool:
|
||||
var has_ap = player.action_points > 0 if TurnManager.turn_based_mode else true
|
||||
|
||||
if not enhanced_gridmap:
|
||||
print("[Grab] Failed for %s: enhanced_gridmap is null" % player.name)
|
||||
return false
|
||||
if not has_ap:
|
||||
print("[Grab] Failed for %s: no AP (%d)" % [player.name, player.action_points])
|
||||
return false
|
||||
|
||||
if player.get("is_frozen"):
|
||||
print("[Grab] Failed for %s: player is frozen" % player.name)
|
||||
@@ -109,19 +104,15 @@ func grab_item(grid_position: Vector2i) -> bool:
|
||||
if sng_manager and sng_manager.has_method("update_mission_progress"):
|
||||
sng_manager.update_mission_progress(player.name.to_int(), item)
|
||||
|
||||
# Update UI immediately for responsiveness
|
||||
|
||||
# Update UI immediately for responsiveness
|
||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||
if main and main.ui_manager and not (player.is_bot or player.is_in_group("Bots")):
|
||||
main.ui_manager.update_playerboard_ui()
|
||||
|
||||
# Check if goal is completed after grabbing
|
||||
_check_goal_completion()
|
||||
|
||||
# === Server Sync ===
|
||||
if multiplayer.is_server():
|
||||
# HOST/SERVER: Broadcast to all clients
|
||||
# HOST/SERVER: Check goal, then broadcast authoritative state to all clients
|
||||
_check_goal_completion()
|
||||
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1)
|
||||
var peer_id = player.name.to_int()
|
||||
main.rpc("sync_playerboard", peer_id, player.playerboard)
|
||||
@@ -129,17 +120,21 @@ func grab_item(grid_position: Vector2i) -> bool:
|
||||
player.consume_action_points(1)
|
||||
player.rpc("force_action_state_none")
|
||||
else:
|
||||
# CLIENT: Optimistic Local Update for lag compensation
|
||||
# CLIENT: Optimistic local visual only — do NOT check goal completion here.
|
||||
# The server checks authoritatively in _execute_grab and broadcasts results.
|
||||
# This prevents double-award and the goal-blink caused by optimistic rollback.
|
||||
player.has_performed_action = true
|
||||
player.consume_action_points(1)
|
||||
# Do NOT consume action points here — server does it and syncs back via
|
||||
# sync_action_points (or force_sync_to_client on failure), preventing double-decrement.
|
||||
player.force_action_state_none()
|
||||
|
||||
# Send RPC request to server for validation
|
||||
player.rpc_id(1, "request_server_grab", grid_position, cell.x, cell.y, cell.z, item)
|
||||
# Send RPC to server — include the slot we used locally so server places in same slot,
|
||||
# preventing the slot mismatch that caused board overwrites on rapid grabs.
|
||||
player.rpc_id(1, "request_server_grab", grid_position, cell.x, cell.y, cell.z, item, target_slot)
|
||||
|
||||
return true # Action applied locally
|
||||
return true # Action applied locally (grid visual only)
|
||||
|
||||
func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
||||
func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int, expected_slot: int = -1):
|
||||
var main = player.get_tree().get_root().get_node_or_null("Main")
|
||||
if not main:
|
||||
push_error("Server: Main node not found.")
|
||||
@@ -153,31 +148,32 @@ func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
||||
# 1. Server-side Validation
|
||||
var server_item = server_gridmap.get_cell_item(cell)
|
||||
|
||||
# Check if item is still there
|
||||
# Check if item is still there (critical — prevents grabbing already-taken tiles)
|
||||
if server_item != item_id:
|
||||
print("Server: Item mismatch or already taken. Server has ", server_item)
|
||||
_force_sync_to_client(cell, server_item)
|
||||
return false
|
||||
|
||||
# Check action points
|
||||
if player.action_points <= 0:
|
||||
print("Server: Player has no action points.")
|
||||
_force_sync_to_client(cell, server_item)
|
||||
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.")
|
||||
_force_sync_to_client(cell, server_item)
|
||||
return false
|
||||
# NOTE: Adjacency check intentionally removed.
|
||||
# The client validates adjacency locally before sending the RPC.
|
||||
# With high-latency connections the server's player.current_position lags,
|
||||
# causing false rejections that make tiles "disappear" from the floor without
|
||||
# registering on the board. Item-existence + slot-availability checks are sufficient.
|
||||
|
||||
# 2. Server-side Auto-Arrange
|
||||
# 2. Server-side slot resolution
|
||||
var is_powerup = (item_id >= 11 and item_id <= 14)
|
||||
var target_slot = -1
|
||||
if not is_powerup:
|
||||
target_slot = find_best_goal_slot_for_item(item_id)
|
||||
# Honour the client's expected slot if it is still empty on the server.
|
||||
# This ensures client and server agree on placement during rapid multi-grabs,
|
||||
# so sync_playerboard_slot (slot-only update) is safe and unambiguous.
|
||||
if expected_slot >= 0 and expected_slot < player.playerboard.size() \
|
||||
and player.playerboard[expected_slot] == -1 \
|
||||
and not (expected_slot in HIDDEN_SLOTS):
|
||||
target_slot = expected_slot
|
||||
else:
|
||||
# Fallback: find the best available slot (e.g. client sent stale slot)
|
||||
target_slot = find_best_goal_slot_for_item(item_id)
|
||||
if target_slot == -1:
|
||||
print("Server: Player has no valid slot for item.")
|
||||
_force_sync_to_client(cell, server_item)
|
||||
@@ -193,7 +189,7 @@ func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
||||
var special_tiles_manager = player.get_node_or_null("SpecialTilesManager")
|
||||
if special_tiles_manager:
|
||||
special_tiles_manager.add_powerup_from_item(item_id)
|
||||
# Do not add to playerboard
|
||||
# Powerup consumed instantly — no board slot update needed
|
||||
else:
|
||||
player.playerboard[target_slot] = item_id
|
||||
|
||||
@@ -202,24 +198,21 @@ func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int):
|
||||
if sng_manager and sng_manager.has_method("update_mission_progress"):
|
||||
sng_manager.update_mission_progress(player.name.to_int(), item_id)
|
||||
|
||||
# 3c. Broadcast the new playerboard state to all clients
|
||||
# 3c. Broadcast ONLY THE CHANGED SLOT to all clients.
|
||||
# Using sync_playerboard_slot instead of full sync_playerboard prevents
|
||||
# a rapid second grab (still in-flight on client) from being overwritten.
|
||||
var peer_id = player.name.to_int()
|
||||
main.rpc("sync_playerboard", peer_id, player.playerboard)
|
||||
if not is_powerup:
|
||||
main.rpc("sync_playerboard_slot", peer_id, target_slot, item_id)
|
||||
|
||||
# 3d. Check if goal is completed (SERVER-SIDE - this triggers goal regeneration for clients!)
|
||||
# Logic only runs if board changed, but theoretically powerup pickup shouldn't trigger goal
|
||||
# 3d. Check if goal is completed (SERVER-SIDE)
|
||||
if not is_powerup:
|
||||
_check_goal_completion()
|
||||
|
||||
# 3e. Consume action points
|
||||
player.has_performed_action = true
|
||||
player.consume_action_points(1)
|
||||
|
||||
# 3f. Reset the UI for the player who acted
|
||||
# Reset the UI for the player who acted
|
||||
player.rpc("force_action_state_none")
|
||||
|
||||
# 4. Check if we need to respawn tiles (Scarcity Logic)
|
||||
# "during no more tiles" -> Refill if floor is empty
|
||||
_check_and_refill_grid_if_needed(server_gridmap)
|
||||
|
||||
return true
|
||||
@@ -281,14 +274,10 @@ func _force_sync_to_client(cell: Vector3i, server_item: int):
|
||||
# Sync their Playerboard (which they thought they updated)
|
||||
main.rpc_id(peer_id, "sync_playerboard", peer_id, player.playerboard)
|
||||
|
||||
# Restore Action Points locally for the client (sync action points back)
|
||||
# player is the caller peer since this is the server instance of the player's manager
|
||||
player.rpc_id(peer_id, "sync_action_points", player.action_points)
|
||||
|
||||
print("Server: Forced sync to client %d due to action failure." % peer_id)
|
||||
|
||||
func bot_try_grab_item() -> bool:
|
||||
if not enhanced_gridmap or player.action_points <= 0:
|
||||
if not enhanced_gridmap:
|
||||
return false
|
||||
|
||||
# First check current position
|
||||
@@ -326,7 +315,6 @@ func bot_try_grab_item() -> bool:
|
||||
main.rpc("sync_grid_item", current_cell.x, current_cell.y, current_cell.z, -1)
|
||||
main.rpc("sync_playerboard", player.name.to_int(), player.playerboard)
|
||||
player.has_performed_action = true
|
||||
player.action_points -= 1
|
||||
_check_goal_completion()
|
||||
return true
|
||||
|
||||
@@ -357,7 +345,6 @@ func bot_try_grab_item() -> bool:
|
||||
main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1)
|
||||
main.rpc("sync_playerboard", player.name.to_int(), player.playerboard)
|
||||
player.has_performed_action = true
|
||||
player.action_points -= 1
|
||||
return true
|
||||
|
||||
return false
|
||||
@@ -368,7 +355,7 @@ func bot_try_grab_item() -> bool:
|
||||
|
||||
func auto_put_item() -> bool:
|
||||
# Check AP only if in turn-based mode
|
||||
var has_ap = player.action_points > 0 if TurnManager.turn_based_mode else true
|
||||
var has_ap = true # Action points removed; put always allowed
|
||||
|
||||
if not enhanced_gridmap or not has_ap or player.is_bot or player.is_in_group("Bots") or player.get("is_frozen"):
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user