Code/BehaviorTree/Node.cs
using Sandbox;
using Sandbox.Diagnostics;

namespace NPBehave
{
    public abstract class Node
    {
        public enum State
        {
            Inactive,
            Active,
            StopRequested,
        }

        protected State currentState = State.Inactive;

        public State CurrentState
        {
            get { return currentState; }
        }

        public Root RootNode;

        private Container _parentNode;
        public Container ParentNode
        {
            get
            {
                return _parentNode;
            }
        }

        private string _label;

        public string Label
        {
            get
            {
                return _label;
            }
            set
            {
                _label = value;
            }
        }

        private string _name;

        public string Name
        {
            get
            {
                return _name;
            }
        }

        public virtual Blackboard Blackboard
        {
            get
            {
                return RootNode.Blackboard;
            }
        }

        public virtual Clock Clock
        {
            get
            {
                return RootNode.Clock;
            }
        }

        public bool IsStopRequested
        {
            get
            {
                return currentState == State.StopRequested;
            }
        }

        public bool IsActive
        {
            get
            {
                return currentState == State.Active;
            }
        }


        public Node(string name)
        {
            _name = name;
        }

        public virtual void SetRoot(Root rootNode)
        {
            RootNode = rootNode;
        }

        public void SetParent(Container parent)
        {
            _parentNode = parent;
        }

#if DEBUG
	    
	    public virtual string DebugIcon => "fiber_manual_record";
	    public virtual string ComputedLabel => string.Empty;
	    
        public TimeSince DebugLastStopRequestAt = 0.0f;
        public TimeSince DebugLastStoppedAt = 0.0f;
        public TimeSince DebugLastSuccessAt = 0.0f;
        public TimeSince DebugLastFailureAt = 0.0f;
        public int DebugNumStartCalls = 0;
        public int DebugNumStopCalls = 0;
        public int DebugNumStoppedCalls = 0;
        public bool DebugLastResult = false;
#endif

        public void Start()
        {
            // Assert.AreEqual(this.currentState, State.INACTIVE, "can only start inactive nodes, tried to start: " + this.Name + "! PATH: " + GetPath());
            Assert.AreEqual(currentState, State.Inactive, $"can only start inactive nodes - {Name} is {currentState}");

#if DEBUG
            RootNode.TotalNumStartCalls++;
            this.DebugNumStartCalls++;
#endif
            currentState = State.Active;
            DoStart();
        }

        /// <summary>
        /// TODO: Rename to "Cancel" in next API-Incompatible version
        /// </summary>
        public void Stop()
        {
            // Assert.AreEqual(this.currentState, State.ACTIVE, "can only stop active nodes, tried to stop " + this.Name + "! PATH: " + GetPath());
            Assert.AreEqual(currentState, State.Active, $"can only stop active nodes, tried to stop {Name} - {currentState}");
            currentState = State.StopRequested;
#if DEBUG
            RootNode.TotalNumStopCalls++;
            this.DebugLastStopRequestAt = 0;
            this.DebugNumStopCalls++;
#endif
            DoStop();
        }

        protected virtual void DoStart()
        {

        }

        protected virtual void DoStop()
        {

        }


        /// THIS ABSOLUTLY HAS TO BE THE LAST CALL IN YOUR FUNCTION, NEVER MODIFY
        /// ANY STATE AFTER CALLING Stopped !!!!
        protected virtual void Stopped(bool success)
        {
            // Assert.AreNotEqual(this.currentState, State.INACTIVE, "The Node " + this + " called 'Stopped' while in state INACTIVE, something is wrong! PATH: " + GetPath());
            Assert.AreNotEqual(currentState, State.Inactive, "Called 'Stopped' while in state INACTIVE, something is wrong!");
            currentState = State.Inactive;
#if DEBUG
            RootNode.TotalNumStoppedCalls++;
            this.DebugNumStoppedCalls++;
            this.DebugLastStoppedAt = 0;
            DebugLastResult = success;
            if(success)
	            DebugLastSuccessAt = 0;
            else
	            DebugLastFailureAt = 0;
            
#endif
	        ParentNode?.ChildStopped(this, success);
        }

        public virtual void ParentCompositeStopped(Composite composite)
        {
            DoParentCompositeStopped(composite);
        }

        /// THIS IS CALLED WHILE YOU ARE INACTIVE, IT's MEANT FOR DECORATORS TO REMOVE ANY PENDING
        /// OBSERVERS
        protected virtual void DoParentCompositeStopped(Composite composite)
        {
            /// be careful with this!
        }

        // public Composite ParentComposite
        // {
        //     get
        //     {
        //         if (ParentNode != null && !(ParentNode is Composite))
        //         {
        //             return ParentNode.ParentComposite;
        //         }
        //         else
        //         {
        //             return ParentNode as Composite;
        //         }
        //     }
        // }

        public override string ToString()
        {
            return !string.IsNullOrEmpty(Label) ? (Name + "{"+Label+"}") : Name;
        }

        protected string GetPath()
        {
	        return ParentNode != null ? $"{ParentNode.GetPath()}/{Name}" : Name;
        }
    }
}