From 3ada934e41dd128a5c1212dc8210696488f5906b Mon Sep 17 00:00:00 2001 From: Ian Woods Date: Wed, 3 Jun 2026 20:53:01 -0700 Subject: [PATCH] B button launches upgrade menu --- Assets/_Project/Scripts/UI/HUDController.cs | 145 ++++++++++++-------- 1 file changed, 89 insertions(+), 56 deletions(-) diff --git a/Assets/_Project/Scripts/UI/HUDController.cs b/Assets/_Project/Scripts/UI/HUDController.cs index e3e169f..45930de 100644 --- a/Assets/_Project/Scripts/UI/HUDController.cs +++ b/Assets/_Project/Scripts/UI/HUDController.cs @@ -25,53 +25,56 @@ namespace TD.UI // ----- Inspector -------------------------------------------------- - [Header("Scene References")] - [Tooltip("The local client's TowerPlacementController.")] - [SerializeField] private TowerPlacementController placementController; + [Header("Scene References")] [Tooltip("The local client's TowerPlacementController.")] [SerializeField] + private TowerPlacementController placementController; [Tooltip("The local client's TowerPaintController (drives the Paint tab + paint cursor).")] [SerializeField] private TowerPaintController paintController; - [Tooltip("The TowerPlacementManager NetworkObject in the scene.")] - [SerializeField] private TowerPlacementManager placementManager; + [Tooltip("The TowerPlacementManager NetworkObject in the scene.")] [SerializeField] + private TowerPlacementManager placementManager; [Tooltip("The local client's CameraController. Used by the minimap for click-to-jump " + "and drag-to-pan.")] - [SerializeField] private CameraController cameraController; + [SerializeField] + private CameraController cameraController; - [Header("Settings")] - [SerializeField] private float rejectionMessageDuration = 2.5f; + [Header("Settings")] [SerializeField] private float rejectionMessageDuration = 2.5f; [Tooltip("Maximum visible height of the chat feed in pixels. Content past this " + "height is clipped — older messages scroll off the top of the visible area " + "but stay in history (scroll up while chat is open to view).")] - [SerializeField] private float chatMaxHeight = 280f; + [SerializeField] + private float chatMaxHeight = 280f; [Tooltip("Maximum messages kept in chat history. Defaults to effectively unlimited " + "(int.MaxValue) — every message sent during a match stays scrollable. " + "Lower the value if a long match ever shows DOM perf issues; this field " + "is the safety valve, not a normal-play limit.")] - [SerializeField] private int chatMaxMessages = int.MaxValue; + [SerializeField] + private int chatMaxMessages = int.MaxValue; - [Tooltip("Color used for SYSTEM chat messages (e.g. 'Life Lost', income changes).")] - [SerializeField] private Color chatSystemColor = new Color(1f, 0.7f, 0.2f); + [Tooltip("Color used for SYSTEM chat messages (e.g. 'Life Lost', income changes).")] [SerializeField] + private Color chatSystemColor = new Color(1f, 0.7f, 0.2f); [Tooltip("Color used for PLAYER chat message bodies. Sender prefix uses the player's slot color.")] - [SerializeField] private Color chatPlayerColor = new Color(0.92f, 0.92f, 0.92f); + [SerializeField] + private Color chatPlayerColor = new Color(0.92f, 0.92f, 0.92f); // ----- Cached UI element references ------------------------------- private Label goldLabel; private Label waveLabel; private Label livesLabel; - private Label nextWaveLabel; // prep countdown ("next: 0:12") - private Label leakedLabel; // local player's origin-leak count ("leaked: 3") - private Label incomeLabel; // top-bar per-wave gold-earned counter ("+150 g/wave") + private Label nextWaveLabel; // prep countdown ("next: 0:12") + private Label leakedLabel; // local player's origin-leak count ("leaked: 3") + private Label incomeLabel; // top-bar per-wave gold-earned counter ("+150 g/wave") private VisualElement playerListContainer; // right-panel scoreboard rows private Label portraitName; private Label levelLabel; private VisualElement statLines; private VisualElement commandGrid; + private VisualElement actionFrame; // hidden via display:none when no actions are available private VisualElement commandTabs; // Build/Paint tab row — shown only for a Builder selection private Button tabBuild; @@ -79,6 +82,7 @@ namespace TD.UI private VisualElement buildProgressContainer; // info-panel sub-view, shown for BuildSiteVisual selections private VisualElement buildProgressFill; // width driven each frame from progress private Label buildProgressPercent; + private Label ttTitle; private Label ttDesc; private Label ttStats; @@ -91,7 +95,7 @@ namespace TD.UI // without rebuilding the elements. private VisualElement enemyHealthBar; private VisualElement enemyHealthFill; - private Label enemyHealthText; + private Label enemyHealthText; // Match-end overlay — built once on Start and toggled on Phase changes. private VisualElement matchEndOverlay; @@ -99,15 +103,15 @@ namespace TD.UI // Buff menu overlay — toggled by the B key via ToggleBuffMenu(). private VisualElement buffMenuOverlay; private VisualElement buffMenuContent; - private Label matchEndTitle; + private Label matchEndTitle; // Chat panel (bottom-left, above portrait) — programmatic. The container // holds both the scrollable feed and the input. Highlight + scroll // interactivity are toggled on the container when typing. private VisualElement chatContainer; - private ScrollView chatFeed; - private TextField chatInput; - private bool chatInputOpen; + private ScrollView chatFeed; + private TextField chatInput; + private bool chatInputOpen; // Frame on which the chat input was opened or closed. Enter on that frame // and the next one is ignored to prevent the open/close-triggering keypress @@ -129,12 +133,12 @@ namespace TD.UI private CommandTab activeTab = CommandTab.Build; private Coroutine rejectionFadeCoroutine; - private bool placementManagerReady; // true once TowerPlacementManager.Instance is non-null + private bool placementManagerReady; // true once TowerPlacementManager.Instance is non-null private bool uiInitialized; - private bool selectionSubscribed; // true once we've successfully hooked SelectionState.OnSelectionChanged - private bool matchStateSubscribed; // true once OnPhaseChanged is hooked + private bool selectionSubscribed; // true once we've successfully hooked SelectionState.OnSelectionChanged + private bool matchStateSubscribed; // true once OnPhaseChanged is hooked private MinimapView minimapView; - private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us + private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us // ----- Hotkeys ---------------------------------------------------- // @@ -156,10 +160,15 @@ namespace TD.UI private readonly struct HotkeyBinding { public readonly Key Key; - public readonly VisualElement Button; // for enabledSelf gating + public readonly VisualElement Button; // for enabledSelf gating public readonly System.Action Action; + public HotkeyBinding(Key k, VisualElement b, System.Action a) - { Key = k; Button = b; Action = a; } + { + Key = k; + Button = b; + Action = a; + } } // ----- Static hit-test probe -------------------------------------- @@ -255,6 +264,7 @@ namespace TD.UI // Cache element references — log a warning for any that are missing // so UXML/USS mismatches surface immediately. + goldLabel = Require