NPCs/Implementation/Base/PatrolState.cs

namespace Opium.AI;

public partial class PatrolState : StateMachine.State
{
	[Property] public PatrolPath Path { get; set; }

	[Property, Range( 0, 30, 0.5f )] public RangedFloat PatrolWaitDelay { get; set; } = 0f;
	[Property, Range( 0.5f, 1.5f, 0.05f )] public RangedFloat WalkSpeedMultiplier { get; set; } = 0.7f;

	/// <summary>
	/// While we're waiting to walk again, how long?
	/// </summary>
	TimeUntil TimeUntilNextWalk = 5;

	/// <summary>
	/// Are we actively patrolling right now? False = Waiting for <see cref="TimeUntilNextWalk"/>
	/// </summary>
	bool isWalking = false;

	/// <summary>
	/// The current index for the path list.
	/// </summary>
	private int _currentIndex;

	public override float RequiredEntryScore => 10f;
	public override float EntryScore
	{
		get
		{
			// If we don't have a path, we do NOT want to enter.
			if ( Path is null ) return 0f;

			// Nothing important happened recently, so lets go back to this if we can
			if ( Agent.LastStimulus is null ) return RequiredEntryScore;
			if ( Agent.LastStimulus.HasExpired ) return RequiredEntryScore;

			return 0f;
		}
	}

	public override void OnStateEnter( StateMachine.State prev )
	{
		base.OnStateEnter( prev );
		OnWalkFinished();
	}

	float CurrentWalkSpeed = 0.75f;
	public override float? GetWishSpeed()
	{
		return CurrentWalkSpeed;
	}

	public override void Tick()
	{
		if ( !isWalking && TimeUntilNextWalk )
		{
			Walk();
		}
	}

	/// <summary>
	/// Called when the Agent is done walking to a destination.
	/// </summary>
	void OnWalkFinished()
	{
		isWalking = false;
		TimeUntilNextWalk = PatrolWaitDelay.GetValue();
		CurrentWalkSpeed = WalkSpeedMultiplier.GetValue();
	}

	private void Walk()
	{
		_currentIndex++;
		_currentIndex %= Path.Count;

		var localPos = Path[_currentIndex];
		var worldPos = Path.Transform.World.PointToWorld( localPos );

		isWalking = true;
		Agent.WalkTo( worldPos, OnWalkFinished );
	}
}