Player/Player.cs
using Sandbox.UI;

namespace CryptidHunt;

public partial class Player : Component
{
	public static Player Instance { get; private set; }

	[Property]
	public PlayerController Controller { get; set; }

	[Property]
	public CameraComponent Camera { get; set; }

	[Property]
	public SceneFile Menu { get; set; }

	public Interactable InteractingWith { get; set; }

	public float RunSpeed { get; set; }
	public float Stamina { get; set; } = 100f;
	public bool Running { get; set; } = false;
	public TimeSince LastRan { get; set; } = 0f;
	public TimeSince LastInteraction { get; set; } = 0f;
	public bool LockInputs { get; set; } = false;
	private int _hp = 3;
	[Property]
	public int HP
	{
		get => _hp;
		set
		{
			if ( value <= 0 )
			{
				_hp = 0;
				Dead = true;
			}
			else
			{
				_hp = value;
			}
		}
	}
	private bool _dead = false;
	public bool Dead
	{
		get => _dead;
		set
		{
			if ( value )
			{
				_dead = true;
				LockInputs = true;
				Death();

			}
			else
			{
				_dead = false;
				LockInputs = false;
			}
		}
	}

	private async void Death()
	{
		Controller.UseInputControls = false;
		Controller.WishVelocity = 0f;

		var colliders = Camera.Components.GetAll<Collider>( FindMode.EverythingInSelfAndDescendants );
		foreach ( var collider in colliders )
			collider.Enabled = true;

		var rigidbodies = Camera.Components.GetAll<Rigidbody>( FindMode.EverythingInSelfAndDescendants );
		foreach ( var rigidbody in rigidbodies )
			rigidbody.Enabled = true;

		Camera.GameObject.SetParent( null );

		await Task.DelaySeconds( 2f );

		GameUI.BlackScreen();

		await Task.DelaySeconds( 5f );

		await Task.MainThread();
		Scene.LoadFromFile( Menu.ResourcePath );
	}

	protected override void OnStart()
	{
		Instance = this;

		if ( Controller.IsValid() )
		{
			RunSpeed = Controller.RunSpeed;
			Controller.RunSpeed = Controller.WalkSpeed;
			_cameraOffset = CameraOffset;
		}
	}

	public TimeUntil NextInteraction { get; set; }
	public SceneTraceResult InteractTrace { get; set; }

	protected override void OnFixedUpdate()
	{
		if ( !Controller.IsValid() ) return;
		if ( Dead ) return;

		if ( !LockInputs && Stamina > 0 && (Input.Pressed( "Run" ) || Input.Down( "Run" ) && (Running || LastRan >= 1f)) )
			Running = true;
		else
			Running = false;


		if ( Running )
		{
			Stamina = Math.Clamp( Stamina - Time.Delta * 5f, 0f, 100f );
			Controller.RunSpeed = RunSpeed;
			LastRan = 0f;
		}
		else
		{
			Controller.RunSpeed = Controller.WalkSpeed;
			Stamina = Math.Clamp( Stamina + Time.Delta * 10f, 0f, 100f );
		}

		if ( !LockInputs )
		{
			Controller.UseInputControls = true;

			if ( Input.Pressed( "Flashlight" ) )
				SetFlashLight( !FlashLightOn, true );

			if ( Input.Pressed( "attack1" ) )
			{
				if ( NextInteraction )
				{
					if ( Holding.IsValid() )
					{
						NextInteraction = 0.5f;
						LastInteraction = 0;
						Holding.Attack( this );
					}
				}

			}

		}
		else
		{
			Controller.UseInputControls = false;
			Controller.WishVelocity = 0f;
		}

		InteractTrace = Scene.Trace.Ray( Camera.WorldPosition, Camera.WorldPosition + Camera.WorldRotation.Forward * 150f )
			.Radius( 5f )
			.WithoutTags( "playerclip" )
			.IgnoreGameObjectHierarchy( GameObject )
			.Run();

		if ( InteractTrace.Hit && InteractTrace.GameObject.Components.TryGet<Interactable>( out var interactable ) )
			InteractingWith = interactable;
		else
			InteractingWith = null;

		if ( Input.Pressed( "use" ) && NextInteraction )
		{
			NextInteraction = 0.2f;

			if ( InteractingWith.IsValid() )
			{
				InteractingWith.Interact( this );
				LastInteraction = 0;
				AddCameraShake( 0.2f, 3f );
			}
		}

		HandleBreathing();
		HandleFootsteps();
	}

	protected override void OnUpdate()
	{
		if ( Dead ) return;
		SetupCamera();
	}

	public void Respawn()
	{

		//CurrentCheckpoint ??= PlayerSpawn.Initial;

		//EnableAllCollisions = true;
		//EnableDrawing = true;

		//WorldPosition = CurrentCheckpoint.Position;
		//Rotation = CurrentCheckpoint.Rotation;

	}

	TimeSince _lastBreath;
	bool _breatheIn;

	public void HandleBreathing()
	{
		if ( HP <= 0f || LockInputs ) return;

		var isWalking = Input.AnalogMove.Length > 0.5f;
		var breathTime = Running ? 1.5f : 2.5f;

		if ( _lastBreath >= breathTime )
		{
			_breatheIn = !_breatheIn;
			_lastBreath = 0f;

			var sound = Sound.Play( _breatheIn ? "breathe_in" : "breathe_out", WorldPosition );
			sound.Volume *= Running ? 8f : isWalking ? 3f : 1.5f;
			sound.Pitch *= Running ? 1.1f : 1f;

		}
	}

	TimeUntil _nextFootstep;
	TimeSince _lastStep = 0f;

	public void HandleFootsteps()
	{
		if ( !Controller.IsValid() ) return;
		if ( LockInputs ) return;

		if ( Controller.IsClimbing && _nextFootstep && MathF.Abs( Controller.Velocity.z ) >= 10f )
		{
			_nextFootstep = 0.3f;
			Sound.Play( "footstep-metal", WorldPosition ).Volume *= 7;
		}

		if ( _lastStep <= (Running ? 0.3f : 0.6f) ) return;
		_lastStep = 0f;

		var footTrace = Scene.Trace.Ray( WorldPosition, WorldPosition + Vector3.Down * 10f )
			.Radius( 2f )
			.IgnoreDynamic()
			.IgnoreGameObjectHierarchy( GameObject )
			.Run();

		if ( !footTrace.Hit ) return;

		var tag = footTrace.Tags
			.Where( x => x != "solid" && x != "world" )
			.FirstOrDefault();

		var sound = tag switch
		{
			"metal" => "footstep-metal",
			"grass" => "footstep-grass",
			"dirt" => "footstep-dirt",
			_ => "footstep-concrete"
		};

		Sound.Play( sound, footTrace.EndPosition ).Volume *= Controller.Velocity.WithZ( 0f ).Length / 15f;
	}
}