Formats/Behavior tree
Behavior trees file format is game-specific (mods may modify it).
Contents
- 1 Introduction
- 2 Syntax Reference
- 2.1 Keywords
- 2.2 The behavior keyword
- 2.3 The action keyword
- 2.4 The sequence keyword
- 2.5 The selector keyword
- 2.6 The fallback keyword
- 2.7 ⚠️ Discouraged The concurrent keyword
- 2.8 The decorator keyword
- 2.9 The return decorator
- 2.10 The invert decorator
- 2.11 The timer keyword
- 2.12 The condition keyword
- 2.13 Operators
- 2.14 Functions
- 2.15 Pre-defined symbols
- 2.16 Notes
Introduction
Bot behaviors are decided individually, at each "frame" (read: server's main loop passage), starting from a root node. Evaluation stops at a node which returns STATUS_RUNNING, or when the root node returns success or failure. An overview can be found in the Wikipedia article.
Syntax Reference
Behavior trees use a specific syntax, which obey following rules (guessed from actual implementation and some C++ readings):
- Each node or leaf returns either
STATUS_SUCCESS,STATUS_FAILUREorSTATUS_RUNNING. - whitespace has no semantic meaning
- comments (C and C++-style) can either:
- start with
//and go to the end of line - start with
/*and go to next*/, including out of current line. These do not nest.
- start with
- actions and conditions taking parameters must enclose those withing parentheses, separated with commas (i.e.
roamInRadius( E_H_REACTOR, 500 )) - if an action or a condition does not need parameters, parentheses are optional
- strings start and end with a double quote
" - values can't be stored or modified (additions, subtractions, multiplications, etc)
- no user-defined function (but files can be included, and C++ functions can be written in the game logic and be used in the behavior tree)
- each possible path should (TODO: verify, even if I fail to understand why one would break that willingly) end by one or more action
Note:
It is not possible to do mathematical operations such as additions, multiplications, divisions, ...
Keywords
There are several keywords:
- behavior
- action
- sequence
- selector
- fallback
- concurrent
- decorator
- condition
The behavior keyword
behavior NAME
Include named behavior at current place. This does not make a new copy of the referenced tree, so if you include the same behavior more than once, node-specific state (e.g. timers) will be shared between the includes.
The action keyword
action NAME
action NAME()
action NAME( param )
action NAME( param1, param2, ..., paramN )
Leaves of the tree, they trigger an action from the bot.
List of actions
Titles provide a link to more detailed pages. TODO: have something more time-resilient (one page per call, with it's changelog, maybe?)
0.53.1
- activateUpgrade
- aimAtGoal
- alternateStrafe
- buy
- changeGoal
- classDodge
- deactivateUpgrade
- equip
- evolve
- evolveTo
- fight
- fireWeapon
- flee
- gesture
- heal
- jump
- moveInDir
- moveTo
- moveToGoal
- repair
- resetStuckTime
- roam
- roamInRadius
- rush
- say
- strafeDodge
- suicide
- teleport
Creating new actions
Actions are exported in the AIActionMap_s AIActions[] C array.
Actions always return a status.
Actions can take a variable number of parameters (they have minimum and maximum parameter numbers).
Parameter types are not described by API.
The sequence keyword
sequence
{
NODE_1
NODE_2
...
NODE_N
}
Sequential nodes evaluate each children one after the other, as long as they return STATUS_SUCCESS.
Return last child's status.
- Sequence breaks if a child returns
STATUS_RUNNING - Starts at last
STATUS_RUNNING, if any. Nodes that completed successfully in a previous frame are not rerun, as long as the sequence is running continually.
The selector keyword
selector
{
NODE_1
NODE_2
...
NODE_N
}
Selector nodes evaluate each children one after the other, as long as they return STATUS_FAILURE.
Return last child's status.
Unlike sequence, this node is stateless: it does not restart from the last STATUS_RUNNING.
The fallback keyword
fallback
{
NODE_1
NODE_2
...
NODE_N
}
Fallback nodes start by evaluating the first children, and switch to the next one if a child returns STATUS_FAILURE. It will continue from the last STATUS_RUNNING child.
Return last child's status.
Difference with sequence:
- Sequence switch to the next child on
STATUS_SUCCESS, while fallback switch to the next child on code>STATUS_SUCCESS</code>
Difference with selector:
- Starts at last
STATUS_RUNNING, if any
⚠️ Discouraged The concurrent keyword
This is a very niche node type, for experts only! As of 0.55.1, the return value is wrong: it may return STATUS_SUCCESS while there are running nodes (should be fixed for 0.56).
concurrent
{
NODE_1
NODE_2
...
NODE_N
}
Concurrent nodes evaluate each children until one returns STATUS_FAILURE. Along with decorator return, it is one of two ways that BT evaluation can continue after a node evaluates to STATUS_RUNNING. It is most similar to sequence, except the different treatment of running nodes. It is much less useful than it sounds first, because the behavior tree can only remember one long-running action at a time. If you have two long-running actions in a concurrent node, they will NOT work correctly, constantly forgetting their goals. So usage of concurrent is limited to combining one-shot actions with a single long-running one. freem discovered the following use case:
concurrent
{
action fight
decorator return( STATUS_SUCCESS )
{
condition goalType == ET_BUILDABLE && goalTeam == TEAM_ALIENS && inAttackRange( E_GOAL )
{
action moveInDir( MOVE_BACKWARD )
}
}
}
The decorator keyword
decorator TYPE( VALUE )
{
NODE_1
NODE_2
...
NODE_N
}
Decorator nodes alters their children's behavior. Decorator types:
- return
- invert
- timer
The return decorator
Force children nodes to return a specific value
The invert decorator
An invert node will negate the return value of its node, STATUS_SUCCESS<code> is turned into <code>STATUS_FAILURE and STATUS_FAILURE is turned into STATUS_SUCCESS. STATUS_RUNNING is unaffected.
The timer keyword
On encountering a timer( N ) node, STATUS_FAILURE is immediately returned if the timer's child node has already returned STATUS_FAILURE within the last N milliseconds.
The node may run more often than every N milliseconds if it keeps returning success.
The condition keyword
condition EXPRESSION
condition EXPRESSION
{
NODE
}
The first form immediately returns EXPRESSION as its status: STATUS_SUCCESS for true and STATUS_FAILURE for false.
The second form returns STATUS_FAILURE if the expression is false, or otherwise the child's status. Note that EXPRESSION is re-evaluated every frame. If it becomes false at any time during execution of the child node, the child subtree aborts and the condition node returns failure.
The idiom
sequence {
condition EXPRESSION
action myLongRunningAction
}
is used to check a condition once before starting an action, but in subsequent continue doing the action without re-evaluating the condition.
Note:
- only executes a single child.
- EXPRESSION can include function calls.
Operators
Operators are listed in the AIOpMap_s conditionOps[] array.
operators sorted by precedence (1st have higher priority):
- "!"
- "<"
- "<="
- ">"
- ">="
- "=="
- "!="
- "&&"
- "||"
Functions
Return value is "boxed" (AIBox<T>()) in the C++ code.
Conditions are exported in the AIConditionMap_s conditionFuncs[] array.
List of functions (as of 0.53.1)
- alertedToEnemy
- aliveTime
- baseRushScore
- buildingIsDamaged
- canEvolveTo
- class
- cvar
- directPathTo
- distanceTo
- goalBuildingType
- goalIsDead
- goalTeam
- goalType
- haveUpgrade
- haveWeapon
- healScore
- inAttackRange
- isVisible
- matchTime
- momentum
- percentAmmo
- percentHealth
- random
- skill
- stuckTime
- team
- teamateHasWeapon
- weapon
Pre-defined symbols
Some values are pre-defined and usable as action's or function's parameters.
Those are exported by calling a macro named 'D' (yes, I know). List of exported symbols:
- human upgrades (including medkit)
- human weapons (excluding blaster)
- team names (aliens, humans, and none)
- alien buildings
- human buildings
-
E_GOAL -
E_ENEMY -
E_DAMAGEDBUILDING -
E_SELF - classes (human ones, alien ones, and
PCL_NONE) - moves (forward, backward, right, left)
- some say commands (all, team, area, area_team)
- task/check status names (running, success, failure)
Notes
Parameters needs to be (un)wrapped in C++ before being accessed.
Parameters can be of the following (C) types:
- float
- int (probably better assume 32 bit signed integers)
- string (probably better assume null-terminated)
To provide a value to an action or check, the C++ code must UnBox (AIUnBox<T>()) it.
