Difference between revisions of "MBT Trees"

From Nexus Mods Wiki
Jump to: navigation, search
Line 243: Line 243:
 
As a result, any atomic context can contain only non-timed nodes.<br/> <br/> <span style="color:black"><span style="background:#ff9900">Warning: Nothing prevents from creating an infinite loop in atomic context. That would result in a globally stalled AI. Nothing prevents you from creating a logic that takes too long to execute under the atomic context. That might result in weird AI behavior where, for example, a save is created several seconds after request and the AI stalls for a short period of time. The save may also get rejected due to timeout.</span></span>
 
As a result, any atomic context can contain only non-timed nodes.<br/> <br/> <span style="color:black"><span style="background:#ff9900">Warning: Nothing prevents from creating an infinite loop in atomic context. That would result in a globally stalled AI. Nothing prevents you from creating a logic that takes too long to execute under the atomic context. That might result in weird AI behavior where, for example, a save is created several seconds after request and the AI stalls for a short period of time. The save may also get rejected due to timeout.</span></span>
  
 
+
&nbsp;
  
 
Atomic context is settable in some nodes as their parameter.<br/> [[File:MBT Trees Documentation Image9.png|thumb|none|417x152px|ProcessMessage node with Atomic=true will execute its subtree atomically every time it receives a message]]
 
Atomic context is settable in some nodes as their parameter.<br/> [[File:MBT Trees Documentation Image9.png|thumb|none|417x152px|ProcessMessage node with Atomic=true will execute its subtree atomically every time it receives a message]]
Line 329: Line 329:
 
*Semaphore = A reverse to Synchronize. Only allows the given number of Semaphore nodes of the same name on the same scope to have their child trees simultaneously executed. All other instances of the same Semaphores must wait for their turn.  
 
*Semaphore = A reverse to Synchronize. Only allows the given number of Semaphore nodes of the same name on the same scope to have their child trees simultaneously executed. All other instances of the same Semaphores must wait for their turn.  
  
[[Category:Documentation]]
+
[[Category:Documentation]] [[Category:Kingdom Come Deliverance]]

Revision as of 16:09, 15 November 2019

Introduction

An example of a larger MBT tree

MBT trees are the basis of AI scripting. Each Smart entity can have a brain which can execute these trees. Each entity holds its own instance of the brain with its unique state.

Trees are composed of nodes and edges. A node represents a singular action (place item), or a function (calculate something), or logic flow control utility (loop 5 times, ifElse condition). Edges are the lines which define relationships between nodes. MBTs follow a strictly hierarchical tree structure.

  • You cannot create loops with edges. Loops are created with loop nodes which repeat execution of their subtree.
  • It is always which node is higher, or lower, or a sibling in the hierarchy.
  • When a node is executed, the next sibling-node or its child-node is next in line
  • Trees can include other trees.
  • Flow control nodes also allow parallel execution

Definition and references to trees (brain trees, subbrain trees, behavior trees) are saved in DB.

Implementation of trees are saved in XML files under ../Libs/AI.

<BehaviorTrees>
  <BehaviorTree name="dude_prox">
    <Variables>
      <Variable .../>
    </Variables>
    <Root OneTimeOnly=""false"" FailState=""Recoverable"" saveVersion="2">
        -- nodes
    </Root>
    <ForestContainer>
        -- unplugged nodes
    <ForestContainer />
    <EditorData>
        -- additional data for the XGEN editor
    </EditorData>
  </BehaviorTree>
</BehaviorTrees>

 

Variables

Each tree can use any number of variables. The variables are used for storing data and setting up parameters of the nodes.

With the (only?) exception of mailbox creation editor window all tools provide type name autocomplete via Tab key or Ctrl+Tab combination.

Types

You can use any standard variables (includes basic CryEngine variables) or create a compound custom types.

Standard variables

(liste are only the relevant types)

