Code/k/GoalStateMachine.cs
using System;
using Sandbox.k.Interfaces;

namespace Sandbox.k
{
    [Serializable]
    public class GoalStateMachine<T> : IStateMachine<T> where T : IGoalState
    {
        [Property] private T _currentGoalState;
        public T CurrentState => _currentGoalState;

        public Action<T> OnStateChange;
        
        private Func<T, bool> _onBeforeChangeState;
        private Func<T, bool> _onAfterChangeState;

        public virtual bool Init(T initialState, 
	        Func<T, bool> onBeforeChangeState = null, 
	        Func<T, bool> onAfterChangeState = null)
        {
            _onBeforeChangeState = onBeforeChangeState;
            _onAfterChangeState = onAfterChangeState;
            
            if (initialState != null)
                ChangeState(initialState);
            
            return true;
        }

        public bool ChangeState(T newState)
        {
            if (_onBeforeChangeState != null
                && !_onBeforeChangeState.Invoke(_currentGoalState))
                return false;
            
            _currentGoalState?.Exit();

            newState.Enter();
            OnStateChange?.Invoke( newState );
            _currentGoalState = newState;

            if (_onAfterChangeState != null
                && !_onAfterChangeState.Invoke(_currentGoalState))
                return false;
            
            return true;
        }

        public bool Refresh()
        {
            if (_currentGoalState == null) 
                return true; // means nothing to do
            
            _currentGoalState.Execute();
            if (_currentGoalState.IsGoalComplete())
            {
                ChangeState((T)_currentGoalState.GetNextState());
            }

            return true;
        }
    }
}