swb_player/CameraMovement.cs
using SWB.Shared;

namespace SWB.Player;

[Group( "SWB" )]
[Title( "CameraMovement" )]
public class CameraMovement : Component, ICameraMovement
{
	[Property] public float Distance { get; set; } = 0f;

	/// <summary>Thirdperson shoulder offset</summary>
	[Property] public float ShoulderOffset { get; set; } = 30f;

	[Property] public PlayerBase Player { get; set; }
	[Property] public GameObject Body { get; set; }
	[Property] public GameObject Head { get; set; }
	[Property] public SkinnedModelRenderer BodyRenderer { get; set; }
	public float InputSensitivity { get; set; } = 1f;

	// <summary> Eye Offset (resets after applying)
	public Angles EyeAnglesOffset { get; set; }

	// Camera Offsets (resets after applying)
	public Angles AnglesOffset { get; set; }
	public Vector3 PosOffset { get; set; }

	float distanceLerp;
	float shoulderOffsetLerp;

	public bool IsFirstPerson
	{
		get
		{
			return Distance == 0f;
		}
	}

	protected override void OnAwake() { }

	protected override void OnStart()
	{
		if ( Player.IsBot )
			Enabled = false;
	}

	protected override void OnUpdate()
	{
		if ( IsProxy || Scene.Camera is null ) return;
		if ( IsFirstPerson )
			FirstPerson();
		else
			Thirdperson();
	}

	public virtual void FirstPerson()
	{
		var eyePos = Player.EyePos;
		var eyeAngles = Player.EyeAngles;

		Input.AnalogLook *= InputSensitivity;
		eyeAngles.pitch += Input.AnalogLook.pitch;
		eyeAngles.pitch = eyeAngles.pitch.Clamp( -89.9f, 89.9f );
		eyeAngles.yaw += Input.AnalogLook.yaw;
		eyeAngles.roll = 0;
		eyeAngles += EyeAnglesOffset;

		EyeAnglesOffset = Angles.Zero;
		InputSensitivity = 1;
		Player.EyeAngles = eyeAngles;

		// Reset thirdperson offsets
		distanceLerp = 0;
		shoulderOffsetLerp = 0;

		// Set the current camera offset
		var targetOffset = Vector3.Zero;
		if ( Player.IsCrouching ) targetOffset += Vector3.Down * 32f;
		Player.EyeOffset = Vector3.Lerp( Player.EyeOffset, targetOffset, Time.Delta * 10f );

		// Offsets
		eyePos += PosOffset;
		PosOffset = Vector3.Zero;

		eyeAngles += AnglesOffset;
		AnglesOffset = Angles.Zero;

		// Set the position of the camera to our calculated position
		Player.Camera.WorldPosition = eyePos;
		Player.Camera.WorldRotation = eyeAngles;
	}

	public virtual void Thirdperson()
	{
		var eyePos = Player.EyePos;
		var camPos = Player.Camera.WorldPosition;
		var camRot = Player.Camera.WorldRotation;
		var camTrace = Scene.Trace.Ray( camPos, camPos + (camRot.Forward * 999999) )
			.WithoutTags( TagsHelper.Clothing, TagsHelper.Trigger, TagsHelper.ViewModel, TagsHelper.Weapon )
			.IgnoreGameObjectHierarchy( Player.GameObject )
			.Run();
		var traceHitPos = camTrace.Hit ? camTrace.HitPosition : camTrace.EndPosition;

		// Direction from the player's eyes to the trace hit
		var dir = (traceHitPos - Player.EyePos).Normal;
		var eyeAngles = dir.EulerAngles.Normal;

		Input.AnalogLook *= InputSensitivity;
		var camPitch = (camRot.Pitch() + Input.AnalogLook.pitch).Clamp( -89.9f, 89.9f );
		var camAngles = new Angles( camPitch, Player.Camera.WorldRotation.Yaw() + Input.AnalogLook.yaw, 0 );
		camAngles += EyeAnglesOffset;
		camRot = camAngles.ToRotation();

		EyeAnglesOffset = Angles.Zero;
		InputSensitivity = 1;
		Player.EyeAngles = eyeAngles;

		// Firstperson only camera offset
		Player.EyeOffset = Vector3.Zero;

		// Thirdperson offsets
		distanceLerp = MathUtil.FILerp( distanceLerp, Distance, 8 );
		shoulderOffsetLerp = MathUtil.FILerp( shoulderOffsetLerp, ShoulderOffset, 8 );

		// Perform a trace backwards to see where we can safely place the camera
		var camSafeTrace = Scene.Trace.Ray( eyePos, eyePos - (camRot.Forward * distanceLerp) + (camRot.Right * shoulderOffsetLerp) )
			.WithoutTags( TagsHelper.Player, TagsHelper.Trigger, TagsHelper.ViewModel, TagsHelper.Weapon )
			.Run();

		if ( camSafeTrace.Hit )
			camPos = camSafeTrace.HitPosition + camSafeTrace.Normal * 4;
		else
			camPos = camSafeTrace.EndPosition;

		// Offsets
		camPos += PosOffset;
		PosOffset = Vector3.Zero;

		// Set the position of the camera to our calculated position
		Player.Camera.WorldPosition = camPos;
		Player.Camera.WorldRotation = camRot;
	}
}