From e41ffcfb675f89ae0e636ea4fffd66427e671ae9 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Tue, 16 Dec 2025 02:37:26 +0800 Subject: [PATCH] feat: Introduce modular player system with dedicated managers for movement, race, input, playerboard, actions, special tiles, and powerups, along with a new main scene and documentation. --- _daily_basis/report_2025-12-16.md | 68 ++++++ scenes/main.gd | 134 ++++++++++-- scenes/main.tscn | 179 ++++++++++++++++ scripts/managers/goals_cycle_manager.gd | 11 + scripts/managers/goals_cycle_manager.gd.uid | 1 + scripts/managers/player_race_manager.gd | 10 +- scripts/managers/playerboard_manager.gd | 19 +- scripts/managers/powerup_manager.gd.uid | 1 + scripts/managers/ui_manager.gd | 217 +++++++++----------- 9 files changed, 494 insertions(+), 146 deletions(-) create mode 100644 _daily_basis/report_2025-12-16.md create mode 100644 scripts/managers/goals_cycle_manager.gd.uid create mode 100644 scripts/managers/powerup_manager.gd.uid diff --git a/_daily_basis/report_2025-12-16.md b/_daily_basis/report_2025-12-16.md new file mode 100644 index 0000000..fddb0a8 --- /dev/null +++ b/_daily_basis/report_2025-12-16.md @@ -0,0 +1,68 @@ +# [ ADT's Report ] +## Date: 2025-12-16 + +--- + +## ✅ main.tscn - Changes +- Added **PowerUpBar** GUI panel (center-top, 4 battery segments) +- Added **LeaderboardPanel** GUI panel (right side, 4 player entries) +- Added **GoalsTimer** GUI panel (top-left, standalone timer with background) + +--- + +## ✅ main.gd - Changes +- Updated `_on_timer_updated()` to use standalone GoalsTimer +- Added `_deferred_init_leaderboard()` for delayed leaderboard initialization +- Added `request_leaderboard_sync()` RPC for client-server leaderboard sync +- Added `sync_leaderboard_data()` RPC to receive leaderboard from server +- Updated `_on_leaderboard_updated()` to broadcast leaderboard to all clients +- Updated `sync_game_start()` to call deferred leaderboard init +- Added `initialize_leaderboard_with_players()` call in `_start_game()` + +--- + +## ✅ ui_manager.gd - Changes +- Changed `setup_powerup_bar_ui()` to get node reference instead of creating +- Changed `setup_leaderboard_ui()` to get node reference instead of creating +- Added `_connect_powerup_manager_deferred()` with 0.3s delay for signal connection +- Updated `setup_timer_labels()` to style standalone GoalsTimer +- Fixed `powerup_bar` variable type from `HBoxContainer` to `PanelContainer` + +--- + +## ✅ playerboard_manager.gd - Changes +- Added `_normalize_tile()` helper (converts holo tiles 11-14 → 7-10) +- Applied normalization to `find_best_goal_slot_for_item()` +- Applied normalization to `auto_put_item()` for junk detection + +--- + +## ✅ player_race_manager.gd - Changes +- Added `_normalize_tile()` helper for holo tile equivalence +- Applied normalization to `check_3x3_section()` for goal completion detection + +--- + +## ✅ goals_cycle_manager.gd - Changes +- Added `_initialize_player_scores()` to init all players with score 0 at cycle start + +--- + +## Summary of Features + +### 1. Power-up Bar & Leaderboard GUI +- Moved from script-instantiated to scene-based nodes +- Power-up bar displays 4 battery segments (12 points max) +- Leaderboard shows 4 player entries with rank, name, score + +### 2. Standalone Timer +- Timer moved from PlayerGoals panels to top-left corner +- Styled with gold border and large font + +### 3. Holo Tile Equivalence +- Holo tiles (11-14) now treated same as normal tiles (7-10) +- Works for auto-grab, auto-put, and goal completion + +### 4. Leaderboard Client Sync Fix +- Server now broadcasts leaderboard data via RPC +- Clients request and receive authoritative player list from server diff --git a/scenes/main.gd b/scenes/main.gd index 70a35dc..185e2b1 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -267,6 +267,11 @@ func _start_game(): # Start the goals cycle timer if goals_cycle_manager: goals_cycle_manager.start_cycle() + + # Initialize leaderboard with all players + if ui_manager: + var all_players = get_tree().get_nodes_in_group("Players") + ui_manager.initialize_leaderboard_with_players(all_players) # ============================================================================= # Player Management @@ -378,6 +383,9 @@ func sync_game_start(player_list: Array, is_turn_based: bool): GameStateManager.players = player_list TurnManager.turn_based_mode = is_turn_based GameStateManager.start_game() + + # Initialize leaderboard for all peers (after a delay to ensure players loaded) + call_deferred("_deferred_init_leaderboard") # ============================================================================= # UI / Action State Management @@ -731,15 +739,14 @@ func sync_grid_item(x: int, y: int, z: int, item: int): # ============================================================================= func _on_timer_updated(time_remaining: float): - # Update timer display on all player goal panels - var time_text = "%02d:%02d" % [int(time_remaining) / 60, int(time_remaining) % 60] + # Update standalone timer display + var time_text = str(int(time_remaining)) - for i in range($AllPlayerGoals.get_child_count()): - var panel = $AllPlayerGoals.get_child(i) - if panel.visible: - var timer_label = panel.get_node_or_null("TimerLabel") - if timer_label: - timer_label.text = time_text + var timer_panel = get_node_or_null("GoalsTimer") + if timer_panel: + var timer_label = timer_panel.get_node_or_null("VBox/TimerLabel") + if timer_label: + timer_label.text = time_text func _on_score_updated(peer_id: int, new_score: int): # Update player's score display @@ -751,26 +758,117 @@ func _on_score_updated(peer_id: int, new_score: int): _update_leaderboard_display() func _on_leaderboard_updated(sorted_scores: Array): - # Update the leaderboard panel + # Update the leaderboard panel locally _update_leaderboard_display() + + # Server broadcasts updated leaderboard to all clients + if multiplayer.is_server(): + var player_data = [] + for p in get_tree().get_nodes_in_group("Players"): + player_data.append({ + "peer_id": p.get_multiplayer_authority(), + "name": p.name, + "score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0 + }) + rpc("sync_leaderboard_data", player_data) + +func _deferred_init_leaderboard(): + """Initialize leaderboard after a delay to ensure all players are loaded.""" + # Longer delay ensures players are synced + await get_tree().create_timer(1.5).timeout + + # Request leaderboard sync from server for accurate data + if not multiplayer.is_server(): + rpc_id(1, "request_leaderboard_sync") + else: + # Server can update directly + _update_leaderboard_display() + +@rpc("any_peer") +func request_leaderboard_sync(): + """Client requests leaderboard data from server.""" + if multiplayer.is_server(): + var sender_id = multiplayer.get_remote_sender_id() + # Build player list with peer_ids and names + var player_data = [] + for p in get_tree().get_nodes_in_group("Players"): + player_data.append({ + "peer_id": p.get_multiplayer_authority(), + "name": p.name, + "score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0 + }) + rpc_id(sender_id, "sync_leaderboard_data", player_data) + +@rpc("authority", "call_local", "reliable") +func sync_leaderboard_data(player_data: Array): + """Receive leaderboard data from server and update UI.""" + var leaderboard_panel = get_node_or_null("LeaderboardPanel") + if not leaderboard_panel: + return + + var vbox = leaderboard_panel.get_node_or_null("MarginContainer/VBox") + if not vbox: + return + + # Sort by score descending + player_data.sort_custom(func(a, b): return a.score > b.score) + + # Update entries + for i in range(4): + var entry = vbox.get_node_or_null("Entry" + str(i + 1)) + if not entry: + continue + + if i < player_data.size(): + var data = player_data[i] + var rank_label = entry.get_node_or_null("RankLabel") + var name_label = entry.get_node_or_null("NameLabel") + var score_label = entry.get_node_or_null("ScoreLabel") + + if rank_label: + rank_label.text = _get_ordinal(i + 1) + if name_label: + name_label.text = "Player " + str(data.name) + if score_label: + score_label.text = str(data.score) + + entry.visible = true + else: + entry.visible = false func _update_leaderboard_display(): var leaderboard_panel = get_node_or_null("LeaderboardPanel") if not leaderboard_panel: return - var sorted_scores = goals_cycle_manager.get_leaderboard() if goals_cycle_manager else [] + # Try both possible paths for vbox + var vbox = leaderboard_panel.get_node_or_null("MarginContainer/VBox") + if not vbox: + vbox = leaderboard_panel.get_node_or_null("VBox") + if not vbox: + return - # Get player names and update entries + # Get all players in game + var all_players = get_tree().get_nodes_in_group("Players") + + # Build scores array with all players + var player_data = [] + for p in all_players: + var peer_id = p.get_multiplayer_authority() + var score = goals_cycle_manager.get_player_score(peer_id) if goals_cycle_manager else 0 + player_data.append({"peer_id": peer_id, "name": p.name, "score": score}) + + # Sort by score descending + player_data.sort_custom(func(a, b): return a.score > b.score) + + # Update entries for i in range(4): # Max 4 entries - var entry = leaderboard_panel.get_node_or_null("Entry" + str(i + 1)) + var entry = vbox.get_node_or_null("Entry" + str(i + 1)) if not entry: continue - if i < sorted_scores.size(): - var score_data = sorted_scores[i] - var player = get_node_or_null(str(score_data.peer_id)) - var player_name = player.name if player else str(score_data.peer_id) + if i < player_data.size(): + var data = player_data[i] var rank_label = entry.get_node_or_null("RankLabel") var name_label = entry.get_node_or_null("NameLabel") @@ -779,9 +877,9 @@ func _update_leaderboard_display(): if rank_label: rank_label.text = _get_ordinal(i + 1) if name_label: - name_label.text = player_name + name_label.text = str(data.name) if score_label: - score_label.text = str(score_data.score) + score_label.text = str(data.score) entry.visible = true else: diff --git a/scenes/main.tscn b/scenes/main.tscn index e746ff5..d030858 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -9324,6 +9324,185 @@ theme_override_constants/margin_bottom = 5 layout_mode = 2 theme_override_constants/separation = 4 +[node name="PowerUpBar" type="PanelContainer" parent="."] +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -110.0 +offset_top = 125.0 +offset_right = 110.0 +offset_bottom = 161.0 +grow_horizontal = 2 + +[node name="HBox" type="HBoxContainer" parent="PowerUpBar"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="PowerLabel" type="Label" parent="PowerUpBar/HBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "POWER " + +[node name="Segment0" type="Panel" parent="PowerUpBar/HBox"] +custom_minimum_size = Vector2(36, 20) +layout_mode = 2 + +[node name="Segment1" type="Panel" parent="PowerUpBar/HBox"] +custom_minimum_size = Vector2(36, 20) +layout_mode = 2 + +[node name="Segment2" type="Panel" parent="PowerUpBar/HBox"] +custom_minimum_size = Vector2(36, 20) +layout_mode = 2 + +[node name="Segment3" type="Panel" parent="PowerUpBar/HBox"] +custom_minimum_size = Vector2(36, 20) +layout_mode = 2 + +[node name="LeaderboardPanel" type="PanelContainer" parent="."] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -210.0 +offset_top = 80.0 +offset_right = -10.0 +offset_bottom = 280.0 +grow_horizontal = 0 + +[node name="MarginContainer" type="MarginContainer" parent="LeaderboardPanel"] +layout_mode = 2 +theme_override_constants/margin_left = 8 +theme_override_constants/margin_top = 8 +theme_override_constants/margin_right = 8 +theme_override_constants/margin_bottom = 8 + +[node name="VBox" type="VBoxContainer" parent="LeaderboardPanel/MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 4 + +[node name="Title" type="Label" parent="LeaderboardPanel/MarginContainer/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 16 +text = "🏆 LEADERBOARD" +horizontal_alignment = 1 + +[node name="Separator" type="HSeparator" parent="LeaderboardPanel/MarginContainer/VBox"] +layout_mode = 2 + +[node name="Entry1" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"] +layout_mode = 2 + +[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry1"] +custom_minimum_size = Vector2(35, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "1st" + +[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry1"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "Player 1" +clip_text = true + +[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry1"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 14 +text = "0" +horizontal_alignment = 2 + +[node name="Entry2" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"] +layout_mode = 2 + +[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry2"] +custom_minimum_size = Vector2(35, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "2nd" + +[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry2"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "Player 2" +clip_text = true + +[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 14 +text = "0" +horizontal_alignment = 2 + +[node name="Entry3" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"] +layout_mode = 2 + +[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry3"] +custom_minimum_size = Vector2(35, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "3rd" + +[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry3"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "Player 3" +clip_text = true + +[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry3"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 14 +text = "0" +horizontal_alignment = 2 + +[node name="Entry4" type="HBoxContainer" parent="LeaderboardPanel/MarginContainer/VBox"] +layout_mode = 2 + +[node name="RankLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry4"] +custom_minimum_size = Vector2(35, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "4th" + +[node name="NameLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry4"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +theme_override_font_sizes/font_size = 14 +text = "Player 4" +clip_text = true + +[node name="ScoreLabel" type="Label" parent="LeaderboardPanel/MarginContainer/VBox/Entry4"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 14 +text = "0" +horizontal_alignment = 2 + +[node name="GoalsTimer" type="PanelContainer" parent="."] +offset_left = 20.0 +offset_top = 20.0 +offset_right = 120.0 +offset_bottom = 90.0 + +[node name="VBox" type="VBoxContainer" parent="GoalsTimer"] +layout_mode = 2 +alignment = 1 + +[node name="TimerLabel" type="Label" parent="GoalsTimer/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "60" +horizontal_alignment = 1 + +[node name="SuffixLabel" type="Label" parent="GoalsTimer/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 12 +text = "seconds" +horizontal_alignment = 1 + [connection signal="pressed" from="Menu/Host" to="." method="_on_host_pressed"] [connection signal="pressed" from="Menu/Join" to="." method="_on_join_pressed"] [connection signal="text_submitted" from="MessageInput" to="." method="_on_message_input_text_submitted"] diff --git a/scripts/managers/goals_cycle_manager.gd b/scripts/managers/goals_cycle_manager.gd index 2eca250..5ee249c 100644 --- a/scripts/managers/goals_cycle_manager.gd +++ b/scripts/managers/goals_cycle_manager.gd @@ -55,8 +55,19 @@ func start_cycle(): emit_signal("cycle_started") if multiplayer.is_server(): + # Initialize scores for all connected players + _initialize_player_scores() rpc("sync_cycle_start") +func _initialize_player_scores(): + """Initialize scores for all connected players to 0.""" + var all_players = get_tree().get_nodes_in_group("Players") + for player in all_players: + var peer_id = player.get_multiplayer_authority() + if not player_scores.has(peer_id): + player_scores[peer_id] = 0 + _update_leaderboard() + @rpc("authority", "call_local", "reliable") func sync_cycle_start(): current_cycle_timer = CYCLE_DURATION diff --git a/scripts/managers/goals_cycle_manager.gd.uid b/scripts/managers/goals_cycle_manager.gd.uid new file mode 100644 index 0000000..5915582 --- /dev/null +++ b/scripts/managers/goals_cycle_manager.gd.uid @@ -0,0 +1 @@ +uid://c6ip5g2kja65e diff --git a/scripts/managers/player_race_manager.gd b/scripts/managers/player_race_manager.gd index aa24487..b5638dd 100644 --- a/scripts/managers/player_race_manager.gd +++ b/scripts/managers/player_race_manager.gd @@ -34,6 +34,12 @@ func get_ordinal_string(number: int) -> String: # Goal Pattern Matching (Core Functionality) # ============================================================================= +func _normalize_tile(tile: int) -> int: + """Convert holo tiles (11-14) to normal tiles (7-10) for goal comparison.""" + if tile >= 11 and tile <= 14: + return tile - 4 + return tile + func check_3x3_section(board: Array, goals_pattern: Array, start_row: int, start_col: int) -> bool: for i in range(3): for j in range(3): @@ -45,7 +51,9 @@ func check_3x3_section(board: Array, goals_pattern: Array, start_row: int, start for i in range(3): for j in range(3): if goals_pattern[i][j] != -1: - if board[start_row + i][start_col + j] != goals_pattern[i][j]: + # Normalize board value to treat holo tiles (11-14) same as normal (7-10) + var normalized_board = _normalize_tile(board[start_row + i][start_col + j]) + if normalized_board != goals_pattern[i][j]: return false return true diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index 9a0500b..ee6cd65 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -14,6 +14,12 @@ func initialize(p_player: Node3D, p_gridmap: Node): player = p_player enhanced_gridmap = p_gridmap +func _normalize_tile(tile: int) -> int: + """Convert holo tiles (11-14) to normal tiles (7-10) for goal comparison.""" + if tile >= 11 and tile <= 14: + return tile - 4 + return tile + # ============================================================================= # GRAB Operations # ============================================================================= @@ -230,8 +236,10 @@ func auto_put_item() -> bool: if current_item == -1: continue + # Normalize item for goal comparison (holo tiles 11-14 = normal tiles 7-10) + var normalized_item = _normalize_tile(current_item) # Item is not in goals at all → definitely junk, put this first - if not current_item in player.goals: + if not normalized_item in player.goals: put_slot = i break @@ -253,8 +261,10 @@ func auto_put_item() -> bool: var goal_row = row - 1 var goal_col = col - 1 var expected_goal = player.goals[goal_row * 3 + goal_col] + # Normalize for comparison (holo tiles 11-14 = normal tiles 7-10) + var normalized_board = _normalize_tile(board_item) # 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: + if normalized_board != expected_goal and expected_goal != -1: put_slot = i break @@ -390,6 +400,9 @@ func find_best_goal_slot_for_item(item: int) -> int: if item == -1: return -1 + # Normalize item - treat holo tiles (11-14) the same as normal tiles (7-10) + var normalized_item = _normalize_tile(item) + # Convert goals to 2D (3x3) var goals_2d = [] for i in range(3): @@ -401,7 +414,7 @@ func find_best_goal_slot_for_item(item: int) -> int: # 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: + if goals_2d[i][j] == normalized_item: var board_row = i + 1 # offset to center in 5x5 var board_col = j + 1 var slot_index = board_row * 5 + board_col diff --git a/scripts/managers/powerup_manager.gd.uid b/scripts/managers/powerup_manager.gd.uid new file mode 100644 index 0000000..d35ec74 --- /dev/null +++ b/scripts/managers/powerup_manager.gd.uid @@ -0,0 +1 @@ +uid://dhm40qesad4cm diff --git a/scripts/managers/ui_manager.gd b/scripts/managers/ui_manager.gd index 22d885a..4591a7d 100644 --- a/scripts/managers/ui_manager.gd +++ b/scripts/managers/ui_manager.gd @@ -137,60 +137,52 @@ func update_button_states(): func set_local_player(player): local_player_character = player - # Connect to powerup signals + # Connect to powerup signals with deferred call (manager needs time to initialize) + _connect_powerup_manager_deferred(player) + +func _connect_powerup_manager_deferred(player): + """Wait for PowerUpManager to be initialized before connecting.""" + await player.get_tree().create_timer(0.3).timeout + var powerup_manager = player.get_node_or_null("PowerUpManager") if powerup_manager: - powerup_manager.points_changed.connect(_on_powerup_points_changed) + if not powerup_manager.points_changed.is_connected(_on_powerup_points_changed): + powerup_manager.points_changed.connect(_on_powerup_points_changed) + # Initialize bar with current values + update_powerup_bar(powerup_manager.get_points(), powerup_manager.get_max_points()) # ============================================================================= # Power-Up Bar UI (Battery Style) # ============================================================================= -var powerup_bar: HBoxContainer +var powerup_bar: PanelContainer var powerup_segments: Array = [] func setup_powerup_bar_ui(main_node): - """Create battery-style power-up bar with 4 segments.""" - var parent = main_node.get_node_or_null("PlayerboardUI") - if not parent: - parent = main_node + """Get reference to existing PowerUpBar in scene and cache segment references.""" + powerup_bar = main_node.get_node_or_null("PowerUpBar") + if not powerup_bar: + push_warning("PowerUpBar node not found in scene") + return - # Create container - powerup_bar = HBoxContainer.new() - powerup_bar.name = "PowerUpBar" - powerup_bar.custom_minimum_size = Vector2(200, 30) - - # Position above playerboard - powerup_bar.position = Vector2(0, -40) - - # Create label - var label = Label.new() - label.text = "POWER: " - label.add_theme_font_size_override("font_size", 14) - powerup_bar.add_child(label) - - # Create 4 battery segments + # Get segment references from scene powerup_segments.clear() - for i in range(4): - var segment = Panel.new() - segment.custom_minimum_size = Vector2(40, 24) - segment.name = "Segment" + str(i) - - # Style the segment - var style = StyleBoxFlat.new() - style.bg_color = Color(0.2, 0.2, 0.2, 0.8) # Dark empty - style.border_color = Color(0.4, 0.8, 0.4, 1.0) # Green border - style.set_border_width_all(2) - style.corner_radius_top_left = 4 if i == 0 else 0 - style.corner_radius_bottom_left = 4 if i == 0 else 0 - style.corner_radius_top_right = 4 if i == 3 else 0 - style.corner_radius_bottom_right = 4 if i == 3 else 0 - segment.add_theme_stylebox_override("panel", style) - - powerup_bar.add_child(segment) - powerup_segments.append(segment) - - parent.add_child(powerup_bar) + var hbox = powerup_bar.get_node_or_null("HBox") + if hbox: + for i in range(4): + var segment = hbox.get_node_or_null("Segment" + str(i)) + if segment: + # Apply initial empty style + var style = StyleBoxFlat.new() + style.bg_color = Color(0.15, 0.15, 0.15, 1.0) + style.border_color = Color(0.3, 0.7, 0.3, 1.0) + style.set_border_width_all(2) + style.corner_radius_top_left = 4 if i == 0 else 0 + style.corner_radius_bottom_left = 4 if i == 0 else 0 + style.corner_radius_top_right = 4 if i == 3 else 0 + style.corner_radius_bottom_right = 4 if i == 3 else 0 + segment.add_theme_stylebox_override("panel", style) + powerup_segments.append(segment) func update_powerup_bar(current_points: int, max_points: int): """Update battery segments based on current power-up points.""" @@ -219,75 +211,22 @@ func _on_powerup_points_changed(current: int, max_points: int): var leaderboard_panel: PanelContainer func setup_leaderboard_ui(main_node): - """Create leaderboard panel on right side of screen.""" - leaderboard_panel = PanelContainer.new() - leaderboard_panel.name = "LeaderboardPanel" - leaderboard_panel.custom_minimum_size = Vector2(180, 180) + """Get reference to existing LeaderboardPanel in scene.""" + leaderboard_panel = main_node.get_node_or_null("LeaderboardPanel") + if not leaderboard_panel: + push_warning("LeaderboardPanel node not found in scene") + return - # Position on right side - leaderboard_panel.set_anchors_preset(Control.PRESET_TOP_RIGHT) - leaderboard_panel.position = Vector2(-200, 100) - - # Style the panel + # Apply styling to the panel var style = StyleBoxFlat.new() - style.bg_color = Color(0.1, 0.1, 0.1, 0.85) - style.border_color = Color(0.8, 0.7, 0.2, 1.0) # Gold border + style.bg_color = Color(0.08, 0.08, 0.12, 0.92) + style.border_color = Color(0.85, 0.75, 0.25, 1.0) # Gold border style.set_border_width_all(2) style.corner_radius_top_left = 8 style.corner_radius_top_right = 8 style.corner_radius_bottom_left = 8 style.corner_radius_bottom_right = 8 leaderboard_panel.add_theme_stylebox_override("panel", style) - - var vbox = VBoxContainer.new() - vbox.name = "VBox" - - # Title - var title = Label.new() - title.text = "LEADERBOARD" - title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - title.add_theme_font_size_override("font_size", 16) - title.add_theme_color_override("font_color", Color(0.9, 0.8, 0.2)) - vbox.add_child(title) - - # Separator - var sep = HSeparator.new() - vbox.add_child(sep) - - # Create 4 player entries - for i in range(4): - var entry = HBoxContainer.new() - entry.name = "Entry" + str(i + 1) - entry.visible = false - - var rank_label = Label.new() - rank_label.name = "RankLabel" - rank_label.custom_minimum_size = Vector2(40, 0) - rank_label.text = _get_rank_text(i + 1) - rank_label.add_theme_font_size_override("font_size", 14) - entry.add_child(rank_label) - - var name_label = Label.new() - name_label.name = "NameLabel" - name_label.custom_minimum_size = Vector2(80, 0) - name_label.clip_text = true - name_label.text = "---" - name_label.add_theme_font_size_override("font_size", 14) - entry.add_child(name_label) - - var score_label = Label.new() - score_label.name = "ScoreLabel" - score_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - score_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL - score_label.text = "0" - score_label.add_theme_font_size_override("font_size", 14) - score_label.add_theme_color_override("font_color", Color(0.8, 1.0, 0.8)) - entry.add_child(score_label) - - vbox.add_child(entry) - - leaderboard_panel.add_child(vbox) - main_node.add_child(leaderboard_panel) func _get_rank_text(rank: int) -> String: match rank: @@ -302,29 +241,59 @@ func _get_rank_text(rank: int) -> String: # ============================================================================= func setup_timer_labels(main_node): - """Add timer labels to each player goals panel.""" - var all_player_goals = main_node.get_node_or_null("AllPlayerGoals") - if not all_player_goals: + """Apply styling to standalone GoalsTimer in scene.""" + var goals_timer = main_node.get_node_or_null("GoalsTimer") + if not goals_timer: + push_warning("GoalsTimer node not found in scene") return - for i in range(all_player_goals.get_child_count()): - var panel = all_player_goals.get_child(i) - - # Skip if timer already exists - if panel.get_node_or_null("TimerLabel"): + # Apply dark background style + var style = StyleBoxFlat.new() + style.bg_color = Color(0.1, 0.1, 0.15, 0.9) + style.border_color = Color(1.0, 0.85, 0.2, 1.0) # Gold border + style.set_border_width_all(2) + style.corner_radius_top_left = 8 + style.corner_radius_top_right = 8 + style.corner_radius_bottom_left = 8 + style.corner_radius_bottom_right = 8 + goals_timer.add_theme_stylebox_override("panel", style) + + # Style the timer label + var timer_label = goals_timer.get_node_or_null("VBox/TimerLabel") + if timer_label: + timer_label.add_theme_color_override("font_color", Color(1.0, 0.85, 0.2)) + + var suffix_label = goals_timer.get_node_or_null("VBox/SuffixLabel") + if suffix_label: + suffix_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7)) + +# Method to update leaderboard with all players in match +func initialize_leaderboard_with_players(players: Array): + """Initialize leaderboard showing all players with score 0.""" + if not leaderboard_panel: + return + + var vbox = leaderboard_panel.get_node_or_null("MarginContainer/VBox") + if not vbox: + vbox = leaderboard_panel.get_node_or_null("VBox") + if not vbox: + return + + for i in range(4): + var entry = vbox.get_node_or_null("Entry" + str(i + 1)) + if not entry: continue - var timer_label = Label.new() - timer_label.name = "TimerLabel" - timer_label.text = "01:00" - timer_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - timer_label.add_theme_font_size_override("font_size", 18) - timer_label.add_theme_color_override("font_color", Color(1.0, 0.9, 0.3)) - - # Add at the top of the panel - var margin = panel.get_node_or_null("MarginContainer") - if margin: - margin.add_child(timer_label) - margin.move_child(timer_label, 0) + if i < players.size(): + var player = players[i] + var name_label = entry.get_node_or_null("NameLabel") + var score_label = entry.get_node_or_null("ScoreLabel") + + if name_label: + name_label.text = str(player.name) if player else "Player " + str(i + 1) + if score_label: + score_label.text = str(player.score) if player and player.get("score") else "0" + + entry.visible = true else: - panel.add_child(timer_label) + entry.visible = false