diff --git a/.gitignore b/.gitignore index 73b8348..121bb74 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /android/ .tmp .vscode/settings.json +daily_report_2024-12-04.md +daily_report_2025-12-06.md diff --git a/scenes/main.gd b/scenes/main.gd index 2dc59a6..069e892 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -595,13 +595,13 @@ func create_specific_player(data: Dictionary): if playerboard_to_set.size() > 0 and player_character.race_manager: player_character.playerboard = playerboard_to_set - # Update position if not already set - if not node_already_exists: - player_character.global_position = Vector3( - data["position"].x * 2 + 1, - 1.0, - data["position"].y * 2 + 1 - ) + # Always update position (including for existing nodes, so client sees host correctly) + player_character.current_position = data["position"] + player_character.global_position = Vector3( + data["position"].x * 2 + 1, + 1.0, + data["position"].y * 2 + 1 + ) # Update playerboard UI for local player if is_local_player: diff --git a/scenes/player.gd b/scenes/player.gd index cbd384e..b6a6be7 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -355,9 +355,10 @@ func _physics_process(delta): rpc("remote_set_position", global_position) last_sent_position = global_position - # Add continuous finish line check - if race_manager and current_position in race_manager.finish_locations and can_finish and not is_player_moving: - start_new_lap() + # Add continuous finish line check (uses lap-aware finish locations) + var current_finish = race_manager.get_current_finish_locations() if race_manager else [] + if race_manager and current_position in current_finish and can_finish and not is_player_moving: + finish_race() # This handles lap increment and calls start_new_lap properly # -------------------------------------------------------------------- # Input @@ -536,14 +537,15 @@ func start_movement_along_path(path: Array, clear_visual: bool = true): 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_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 - if current_position in finish_locations and can_finish: + # 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") @@ -1030,6 +1032,12 @@ func sync_grid_item(x: int, y: int, z: int, item: int): 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"): @@ -1053,7 +1061,8 @@ func _after_action_completed(): action_manager.after_action_completed() func is_finish_position(pos: Vector2i) -> bool: - return pos in finish_locations + 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) @@ -1092,3 +1101,8 @@ func sync_position(pos: Vector2i): 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) diff --git a/scripts/managers/player_race_manager.gd b/scripts/managers/player_race_manager.gd index b578be7..d82a228 100644 --- a/scripts/managers/player_race_manager.gd +++ b/scripts/managers/player_race_manager.gd @@ -8,15 +8,16 @@ var current_lap: int = 0 var first_lap_goals: Array[int] = [] var second_lap_goals: Array[int] = [] var race_position: int = 0 +var has_finished_race: bool = false static var lap1_finishers: int = 0 static var lap2_finishers: int = 0 # Goals and Playerboard -var goals: Array[int] = [0,0,0,0,0,0,0,0,0] -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] +var goals: Array[int] = [0, 0, 0, 0, 0, 0, 0, 0, 0] +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] var can_finish: bool = false -# Finish locations (copied from player.gd) +# Finish locations (right side for 1st lap) var finish_locations = [ Vector2i(13, 0), Vector2i(13, 1), Vector2i(13, 2), Vector2i(13, 3), Vector2i(13, 4), Vector2i(13, 5), Vector2i(13, 6), Vector2i(13, 7), @@ -24,6 +25,21 @@ var finish_locations = [ Vector2i(13, 12), Vector2i(13, 13) ] +# Spawn locations (left side for 2nd lap finish) +var spawn_locations = [ + Vector2i(0, 0), Vector2i(0, 1), Vector2i(0, 2), Vector2i(0, 3), + Vector2i(0, 4), Vector2i(0, 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) +] + +# Helper function to get current finish locations based on lap +func get_current_finish_locations() -> Array: + if current_lap == 0: + return finish_locations # 1st lap: finish at right side + else: + return spawn_locations # 2nd lap: finish at left side (spawn locations) + func initialize(p_player: Node3D, p_gridmap: Node): player = p_player enhanced_gridmap = p_gridmap @@ -100,7 +116,8 @@ func update_finish_availability(): func highlight_finish_line(): if not player.is_multiplayer_authority() or player.is_bot: return - for finish_pos in finish_locations: + var current_finish = get_current_finish_locations() + for finish_pos in current_finish: if enhanced_gridmap: enhanced_gridmap.set_cell_item( Vector3i(finish_pos.x, 0, finish_pos.y), @@ -110,7 +127,8 @@ func highlight_finish_line(): func unhighlight_finish_line(): if not player.is_multiplayer_authority() or player.is_bot: return - for finish_pos in finish_locations: + var current_finish = get_current_finish_locations() + for finish_pos in current_finish: if enhanced_gridmap: enhanced_gridmap.set_cell_item( Vector3i(finish_pos.x, 0, finish_pos.y), @@ -118,7 +136,8 @@ func unhighlight_finish_line(): ) func is_at_finish_line() -> bool: - return player.current_position in finish_locations + var current_finish = get_current_finish_locations() + return player.current_position in current_finish func finish_race(): if current_lap == 0: @@ -129,32 +148,55 @@ func finish_race(): player.rpc("display_message", message) current_lap += 1 - player.rpc("start_new_lap") + start_new_lap() elif current_lap == 1: lap2_finishers += 1 race_position = lap2_finishers - var message = "Finish 2nd lap on " + get_ordinal_string(race_position) + var message = "RACE COMPLETE! Finished " + get_ordinal_string(race_position) if player.is_multiplayer_authority(): player.rpc("display_message", message) + player.rpc("complete_race", race_position) + +# Called when player finishes the entire race (2nd lap complete) +func on_race_completed(final_position: int): + has_finished_race = true + race_position = final_position + can_finish = false + + # Disable all player input + player.set_process_input(false) + player.set_process_unhandled_input(false) + player.is_my_turn = false + player.action_points = 0 + + # Clear any highlights + if player.action_manager: + player.action_manager.clear_highlights() + player.action_manager.clear_playerboard_highlights() + + # Unhighlight finish line + unhighlight_finish_line() + + print("Player %s finished the race in position %d!" % [player.name, final_position]) func start_new_lap(): if current_lap == 1: - var valid_finish_pos = find_valid_position_in_finish_line() - if valid_finish_pos != Vector2i(-1, -1): - player.current_position = valid_finish_pos - player.update_player_position(player.current_position) - + # Update goals to 2nd lap goals goals = second_lap_goals.duplicate() can_finish = false + # Sync with all clients if player.is_multiplayer_authority(): player.rpc("sync_position", player.current_position) player.rpc("sync_playerboard", playerboard) player.rpc("sync_goals", goals) + + print("Started 2nd lap with new goals: ", goals) func find_valid_position_in_finish_line() -> Vector2i: - for pos in finish_locations: + var current_finish = get_current_finish_locations() + for pos in current_finish: if not player.is_position_occupied(pos): return pos return Vector2i(-1, -1) diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index 6ecfe95..84d58e3 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -48,15 +48,29 @@ func grab_item(grid_position: Vector2i) -> bool: if not player.is_multiplayer_authority(): return false - # === Branching Logic: Host vs Client === + # === Optimistic Local Update (immediate visual feedback) === + # Apply changes locally first, server will validate/sync + enhanced_gridmap.set_cell_item(cell, -1) # Remove item visually immediately + player.playerboard[target_slot] = item # Add to playerboard immediately + + # Update UI immediately for responsiveness + var main = player.get_tree().get_root().get_node_or_null("Main") + if main and main.ui_manager: + main.ui_manager.update_playerboard_ui() + + # === Server Sync === if multiplayer.is_server(): - # HOST/SERVER: Call the logic directly - _execute_grab(grid_position, cell, item) + # HOST/SERVER: Broadcast to all clients + main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) + player.rpc("sync_playerboard", player.playerboard) + player.has_performed_action = true + player.consume_action_points(1) + player.rpc("force_action_state_none") else: - # CLIENT: Send RPC request to server (peer 1) + # CLIENT: Send RPC request to server for validation player.rpc_id(1, "request_server_grab", grid_position, cell.x, cell.y, cell.z, item) - return true # Request was sent or processed + return true # Action applied locally func _execute_grab(grid_pos: Vector2i, cell: Vector3i, item_id: int): var main = player.get_tree().get_root().get_node_or_null("Main") @@ -178,10 +192,10 @@ func auto_put_item() -> bool: if valid_put_positions.is_empty(): return false - # Step 2: Find a tile that should NOT be on the board + # Step 2: Find a tile that should NOT be on the board (prioritize non-goal items) var put_slot = -1 - # Count how many times each goal item appears in central 3x3 + # Count how many times each goal item is needed in the goals var goal_counts = {} for i in range(3): for j in range(3): @@ -189,31 +203,18 @@ func auto_put_item() -> bool: if g != -1: goal_counts[g] = goal_counts.get(g, 0) + 1 - # Now scan playerboard + # Priority 1: Find items that are NOT in goals at all (junk tiles) 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 + # Item is not in goals at all → definitely junk, put this first 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 + # Priority 2: Find items outside central 3x3 OR items in central 3x3 that don't match goal position if put_slot == -1: for i in range(player.playerboard.size()): var board_item = player.playerboard[i] @@ -221,31 +222,75 @@ func auto_put_item() -> bool: continue var row = i / 5 var col = i % 5 - # If it's outside the central 3x3, it shouldn't be there + + # If it's outside the central 3x3, it's potentially movable 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 + else: + # It's inside central 3x3 - check if it matches the corresponding goal position + var goal_row = row - 1 + var goal_col = col - 1 + var expected_goal = player.goals[goal_row * 3 + goal_col] + # If the item doesn't match what should be in this goal position, it's misplaced + if board_item != expected_goal and expected_goal != -1: put_slot = i break + # Priority 3: Find excess goal items (we have more than needed in central area) + if put_slot == -1: + for i in range(player.playerboard.size()): + var current_item = player.playerboard[i] + if current_item == -1: + continue + if not current_item in player.goals: + continue + + # Count how many of this item we have in the correct central positions + var correctly_placed_count = 0 + for goal_row in range(3): + for goal_col in range(3): + if player.goals[goal_row * 3 + goal_col] == current_item: + var board_idx = (goal_row + 1) * 5 + (goal_col + 1) + if player.playerboard[board_idx] == current_item: + correctly_placed_count += 1 + + # Count total of this item on the board + var total_count = 0 + for board_item in player.playerboard: + if board_item == current_item: + total_count += 1 + + # If we have all needed copies already correctly placed, extras can go + if correctly_placed_count >= goal_counts.get(current_item, 0) and total_count > correctly_placed_count: + put_slot = i + break + if put_slot == -1: return false # Nothing suitable to put - # Step 3: Perform the put + # Step 3: Perform the put with optimistic local update 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(): + # === Optimistic Local Update (immediate visual feedback) === + enhanced_gridmap.set_cell_item(cell, item) # Add item to grid visually immediately + player.playerboard[put_slot] = -1 # Remove from playerboard immediately + + # Update UI immediately for responsiveness + var main = player.get_tree().get_root().get_node_or_null("Main") + if main and main.ui_manager: + main.ui_manager.update_playerboard_ui() + main.ui_manager.current_action_state = main.ui_manager.ActionState.NONE + + # === Server Sync === 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 # =============================================================================