Player/PlayerController.Camera.cs
using System.Net.Security;

namespace Opium;

public partial class PlayerController
{
	[Property] public bool LockMovement { get; set; } = false;
	[Property] public bool LockCameraMovement { get; set; } = false;
	[Property] public CameraComponent MainCamera { get; set; }
	[Property] public CameraComponent ViewModelCamera { get; set; }
	[Property] public GameObject ViewModelOrigin { get; set; }

	public Vector3 CameraPositionOffset { get; set; } = Vector3.Zero;
	public Rotation CameraRotationOffset { get; set; } = Rotation.Identity;
	public float FieldOfViewOffset { get; set; } = 0f;

	// Actor
	public override GameObject CameraObject => MainCamera.GameObject;

	// Fields
	Rotation lerpedRotationOffset = Rotation.Identity;
	Vector3 lerpedAimOffset = Vector3.Zero;
	Vector3 velocity = Vector3.Zero;
	
	float cameraSway = 0;
	float cameraBobSpeed = 5.0f;
	float cameraBobAmount = 5.0f;
	float lean = 0.0f;
	float fovOffset = 0.0f;
	float lookyawoffset = 0.0f;
	float jumpoffset = 0.0f;
	float duckoffset = 0.0f;
	float avoidanceDistance = 0f;

	public Vector3 ExtraCamOffset { get; set; }

