#nullable enable using System.Globalization; using System.Linq; using Godot; using SideScrollerGame.Hero.Rules; namespace SideScrollerGame.Hero; public partial class HeroStateHudController : Control { public override void _Ready() { EnsureLabel(); Refresh(); } public void Bind(HeroRuntimeService runtime) { if (m_Runtime is not null) { m_Runtime.StateChanged -= HandleStateChanged; } m_Runtime = runtime; runtime.StateChanged += HandleStateChanged; Refresh(); } public void Refresh() { Label label = EnsureLabel(); if (m_Runtime is null) { label.Text = "Hero: unbound"; return; } HeroRunState state = m_Runtime.State; string slots = string.Join(", ", state.PrimaryWeaponSlots.Select((weaponId, index) => index == state.SelectedPrimaryWeaponSlotIndex ? $"[{Display(weaponId)}]" : Display(weaponId))); string nextThreshold = m_Runtime.NextPointThreshold?.ToString(CultureInfo.InvariantCulture) ?? "max"; label.Text = $"Hero: {state.LifeState}\n" + $"Level: {state.Level} Points: {state.Points} Next: {nextThreshold}\n" + $"Shields: {state.ShieldCharges} Retries: {state.RetryCount}\n" + $"Primary: {slots}\n" + $"Secondary: {Display(state.CurrentSecondaryWeaponId)}\n" + $"Special: {Display(state.CurrentSpecialWeaponId)} ammo={state.SpecialAmmo}\n" + $"Squadron: {Display(state.SquadronMateTypeId)} x{state.SquadronMateCount}\n" + $"Last: {state.LastStateChange}"; } private void HandleStateChanged(HeroRunState state) { Refresh(); } private Label EnsureLabel() { if (m_StateLabel is not null) { return m_StateLabel; } m_StateLabel = GetNodeOrNull