swb_base/PlayerCameraHandler.cs

Camera handler component for a player weapon. It updates the player camera field of view (FOV) each frame, interpolates FOV changes for aiming/zooming and restores FOV on destroy.

using SWB.Shared;

namespace SWB.Base;

public class PlayerCameraHandler : Component
{
	[Property] public float ThirdPersonAimZoom { get; set; } = 1.4f;

	public Weapon Weapon { get; set; }

	float targetPlayerFOV = -1;
	float finalPlayerFOV;
	float playerFOVSpeed = 1;
	float cachedFOV = -1f;
	float cachedVerticalFOV;

	IPlayerBase player => Weapon.Owner;

	protected override void OnStart()
	{
		if ( IsProxy )
			Enabled = false;
	}

	protected override void OnEnabled()
	{
		targetPlayerFOV = -1;
		playerFOVSpeed = 1;
	}

	protected override void OnDestroy()
	{
		if ( IsProxy || player is null ) return;
		player.FieldOfView = Screen.CreateVerticalFieldOfView( Preferences.FieldOfView );
	}

	protected override void OnUpdate()
	{
		if ( targetPlayerFOV == -1 )
		{
			targetPlayerFOV = Preferences.FieldOfView;
			finalPlayerFOV = Preferences.FieldOfView;
		}

		var decayRate = playerFOVSpeed * 10f;
		if ( decayRate > 0f )
		{
			var halfLife = System.MathF.Log( 2f ) / decayRate;
			finalPlayerFOV = MathX.ExponentialDecay( finalPlayerFOV, targetPlayerFOV, halfLife, RealTime.Delta );
		}

		if ( finalPlayerFOV != cachedFOV )
		{
			cachedFOV = finalPlayerFOV;
			cachedVerticalFOV = Screen.CreateVerticalFieldOfView( finalPlayerFOV );
		}
		player.FieldOfView = cachedVerticalFOV;

		// Initialize the target vectors for this frame
		targetPlayerFOV = Preferences.FieldOfView;

		HandleIronFOV();
	}

	void HandleIronFOV()
	{
		var isAiming = !Weapon.ShouldTuckVar && Weapon.IsAiming;
		if ( isAiming && !Weapon.IsReloading )
		{
			var aimZoom = Weapon.AimInfo.PlayerFOVZoom;

			if ( !player.IsFirstPerson && aimZoom <= 0 )
				aimZoom = ThirdPersonAimZoom;

			if ( aimZoom > 0 )
				targetPlayerFOV = CalculateZoomedFOV( Preferences.FieldOfView, aimZoom );

			if ( Weapon.IsScoping && Weapon.ScopeInfo.FOV > 0 )
				targetPlayerFOV = Weapon.ScopeInfo.FOV;

			playerFOVSpeed = Weapon.AimInfo.AimInFOVSpeed;
		}
		else
		{
			playerFOVSpeed = Weapon.AimInfo.AimOutFOVSpeed;
		}
	}

	/// <summary>
	/// Take the camera’s current FOV, convert it into a projected view size, divide that size by the zoom factor, then convert it back into a new FOV.
	/// </summary>
	static float CalculateZoomedFOV( float baseFOV, float zoom )
	{
		var halfFOV = MathX.DegreeToRadian( baseFOV ) * 0.5f;
		return MathX.RadianToDegree( 2f * System.MathF.Atan( System.MathF.Tan( halfFOV ) / zoom ) );
	}
}