ArmorDisplay.cs
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
using SWB.Player;
using System;

// The armour bar. Sits directly above SWB's health box and is built to the
// same look, so the two read as a pair: health on the bottom, the vest
// protecting it on top.
//
// A plain Panel rather than a PanelComponent, because it lives inside
// SWB's RootDisplay the same way HealthDisplay does. RootDisplay already
// handles only existing on the local player's screen and hiding while
// scoped, so this gets both for free.
//
// Hidden entirely when there's no vest. An empty bar would just be a
// permanent reminder of something you didn't buy.
public class ArmorDisplay : Panel
{
	readonly PlayerBase _player;

	Label _valueLabel;
	Panel _fill;

	public ArmorDisplay( PlayerBase player )
	{
		_player = player;

		StyleSheet.Load( "/ArmorDisplay.cs.scss" );

		Add.Label( "armor", "name" );
		_valueLabel = Add.Label( "", "value" );

		var bar = Add.Panel( "bar" );
		_fill = bar.Add.Panel( "fill" );
	}

	public override void Tick()
	{
		// Looked up each tick rather than cached: it's one component
		// lookup, and caching it would mean handling the loadout not
		// existing yet on the first few frames.
		var loadout = _player?.Components.Get<PlayerLoadout>();

		var show = loadout is not null && _player.IsAlive && loadout.HasArmor;
		SetClass( "hide", !show );

		if ( !show )
			return;

		var max = MathF.Max( loadout.MaxArmorHealth, 1f );
		var fraction = MathX.Clamp( loadout.ArmorHealth / max, 0f, 1f );

		// Rounded UP for display. The vest still works at 0.3, and showing
		// "0" while it's still cutting your damage would be a lie.
		_valueLabel.Text = MathF.Ceiling( loadout.ArmorHealth ).ToString();

		_fill.Style.Width = Length.Fraction( fraction );

		// Goes amber in its last quarter, as a heads-up that the next few
		// hits are the last ones it'll take.
		SetClass( "low", fraction <= 0.25f );
	}
}