int  
string  
float  
double  
bool  
common:wuid A type added by Warhorse. Any instance of any object (a specific NPC, a specific animation played by specific NPC etc.) has a unique WUID. WUIDs are assigned dynamically in run-time. As opposed to standard GUID (equivalent to uuids in DB), which are static and known beforehand.
common:alignment  
common:behaviorInfo Holds info about called behavior.
common:context  
common:quat  
common:quat  
common:senderInfo:senderInfoBehavior Holds info about a sender of a message
common:transform  
common:vec3  
additionalMoveParams Used in Move node
diceEvent  
haltingReason  
information  
informationDiff  
informationTagChanged  
npcDeath  
pathFindingParams  
perceptionInfo  

Custom types

Custom types are defined in ..\Data\Libs\AI\TypeDefinitions.xml

Warning: Only one mode can modify this file at a time. Otherwise you will have to merge the changes on your own.

They are compound types akin to C’s struct. A type can have any number of members. Each member must be a standard variable type or other compound type.

A type can have any number of subtypes. Subtype is then referred to as type:subtype

Enumerated types

Just like the other variables enums are also either standard or custom defined in TypeDefitions.xml.

Enum variable type name follows syntax enum:enumType

Variables scope

Each tree has its own scope, therefore if a tree includes another tree which includes another, there are several nested scopes as a result. Variables are accessible from the tree that declares them and from any included tree. Exception to this rule is a situation where more nested trees declare the same variable. In that case the included tree can access only its own variable.

There are also some globally accessible variables created by code (Example: _player which holds the reference to player WUID).

Some, typically short-lived, variables are also created by code dynamically during runtime. (Example: __from and __to temporarily hold info about current parent and child object names during execution of GraphSearch node)

Brains can have several subbrains and/or behaviors. In that case brain variables are global in terms of the brain – they are accessible from any tree that runs on this brain.

Variable properties

Variable creation/edit dialog
List of variables with FW/P indications

Forward Declaration (FW)
A variable can also be forward-declared. That tells the AI engine that when this tree is started it expects the variable of this type and name to already exist in the parent tree. If the variable does not exist it results in runtime error.

Persistent (P)

Persistent variable value is saved in save file. Normal variables are not stored on game save.

Value

Initial value of the standard variables can be defined in the “Value” box in variable creation/edit window. Initial values of compound variables can only be defined in TypeDefinitions.xml.

Naming conventions

varName = ordinary variable

t_varName = a variable that is intended for use in some included tree

b_varName = brain variables

__varName = a predefined variable that is created by the code. They may be local, temporary local, or static global.

global: __player, __null, __land, __version, __playerDog
local: __area, __object
local temporary: __from, __to

Nodes

All MBT trees consist of node connected by edges. While nodes define an operation or flow control, edges define the order in which the nodes are executed.

States

All nodes go through basic sequence of states (None -> Running -> Success/Fail) and can enter several more additional states (Halt, Suspend) when an interruption occurs. In run-time (and in AI replays) the nodes are colored according to their states. You can also add breakpoints to specific states of a node (right-click on the node header).

  • None - gray
  • Running - blue
  • Success - green
  • Fail - red
  • Suspended - violet
  • Halting - yellow
  • Suspending – brown

Red also often signalizes a node (or, in this case, a whole subtree) which was killed mid-execution by some higher-order logic. These fails are only propagated as far as the node which caused this subtree to be killed and don’t cause the fail to be propagate higher (see more in Flow Control).
An example: Two subtrees are executed in parallel (Parallel node with 2 childs). The Parallel node is set to end (succeeds) if Any child succeeds, ‘Child 0’ is some eternal loop (e.g. “move in circles”). ‘Child 1’ waits for a signal (e.g. a message) and then instantly succeeds. When that happens all currently executed nodes in Child 0 fail BUT the Parallel node succeeds and a SUCCESS is propagated to the parent node of the Parallel node.

Basic states:

None = a node was not executed at all or it was executed and its final state was propagated further. A grey node which was executed can be told apart by yellow edge leading to it.
 
The rightmost sequence was executed, or attempted, twice. This tree section is not currently being executed. Note: You cannot know, from this image only, if the node failed or succeeded.
 

Running = a node is initialized and its validity is evaluated. If the evaluation fails it results in error and node failure. If evaluation passes the node’s function is executed. Some nodes can go through several internal updates (you can add breakpoints to these). Some nodes are executed instantly (math operation), some may remain in execution for a longer time period (Move, which tells NPC to reach a point).

