Effects/SnipeShip.cs
using Sandbox.Utility;

namespace PlanetMeat;

[Group( "Planet Meat - Effects" ), Title( "Snipe Ship" ), Icon( "flight_land" )]
public sealed class SnipeShip : Component
{
	public const float AWAY_HEIGHT = 5000.0f;
	private const float FACING_LERP_SPEED = 0.5f;

	[RequireComponent] public SnipeZone Zone { get; set; }
	[RequireComponent] public DelayedStages Stages { get; set; }

	[Property] public GameObject ShipAnchor { get; set; }
	[Property] public GameObject ShipModel { get; set; }

	private Vector3 _lerpPositionStart;
	private Vector3 _lerpPositionEnd;
	private Rotation _lerpAnglesStart;
	private Rotation _lerpAnglesEnd;
	private bool _lerpEaseOut;
	private bool _lerping = false;

	private float Facing => Zone.DriftAngle.RadianToDegree();
	private Angles FacingAngles => ShipAnchor.WorldRotation.Angles().WithYaw( Facing );

	private Vector3 GetApproachPoint( bool arriving )
	{
		return new Vector3( Vector2.FromDegrees( ShipAnchor.WorldRotation.Angles().yaw ) * AWAY_HEIGHT * 2.0f * (arriving ? -1.0f : 1.0f), AWAY_HEIGHT );
	}

	private static Rotation GetApproachRotation( bool arriving )
	{
		return new Angles( 0.0f, 0.0f, 90.0f * (arriving ? 1.0f : -1.0f) );
	}

	protected override void OnStart()
	{
		base.OnStart();
		Stages.StageStarted += OnStageStarted;

		ShipAnchor.WorldRotation = FacingAngles;

		_lerpPositionStart = GetApproachPoint( true );
		_lerpPositionEnd = Vector3.Zero;
		_lerpAnglesStart = GetApproachRotation( true );
		_lerpAnglesEnd = Rotation.Identity;
		StartLerp( true );
	}

	private void StartLerp( bool easeOut )
	{
		_lerpEaseOut = easeOut;
		_lerping = true;
		Lerp( 0.0f );
	}

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

		if ( _lerping )
			Lerp( Stages.Fraction );

		ShipAnchor.WorldRotation = Rotation.Lerp(
			ShipAnchor.WorldRotation,
			FacingAngles,
			FACING_LERP_SPEED * Time.Delta
		);
	}

	private void Lerp( float frac )
	{
		var f = float.IsNaN( frac ) ? 0.0f : frac.Clamp( 0.0f, 1.0f );
		ShipModel.LocalRotation = _lerpAnglesStart.LerpTo( _lerpAnglesEnd, f );

		f = _lerpEaseOut ? Easing.QuadraticOut( f ) : Easing.QuadraticIn( f );
		f = float.IsNaN( f ) ? 0.0f : f.Clamp( 0.0f, 1.0f );
		ShipAnchor.LocalPosition = _lerpPositionStart.LerpTo( _lerpPositionEnd, f );

		if ( frac >= 1.0f )
			_lerping = false;
	}

	public void OnStageStarted( int stage )
	{
		if ( stage == DelayedStages.STAGE_INDEX_DESTROY )
		{
			_lerpPositionStart = ShipAnchor.LocalPosition;
			_lerpPositionEnd = GetApproachPoint( false );
			_lerpAnglesStart = ShipModel.LocalRotation;
			_lerpAnglesEnd = GetApproachRotation( false );
			StartLerp( false );
		}
		else
		{
			_lerping = false;
		}
	}
}