Example/Complex/PlayerWalkControllerComplex.Movement.cs
using Sandbox;
using System;
namespace XMovement;

public partial class PlayerWalkControllerComplex : Component
{
	/// <summary>
	/// How quickly does the player move by default?
	/// </summary>
	[Property, Group( "Config" )] public float DefaultSpeed { get; set; } = 180.0f;

	[Property, FeatureEnabled( "Running" )] public bool EnableRunning { get; set; } = true;
	/// <summary>
	/// The Input Action that the alternate speed mode is triggered by.
	/// </summary>
	[Property, InputAction, Feature( "Running" )] public string RunAction { get; set; } = "Run";
	/// <summary>
	/// If the player should run by default, pressing the run action will swap to default speed
	/// </summary>
	[Property, Feature( "Running" )] public bool RunByDefault { get; set; } = false;
	/// <summary>
	/// The speed the player moves at while in the alternate speed mode.
	/// </summary>
	[Property, Feature( "Running" )] public float RunSpeed { get; set; } = 320.0f;
	[Sync] public bool IsRunning { get; set; }

	[Property, FeatureEnabled( "Walking" )] public bool EnableWalking { get; set; } = true;
	/// <summary>
	/// The Input Action that the alternate speed mode is triggered by.
	/// </summary>
	[Property, InputAction, Feature( "Walking" )] public string WalkAction { get; set; } = "Walk";
	/// <summary>
	/// The speed the player moves at while in the alternate speed mode.
	/// </summary>
	[Property, Feature( "Walking" )] public float WalkSpeed { get; set; } = 120.0f;
	[Sync] public bool IsWalking { get; set; }

	[Property, FeatureEnabled( "Crouching" )] public bool EnableCrouching { get; set; } = true;

	/// <summary>
	/// The Input Action that crouching is triggered by.
	/// </summary>
	[Property, InputAction, Feature( "Crouching" )] public string CrouchAction { get; set; } = "Duck";
	/// <summary>
	/// The speed the player moves at while crouching.
	/// </summary>
	[Property, Feature( "Crouching" )] public float CrouchSpeed { get; set; } = 80.0f;
	[Sync] public bool IsCrouching { get; set; }

	[Property, FeatureEnabled( "Jumping" )] public bool EnableJumping { get; set; } = true;
	/// <summary>
	/// The Input Action that jumping is triggered by.
	/// </summary>
	[Property, InputAction, Feature( "Jumping" )] public string JumpAction { get; set; } = "Jump";
	/// <summary>
	/// Can the player hold down jump to repeatedly jump?
	/// </summary>
	[Property, Feature( "Jumping" )] public bool AllowPogosticking { get; set; } = false;
	/// <summary>
	/// How powerful is the player's jump?
	/// </summary>
	[Property, Feature( "Jumping" )] public float JumpPower { get; set; } = 268.3281572999747f;

	/// <summary>
	/// Do we want to jump next movement update?
	/// </summary>
	public bool WantsJump { get; set; }
	public Vector3 WishMove { get; private set; }

	public virtual void DoMovement()
	{
		Controller.PrepareMovement();

		BuildWishVelocity();
		BuildInput();

		if ( Controller.IsOnGround && WantsJump && CanJump() ) Jump();

		CheckWater();
		CheckLadder();
		CheckNoclip();

		if ( IsNoclipping )
		{
			DoNoclipMove();
		}
		else if ( IsTouchingLadder )
		{
			LadderMove();
		}
		else if ( IsSwimming )
		{
			SwimMove();
		}
		else
		{
			Controller.Move();
		}

		ResetFrameInput();

		LadderLatchCheck();

		Animate();
	}

	public void Jump()
	{
		Controller.LaunchUpwards( JumpPower );
		BroadcastPlayerJumped();
	}

	/// <summary>
	/// Fired (broadcast to all clients) when this player jumps.
	/// </summary>
	public event Action OnJumped;

	/// <summary>
	/// A network message that lets other users that we've triggered a jump.
	/// </summary>
	[Rpc.Broadcast]
	public void BroadcastPlayerJumped()
	{
		AnimationHelper?.TriggerJump();
		OnJumped?.Invoke();
	}

	private void BuildFrameInput()
	{
		if ( AllowPogosticking && Input.Down( JumpAction ) || (IsInVR && Input.VR.RightHand.ButtonA.IsPressed) ) WantsJump = true;
		else if ( Input.Pressed( JumpAction ) || (IsInVR && Input.VR.RightHand.ButtonA.Delta) ) WantsJump = true;
	}
	private void ResetFrameInput()
	{
		WantsJump = false;
	}
	private void BuildInput()
	{
		var run = Input.Down( RunAction ) || (IsInVR && Input.VR.LeftHand.ButtonA);
		var walk = Input.Down( WalkAction );
		var crouch = Input.Down( CrouchAction ) || (IsInVR && Input.VR.LeftHand.JoystickPress.IsPressed);

		if ( RunByDefault )
			IsRunning = !run && EnableRunning;
		else
			IsRunning = run && EnableRunning;

		IsWalking = walk && EnableWalking;
		IsCrouching = crouch || !CanUncrouch();
	}

	public virtual float GetWishSpeed()
	{
		if ( IsCrouching ) return CrouchSpeed;
		if ( IsRunning ) return RunSpeed;
		if ( IsWalking ) return WalkSpeed;
		return DefaultSpeed;
	}

	public void BuildWishVelocity()
	{
		WishMove = Input.AnalogMove;

		if ( IsInVR ) WishMove = new Vector3( Input.VR.LeftHand.Joystick.Value.y, -Input.VR.LeftHand.Joystick.Value.x );

		var rot = EyeAngles.WithPitch( 0f ).ToRotation();

		var wishDirection = WishMove.Normal * rot;
		wishDirection = wishDirection.WithZ( 0 );

		Controller.WishVelocity = wishDirection * GetWishSpeed();
	}

	public bool CanJump()
	{
		if ( !EnableJumping ) return false;
		if ( IsSwimming ) return false;
		return true;
	}
	public bool CanUncrouch()
	{
		var b = Controller.Height;
		if ( !IsCrouching ) return true;
		Controller.Height = 72;
		var tr = Controller.TraceDirection( Vector3.Zero );
		Controller.Height = b;
		return !tr.Hit;
	}
}