The Move node is currently being executed. The NPC is trying to reach the destination stored in variable t_destination. The IfElseCondition node is also being executed as it waits for the result of its subtree (the Move node in this case). Depending on the result the IfElse node will propagate fail or success state to its parent node.

Success = the execution finished and did NOT result in error/fail. The node remains green as long as the subsection of the tree it belongs to (defined by a node which absorbs or propagates the subtree success/fail) is being executed. In other words, it remains green until the whole subtree it belongs to can be, in theory, executed again.

Nodes SetQuest and AddLink already succeeded. Execution is currently paused on InstantSendMessage node due to breakpoint (indicated by the red dot by the Input port). The IfCondition and Sequence nodes are in execution as they wat for the success/fail result in their respective subtrees.

Fail/Error = the node is invalid and produced an error or purposefully produced a fail (see Flow Control). An error usually comes with some console message but internally it only produces a fail and is thus indistinguishable from a purposeful node failure.

This subtree created a fail state on purpose (see more in Flow Control). The fail was propagated to the Selector node which is designed to work this way: If a fail happens in its subtree it attempts to execute the next subtree.

 

Types and classes

This section is not an extensive list of all or most nodes. It provides basic examples and aim to explain the core principles of tree scripting.

Timed vs. instant

Timed nodes are nodes which don’t get executed instantly but rather take more time or are executed continually. All of these nodes are indicated by the clock icon in the node header.

Wait node is a typical example. It waits for the specified time duration and then succeeds. -1, in this case causes eternal Wait. This is typically used to “put a subtree to sleep” and a waiting Wait node takes next to no toll on the performance

Timed nodes cannot be used under Atomic context. That would result in a runtime error.

Instant variants of timed nodes

Some timed nodes also have their instant counterparts (the word Instant is in the name) which allows to use the same or limited version of the same node in the atomic context. Example:

  • MakeMeIdle = Attempts an interruption of any currently running animation and a blend into the idle animation. This takes time to do it nicely.
  • InstantMakeMeIdle = Does the same in much uglier whiplash-inducing way but instantly.

Atomic context

Atomic context ensures that the whole set of nodes under said atomic context, act similarly to a single node. That is, all of them or none must be executed no matter what.

Atomic context is used for:

  • preventing a save request to create a save file before the whle atomic part is executed. When a save request is created the AI system attempts to pause all all AI, that is all trees, and save the brain state.
  • Preventing a “tree-killing from above” to kill the atomic part mid-execution.

As a result, any atomic context can contain only non-timed nodes.

Warning: Nothing prevents from creating an infinite loop in atomic context. That would result in a globally stalled AI. Nothing prevents you from creating a logic that takes too long to execute under the atomic context. That might result in weird AI behavior where, for example, a save is created several seconds after request and the AI stalls for a short period of time. The save may also get rejected due to timeout.

 

Atomic context is settable in some nodes as their parameter.
ProcessMessage node with Atomic=true will execute its subtree atomically every time it receives a message

 

Or it can be added in-line by using AtomicDecorator node.

Atomic decorator added to ensure atomic execution of the entire sub-Sequence.

Warning: Although it seems that everything will be atomically executed when the Semaphore opens its subtree a save can be created during the micro-window presented by the “dangerous gap” between the Semaphore opening and AtomicDecorator initialization. It such cases tree script must support proper reconstruction of the Semaphore after loading the save.

Gates

Nodes of the type Gate (sometimes caller barrier) wait until the condition is met. Then they open their child tree.

Decorators/Wrappers

Decorators apply their effects as long as their child is in execution. Examples:

  • AtomicDecorator = defines that child tree must be viewed as a singular operation by the AI system -All of it is executed despite any interruptions. The interruptions must wait until atomic tree is finished.
  • LuaWrapper = Executes one LUA code on INIT and the other LUA on when the child tree ends.
  • BuffDecorator = the NPC has the defined buff as long as the child is in execution. Must not use persistent buffs.
  • TagIt = creates a temporary self-link with a given link name on the given entity. The link is deleted when TagIt succeeds.

