extends Node # UIManager - Handles all UI setup and updates const item_tex = [ preload("res://assets/textures/player_board_and_blue_print/tile_null.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_heart.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_diamond.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_star.tres"), preload("res://assets/textures/player_board_and_blue_print/tile_coin.tres") ] # Node references - will be set by Main var action_menu var move_button var grab_button var put_button var randomize_button var arrange_button var playerboard_ui var local_player_character var _previous_playerboard_state: Array = [] enum ActionState { NONE, MOVING, GRABBING, PUTTING, RANDOMIZING, ARRANGING, } var current_action_state = ActionState.NONE func initialize(main_node): # Get node references from main scene action_menu = main_node.get_node("ActionMenu") move_button = main_node.get_node("ActionMenu/ActionButtonContainer/MoveButton") grab_button = main_node.get_node("ActionMenu/ActionButtonContainer/GrabButton") put_button = main_node.get_node("ActionMenu/ActionButtonContainer/PutButton") randomize_button = main_node.get_node("ActionMenu/ActionButtonContainer/RandomizeButton") arrange_button = main_node.get_node("ActionMenu/ActionButtonContainer/ArrangeButton") playerboard_ui = main_node.get_node("PlayerboardUI") func setup_action_buttons(action_state_callback): move_button.pressed.connect(func(): action_state_callback.call(ActionState.MOVING)) grab_button.pressed.connect(func(): action_state_callback.call(ActionState.GRABBING)) put_button.pressed.connect(func(): if local_player_character: local_player_character.auto_put_item() ) randomize_button.pressed.connect(func(): action_state_callback.call(ActionState.RANDOMIZING)) arrange_button.pressed.connect(func(): if local_player_character and local_player_character.action_points >= 2: action_state_callback.call(ActionState.ARRANGING) ) func setup_playerboard_ui(): for child in playerboard_ui.get_children(): child.queue_free() playerboard_ui.columns = 5 for i in range(25): var slot = TextureRect.new() var highlight_rect = TextureRect.new() var hr_tex = load("res://assets/models/pboard/HighlightRect.tres") var select_rect = TextureRect.new() var sr_tex = load("res://assets/models/pboard/SelectRect.tres") var adjacent_rect = TextureRect.new() var ar_tex = load("res://assets/models/pboard/AdjacentRect.tres") slot.custom_minimum_size = Vector2(36, 36) slot.texture = item_tex[0] playerboard_ui.add_child(slot, true) highlight_rect.texture = hr_tex highlight_rect.size = Vector2(36, 36) select_rect.texture = sr_tex select_rect.size = Vector2(36, 36) adjacent_rect.texture = ar_tex adjacent_rect.size = Vector2(36, 36) slot.add_child(highlight_rect) slot.add_child(select_rect) slot.add_child(adjacent_rect) slot.get_child(0).hide() slot.get_child(1).hide() slot.get_child(2).hide() func update_playerboard_ui(): if not local_player_character: return # Center 3x3 slot indices in a 5x5 grid (0-indexed) # Row 1: 6, 7, 8 # Row 2: 11, 12, 13 # Row 3: 16, 17, 18 var center_slots = [6, 7, 8, 11, 12, 13, 16, 17, 18] var goals = local_player_character.goals if local_player_character.goals else [] for i in range(25): var slot = playerboard_ui.get_child(i) # Safety check: Ensure playerboard has enough items if i >= local_player_character.playerboard.size(): continue var item = local_player_character.playerboard[i] # Default texture (empty) slot.texture = item_tex[0] slot.modulate = Color.WHITE # Check if this is a center slot that should show a goal var center_index = center_slots.find(i) if center_index != -1 and center_index < goals.size(): var goal_value = goals[center_index] if item != -1: # Player has a tile in this slot - show it at full brightness match item: 7: slot.texture = item_tex[1] 8: slot.texture = item_tex[2] 9: slot.texture = item_tex[3] 10: slot.texture = item_tex[4] slot.modulate = Color.WHITE else: # Show goal tile dimmed (not collected yet) match goal_value: 7: slot.texture = item_tex[1] 8: slot.texture = item_tex[2] 9: slot.texture = item_tex[3] 10: slot.texture = item_tex[4] _: slot.texture = item_tex[0] # Dim uncollected goals with black overlay slot.modulate = Color(0.3, 0.3, 0.3, 1.0) else: # Non-center slot - just show playerboard item normally match item: 7: slot.texture = item_tex[1] 8: slot.texture = item_tex[2] 9: slot.texture = item_tex[3] 10: slot.texture = item_tex[4] # Non-center slots always full brightness slot.modulate = Color.WHITE # Check for new special tile placement to trigger effect if i < _previous_playerboard_state.size(): var prev_item = _previous_playerboard_state[i] # If slot was empty or different, and now has a special tile (7-10) if item != prev_item and item >= 7 and item <= 10: _pulse_slot_effect(slot) # Update cache _previous_playerboard_state = local_player_character.playerboard.duplicate() func _pulse_slot_effect(slot: Control): """Visual feedback when a special tile is placed.""" var tween = create_tween() # Reset scale first to be safe slot.scale = Vector2.ONE slot.pivot_offset = slot.size / 2 # Center pivot # Pop effect tween.tween_property(slot, "scale", Vector2(1.4, 1.4), 0.15).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(slot, "scale", Vector2(1.0, 1.0), 0.2).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) # Flash effect var original_modulate = slot.modulate slot.modulate = Color(1.5, 1.5, 1.5) # Overbright tween.parallel().tween_property(slot, "modulate", original_modulate, 0.3) func update_button_states(): if not local_player_character or local_player_character.is_in_group("Bots"): move_button.visible = false grab_button.visible = false put_button.visible = false randomize_button.visible = false arrange_button.visible = false return move_button.visible = true grab_button.visible = true put_button.visible = true randomize_button.visible = true arrange_button.visible = true move_button.disabled = false grab_button.disabled = false put_button.disabled = false arrange_button.disabled = false func set_local_player(player): local_player_character = player # 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.""" # player._ready waits 0.5s before creating managers, so wait longer await player.get_tree().create_timer(0.8).timeout var powerup_manager = player.get_node_or_null("PowerUpManager") if powerup_manager: 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()) else: push_warning("[UIManager] PowerUpManager not found on player after 0.8s wait") # ============================================================================= # Power-Up Bar UI (Battery Style) # ============================================================================= var powerup_bar: PanelContainer var powerup_segments: Array = [] func setup_powerup_bar_ui(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 # Get segment references from scene powerup_segments.clear() 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.""" var bars_filled = current_points / 4 # 4 points per bar for i in range(powerup_segments.size()): var segment = powerup_segments[i] var style = segment.get_theme_stylebox("panel").duplicate() if i < bars_filled: # Filled segment - bright green style.bg_color = Color(0.3, 0.9, 0.3, 1.0) else: # Empty segment - dark style.bg_color = Color(0.2, 0.2, 0.2, 0.8) segment.add_theme_stylebox_override("panel", style) var _previous_bars: int = 0 func _on_powerup_points_changed(current: int, max_points: int): var new_bars = current / 4 # Detect if a new bar was filled if new_bars > _previous_bars and powerup_bar: # Pulse effect on newly filled segment var segment_index = new_bars - 1 if segment_index >= 0 and segment_index < powerup_segments.size(): var segment = powerup_segments[segment_index] _pulse_segment(segment) _previous_bars = new_bars update_powerup_bar(current, max_points) func _pulse_segment(segment: Panel): """Create a visual pulse effect on a powerup segment.""" var original_scale = segment.scale var tween = segment.create_tween() tween.set_loops(2) tween.tween_property(segment, "scale", Vector2(1.3, 1.3), 0.1).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(segment, "scale", original_scale, 0.15).set_trans(Tween.TRANS_SINE) # ============================================================================= # Leaderboard UI # ============================================================================= var leaderboard_panel: PanelContainer func setup_leaderboard_ui(main_node): """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 # Apply styling to the panel var style = StyleBoxFlat.new() 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) func _get_rank_text(rank: int) -> String: match rank: 1: return "1st" 2: return "2nd" 3: return "3rd" 4: return "4th" _: return str(rank) + "th" # ============================================================================= # Timer Labels for Goal Panels # ============================================================================= func setup_timer_labels(main_node): """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 # 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 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: # Use display_name if available, otherwise fallback to node name var player_display_name = player.display_name if player and player.get("display_name") else "" if player_display_name.is_empty(): player_display_name = str(player.name) if player else "Player " + str(i + 1) name_label.text = player_display_name if score_label: score_label.text = str(player.score) if player and player.get("score") else "0" entry.visible = true else: entry.visible = false