Player/FlightCamera.cs
/// <summary>
/// Camera for the flight ship. Supports third-person (default) and first-person cockpit view.
/// Toggle with the "View" input action (C / Right Joystick Button).
/// Updated by the owner's PlayerPawn each frame — continues running while dead for the death cam.
/// </summary>
public sealed class FlightCamera
{
	private float   _fovOffset     = 0f;
	private bool    _isFirstPerson = false;

	// Death cam state
	private bool    _isDead        = false;
	private Vector3 _deathPosition;

	public void OnDied( Vector3 deathWorldPos )
	{
		_isDead        = true;
		// Sit slightly above and behind where the ship exploded
		_deathPosition = deathWorldPos;
	}

	public void Update( PlayerPawn player )
	{
		var camera = Game.ActiveScene?.Camera;
		if ( camera == null ) return;

		if ( !player.IsAlive )
		{
			UpdateDeathCam( camera, player );
			return;
		}

		// Back to flight cam when alive again
		_isDead = false;

		if ( Input.Pressed( "view" ) )
			_isFirstPerson = !_isFirstPerson;

		if ( _isFirstPerson )
			UpdateFirstPerson( camera, player );
		else
			UpdateThirdPerson( camera, player );

		camera.ZFar = 20500f;

		// Screen shake from speed — zero when not alive
		player.ChargeEffect ??= new ScreenShake.Charge();
		player.ChargeEffect.Size = player.Speed.LerpInverse( 0f, 500f, true ) * 12.5f;

		ScreenShake.Apply();
	}

	private void UpdateDeathCam( CameraComponent camera, PlayerPawn player )
	{
		camera.ZFar = 20500f;
		player.ChargeEffect ??= new ScreenShake.Charge();
		player.ChargeEffect.Size = 0f;
		ScreenShake.Apply();

		var target = player.DeathCamTarget;
		var lookAt  = (target != null && target.IsValid() && target.IsAlive)
			? target.WorldPosition
			: _deathPosition + camera.WorldRotation.Forward * 500f; // no killer — keep looking forward

		var toTarget = lookAt - _deathPosition;
		var dist     = toTarget.Length;

		// Smoothly rotate toward the killer
		var wantRot  = dist > 1f
			? Rotation.LookAt( toTarget.Normal, Vector3.Up )
			: camera.WorldRotation;
		camera.WorldRotation = Rotation.Lerp( camera.WorldRotation, wantRot, Time.Delta * 4f );
		camera.WorldPosition = _deathPosition;

		// Zoom in when the killer is far away: 90° FOV at ≤300 units, 30° at ≥3000 units
		var t       = ((dist - 300f) / 2700f).Clamp( 0f, 1f );
		var zoomFov = 90f - t * 60f;
		_fovOffset  = _fovOffset.LerpTo( 0f, Time.Delta * 4f );
		camera.FieldOfView = Screen.CreateVerticalFieldOfView( zoomFov );
	}

	private void UpdateThirdPerson( CameraComponent camera, PlayerPawn player )
	{
		var centre     = player.WorldPosition;
		var orbitPoint = centre + player.WorldRotation.Forward * 90f;
		var offset     = centre - orbitPoint + player.WorldRotation.Up * 20f;

		camera.WorldRotation = Rotation.Lerp( camera.WorldRotation, player.WorldRotation, Time.Delta * 10f );
		camera.WorldPosition = orbitPoint + offset * 2.5f;

		var speed        = (player.Speed / 50f).Clamp( 0f, 1f );
		var forwardSpeed = player.Velocity.Normal.Dot( player.WorldRotation.Forward );
		_fovOffset = _fovOffset.LerpTo( speed * 10f * MathF.Abs( forwardSpeed ), Time.Delta * 4f );

		camera.FieldOfView = Screen.CreateVerticalFieldOfView( 120f ) + _fovOffset;
	}

	private void UpdateFirstPerson( CameraComponent camera, PlayerPawn player )
	{
		camera.WorldPosition = player.WorldPosition
		                     + player.WorldRotation.Forward * 40f
		                     + player.WorldRotation.Up      * 8f;
		camera.WorldRotation = player.WorldRotation;

		var speed        = (player.Speed / 50f).Clamp( 0f, 1f );
		var forwardSpeed = player.Velocity.Normal.Dot( player.WorldRotation.Forward );
		_fovOffset = _fovOffset.LerpTo( speed * 8f * MathF.Abs( forwardSpeed ), Time.Delta * 4f );

		camera.FieldOfView = Screen.CreateVerticalFieldOfView( 90f ) + _fovOffset;
	}
}