Npcs/Layers/NavigationLayer.cs
namespace Sandbox.Npcs.Layers;

/// <summary>
/// Handles Npc navigation
/// </summary>
public class NavigationLayer : BaseNpcLayer
{
	public NavMeshAgent Agent { get; private set; }

	public Vector3? MoveTarget { get; private set; }

	[Property]
	public float StopDistance { get; private set; } = 10f;

	/// <summary>
	/// The desired movement speed for the agent. Schedules can raise this to make the NPC run.
	/// </summary>
	public float WishSpeed { get; set; } = 100f;

	protected override void OnStart()
	{
		if ( !Npc.IsValid() ) return;

		Agent = Npc.GetComponent<NavMeshAgent>();
	}

	/// <summary>
	/// Command this layer to move to a target
	/// </summary>
	public void MoveTo( Vector3 target, float stopDistance = 10f )
	{
		MoveTarget = target;
		StopDistance = stopDistance;

		if ( Agent.IsValid() )
		{
			Agent.MoveTo( target );

			// Use the agent's resolved navmesh position so distance checks are accurate
			if ( Agent.TargetPosition.HasValue )
			{
				MoveTarget = Agent.TargetPosition.Value;
			}
		}
	}

	protected override void OnUpdate()
	{
		if ( IsProxy ) return;

		if ( Agent.IsValid() )
		{
			Agent.MaxSpeed = WishSpeed;
			Npc.Animation.SetMove( Agent.Velocity, Agent.WorldRotation );
		}
	}

	public override string GetDebugString()
	{
		if ( !MoveTarget.HasValue ) return null;

		var status = GetStatus();
		var dist = Npc.WorldPosition.Distance( MoveTarget.Value ).CeilToInt();
		return $"Nav: {status} ({dist}u)";
	}

	/// <summary>
	/// Current navigation status — reached target, still moving, or failed.
	/// </summary>
	public TaskStatus GetStatus()
	{
		if ( !MoveTarget.HasValue ) return TaskStatus.Success;

		var distance = Npc.WorldPosition.Distance( MoveTarget.Value );

		// Npc.DebugOverlay.Sphere( new Sphere( MoveTarget.Value, 16 ), Color.Green, 0.1f );

		if ( distance <= StopDistance )
			return TaskStatus.Success;

		if ( Agent.IsValid() && !Agent.IsNavigating )
			return TaskStatus.Failed;

		return TaskStatus.Running;
	}

	public override void ResetLayer()
	{
		MoveTarget = null;

		if ( Agent.IsValid() )
		{
			Agent.Stop();
		}
	}
}