using System.Collections.Generic; using ProjectI.Builds; using ProjectI.Combat; using ProjectI.Sessions; using UnityEngine; using UnityEngine.UIElements; namespace ProjectI.UI { [RequireComponent(typeof(UIDocument))] public sealed class PrototypeCombatHud : MonoBehaviour { [SerializeField] private AbilityDefinition[] abilityDefinitions; [SerializeField] private VisualTreeAsset buildPanel; [SerializeField] private VisualTreeAsset hudPanel; [Header("Enemy health bars")] [Tooltip("Seconds of received enemy health damage shown in grey behind the remaining red health. Zero disables the grey trail.")] [SerializeField, Range(0, 5)] private float enemyDamageTrailSeconds = .5f; private readonly List passives = new List(); private readonly List actives = new List(); private readonly List ultimates = new List(); private readonly Dictionary catalog = new Dictionary(); private readonly Dictionary abilityNames = new Dictionary(); private sealed class Choice { public Button Button; public Label Marker; public int Id, Slot; } private readonly List choices = new List(); private AbilityPickerState picker; private Label detailTitle; private VisualElement uiScreen, floatingCard; private Label floatingTitle, floatingBody; private VisualElement floatingAnchor; private bool floatingHovered,floatingAnchorHovered; private IVisualElementScheduledItem floatingLayoutTask; private DamageNumberPresenter damageNumbers; private EnemyHealthBarPresenter enemyBars; private RunPanels runPanels; private CoreInspectionPanel inspection; private PlayerBuffPresenter buffPresenter; public bool InspectionOpen=>inspection!=null&&inspection.IsOpen; public void ToggleInspection(){if(inspection==null)return;if(inspection.IsOpen)inspection.Close(true);else inspection.Open();} public void CloseInspection(bool relock=false)=>inspection?.Close(relock); private readonly AbilitySlotView[] abilityTiles = new AbilitySlotView[4]; private ReticleView reticle; private VisualElement reticleAnchor; private float displayedHitTime = -10; private bool reticleWasVisible; private ProjectI.Enemies.PrototypeEncounterDirector encounter; private float reloadObservedDuration; private int reloadObservedSlot = -1; private readonly Label[] teammateLabels = new Label[3]; private readonly ProgressBar[] teammateProgress = new ProgressBar[3]; private PrototypeSessionController session; private PlayerBuildState preview; private VisualElement menu; private TemplateContainer buildTree; private TemplateContainer hudTree; private VisualElement hud; private DamageVignetteElement damageFlash; private VisualElement downedBanner; private VisualElement targetPanel; private Label draftStats; private Label draftDetails; private Label buildStatus; private Label buildFeedback; private Label acceptedBuild; private Label healthLabel; private Label weaponLabel; private Label ammoLabel; private Label shieldLabel, overshieldLabel, ammoCapacity, meleeLabel; private ProgressBar shieldBar, reloadBar, bossBar; private VisualElement overshieldFill; private Label downedText; private Label revivePrompt; private Label targetLabel; private Label hitDamage; private ProgressBar healthBar; private ProgressBar reviveBar; private ProgressBar targetBar; private Button applyButton; private Button resetButton; private IVisualElementScheduledItem refreshTask; private bool draftValid; private string localFeedback = ""; private void OnEnable() { if (buildPanel == null || hudPanel == null) return; var root = GetComponent().rootVisualElement; menu = root.Q("menu"); var screen = root.Q("screen"); if (menu == null || screen == null) return; session = PrototypeSessionController.Instance != null ? PrototypeSessionController.Instance : FindAnyObjectByType(); BuildCatalog(); buildTree = buildPanel.CloneTree(); buildTree.AddToClassList("combat-build-template"); menu.Add(buildTree); hudTree = hudPanel.CloneTree(); hudTree.AddToClassList("combat-hud-template"); hudTree.pickingMode = PickingMode.Ignore; screen.Add(hudTree); uiScreen = screen; floatingCard = new ScrollView(ScrollViewMode.Vertical); floatingCard.AddToClassList("ability-floating-scroll"); floatingCard.AddToClassList("ability-detail-card"); for (int i = 0; i < buildTree.styleSheets.count; i++) floatingCard.styleSheets.Add(buildTree.styleSheets[i]); floatingCard.style.position = Position.Absolute; floatingCard.style.marginTop = 0;floatingCard.style.marginBottom = 0; floatingCard.style.minHeight = 0; floatingCard.style.display = DisplayStyle.None; floatingTitle = new Label(); floatingTitle.AddToClassList("combat-eyebrow");floatingTitle.AddToClassList("combat-wrap"); floatingBody = new Label(); floatingBody.AddToClassList("combat-small"); floatingBody.AddToClassList("combat-wrap"); floatingCard.Add(floatingTitle); floatingCard.Add(floatingBody); screen.Add(floatingCard); floatingCard.RegisterCallback(_=>floatingHovered=true); floatingCard.RegisterCallback(_=>{floatingHovered=false;ScheduleHideFloatingCard();}); floatingCard.RegisterCallback(_=>ScheduleHideFloatingCard()); floatingLayoutTask=floatingCard.schedule.Execute(ResolveFloatingLayout); damageNumbers = new DamageNumberPresenter(screen, () => hud != null && !hud.ClassListContains("combat-hidden")); enemyBars = new EnemyHealthBarPresenter(screen, () => hud != null && !hud.ClassListContains("combat-hidden"), () => enemyDamageTrailSeconds); runPanels = new RunPanels(screen, menu, session); inspection=new CoreInspectionPanel(screen,session,()=>Application.isFocused&&session!=null&&session.Connected&&!session.Busy&&menu.ClassListContains("hidden") &&(screen.Q("settingsPanel")==null||screen.Q("settingsPanel").ClassListContains("hidden"))); CacheControls(); buffPresenter=new PlayerBuffPresenter(hudTree.Q("buffStrip")); ConfigureSelections(LocalBuildPreference.Selected); applyButton.clicked += ApplyBuild; resetButton.clicked += ResetPractice; if (session != null) session.Changed += Refresh; refreshTask = root.schedule.Execute(Refresh).Every(100); RefreshDraft(); Refresh(); } private void BuildCatalog() { passives.Clear(); actives.Clear(); ultimates.Clear(); catalog.Clear(); abilityNames.Clear(); if (abilityDefinitions != null) { foreach (var definition in abilityDefinitions) { if (definition == null || catalog.ContainsKey(definition.id)) continue; catalog.Add(definition.id, definition.Tuning); abilityNames.Add(definition.id, definition.displayName); if (definition.slot == AbilitySlot.Passive) passives.Add(definition); else if (definition.slot == AbilitySlot.Active) actives.Add(definition); else if (definition.slot == AbilitySlot.Ultimate) ultimates.Add(definition); } } passives.Sort((a, b) => a.id.CompareTo(b.id)); actives.Sort((a, b) => a.id.CompareTo(b.id)); ultimates.Sort((a, b) => a.id.CompareTo(b.id)); preview = new PlayerBuildState(catalog, new AbilityUnlockState(catalog.Keys)); } private void CacheControls() { draftStats = buildTree.Q