swb_player/PlayerBase.Health.cs
namespace SWB.Player;

public partial class PlayerBase
{
	[Property] public int MaxHealth { get; set; } = 100;
	[Sync] public int Health { get; set; } = 100;
	[Sync] public int Kills { get; set; }
	[Sync] public int Deaths { get; set; }
	[Sync] public bool GodMode { get; set; }

	public bool IsAlive => Health > 0;

	public void OnDamage( in DamageInfo info )
	{
		if ( info is not Shared.DamageInfo )
		{
			TakeDamage( Shared.DamageInfo.FromDamageInfo( info ) );
			return;
		}

		info.Shape = null; // Remove physics shape to avoid issues with networking
		info.Hitbox = null; // Remove hitbox to avoid issues with networking
		TakeDamage( info as Shared.DamageInfo );
	}

	[Rpc.Broadcast]
	public virtual void TakeDamage( Shared.DamageInfo info )
	{
		if ( !this.IsValid() || IsProxy || !IsAlive || GodMode )
			return;

		if ( info.Tags.Has( "head" ) )
			info.Damage *= 2;

		// GAME HOOK (not stock SWB): let anything attached to this player
		// reduce the damage before it lands. Armour uses this.
		//
		// It has to be here rather than anywhere else, because the next
		// three lines subtract the damage AND decide whether it killed you.
		// Giving health back afterwards would work on every shot except the
		// lethal one, which is the only one that matters.
		var mitigator = Components.Get<IDamageMitigator>();

		if ( mitigator is not null )
			info.Damage = mitigator.Mitigate( info.Damage );

		Health -= (int)info.Damage;

		if ( info.HitFlinch > 0 )
			DoHitFlinch( info.HitFlinch );

		if ( info.MovementImpact.Duration > 0 )
			ApplyMovementImpact( info.MovementImpact );

		if ( Health <= 0 )
			OnDeath( info );
	}
}