Player/PlayerController.Movement.cs
namespace Opium;

public partial class PlayerController
{
	[Property] public CameraEffect JumpCameraEffect { get; set; }
	[Property] public CameraEffect LandCameraEffect { get; set; }

	bool wasOnGround = true;
	float totalJumps = 0;
	TimeSince lastJump = 1;

	public override Angles GetEyeAngles()
	{
		return EyeAngles;
	}

	protected void UpdateMouse()
	{
		if ( !LockCameraMovement && !LockMouseMovementOverride )
			EyeAngles += Input.AnalogLook;

		EyeAngles = EyeAngles
			.WithPitch( EyeAngles.pitch.Clamp( -90, 90 ) )
			.WithRoll( 0f );

		Transform.Rotation = new Angles( 0, EyeAngles.yaw, 0 );
	}

	float CalculateJumpForce( float totalJumps )
	{
		return 250 / (1 + totalJumps * 0.05f);
	}

	float JumpStaminaDrain => 10f;

	protected void Jump()
	{
		if ( JumpCameraEffect is not null )
		{
			JumpCameraEffect.Player = this;
			JumpCameraEffect.Enabled = true;
		}

		GetMechanic<StaminaMechanic>().RemoveStamina( JumpStaminaDrain );
		CharacterController.Punch( Vector3.Up * CalculateJumpForce( totalJumps ) );
	}

	protected override void BuildWishInput()
	{
		WishMove = Input.AnalogMove.Normal;
	}


	private void ApplyAccceleration()
	{
		if ( CurrentAccelerationOverride is not null )
		{
			CharacterController.Acceleration = CurrentAccelerationOverride.Value;
		}
		else
		{
			CharacterController.Acceleration = baseAcceleration;
		}
	}

	protected override void BuildWishVelocity()
	{
		base.BuildWishVelocity();

		if ( LockMovement || LockMovementOverride )
			WishVelocity = 0f;
	}

	private void UpdateJump()
	{
		if ( !LockMovement && !LockMovementOverride && lastGrounded < 0.2f && lastJump > 0.3f && Input.WantsToJump && totalJumps < 5 )
		{
			lastJump = 0;
			totalJumps++;

			CharacterController.Punch( Vector3.Up * CalculateJumpForce( totalJumps ) );
		}
	}

	protected override void OnMechanicsUpdate()
	{
		// Do nothing
	}

	protected override void UpdateMovement()
	{
		if ( CharacterController is null ) return;

		Vector3 halfGravity = Scene.PhysicsWorld.Gravity * Time.Delta * 0.5f;

		BuildWishInput();

		// Mechanics
		DoMechanicsUpdate();

		BuildWishVelocity();

		ApplyAccceleration();
		UpdateJump();

		if ( lastJump > 0.15f )
		{
			totalJumps = Math.Max( 0, totalJumps - (Time.Delta * 0.5f) );
		}

		CharacterController.ApplyFriction( GetFriction() );

		if ( !wasOnGround && CharacterController.IsOnGround )
		{
			OnLanded();
		}

		wasOnGround = CharacterController.IsOnGround;

		if ( CharacterController.IsOnGround )
		{
			CharacterController.Accelerate( WishVelocity );
			CharacterController.Velocity = CharacterController.Velocity.WithZ( 0 );
		}
		else
		{
			CharacterController.Velocity += halfGravity;
			CharacterController.Accelerate( WishVelocity );
		}

		//
		// Don't walk through other players, let them push you out of the way
		//
		var pushVelocity = PlayerPusher.GetPushVector( Transform.Position + Vector3.Up * 40.0f, Scene, GameObject );
		if ( !pushVelocity.IsNearlyZero() )
		{
			var travelDot = CharacterController.Velocity.Dot( pushVelocity.Normal );
			if ( travelDot < 0 )
			{
				CharacterController.Velocity -= pushVelocity.Normal * travelDot * 0.6f;
			}

			CharacterController.Velocity += pushVelocity * 128.0f;
		}

		CharacterController.Move();

		if ( !CharacterController.IsOnGround )
		{
			CharacterController.Velocity += halfGravity;
		}
		else
		{
			CharacterController.Velocity = CharacterController.Velocity.WithZ( 0 );
		}

		if ( CharacterController.IsOnGround )
		{
			lastGrounded = 0;
		}
		else
		{
			lastUngrounded = 0;
		}
	}

	protected void OnLanded()
	{
		if ( LandCameraEffect is not null )
		{
			LandCameraEffect.Player = this;
			LandCameraEffect.Enabled = true;
		}
	}
}