From 75a636c97c9e445eee9ce59efeb2fdc970af3e29 Mon Sep 17 00:00:00 2001 From: Yogi Wiguna Date: Mon, 23 Mar 2026 17:45:16 +0800 Subject: [PATCH] feat: Implement core game scene logic in `main.gd` and introduce `goals_cycle_manager.gd` for managing game goals and cycles. --- scenes/main.gd | 5 +- scripts/managers/goals_cycle_manager.gd | 66 +++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/scenes/main.gd b/scenes/main.gd index 359f208..975b3eb 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -1463,7 +1463,10 @@ func sync_player_goals(player_id: int, goals: Array): call_deferred("_deferred_set_player_goals", player_id, goals) func _deferred_set_player_goals(player_id: int, goals: Array): - await get_tree().create_timer(0.25).timeout + # Reduce delay to 0.05s for snappier updates (just enough to ensure nodes are in tree) + if not get_node_or_null(str(player_id)): + await get_tree().create_timer(0.1).timeout + var player = get_node_or_null(str(player_id)) if player and player.race_manager: player.goals = goals.duplicate() diff --git a/scripts/managers/goals_cycle_manager.gd b/scripts/managers/goals_cycle_manager.gd index c79c3dc..b7ee77c 100644 --- a/scripts/managers/goals_cycle_manager.gd +++ b/scripts/managers/goals_cycle_manager.gd @@ -214,9 +214,60 @@ func sync_cycle_end(): func on_goal_completed(player: Node, time_remaining: float): """Called when a player completes their goal pattern.""" + + # 1. LOCAL OPTIMISTIC UPDATE (for smoothness) + if player.is_multiplayer_authority() and not multiplayer.is_server(): + _handle_local_goal_completion(player, time_remaining) + return + if not multiplayer.is_server(): return + # SERVER LOGIC continues... + _process_goal_completion(player, time_remaining) + +func _handle_local_goal_completion(player: Node, time_remaining: float): + print("[GoalsCycle] Client: Handling goal completion locally for smoothness.") + + # Clear playerboard locally + player.playerboard.fill(-1) + + # Generate new goals locally (optimistic) + var new_goals = GoalManager.initialize_random_goals(9, 7, 10, 1.0) + var int_goals: Array[int] = [] + for g in new_goals: + int_goals.append(g) + player.goals = int_goals + + # Update UI immediately + if main_scene and main_scene.ui_manager: + main_scene.ui_manager.update_playerboard_ui() + + # Notify server to sync score and broadcast to others + rpc_id(1, "request_server_goal_completion", time_remaining, int_goals) + + # Visual/Sfx + SfxManager.play("complete_mission") + +@rpc("any_peer") +func request_server_goal_completion(time_remaining: float, client_generated_goals: Array): + if not multiplayer.is_server(): return + + var sender_id = multiplayer.get_remote_sender_id() + # Bots call it locally, so sender_id might be 1 or 0 + if sender_id == 0: sender_id = 1 + + var player_node = main_scene.get_node_or_null(str(sender_id)) + if player_node: + # Use provided goals from client to ensure sync + var int_goals: Array[int] = [] + for g in client_generated_goals: int_goals.append(g) + + # Process completion with provided goals + _process_goal_completion(player_node, time_remaining, int_goals) + +func _process_goal_completion(player: Node, time_remaining: float, provided_goals: Array = []): + """Internal server-side logic for completion rewards and broadcasting.""" # Use name.to_int() for ID because bots share authority 1 var peer_id = player.name.to_int() @@ -242,18 +293,23 @@ func on_goal_completed(player: Node, time_remaining: float): rpc("sync_player_score", peer_id, player_scores[peer_id]) rpc("sync_goal_count", peer_id, player_goal_counts[peer_id]) - # Clear playerboard tiles (they convert to powerup bar reward) + # Clear playerboard tiles player.playerboard.fill(-1) - # Use main scene's RPC which properly looks up player by ID on each client if main_scene: main_scene.rpc("sync_playerboard", peer_id, player.playerboard) - # Regenerate goals for this player - regenerate_goals_for_player(player) + # Regenerate goals for this player (or use provided ones) + if provided_goals.size() > 0: + player.goals = provided_goals + if main_scene: + main_scene.rpc("sync_player_goals", peer_id, provided_goals) + else: + regenerate_goals_for_player(player) - # Randomize 9 tiles around player + # Randomize tiles around player _randomize_tiles_around_player(player) + # Only play RPC if not already handled locally by that player SfxManager.rpc("play_rpc", "complete_mission") print("[GoalsCycle] Player %d completed goal! +%d points (base: %d, time bonus: %d)" % [peer_id, score_earned, BASE_SCORE, time_bonus])