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()
{
}
}
}