Health.cs
using Sandbox;

public interface IPlayerHealthEvent : ISceneEvent<IPlayerHealthEvent>
{
	void OnSpawned(GameObject player) { }
	void OnTakeDamage( GameObject player, ref DamageInfo damage ) { }
	void OnDied( GameObject player ) { }

	/// <summary>
	/// Fired when the player's camera has been destroyed
	/// </summary>
	void OnPostDeath(GameObject player) { }
}

public sealed class Health : Component, Component.IDamageable
{
	[Property]
	public int StartingHealth { get; set; } = 100;

	[Property]
	public int MaxHealth { get; set; } = 100;

	[Property]
	public SoundEvent Hurt { get; set; }

	[Property]
	public SoundEvent Death { get; set; }

	public bool IsAlive => _currentHealth > 0;

	public float CurrentHealth => _currentHealth;
	private float _currentHealth;

	protected override void OnEnabled()
	{
		base.OnEnabled();

		_currentHealth = StartingHealth;
		GetComponent<HUD>().Health = (int)_currentHealth;

		IPlayerHealthEvent.Post( x => x.OnSpawned( GameObject ) );
	}

	void IDamageable.OnDamage( in DamageInfo damage )
	{
		if ( !IsAlive ) return;

		// Log.Info( $"hurt for {damage}" );
		_currentHealth -= damage.Damage;

		if ( _currentHealth > MaxHealth ) _currentHealth = MaxHealth;
		if ( _currentHealth <= 0 ) _currentHealth = 0;

		var clonedDamage = damage;
		Scene.RunEvent<IPlayerHealthEvent>( x => x.OnTakeDamage( GameObject, ref clonedDamage ) );

		if ( _currentHealth == 0 )
		{
			Sound.Play( Death, WorldPosition ).Parent = GameObject;
			Die(damage);
		}
		else
		{
			if (damage.Damage > 0)
			{
				Sound.Play( Hurt, WorldPosition ).Parent = GameObject;
			}
		}
	}

	void Die(DamageInfo finalDamage)
	{
		Scene.RunEvent<IPlayerHealthEvent>( x => x.OnDied( GameObject ) );
		// Turn off various features of the player controller
		var player = GetComponent<PlayerController>();
		// Stop moving around
		player.UseInputControls = false;
		// Cancel current movement
		player.WishVelocity = 0;
		// Stop controlling the model's animations
		player.Renderer = null;
		// Clear all parameters on the model
		var smr = player.GetComponentInChildren<SkinnedModelRenderer>();
		smr.ClearParameters();
		// Close the eyes and stop animating
		smr.UseAnimGraph = false;
		smr.Sequence.Name = "Eyes_Closed";
		// Turn on our ragdoll component
		var ragdoll = GameObject.Components.Get<ModelPhysics>( FindMode.EverythingInDescendants );
		ragdoll.Enabled = true;
		if (finalDamage.Tags.Has("explosion"))
		{
			foreach (var body in ragdoll.Bodies)
			{
				var rb = body.Component;
				rb.ApplyForce( Game.Random.Angles().AsVector3().WithZ( 700f ) );
			}
		}
		// Free it from the player object so it sticks around
		ragdoll.GameObject.SetParent( null );
	}
}