PlayerCrouch.cs
using Sandbox;

public enum CrouchBehavior
{
	Toggle,
	Hold
}

public sealed class PlayerCrouch : Component
{
	[Property]
	public PlayerController Controller { get; set; }

	[Property]
	public CrouchBehavior Behavior { get; set; } =
		CrouchBehavior.Toggle;

	[Property]
	public float StandingJumpSpeed { get; set; } = 280f;

	[Property]
	public float CrouchedJumpSpeed { get; set; } = 220f;

	/// <summary>
	/// When false, the physical duck/stand collider state is frozen while airborne.
	/// Crouch input is still tracked so a standing player can queue a landing slide
	/// without shrinking the collider in mid-air. Sliding itself may still force the
	/// compact collider for ramp launches and other brief airborne slide states.
	/// </summary>
	[Property]
	public bool AllowAirborneStanceChanges { get; set; } = false;

	/// <summary>
	/// The crouch stance requested by the player.
	///
	/// Slide can force physical crouch without changing this
	/// independently.
	/// </summary>
	public bool WantsCrouch { get; private set; }

	public bool IsCrouchButtonHeld { get; private set; }

	protected override void OnStart()
	{
		Controller ??=
			Components.Get<PlayerController>();

		if ( Controller is null )
		{
			Log.Warning(
				"PlayerCrouch could not find PlayerController."
			);

			return;
		}

		WantsCrouch =
			false;

		IsCrouchButtonHeld =
			false;

		Log.Info(
			$"PlayerCrouch active | Proxy: {IsProxy} | Owner: {Network.OwnerId}"
		);
	}

	/// <summary>
	/// Updates only player crouch intent.
	/// </summary>
	public void UpdateIntent(
		bool buttonDown,
		bool buttonPressed
	)
	{
		if ( IsProxy )
			return;

		IsCrouchButtonHeld =
			buttonDown;

		switch ( Behavior )
		{
			case CrouchBehavior.Toggle:
				{
					if ( buttonPressed )
					{
						WantsCrouch =
							!WantsCrouch;
					}

					break;
				}

			case CrouchBehavior.Hold:
				{
					WantsCrouch =
						buttonDown;

					break;
				}
		}
	}

	/// <summary>
	/// Apply the stance chosen by PlayerLocomotion.
	///
	/// forceCrouch is used by Slide.
	/// forceStand is used when active sprint intent should
	/// return us to a standing gait.
	///
	/// PlayerController still handles actual headroom.
	/// </summary>
	public void ApplyResolvedStance(
		bool forceCrouch,
		bool forceStand
	)
	{
		if ( IsProxy )
			return;

		if ( Controller is null )
			return;

		bool resolvedCrouch;

		if ( forceCrouch )
		{
			resolvedCrouch =
				true;
		}
		else if ( forceStand )
		{
			resolvedCrouch =
				false;
		}
		else
		{
			resolvedCrouch =
				WantsCrouch;
		}

		// Mid-air collider resizing can produce a visible vertical pop because the
		// controller has to reconcile a different body height while unsupported.
		// Freeze the physical stance for the duration of the jump, but keep
		// WantsCrouch untouched so landing-slide intent survives until OnLanded.
		if (
			!AllowAirborneStanceChanges &&
			!forceCrouch &&
			Controller.IsAirborne
		)
		{
			resolvedCrouch =
				Controller.IsDucking;
		}

		Controller.UpdateDucking(
			resolvedCrouch
		);
	}

	/// <summary>
	/// Once continuing sprint has won the stance decision,
	/// the crouch used to enter a slide should not silently
	/// return later when sprint stops.
	/// </summary>
	public void ClearCrouchIntent()
	{
		WantsCrouch =
			false;
	}

	public float GetJumpSpeed()
	{
		if (
			Controller is not null &&
			Controller.IsDucking &&
			Controller.IsOnGround
		)
		{
			return CrouchedJumpSpeed;
		}

		return StandingJumpSpeed;
	}

	public void ResetToStanding()
	{
		WantsCrouch =
			false;

		IsCrouchButtonHeld =
			false;

		if (
			Controller is not null &&
			!IsProxy
		)
		{
			Controller.UpdateDucking(
				false
			);
		}
	}

	// =========================================================
	// TEMPORARY COMPATIBILITY
	// =========================================================

	public void SetCrouchRequested(
		bool crouched
	)
	{
		WantsCrouch =
			crouched;
	}

	public void RequestCrouch()
	{
		WantsCrouch =
			true;
	}

	public void RequestStanding()
	{
		WantsCrouch =
			false;
	}

	public void CancelCrouchForSprint()
	{
		WantsCrouch =
			false;
	}
}