NPCs/FSM/StateMachine.State.cs
namespace Opium.AI;

public partial class StateMachine
{
	public abstract partial class State : Component
	{
		/// <summary>
		/// We can only enter this state FROM these selected states.
		/// </summary>
		public virtual List<Type> FromStates { get; }

		[Property, Category( "Performance" )] public float TickFrequency { get; set; }

		[Property, ReadOnly, Category( "Data" )] public TimeUntil NextTick { get; set; }
		[Property, ReadOnly, Category( "Data" )] public Agent Agent { get; set; }

		[Property] public int Priority { get; set; }

		/// <summary>
		/// Should we be entering this state?
		/// </summary>
		[Property, Category( "Actions" )] public Func<bool> ShouldEnterStateAction { get; set; }

		protected float TimeInState => (Agent.StateMachine.CurrentState != this ? 0f : Agent.StateMachine.TimeInState);

		[Property, Category( "Audio" )] public string OnStateEnterSound { get; set; }

		SoundHandle SoundHandle;

		public virtual void OnEvent( string eventName, params object[] obj )
		{
		}

		/// <summary>
		/// How much score does this state machine state have to enter it right now?
		/// This will be weighed up against a bunch of compatible states, and the highest one will be picked.
		/// </summary>
		/// <returns></returns>
		public virtual float EntryScore
		{
			get => 0f;
		}

		/// <summary>
		/// What score do we need to try to get into this state?
		/// </summary>
		public virtual float RequiredEntryScore
		{
			get => 1f;
		}

		/// <summary>
		/// Can we transition from the current state, into a NEW state? Check the FromStates from our new state, and then check the type of our current state.
		/// </summary>
		/// <param name="currentState"></param>
		/// <returns></returns>
		protected bool IsAllowedTransitionState( State currentState )
		{
			if ( currentState is null ) return true;

			// Fallback
			if ( FromStates is null ) return true;

			foreach ( var stateType in FromStates )
			{
				if ( stateType == currentState.GetType() )
				{
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// Should we be entering this state?
		/// </summary>
		/// <param name="machine"></param>
		/// <returns></returns>
		public virtual bool ShouldEnterState( StateMachine machine )
		{
			if ( ShouldEnterStateAction is not null ) return ShouldEnterStateAction.Invoke();

			return IsAllowedTransitionState( machine.CurrentState ) && ( EntryScore >= RequiredEntryScore );
		}

		public virtual void OnStateEnter( State prev )
		{
			if ( OnStateEnterSound is not null )
			{
				SoundHandle = Agent.PlayVoice( OnStateEnterSound );
			}
		}

		public virtual UpperBodyState GetUpperAnimationState()
		{
			return UpperBodyState.Default;
		}

		public virtual LowerBodyState GetLowerAnimationState()
		{
			return LowerBodyState.Default;
		}

		public virtual FullBodyState? GetFullBodyState()
		{
			return null;
		}

		public virtual void OnStateExit( State next )
		{
			if ( SoundHandle is not null )
			{
				SoundHandle?.Stop( 0.25f );
			}
		}

		public virtual bool CanTick()
		{
			if ( NextTick ) return true;
			if ( TickFrequency <= 0 ) return true;
			return false;
		}

		[ConVar( "ai_disabled" )] public static bool DisableAI { get; set; } = false;

		internal void InternalTick()
		{
			if ( DisableAI || !Agent.IsAlive ) return;

			NextTick = TickFrequency;
			Tick();
		}

		public virtual float? GetWishSpeed()
		{
			return null;
		}

		public virtual void Tick()
		{
		}
	}
}