Game/2D/Gameplay/HypeMeter.razor

A UI Razor component that draws a 2D Hype Meter for the ChainReactionGame. It renders animated bubbles and a fill bar, subscribes to game state changes, updates on screen-shake changes, and computes fill percentage and CSS class based on game hype values.

NetworkingFile Access
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox


<root>
    @if (Game?.IsRunning == true @*&& (Game?.HypeMeter ?? 0f) > 0.01f*@)
    {
        <div class="hype-wrap @_currentShake">
            <div class="hype-track">
                @for (int i = 0; i < 6; i++)
                {
                    float bBottom = (i * 16f) % 90f;
                    float bDur    = 1.5f + (i * 0.25f) % 1.0f;
                    float bDelay  = (i * 0.3f) % 2.0f;
                    <div class="hype-bubble"
                         style="bottom:@bBottom.ToString("F1")%;animation-duration:@bDur.ToString("F1")s;animation-delay:@bDelay.ToString("F1")s;">
                    </div>
                }

                <div class="hype-fill @GetFillClass()" style="height:@GetFillPct()%;">
                    @if ((Game?.HypeMeter ?? 0f) >= 0.2f)
                    {
                        <div class="hype-mult-label">
                            @(Game?.HypeMaxActive == true ? "🔥" : "▲")
                        </div>
                    }
                </div>
            </div>
        </div>
    }
</root>

@code {
    ChainReactionGame Game => ChainReactionGame.Instance;

    bool _sub = false;
    string _currentShake = "";
    protected override void OnTreeFirstBuilt() => TrySub();

    void TrySub()
    {
        var g = Game;
        if (g == null || _sub) return;
		
        g.OnStateChanged += StateHasChanged;
        _sub = true;
    }

    protected override void OnUpdate()
    {
        if (!_sub) TrySub();

        // Screen Shake - everytime we call StateHasChanged() it rebuilds state
        // and it plays the shake in the html code (since the _currentShake != "" but now has a class)
        var shakeClass = ScreenShaker.Instance?.ShakeClass ?? "";
        if (shakeClass != _currentShake)
        {
            _currentShake = shakeClass;
            StateHasChanged();
        }
    }

    float GetFillPct() => (Game?.HypeMeter ?? 0f) * 100f;

    string GetFillClass()
    {
        float h = Game?.HypeMeter ?? 0f;
        if (Game?.HypeMaxActive == true) return "hype-fill-max";
        if (h >= 0.8f) return "hype-fill-high";
        if (h >= 0.5f) return "hype-fill-mid";
        if (h >= 0.2f) return "hype-fill-low";
        return "hype-fill-empty";
    }

    protected override int BuildHash() =>
        System.HashCode.Combine(Game?.IsRunning, Game?.HypeMeter, Game?.HypeMaxActive, _currentShake);
}