- 1 Introduction
- 2 Variables
- 3 Nodes
- 4 Flow Control
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>
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.
You can use any standard variables (includes basic CryEngine variables) or create a compound custom types.
(liste are only the relevant types)
|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:behaviorInfo||Holds info about called behavior.|
|common:senderInfo:senderInfoBehavior||Holds info about a sender of a message|
|additionalMoveParams||Used in Move node|
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
Just like the other variables enums are also either standard or custom defined in TypeDefitions.xml.
Enum variable type name follows syntax enum:enumType
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.
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 variable value is saved in save file. Normal variables are not stored on game save.
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.
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
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.
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.
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).
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.
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.
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.
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 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.
Or it can be added in-line by using AtomicDecorator node.
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.
Nodes of the type Gate (sometimes caller barrier) wait until the condition is met. Then they open their child tree.
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.
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 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.
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.
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
- 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.
- 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.
- 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
- 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.
|Kingdom Come: Deliverance: Forum | Before you start | Tutorials & Instructions | Basic Mods | Tools | Documentation | Glossary | EULA|