	/// <summary>
	/// Runs every update for the camera. This code's a big mess.
	/// </summary>
	private void UpdateCamera()
	{
		var camera = MainCamera;
		if ( camera is null ) return;

		camera.FieldOfView = Screen.CreateVerticalFieldOfView( GameSettingsSystem.Current.FieldOfView );

		if ( ViewModelCamera is not null )
		{
			ViewModelCamera.FieldOfView = Screen.CreateVerticalFieldOfView( GameSettingsSystem.Current.WeaponFieldOfView );
		}

		if ( Opium.GameManager.IsPaused ) return;

		var targetCameraPos = Transform.Position + new Vector3( 0, 0, EyeHeight );

		if ( !IsAlive )
		{
			camera.Transform.Position = targetCameraPos + CameraPositionOffset + ExtraCamOffset;
			camera.Transform.Rotation = EyeAngles;
			lerpedRotationOffset = Rotation.Lerp( lerpedRotationOffset, CameraRotationOffset, Time.Delta * 7.5f );
			camera.Transform.Rotation *= lerpedRotationOffset;
			CameraRotationOffset = Rotation.Identity;

			return;
		}

		var targetEyeHeight = GetEyeHeightOffset();
		EyeHeight = EyeHeight.LerpTo( targetEyeHeight, Time.Delta * 10.0f );

		// smooth view z, so when going up and down stairs or ducking, it's smooth af
		if ( lastUngrounded > 0.2f )
		{
			targetCameraPos.z = camera.Transform.Position.z.LerpTo( targetCameraPos.z, Time.Delta * 25.0f );
		}

		var localCameraOffset = Vector3.Zero;
		var bobSpeed = cameraBobSpeed * GameSettingsSystem.Current.ViewbobScale;

		velocity = velocity.LerpTo( CharacterController.Velocity, Time.Delta * 10f );
		if ( !LockCameraMovement )
		{
			if ( !velocity.Length.AlmostEqual( 0 ) && CharacterController.IsOnGround )
			{
				cameraBobAmount += Time.Delta * bobSpeed * velocity.Length.LerpInverse( 0, 75 );
				float xOffset = MathF.Sin( cameraBobAmount * 1.25f ) * 0.25f;
				float yOffset = MathF.Cos( cameraBobAmount * 2 ) * 0.25f;
				localCameraOffset += camera.Transform.Rotation.Right * xOffset;
				localCameraOffset += camera.Transform.Rotation.Up * yOffset;
				targetCameraPos += localCameraOffset;

				lean = lean.LerpTo( velocity.Dot( camera.Transform.Rotation.Right ), Time.Delta * 20f );

				lookyawoffset = velocity.Dot( camera.Transform.Rotation.Right ) * 0.1f;
			}
			else
			{
				cameraBobAmount = 0.0f;
				cameraSway = (MathF.Sin( Time.Now * 2.0f ) * 0.75f).SnapToGrid( 0.08f );
				targetCameraPos += camera.Transform.Rotation.Right * cameraSway * 0.2f;
				lean = lean.LerpTo( 0.0f, Time.Delta * 2.0f );

				lookyawoffset = lookyawoffset.LerpTo( Input.AnalogLook.yaw * 4.0f, Time.Delta * 2.0f );
			}
		}

		jumpoffset = jumpoffset.LerpTo( velocity.z * 0.5f, Time.Delta * 2f );

		bool hasExtraCamOffset = !ExtraCamOffset.IsNearlyZero();

		ViewModelOrigin.Transform.LocalRotation = Rotation.Identity;

		if ( hasExtraCamOffset )
		{
			targetCameraPos = Transform.Position + new Vector3( 0, 0, EyeHeight );
			bobSpeed = 0;
			cameraSway = 0;
			lean = 0;
			lookyawoffset = 0;
		}
		else
		{
			ViewModelOrigin.Transform.LocalPosition = localCameraOffset;
			ViewModelOrigin.Transform.LocalPosition += Vector3.Up * jumpoffset * 0.075f;

			
			ViewModelOrigin.Transform.LocalRotation *= Rotation.FromPitch( -cameraSway );
		}
		
		camera.Transform.Position = targetCameraPos + CameraPositionOffset + ExtraCamOffset;
		camera.Transform.Rotation = EyeAngles;

		lerpedRotationOffset = Rotation.Lerp( lerpedRotationOffset, CameraRotationOffset, Time.Delta * 7.5f );
		camera.Transform.Rotation *= lerpedRotationOffset;

		if ( !hasExtraCamOffset )
		{
			ViewModelOrigin.Transform.LocalRotation *= lerpedRotationOffset * 0.2f;
			ViewModelOrigin.Transform.LocalPosition += CameraPositionOffset * 0.2f;
		}

		if ( Inventory.Current?.ViewModel?.Components.Get<ViewModel>() is ViewModel vm && vm.WeaponLength > 0f && !hasExtraCamOffset )
		{
			var bone = vm.ModelRenderer.SceneModel.GetBoneLocalTransform( "camera" );
			camera.Transform.Position += bone.Position;
			camera.Transform.Rotation *= bone.Rotation;

			var dist = vm.WeaponLength;
			Vector3 offset = vm.WeaponAvoidanceOffset;
			Angles angles = vm.WeaponAvoidanceAngles;

			if ( Components.Get<InteractUse>()?.LookingAtObject is not null )
			{
				dist /= 2;
			}

			if ( Inventory.Current is MeleeWeapon meleeWeapon && meleeWeapon.CurrentAttack?.State != AttackState.Inactive )
			{
				dist /= 4;
			}

			var tr = Scene.Trace.Ray( ViewModelOrigin.Transform.Position, ViewModelOrigin.Transform.Position + ViewModelOrigin.Transform.Rotation.Forward * dist )
				.IgnoreGameObjectHierarchy( GameObject )
				.Run();

			var distToMove = dist - tr.Distance;

			avoidanceDistance = avoidanceDistance.LerpTo( distToMove, Time.Delta * 10f );

			ViewModelOrigin.Transform.LocalPosition += ViewModelOrigin.Transform.Rotation.Forward * avoidanceDistance * offset.x;
			ViewModelOrigin.Transform.LocalPosition += ViewModelOrigin.Transform.Rotation.Right * avoidanceDistance * offset.y;
			ViewModelOrigin.Transform.LocalPosition += ViewModelOrigin.Transform.Rotation.Up * avoidanceDistance * offset.z;
			ViewModelOrigin.Transform.LocalRotation *= Rotation.From( angles * -(avoidanceDistance / dist) );
		}
		else
		{
			ViewModelOrigin.Transform.LocalPosition = 0;
			avoidanceDistance = avoidanceDistance.LerpTo( 0f, Time.Delta * 10f );
		}

		float targetFov = FieldOfViewOffset;

		if ( Inventory.Current is MeleeWeapon melee )
		{
			if ( melee.CurrentAttack != null && melee.CurrentAttack.CurrentStateInfo.FieldOfViewOffset != 0f )
			{
				targetFov += melee.CurrentAttack.CurrentStateInfo.FieldOfViewOffset;

				duckoffset = duckoffset.LerpTo( 0.0f, Time.Delta * 2.0f );
			}
			if ( melee.CurrentAttack == null )
			{
				var lookpitchoffet = EyeAngles.pitch.Remap( -90, 90, 10, -10 );

				ViewModelOrigin.Transform.LocalRotation *= Rotation.FromPitch( -lookpitchoffet );
				ViewModelOrigin.Transform.LocalRotation *= Rotation.FromRoll( -lookyawoffset );

				if ( Crouching )
				{
					duckoffset = duckoffset.LerpTo( -75.0f, Time.Delta * 10.0f );
				}
				else
				{
					duckoffset = duckoffset.LerpTo( 0.0f, Time.Delta * 10.0f );
				}
			}
		}
		if ( Inventory.Current is RangedWeapon range )
		{
			var lookpitchoffet = EyeAngles.pitch.Remap( -90, 90, 5, -5 );
			duckoffset = duckoffset.LerpTo( 0, Time.Delta * 5f );
			ViewModelOrigin.Transform.LocalRotation *= Rotation.FromPitch( -lookpitchoffet );
			ViewModelOrigin.Transform.LocalRotation *= Rotation.FromRoll( -lookyawoffset );

			var up = Vector3.Up * 2f + Vector3.Right * -2f;
			var targetAimOffset = range.IsAiming ? up : 0;
			lerpedAimOffset = lerpedAimOffset.LerpTo( targetAimOffset, Time.Delta * 10f );
			ViewModelOrigin.Transform.LocalPosition += lerpedAimOffset;
		}

		ViewModelOrigin.Transform.LocalRotation *= Rotation.FromRoll( duckoffset );

		fovOffset = fovOffset.LerpTo( targetFov, Time.Delta * 6f );

		camera.FieldOfView += fovOffset;

		camera.Transform.Rotation *= Rotation.FromPitch( cameraSway );
		camera.Transform.Position += camera.Transform.Rotation.Forward * cameraSway * 0.5f;
		camera.Transform.Rotation *= Rotation.FromRoll( lean * 0.03f );

		CameraPositionOffset = Vector3.Zero;
		CameraRotationOffset = Rotation.Identity;
		FieldOfViewOffset = 0;
		CharacterController.Height = targetEyeHeight;
	}

	public override bool SetupViewModel( BaseWeapon weapon, bool isActive )
	{
		if ( !isActive )
		{
			if ( weapon.ViewModel.IsValid() )
			{
				weapon.ViewModel.Enabled = false;
			}
		}
		else
		{
			if ( weapon.ViewModel.IsValid() )
			{
				weapon.ViewModel.Enabled = true;
			}
			else
			{
				weapon.ViewModel = weapon.ViewModelPrefab.Clone( new CloneConfig()
				{
					Parent = ViewModelOrigin,
					Transform = global::Transform.Zero.WithScale( 1f ),
					StartEnabled = true
				} );

				weapon.ViewModel.Tags.Add( "viewmodel" );

				var vm = weapon.ViewModel.Components.Get<ViewModel>( true );
				vm.Weapon = weapon;
				vm.DoCodeAnimations();

				weapon.OnCreateViewModel();
			}
		}

		return true;
	}
}