using System; namespace Quantum { public unsafe abstract partial class BTNode { [BotSDKHidden] public String Label; [BotSDKHidden] public Int32 Id; [NonSerialized] internal BTNode Parent; [NonSerialized] internal Int32 ParentIndex; public abstract BTNodeType NodeType { get; } /// /// Called once, for every Node, when the BT is being initialized /// public virtual void Init(Frame frame, AIBlackboardComponent* blackboard, BTAgent* agent) { var statusList = frame.ResolveList(agent->NodesStatus); statusList.Add(0); } // -- STATUS -- public BTStatus GetStatus(Frame frame, BTAgent* agent) { var nodesAndStatus = frame.ResolveList(agent->NodesStatus); return (BTStatus)nodesAndStatus[Id]; } public void SetStatus(Frame frame, BTStatus status, BTAgent* agent) { var nodesAndStatus = frame.ResolveList(agent->NodesStatus); nodesAndStatus[Id] = (Byte)status; } /// /// Called whenever the BT execution includes this node as part of the current context /// /// public virtual void OnEnter(BTParams btParams) { } public virtual void OnEnterRunning(BTParams btParams) { } /// /// Called when traversing the tree upwards and the node is already finished with its job. /// Used by Composites and Leafs to remove their Services from the list of active services /// as it is not anymore part of the current subtree. /// Dynamic Composites also remove themselves /// /// public virtual void OnExit(BTParams btParams) { } public virtual void OnAbort(BTParams btParams) { } /// /// Called when getting out of a sub-branch and this node is being discarded /// /// public unsafe virtual void OnReset(BTParams btParams) { SetStatus(btParams.Frame, BTStatus.Inactive, btParams.Agent); } public void EvaluateAbortNode(BTParams btParams) { if (btParams.Agent->AbortNodeId == Id) { btParams.Agent->AbortNodeId = 0; } } public BTStatus RunUpdate(BTParams btParams, bool continuingAbort = false) { var oldStatus = GetStatus(btParams.Frame, btParams.Agent); if (oldStatus == BTStatus.Success || oldStatus == BTStatus.Failure) { return oldStatus; } if (oldStatus == BTStatus.Abort) { if (btParams.Agent->IsAborting == true) { EvaluateAbortNode(btParams); } return oldStatus; } // If this node was inactive, this means that we're entering on it for the first time, so we call OnEnter // An exception from this rule is when we chose this node to continue an abort process. In that case, // we already executed OnEnter before, so we don't repeat it if (oldStatus == BTStatus.Inactive && continuingAbort == false) { OnEnter(btParams); } var newStatus = BTStatus.Failure; try { newStatus = OnUpdate(btParams); if (btParams.Agent->IsAborting) { newStatus = BTStatus.Abort; } // Used for debugging purposes if (newStatus == BTStatus.Success) { BTManager.OnNodeSuccess?.Invoke(btParams.Entity, Guid.Value); BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); } if (newStatus == BTStatus.Failure) { BTManager.OnNodeFailure?.Invoke(btParams.Entity, Guid.Value); BTManager.OnNodeExit?.Invoke(btParams.Entity, Guid.Value); } } catch (Exception e) { Log.Error("Exception in Behaviour Tree node '{0}' ({1}) - setting node status to Failure", Label, Guid); Log.Exception(e); } SetStatus(btParams.Frame, newStatus, btParams.Agent); if ((newStatus == BTStatus.Running || newStatus == BTStatus.Success) && (oldStatus == BTStatus.Failure || oldStatus == BTStatus.Inactive)) { OnEnterRunning(btParams); } if (newStatus == BTStatus.Running && NodeType == BTNodeType.Leaf) { // If we are a leaf, we can store the current node // We know that there has only one leaf node running at any time, no parallel branches possible // The Run() method also return a tuple btParams.Agent->Current = this; } return newStatus; } /// /// Used by Decorators to evaluate if a condition succeeds or not. /// Upon success, allow the flow to continue. /// Upon failure, blocks the execution so another path is taken /// /// /// public virtual Boolean DryRun(BTParams btParams) { return false; } public virtual Boolean OnDynamicRun(BTParams btParams) { return true; } /// /// Called every tick while this Node is part of the current sub-tree. /// Returning "Success/Failure" will make the tree continue its execution. /// Returning "Running" will store this Node as the Current Node and re-execute it on the next frame /// unless something else interrputs /// /// /// protected abstract BTStatus OnUpdate(BTParams btParams); } }