Example/PlayerWalkControllerSimple.cs
using Sandbox;
using Sandbox.Citizen;
namespace XMovement;

public partial class PlayerWalkControllerSimple : Component
{
	[RequireComponent] public PlayerMovement Controller { get; set; }
	[RequireComponent] public CitizenAnimationHelper AnimationHelper { get; set; }
	[Property] public SkinnedModelRenderer ModelRenderer { get; set; }
	[Property, Group( "Head" )] public GameObject Head { get; set; }
	[Property, Group( "Head" )] public float HeadHeight { get; set; } = 64f;

	/// <summary>
	/// How quickly does the player move by default?
	/// </summary>
	[Property, Group( "Config" )] public float RunSpeed { get; set; } = 320.0f;
	[Property, Group( "Config" )] public float WalkSpeed { get; set; } = 120.0f;
	[Property, Group( "Config" )] public float CrouchSpeed { get; set; } = 80.0f;

	/// <summary>
	/// How powerful is the player's jump?
	/// </summary>
	[Property, Group( "Config" )] public float JumpPower { get; set; } = 268.3281572999747f;

	[Sync] public bool IsCrouching { get; set; }

	[Sync] public bool IsSlowWalking { get; set; }

	[Sync] public bool IsNoclipping { get; set; }

	[Sync] public Angles LocalEyeAngles { get; set; }
	public Angles EyeAngles
	{
		get
		{
			return LocalEyeAngles + GameObject.LocalRotation.Angles();
		}
		set
		{
			LocalEyeAngles = value - GameObject.LocalRotation.Angles();
		}
	}

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

	protected override void OnUpdate()
	{
		base.OnUpdate();
		if ( !IsProxy )
		{
			DoEyeLook();
			BuildFrameInput();
			if ( Controller.MovementFrequency == PlayerMovement.MovementFrequencyMode.PerUpdate ) DoMovement();
		}
	}

	protected override void OnFixedUpdate()
	{
		if ( !IsProxy )
		{
			if ( Controller.MovementFrequency == PlayerMovement.MovementFrequencyMode.PerFixedUpdate ) DoMovement();
		}
		Animate();
	}

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

		BuildWishVelocity();
		BuildInput();

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

		Controller.Move();

		ResetFrameInput();

		Animate();
	}

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

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

	private void BuildFrameInput()
	{
		if ( Input.Pressed( "Jump" ) ) WantsJump = true;
	}
	private void ResetFrameInput()
	{
		WantsJump = false;
	}
	private void BuildInput()
	{
		IsSlowWalking = Input.Down( "Run" );
		IsCrouching = Input.Down( "Duck" ) || !CanUncrouch();
	}

	protected float GetWishSpeed()
	{
		if ( IsCrouching ) return CrouchSpeed;
		if ( IsSlowWalking ) return WalkSpeed;
		return RunSpeed;
	}

	public void BuildWishVelocity()
	{
		var rot = EyeAngles.WithPitch( 0f ).ToRotation();

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

		Controller.WishVelocity = wishDirection * GetWishSpeed();
	}

	private 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;
	}

	private float _smoothEyeHeight;
	float LastSmoothEyeHeight = 0;
	public void DoEyeLook()
	{
		if ( !IsProxy )
		{
			var eyeHeightOffset = GetEyeHeightOffset();
			_smoothEyeHeight = _smoothEyeHeight.LerpTo( eyeHeightOffset, Time.Delta * 10f );
			Controller.Height = 72 + _smoothEyeHeight;

			LocalEyeAngles += Input.AnalogLook;
			LocalEyeAngles = LocalEyeAngles.WithPitch( LocalEyeAngles.pitch.Clamp( -89f, 89f ) );

			// This moves our feet up when crouching in air
			var delta = _smoothEyeHeight - LastSmoothEyeHeight;
			if ( !delta.AlmostEqual( 0 ) && !Controller.IsOnGround )
			{
				var delvel = Controller.Velocity;
				if ( Controller.MovementFrequency == PlayerMovement.MovementFrequencyMode.PerUpdate ) delvel *= Time.Delta;
				delvel *= WorldScale;
				var vel = delvel - new Vector3( 0, 0, (delta * WorldScale.z) / Time.Delta );
				Controller.MoveBy( vel, true );
			}
			LastSmoothEyeHeight = _smoothEyeHeight;
		}
		if ( Head.IsValid() )
		{
			Head.WorldRotation = EyeAngles.ToRotation();
			Head.LocalPosition = new Vector3( 0, 0, HeadHeight + _smoothEyeHeight );
		}
	}
	protected float GetEyeHeightOffset()
	{
		if ( IsCrouching ) return -36f;
		return 0f;
	}

	public void Animate()
	{
		var rotateDifference = 0f;

		var targetAngle = new Angles( 0, EyeAngles.yaw, 0 ).ToRotation();

		var turnSpeed = 0.02f;
		ModelRenderer.WorldRotation = Rotation.Slerp( ModelRenderer.WorldRotation, targetAngle, Controller.WishVelocity.Length * Time.Delta * turnSpeed );
		ModelRenderer.WorldRotation = ModelRenderer.WorldRotation.Clamp( targetAngle, 45.0f, out var shuffle ); // lock facing to within 45 degrees of look direction
		rotateDifference = shuffle;

		AnimationHelper.WithWishVelocity( Controller.WishVelocity );
		AnimationHelper.WithVelocity( Controller.Velocity );
		AnimationHelper.IsGrounded = Controller.IsOnGround;
		AnimationHelper.WithLook( EyeAngles.Forward * 100, 1, 1, 1.0f );
		AnimationHelper.DuckLevel = IsCrouching ? 100 : 0;
	}
}