UI/Player/Info.razor
@using Sandbox.UI
@using Sandbox.UI.Components
@inherits Panel
<style>
    Info {
        position: absolute;
        width: 100%;
        bottom: 0;
        height: 100%;
        align-items: center;
        justify-content: center;
        transition: all 0.5s sin-ease-in-out;
        flex-direction: row;
    }

    .arc-root {
        position: absolute;
        width: 600px;
        height: 600px;
        margin-bottom: 16px;
        margin-right: 12px;
    }

    .seg {
        position: absolute;
        opacity: 0.22;
        width: 10px;
        height: 18px;
    }

    .seg.health {
        background-color: rgba(95, 230, 120, 0.95);
    }

    .seg.health.active {
        opacity: 1;
        box-shadow: 0 0 8px rgba(95, 230, 120, 0.45);
    }

    .seg.armour {
        background-color: rgba(65, 195, 255, 0.95);
    }

    .seg.armour.active {
        opacity: 1;
        box-shadow: 0 0 8px rgba(65, 195, 255, 0.4);
    }

    .arc-label {
        position: absolute;
        font-family: "Wallpoet";
        font-size: 24px;
        color: rgba(210, 245, 255, 0.9);
        text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
    }

    .arc-label.hp {
        left: 144px;
        bottom: 122px;
    }

    .arc-label.sh {
        right: 144px;
        bottom: 122px;
    }

    .sWarning {
        text-align: center;
        position: absolute;
        font-family: "Wallpoet";
        font-size: 44px;
        top: 22%;
        left: 50%;
        transform: translate(-50%, -50%);
        color: rgba(39, 181, 238, 1);
        z-index: 30;
    }
</style>

<root>
    @if ( _showWarning )
    {
        <label class="sWarning">- -[WARNING SHIELD DOWN]- -</label>
    }
    <div class="arc-root">
        @foreach ( var s in GetHealthArc() )
        {
            <div class="seg health @(s.Active ? "active" : "")"
                 style="left:@($"{s.X:F1}px"); top:@($"{s.Y:F1}px"); transform:rotate(@($"{s.Angle + 90f:F1}deg"));"></div>
        }
        @foreach ( var s in GetShieldArc() )
        {
            <div class="seg armour @(s.Active ? "active" : "")"
                 style="left:@($"{s.X:F1}px"); top:@($"{s.Y:F1}px"); transform:rotate(@($"{s.Angle + 90f:F1}deg"));"></div>
        }
        <div class="arc-label hp">HP @MathF.Round(_health)</div>
        <div class="arc-label sh">SH @MathF.Round(_shield)</div>
    </div>
</root>

@code
{
    private PlayerPawn Pawn => LocalPlayer.Pawn;
    private float _health => Pawn?.Health ?? 0f;
    private float _healthMax => Pawn?.MaxHealth ?? 100f;
    private float _shield => Pawn?.Shield ?? 0f;
    private float _shieldMax => Pawn?.MaxShield ?? 0f;

    private bool _showWarning =>
        PilotGame.Gamemode != FPGameMode.Instagib &&
        (Pawn?.IsAlive ?? false) &&
        _health > 0f &&
        _shieldMax > 0f &&
        _shield <= 0f;

    private float HealthProgress => _healthMax > 0f ? Math.Clamp( _health / _healthMax, 0f, 1f ) : 0f;
    private float ShieldProgress => _shieldMax > 0f ? Math.Clamp( _shield / _shieldMax, 0f, 1f ) : 0f;

    private readonly struct ArcSeg
    {
        public ArcSeg( float x, float y, float angle, bool active )
        {
            X = x;
            Y = y;
            Angle = angle;
            Active = active;
        }

        public float X { get; }
        public float Y { get; }
        public float Angle { get; }
        public bool Active { get; }
    }

    private IEnumerable<ArcSeg> GetHealthArc() => BuildArc( HealthProgress, 256f, 135, 225f, 28);
    private IEnumerable<ArcSeg> GetShieldArc() => BuildArc( ShieldProgress, 256f, 45, -45f, 28 );

    private static IEnumerable<ArcSeg> BuildArc( float progress, float radius, float start, float end, int count )
    {
        const float cx = 300f;
        const float cy = 300f;
        for ( int i = 0; i < count; i++ )
        {
            var t = count <= 1 ? 1f : i / (float)(count - 1);
            var angle = start + (end - start) * t;
            var rad = angle.DegreeToRadian();
            var x = cx + MathF.Cos( rad ) * radius;
            var y = cy + MathF.Sin( rad ) * radius;
            yield return new ArcSeg( x, y, angle, (i + 1) / (float)count <= progress );
        }
    }

    protected override int BuildHash()
        => HashCode.Combine( _health, _shield, _healthMax, _shieldMax, Pawn?.IsAlive );
}