Animations

All nodes which result in some animation have one thing in common - the animation continues even if the node already succeeded. The animation may continue until forceful stop, or if another animation is played (PlayAnimation interrupts walking)

  • PlayAnimation = starts playing the given animation and immediately succeeds.
  • AnimationEndWait = succeeds if the animation instance stored in the variable has ended. Often used right after PlayAnimation node.
  • StopAnimation/AbbrotAllAnimation/MakeMeIdle = all slightly different ways of stopping a running animation
  • Move = Tell the NPC to reach the destination. May succeed earlier, in which case the NPC continues walking as long as another Move or animation node is started.
  • MoveAndAct = A combination of Move and PlayAnimation which allows smoother transition between Move and Animation.

Link nodes

Link systems is one of the critical parts of scripting. Please read more in the in the system’s documentation

  • GraphSearch + Filter node = allows creation of extensive queries for the link system and stores the returned set of entities in your variables
  • AddLink = Creates a link from entity to entity
  • RemoveLink = Removes the given link. Only dynamically created links are removable
  • LinkOperationBarrier = A gate type node (despite name). Opens its child when the given operation on the given link on the given entities is executed – a link is added, removed, etc.

LOD nodes

The AI system automatically attempts to turn an NPC entity into a low-profile (LOD) mode when the entity is far from the player. This allows optimization. The other mode is called “Detail”. You can define what sort of light-weight logic the entity executes when it’s switched into LOD mode. An NPC CANNOT attempt to execute PlayAnimation when it’s in the LOD mode – this would result in error. Therefore, all PlayAnimation nodes most be, in some way, protected by a LOD node.

  • LODGuardian = automatically “kills” the Detail subtree and switches to the LOD subtree when LOD mode is attempted. PlayAnimation cannot be placed in LOD subtree.
  • LODCheck = a simple if-else type check with implicit LODLock.
  • LODLock = A decorator type node. Prevents switching into LOD mode as long a the child is being executed.

Flow Control

The goal of the tree scripting is to execute nodes in the right order and control that order as effectively as possible. Edges define which node is followed by another but are only applicable if there is a child port in the parent node. Most “operational” nodes (that is nodes which execute some function) do not feature a child out-port and thus cannot have a subtree or sub node. Standard programs like C are implicitly sequential (one line of code is executed after another) and provide a set of statements (if-else, while, switch etc.) to help you alter this primitive linear execution. The MBT trees lack these statements by necessity and instead provide you with a set of nodes (unofficially called Flow Control nodes) which typically do not execute any procedure but instead define how their subtrees are executed. They also propagate or react to fail/success states. They can produce their own successes and fails.

Flow control nodes

Basic:

  • Sequence = executes child subtrees in sequence
  • Parallel = executes child subtrees in parallel. Can success if Any or All subtrees succeed
  • Success = produces success. Cannot fail
  • Fail = produces fail. Cannot succeed.

Decisions:

  • IfCondition = executes child if true.
  • IfElseCondition = self-explanatory
  • Switch = Only accepts IfCondition nodes as child nodes. Works the same way as C language switch-case.
  • ContinuousSwitch = evaluates all child nodes with every AI tick and thus can kill whichever child is already in execution. Demands performance.
  • Selector = attempts successful execution of its child subtree. If a subtree fails, the next child is attempted.
  • LuaGate = Similar to IfElse. Decides based on the return value of the LUA code within.

Loops:

  • Loop = repeats child indefinitely or a given number of times
    LoopUntil = loops child until defined state occurs
  • While = loops as long as the condition is true. Can consume (stop propagation of) child failure (unlike Loop)
  • For = similar to C-like for cycle
  • ForEach = specialized For. Self-explanatory

Advanced:

  • Synchronize = waits for a given number of other Synchronize nodes of the same name on the same scope. Then all of these Synchronize nodes execute their child trees.
  • Semaphore = A reverse to Synchronize. Only allows the given number of Semaphore nodes of the same name on the same scope to have their child trees simultaneously executed. All other instances of the same Semaphores must wait for their turn.