Player/PlayerController.cs
using Sandbox.Citizen;

namespace Opium;

[Group( "Walker" )]
[Title( "Player Controller" )]
public partial class PlayerController : Actor
{
	[Property] public float CrouchMoveSpeedMultiplier { get; set; } = 0.6f;

	[Property] public CitizenAnimationHelper AnimationHelper { get; set; }
	[Property] public PlayerInventory Inventory { get; set; }
	[Property] public PlayerInformation PlayerInfo { get; set; }

	[Property] public bool Crouching { get; set; }
	[Property] public Angles EyeAngles { get; set; }

	public bool IsHoldingObject = false;

	public bool WishCrouch;
	TimeSince lastGrounded = 1;
	TimeSince lastUngrounded = 1;

	protected float GetEyeHeightOffset()
	{
		if ( CurrentEyeHeightOverride is not null ) return CurrentEyeHeightOverride.Value;
		return 65f;
	}

	public float EyeHeight { get; protected set; } = 0f;

	/// <summary>
	/// SINGLEPLAYER: This is the local player.
	/// </summary>
	public static Opium.PlayerController Local { get; set; }

	protected override void OnStart()
	{
		Local = this;

		base.OnStart();

		// We want to accept player input
		Input = new PlayerInput();

		MainCamera.GameObject.SetParent( null, true );
		PlayerInfo = GameObject.Components.Get<PlayerInformation>( FindMode.InSelf );

		// Set first EyeAngles
		EyeAngles = Transform.Rotation.Angles();
	}

	protected override void OnUpdate()
	{
		// Input polling
		base.OnUpdate();

		UpdateMouse();
		UpdateCamera();
	}

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

		UpdateAnimation();
		
		// tony: I've moved this to OnUpdate for now, because something is very wrong?
		UpdateMovement();

		UpdateWeaponInput();
		UpdateDamageScreenEffect();
	}

	private void UpdateAnimation()
	{
		if ( AnimationHelper is null ) return;

		var wv = WishVelocity.Length;

		AnimationHelper.WithWishVelocity( WishVelocity );
		AnimationHelper.WithVelocity( CharacterController.Velocity );
		AnimationHelper.IsGrounded = CharacterController.IsOnGround;
		AnimationHelper.DuckLevel = Crouching ? 1.0f : 0.0f;

		AnimationHelper.HoldType = CitizenAnimationHelper.HoldTypes.None;
		AnimationHelper.Handedness = CitizenAnimationHelper.Hand.Both;

		AnimationHelper.MoveStyle = wv < 160f ? CitizenAnimationHelper.MoveStyles.Walk : CitizenAnimationHelper.MoveStyles.Run;

		var lookDir = EyeAngles.ToRotation().Forward * 1024;
		AnimationHelper.WithLook( lookDir, 1, 0.5f, 0.25f );
	}

	protected void UpdateWeaponInput()
	{
		var weapon = Inventory.Current;
		if ( weapon is null ) return;

		var interactor = Components.Get<PlayerInteractor>();
		interactor?.PlayerInteractorInput();

		if ( interactor.timeSinceCarriedObject < 1f )
		{
			return;
		}

		// Are we wanting to shoot a gun? (Input)
		if ( Input.WantsToShoot && !LockMouseMovementOverride && !LockCameraMovement )
		{
			// Can we actually fire the gun? (Gun)
			if ( weapon.CanShoot() )
			{
				weapon.Shoot();
			}
		}

		// Are we wanting to shoot a gun? (Input)
		if ( Input.WantsToAim && !LockMouseMovementOverride && !LockCameraMovement )
		{
			// Can we actually fire the gun? (Gun)
			if ( weapon.CanAim() )
			{
				weapon.Aim();
			}
		}
	}
}