PlayerMovementInput.cs
using Sandbox;

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

	[Property]
	public string CrouchAction { get; set; } = "crouch";

	[Property]
	public string SprintAction { get; set; } = "run";

	[Property]
	public string JumpAction { get; set; } = "Jump";

	// =========================================================
	// CURRENT INPUT STATE
	// =========================================================

	public bool ControlsEnabled { get; private set; }

	public Vector3 MoveInput { get; private set; }

	public bool SprintDown { get; private set; }

	public bool CrouchDown { get; private set; }

	/// <summary>
	/// When true, raw local Input is ignored and a bot/automation component feeds
	/// this same input surface through SetBotInput(). PlayerLocomotion therefore
	/// remains identical for humans and bots.
	/// </summary>
	public bool IsBotDriven { get; private set; }

	// =========================================================
	// QUEUED TRANSITIONS
	// =========================================================

	private bool _sprintPressedQueued;
	private bool _sprintReleasedQueued;

	private bool _crouchPressedQueued;
	private bool _crouchReleasedQueued;

	private bool _jumpPressedQueued;

	private bool _previousSprintDown;
	private bool _previousCrouchDown;

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

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

			return;
		}

		//
		// PlayerLocomotion owns gameplay movement.
		//
		Controller.UseInputControls =
			false;

		ControlsEnabled =
			false;

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

	protected override void OnUpdate()
	{
		if ( IsProxy )
			return;

		if ( Controller is null )
			return;

		Controller.UseInputControls =
			false;

		PlayerState playerState =
			Components.Get<PlayerState>();

		if ( playerState?.IsBot == true )
		{
			IsBotDriven = true;
		}

		// A bot uses this component as the shared input contract, but must never
		// sample the host's keyboard/gamepad. Synthetic commands are supplied by
		// OMRBotController and future bot-brain phases.
		if ( IsBotDriven )
		{
			if ( !ControlsEnabled )
			{
				MoveInput = Vector3.Zero;
				SprintDown = false;
				CrouchDown = false;
				_previousSprintDown = false;
				_previousCrouchDown = false;
				ClearQueuedActions();
			}

			return;
		}

		bool currentSprintDown =
			Input.Down(
				SprintAction
			);

		bool currentCrouchDown =
			Input.Down(
				CrouchAction
			);

		//
		// While controls are disabled we still follow the
		// physical state of the buttons so enabling controls
		// later does not generate fake press events.
		//
		if ( !ControlsEnabled )
		{
			MoveInput =
				Vector3.Zero;

			SprintDown =
				currentSprintDown;

			CrouchDown =
				currentCrouchDown;

			_previousSprintDown =
				currentSprintDown;

			_previousCrouchDown =
				currentCrouchDown;

			ClearQueuedActions();

			return;
		}

		MoveInput =
			Input.AnalogMove;

		// =====================================================
		// SPRINT
		// =====================================================

		if (
			currentSprintDown &&
			!_previousSprintDown
		)
		{
			_sprintPressedQueued =
				true;
		}

		if (
			!currentSprintDown &&
			_previousSprintDown
		)
		{
			_sprintReleasedQueued =
				true;
		}

		SprintDown =
			currentSprintDown;

		_previousSprintDown =
			currentSprintDown;

		// =====================================================
		// CROUCH / SLIDE
		// =====================================================

		if (
			currentCrouchDown &&
			!_previousCrouchDown
		)
		{
			_crouchPressedQueued =
				true;
		}

		if (
			!currentCrouchDown &&
			_previousCrouchDown
		)
		{
			_crouchReleasedQueued =
				true;
		}

		CrouchDown =
			currentCrouchDown;

		_previousCrouchDown =
			currentCrouchDown;

		// =====================================================
		// JUMP
		// =====================================================

		if (
			Input.Pressed(
				JumpAction
			)
		)
		{
			_jumpPressedQueued =
				true;
		}
	}

	public void SetBotDriven(
		bool botDriven
	)
	{
		IsBotDriven = botDriven;

		if ( !botDriven )
		{
			MoveInput = Vector3.Zero;
			SprintDown = false;
			CrouchDown = false;
			_previousSprintDown = false;
			_previousCrouchDown = false;
			ClearQueuedActions();
		}
	}

	/// <summary>
	/// Synthetic equivalent of the human movement/button sampling path. Future
	/// navigation/tactical AI should call this rather than manipulating
	/// PlayerController directly.
	/// </summary>
	public void SetBotInput(
		Vector3 moveInput,
		bool sprintDown,
		bool crouchDown,
		bool jumpPressed
	)
	{
		if ( !IsBotDriven )
			return;

		if ( !ControlsEnabled )
		{
			MoveInput = Vector3.Zero;
			SprintDown = false;
			CrouchDown = false;
			_previousSprintDown = false;
			_previousCrouchDown = false;
			ClearQueuedActions();
			return;
		}

		MoveInput = moveInput;

		if ( sprintDown && !_previousSprintDown )
			_sprintPressedQueued = true;

		if ( !sprintDown && _previousSprintDown )
			_sprintReleasedQueued = true;

		SprintDown = sprintDown;
		_previousSprintDown = sprintDown;

		if ( crouchDown && !_previousCrouchDown )
			_crouchPressedQueued = true;

		if ( !crouchDown && _previousCrouchDown )
			_crouchReleasedQueued = true;

		CrouchDown = crouchDown;
		_previousCrouchDown = crouchDown;

		if ( jumpPressed )
			_jumpPressedQueued = true;
	}

	public bool ConsumeSprintPressed()
	{
		bool value =
			_sprintPressedQueued;

		_sprintPressedQueued =
			false;

		return value;
	}

	public bool ConsumeSprintReleased()
	{
		bool value =
			_sprintReleasedQueued;

		_sprintReleasedQueued =
			false;

		return value;
	}

	public bool ConsumeCrouchPressed()
	{
		bool value =
			_crouchPressedQueued;

		_crouchPressedQueued =
			false;

		return value;
	}

	public bool ConsumeCrouchReleased()
	{
		bool value =
			_crouchReleasedQueued;

		_crouchReleasedQueued =
			false;

		return value;
	}

	public bool ConsumeJumpPressed()
	{
		bool value =
			_jumpPressedQueued;

		_jumpPressedQueued =
			false;

		return value;
	}

	public void SetControlsEnabled(
		bool enabled
	)
	{
		ControlsEnabled =
			enabled;

		if ( Controller is not null )
		{
			Controller.UseInputControls =
				false;

			if ( !enabled )
			{
				Controller.WishVelocity =
					Vector3.Zero;
			}
		}

		if ( !enabled )
		{
			MoveInput =
				Vector3.Zero;

			ClearQueuedActions();
		}
	}

	private void ClearQueuedActions()
	{
		_sprintPressedQueued =
			false;

		_sprintReleasedQueued =
			false;

		_crouchPressedQueued =
			false;

		_crouchReleasedQueued =
			false;

		_jumpPressedQueued =
			false;
	}
}