Units/Seeker.cs
namespace PlanetMeat;

[Group( "Planet Meat - Units" ), Title( "Seeker" ), Icon( "explore" )]
public sealed class Seeker : Component, ITargetableListener
{
	[RequireComponent] public NavMeshAgent NavAgent { get; set; }
	public Animator Animator => field ??= Components.Get<Animator>( FindMode.EnabledInSelfAndDescendants );

	[Property]
	private float HappyRange
	{
		get;
		set
		{
			field = value;
			HappyRangeSq = value * value;
		}
	} = 0.0f;

	private float HappyRangeSq
	{
		set;
		get
		{
			if ( float.IsNaN( field ) )
				field = HappyRange * HappyRange;
			return field;
		}
	} = float.NaN;

	private Targetable FallbackTarget
	{
		get
		{
			foreach ( var f in Scene.GetAllComponents<SeekerFallback>() )
			{
				if ( f.Components.Get<Targetable>() is Targetable t )
					return t;
			}
			return null;
		}
	}
	public Targetable Target
	{
		get
		{
			if ( !(field?.IsValid ?? false) )
				field = FallbackTarget;
			return field;
		}
		set;
	}
	public Targetable MyTargetable => field ??= this.FindNearestUpwards<Targetable>();

	public Vector3 NavTarget
	{
		get;
		set
		{
			if ( !Navving || field != value )
			{
				Navving = true;
				NavAgent.MoveTo( value );
				field = value;
			}
		}
	}

	public bool Navving
	{
		get => NavAgent.Enabled;
		private set
		{
			if ( !value )
			{
				NavAgent.Stop();
				Animator.StopMoving();
			}
			NavAgent.Enabled = value;
		}
	}

	protected override void OnStart()
	{
		base.OnStart();
		Navving = false;
		SeekTarget();

		if ( Components.Get<Targeter>( FindMode.EnabledInSelfAndDescendants ) is Targeter t )
			t.SoleFocus = Target;
	}

	protected override void OnPreRender()
	{
		base.OnPreRender();
		if ( Navving )
		{
			var vel = NavAgent.Velocity;
			if ( !vel.IsNearlyZero() )
			{
				if ( Target is Targetable t )
					Animator.TurnTo( t.WorldPosition - WorldPosition );
				else
					Animator.TurnTo( vel.Normal );
				Animator.Move( vel, NavTarget );
			}
		}
	}

	protected override void OnFixedUpdate()
	{
		base.OnFixedUpdate();
		if ( IsProxy )
			return;

		if ( Navving )
		{
			if ( Target is Targetable t )
			{
				if ( t.WorldPosition.DistanceSquared( WorldPosition ) <= HappyRangeSq )
					Navving = false;
				else
					SeekTarget();
			}
		}
	}

	private void SeekTarget()
	{
		var t = Target;
		var targetPos = t.WorldPosition;
		if ( Scene.NavMesh.GetClosestPoint( targetPos + (WorldPosition - targetPos).Normal * 0.5f * t.BullseyeRadius ) is Vector3 navToTarget )
			NavTarget = navToTarget;
	}

	public void OnKilled()
	{
		Navving = false;
	}
}