User:Gazyi/CS:GO Bot Behavior Trees
Remember to check for any notes left by the tagger at this article's talk page.
In Counter-Strike: Global Offensive, bots can be given a Behavior Tree to follow. A behavior tree dictates how the bot senses things (vision, hearing, damage sensing), moves, attacks, and does other actions.
They use Valve's proprietary KeyValues3 format (.kv3
), which is a text-based format with somewhat strict syntax. They typically use the bt_
prefix, to signify that file is a Behavior Tree. They are stored in csgo/scripts/ai
. One can create and use his own behavior trees, see below.
Officially, behavior trees are used for the game modes Co-op Strike, Guardian and Deathmatch.
Behavior trees were first added to the game on September 16, 2019. Since September 1, 2020, behavior trees can be packed into BSP files.
Using a Behavior Tree
The ConVar mp_bot_ai_bt
can assign a behavior tree to all bots on the server. Its value is a string representing the path to a .kv3 file, starting from csgo/
. For example, the following line is used for official Deathmatch and can be found in csgo/cfg/gamemode_deathmatch.cfg
:
mp_bot_ai_bt "scripts/ai/deathmatch/bt_default.kv3"
For the Co-op game mode, info_enemy_terrorist_spawn entities have the KeyValue behavior_tree_file
that can be used to specify a behavior tree only for bots spawning at this entity.
Related Console Commands
ConVar | Default Value | Description |
---|---|---|
cv_bot_ai_bt_debug_target
|
-1 | Draw the behavior tree of the given bot. |
cv_bot_ai_bt_hiding_spot_show
|
0 | Draw hiding spots. |
cv_bot_ai_bt_moveto_show_next_hiding_spot
|
0 | Draw the hiding spot the bot will check next. |
mp_bot_ai_bt
|
"" | Use the specified behavior tree file to drive the bot behavior. |
mp_bot_ai_bt_clear_cache
|
ConCommand | Clears the cache for behavior tree files. |
Format
The first line of a .kv3
file is always a header specifying the KV3 version. For CS:GO, use this header:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
Now define general bot configuration. Use "config" key, which should contain full path to that file, including file extension:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
config = "scripts/ai/<path to bt_config>/<bt_config_name>.kv3"
}
Behavior trees always start with root. Define it with "root" key. Every behavior node should be inside it.
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
config = "scripts/ai/<path to bt_config>/<bt_config_name>.kv3"
root =
{
<Behavior Node>
}
}
Behavior Node format depends on its type and parameters:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
config = "scripts/ai/<path to bt_config>/<bt_config_name>.kv3"
root =
{
type = <Node Type>
<parameter> = <value>
...
child =
{
<Behavior Node>
}
}
}
Some nodes allows to define new global variables. Their input name should contain both double and single quotes:
type = "action_set_global_counter"
input_name = "'Test'" // Note those quotation marks.
input_value = 1
Configuration
- Explain parameters in bt_config
These parameters define general bot reaction and combat behavior for every bot skill ( low, fair, normal, tough, hard, very_hard, expert, default ), like a botprofile.db.
Default format is:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
<skill level>
{
<Variables from table below>
attack = <Attack table>
}
}
List of parameters
Variable | Description |
---|---|
aim_target_acquisition_lerp_time
|
|
aim_target_acquisition_lerp_time_deviation
|
|
aim_target_acquisition_angle_penalty
|
|
aim_target_acquisition_angle_penalty_deviation
|
|
aim_target_acquisition_angle_penalty_reduction_ratio
|
|
aim_target_acquisition_angle_tolerance
|
|
aim_target_acquisition_angle_lerp_bias
|
|
aim_target_tracking_lerp_time
|
|
aim_target_tracking_lerp_time_deviation
|
|
aim_target_tracking_focus_interval
|
|
aim_target_tracking_focus_interval_deviation
|
|
aim_target_tracking_angle_lerp_bias
|
|
aim_new_target_angle_tolerance
|
|
aim_max_duration
|
|
aim_max_duration_deviation
|
|
aim_punch_angle_reaction_chance
|
|
look_around_awareness_yaw_range
|
Max angle to horizontal rotate viewcamera from this viewpoint. |
look_around_awareness_pitch_range
|
Max angle to vertical rotate viewcamera from this viewpoint. |
look_around_focus_interval
|
Delay between next viewcamera rotation. |
look_around_focus_interval_deviation
|
|
look_around_lerp_time
|
Time to rotate viewcamera to next position. |
look_around_lerp_time_deviation
|
|
look_around_lerp_bias
|
Smooths viewcamera movement. |
reaction_time
|
Delay before reaction on event. |
combat_crouch_chance
|
|
combat_dodge_command_duration
|
|
combat_dodge_command_duration_deviation
|
Attack Table
Attack table defines handling of different weapon types.
Example:
attack =
[
// duration, duration deviation, cadence, cooldown, cooldown deviation
// KNIFE PISTOL SUBMACHINEGUN
[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ 0.95, 0.25, 0.5, 0.15, 0.05 ],[ 0.42, 0.07, 0.0, 0.15, 0.05 ],
// RIFLE SHOTGUN SNIPER_RIFLE
[ 0.22, 0.07, 0.0, 0.45, 0.15 ],[ 0.3, 0.0, 0.0, 0.95, 0.25 ],[ 0.22, 0.07, 0.0, 2.0, 0.0 ],
// MACHINEGUN C4 TASER
[ 0.75, 0.15, 0.0, 0.3, 0.1 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ], [ -1.0, -1.0, -1.0, -1.0, -1.0 ],
// GRENADE EQUIPMENT STACKABLEITEM
[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ],
// FISTS BREACHCHARGE BUMPMINE
[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ],
// TABLET MELEE SHIELD
[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ],
// WEAPONTYPE_ZONE_REPULSOR UNKNOWN
[ -1.0, -1.0, -1.0, -1.0, -1.0 ],[ -1.0, -1.0, -1.0, -1.0, -1.0 ]
]
Nodes
This is a list of all nodes on November 9, 2021.
- Complete node parameters and descriptions, check for inaccuracies.
- Define variable types for parameters.
- Explain all parameters.
Combinators
Type | Parameters | Description |
---|---|---|
parallel
|
Executes all child functions in one script tick. | |
selector
|
Executes child functions as sequence until meeting child with true condition. | |
sequencer
|
Executes child functions as sequence until meeting child with false condition. | |
subtree
|
string file, string name
|
Executes behavior tree from file. |
Decorators
Type | Parameters | Description |
---|---|---|
decorator_bot_service
|
array memory_to_expire(variable? domain (GroupID or AllBots), flag key (ShortTermAttackMemory, ShortTermDamageMemory, ShortTermAreaDamageMemory, ShortTermInvestigateMemory, LongTermMemory, DamageThroughSmokeMemory, Threats), int time, int distance), array tagged_entities_to_expire, int_flag? basic_chatter_enable, vector? input_chatter_enemies, int chatter_outnumbered_threshold
|
Kinda main bot behavior function? Usually contains all subtree routines. |
decorator_buy_service
|
array output
|
Returns array of items that bot will buy, then executes function. |
decorator_dec_global_counter
|
variable input_name
|
Decreases input variable by 1, then executes function? |
decorator_find_utility_strat
|
vector input_location, int distance_threshold, vector output_location, vector output_angles, string output_weapon
|
Unused. |
decorator_game_event
|
Warning:Release Notes for 21/10/2021 say about it but it doesn't exist in code?
| |
decorator_hiding_spot_service
|
variable? domain (GroupID or AllBots), vector output_hiding_spot, int distance_threshold, int expiration_time
|
Returns hiding spot, then executes function? Usually located right after script root. |
decorator_invert
|
Unused. Valve scripts usually use negated = 1 flag in node parameters.
| |
decorator_maybe
|
float chance
|
Executes child functions with specified chance (1 - 100%, 0.7 - 70% etc). |
decorator_memory
|
vector? input, memory? output, string output_domain
|
Saves input in specified memory buffer? |
decorator_need_healing
|
int health_threshold
|
Executes child functions if bot health is lower that input threshold. |
decorator_picker_blocked_by_smoke
|
vector? input, int distance_threshold
|
Executes child functions if input position is behind smoke volume? |
decorator_picker_dedup
|
vector? input, memory? against, int distance_threshold
|
??? Only used in noise picker in Valve scripts. |
decorator_picker_grenade_type
|
array input, array types (EXPLOSIVE, FLASH, FIRE, DECOY, SMOKE, SENSOR, SNOWBALL)
|
Executes child functions on specified grenade types. |
decorator_picker_max_score
|
array input
|
??? |
decorator_picker_nearby
|
array input, int cutoff_distance
|
Executes child functions if input entities are within distance. |
decorator_picker_random_by_distance
|
array input, int distance_min, int distance_max
|
Executes child functions if random entity from input is within distance limits? |
decorator_picker_reaction_time
|
variable? input_domain (GroupID or AllBots), memory? input, vector? output
|
Executes child functions after delay, that depends of bot skill reaction time? |
decorator_picker_visible
|
array input, int_flag? check_fov
|
Executes function if input entity in LOS? |
decorator_picker_weight_as_distance
|
array input
|
??? Only used in noise picker in Valve scripts. |
decorator_random_approach_point
|
vector output
|
Returns a random point in navmesh. |
decorator_random_int
|
int min, int max, int output
|
Returns a random int the range of min to max. |
decorator_ranker_dist
|
array input
|
Sorts entities depending on distance? |
decorator_remove
|
variable? input_domain (GroupID or AllBots), memory? input, array? remove
|
Removes selected entities from input memory. |
decorator_remove_key
|
variable? input
|
Removes input variable from script scope. |
decorator_repeat
|
Executes child functions in loop. | |
decorator_route_service
|
array config (array routes, array strategies), vector? output_waypoint, string output_waypoint_name, string output_domain
|
Unused. |
decorator_run_once
|
int max_attempts = 1
|
Executes child functions limited amount of times? |
decorator_sensor
|
flag entity_type_filter (ALL, PLAYERS, HUMAN_PLAYERS, NOISE, DAMAGE, AREA_DAMAGE, CLASSNAME, GRENADE), function? shape(flag type (sensor_shape_fov, sensor_shape_sphere), int radius), flag team_filter (ANY, CT, TERRORIST, SAME, OPPOSITE, ENEMY), int_flag orphan_only, int_flag priority, string class_name, vector? output
|
Returns entity detected by sensor and executes child functions. |
decorator_set_barrier
|
Unused. | |
decorator_set_reaction_time
|
array? input
|
Executes child functions on input after delay, that depends of bot skill reaction time? |
decorator_succeed
|
Executes child functions if previous decorator is succeed? | |
decorator_tag_entity
|
vector? input, array? output, flag operation_type (BT_DECORATOR_TAG_ENTITY_CLEAR, BT_DECORATOR_TAG_ENTITY_SET), int expiration_time
|
??? Used in picking target tree in Valve scripts. |
decorator_tag_threshold
|
vector? entity_input, vector? tagged_entities_input, int amount, flag check_type (BT_DECORATOR_TAG_THRESHOLD_AT_MOST, BT_DECORATOR_TAG_THRESHOLD_AT_LEAST)
|
??? Used to determine if it's safe to do objective in Valve scripts? |
decorator_token_service
|
variable? domain, string output_token_name, string output_token_domain, function config(array tokens, array assignments)
|
In Guardian Mode: defines bot GroupID, then executes child functions. |
decorator_try_lock
|
variable? domain
|
Checks if other bot is already doing same function??? |
decorator_wait_success
|
int timeout
|
Executres child functions after delay? |
decorator_wait_success
|
int timeout
|
Executres child functions after delay? |
Conditions
Type | Parameters | Description |
---|---|---|
condition_barrier
|
Unused. | |
condition_distance_less
|
vector input, int distance_threshold_min, int distance_threshold_max
|
Returns true, if distance to input position is less than distance_threshold? |
condition_has_parachute
|
Returns true, if bot has parachute in inventory. | |
condition_inactive
|
array input(memory? key), int round_start_threshold_seconds, int sensor_inactivity_threshold_seconds
|
Returns true, if bot doesn't have any memory and sensor input within timing. |
condition_is_airborne
|
Returns true, if bot is mid-air. | |
condition_is_at_bomb_site
|
Returns true, if bot is at bombsite zone. | |
condition_is_empty
|
int_flag? global, any input
|
Returns true, if input variable is empty or doesn't exist. |
condition_is_equal
|
any source, any destination
|
Returns true, if source variable is equal to "destination" input. |
condition_is_greater
|
any source, any destination
|
Unused. Returns true, if source variable is greater than "destination" input. |
condition_is_greater_equal
|
any source, any destination
|
Unused. Returns true, if source variable is equal or greater than "destination" input. |
condition_is_inv_slot_empty
|
flag slot (KNIFE, PISTOL, RIFLE, GRENADES)
|
Returns true, if input equipment slot is empty. |
condition_is_less
|
any source, any destination
|
Unused. Returns true, if source variable is less than "destination" input. |
condition_is_less_equal
|
any source, any destination
|
Unused. Returns true, if source variable is equal or less than "destination" input. |
condition_is_reloading
|
Unused. Returns true, if active weapon is in reloading state. | |
condition_is_weapon_equipped
|
string weapon
|
Returns true, if bot has active weapon with input classname. |
condition_is_weapon_suitable
|
string weapon
|
Unused. |
condition_out_of_ammo
|
Returns true, if active weapon is out of ammo. | |
condition_owns_item
|
string weapon
|
Returns true, if bot has item with input classname. |
Actions
Type | Parameters | Description |
---|---|---|
action_acquire_items
|
array items, int_flag? remove_all_items
|
Equips bot with items from array. |
action_aim
|
vector input, int_flag? acquire_only, flag? ready
|
Rotates bot towards input point? |
action_aim_projectile
|
vector input, vector output
|
Calculates angles for throwing at input position? |
action_assign_guardian_loadout
|
string input_token_name, array assignments(string token, array loadout(array items, array wave_numbers, int wave_numbers_min, int wave_numbers_max, int weight))
|
In Guardian mode: Chooses bot spawn loadout. |
action_attack
|
vector input, flag? output (e.g. output = "Attacking"), flag? ready (e.g. ready = "AimReady")
|
Bot attack input entity? |
action_buy
|
Bot buys weapons. | |
action_choose_bomb_site_area
|
int input, navzone? output
|
Gets bombzone nav position from index. Supports convars. |
action_choose_guardian_bomb_plant_location
|
vector? output
|
Chooses plant position in navmesh? Guardian mode only? |
action_choose_random_waypoint
|
navzone? input, vector output
|
Chooses random waypoint inside input navzone. |
action_choose_random_waypoint_within_radius
|
vector origin, vector output, float radius
|
Choose random waypoint within specified origin and radius. |
action_choose_team_spawn_area
|
navzone? output
|
Gets all navzones with spawnpoints? |
action_combat_positioning
|
vector input, flag? is_attacking
|
Bot tries to reach best combat position against input entity? |
action_commit_suicide
|
Kills bot. | |
action_compare_global_counter
|
variable? input_name (e.g. input_name = "'Test'"), int input_value
|
Compares value from variable "input_name" to input value. |
action_coordinated_buy
|
flag team_filter (ANY, CT, TERRORIST, SAME, OPPOSITE, ENEMY), int save_threshold, flag? id_no_purchase (e.g. id_no_purchase = "DidNotPurchase"), array purchases(array items, string id)
|
In Guardian mode: restricts buying items to specific groupIDs? |
action_crouch
|
Forces bot to crouch. | |
action_custom_buy
|
array item_aliases
|
Unused. |
action_drop_active_weapon
|
Unused. Forces bot to drop active weapon. | |
action_equip_item
|
string item, array items_one_of
|
Forces bot to select item with classname from input. |
action_equip_weapon
|
string weapon (classname or BEST)
|
Force bot to select item with classname from input or "best" weapon in inventory. |
action_flee_area_damage
|
vector? input, vector output, int max_search_range, int threat_min_keep_distance
|
Returns safe spot from that position within search radius. |
action_hide
|
Unused. | |
action_inspect_current_weapon
|
Unused. Forces bot to play inspect animation. | |
action_jump
|
Forces bot to jump. | |
action_look_at
|
vector input_angles, navzone? input_location
|
Rotates bot at specified angle. |
action_move_to
|
vector destination, flag movement_type (BT_ACTION_MOVETO_RUN, BT_ACTION_MOVETO_WALK), flag route_type (BT_ACTION_MOVETO_FASTEST_ROUTE,BT_ACTION_MOVETO_SAFEST_ROUTE), vector? hiding_spot, vector? threat, int damaging_areas_penalty_cost, int nearest_area_distance_threshold, int hiding_spot_check_distance_threshold, int arrival_epsilon, float additional_arrival_epsilon_2d, int_flag? auto_look_adjust
|
Forces bot to move at destination point. |
action_parachute_positioning
|
??? | |
action_pull_trigger
|
int ratio
|
Forces bot primary attack. |
action_reload
|
Forces bot to reload weapon. | |
action_say
|
string phrase, flag? high_priority
|
Forces bot to say radio command phrase. List of phrases is located in botchatter.db
|
action_secondary_attack
|
Unused. Forces bot secondary attack. | |
action_select_areas_within_radius
|
vector input, float radius, navzone? output
|
Returns navzones within specified origin and radius? |
action_set_global_counter
|
variable? input_name, int input_value
|
Sets input value to variable "input_name". |
action_set_global_flag
|
variable? name, int expiration_time_min, int expiration_time_max
|
Sets global variable "name" to 1 for specified time? |
action_set_value_float
|
variable? key, float value
|
Sets float value to variable "key". |
action_set_value_vector
|
variable? key, vector value
|
Sets vector value to variable "key". |
action_standup
|
Unused. Forces bot to uncrouch. | |
action_teleport
|
vector destination
|
Teleports bot to destination position. |
action_use
|
Forces bot "+use" | |
action_wait
|
int wait_time_min, int wait_time_max
|
Freezes bot AI and script execution for defined amount of time. |
Reserved Variables
Those variables are defined in game code. They can be used to get some information about game mode and bot and as conditions.
Variable | Description |
---|---|
int NumberOfTerrorists
|
Number of players in T team. |
int NumberOfAliveTerrorists
|
Number of alive players in T team. |
int NumberOfCounterTerrorists
|
Number of players in CT team. |
int NumberOfAliveCounterTerrorists
|
Number of alive players in CT team. |
int GuardianWaveNumber
|
Wave number in Guardian Mode. |
int AccountBalance
|
Amount of player money. |
float? BlindnessPercentage
|
Amount of blindness applied by flash grenade. |
??? BombIsBeingDefused
|
Returns ??? when bomb is being defused. |
variable? LastCoopSpawnPointName
|
Targetname of last spawn point spawned that bot in Co-op Strike. |
Vector LastCoopSpawnPointLocation
|
Origin point of last spawn point spawned that bot in Co-op Strike. |
int AmmoCount/weapon_name
|
Remained amount of ammo in weapon with classname "weapon_name" (clip + reserve). |
int AmmoCount/current
|
Remained amount of ammo in active weapon (clip + reserve). |
Examples
Default Deathmatch Behavior Tree
Found in csgo/scripts/ai/bt_default.kv3
.
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
{
config = "scripts/ai/deathmatch/bt_config.kv3"
root =
{
type = "decorator_bot_service"
memory_to_expire =
[
{
key = "ShortTermAttackMemory"
time = 0.7
distance = 0
},
{
key = "LongTermMemory"
time = 10
distance = 500
},
{
key = "ShortTermInvestigateMemory"
time = 3
distance = 200
}
]
child =
{
type = "decorator_buy_service"
output = "ShouldBuy"
child =
{
type = "parallel"
children =
[
{
type = "decorator_repeat"
child =
{
type = "parallel"
children =
[
// memorize enemies through vision
{
type = "subtree"
file = "scripts/ai/modules/bt_memorize_enemies_vision.kv3"
name = "MemorizeEnemiesVision"
},
// memorize noises happening right now
{
type = "subtree"
file = "scripts/ai/modules/bt_memorize_noises.kv3"
name = "MemorizeNoises"
},
// record the nearest memorized event to investigate
{
type = "subtree"
file = "scripts/ai/modules/bt_memorize_nearest_investigation.kv3"
name = "MemorizeNearestInvestigation"
}
]
}
},
{
type = "decorator_repeat"
child =
{
type = "selector"
children =
[
// Buy if we have to
{
type = "condition_is_empty"
input = "ShouldBuy"
negated = 1
child =
{
// sequencer: evaluate first to last child, in order
type = "sequencer"
children =
[
{
type = "action_wait"
wait_time_min = 3
wait_time_max = 3
},
{
type = "action_buy"
},
{
type = "decorator_remove_key"
input = "ShouldBuy"
}
]
}
},
// Else: face the damage source if we're taking damage
{
type = "decorator_sensor"
entity_type_filter = "DAMAGE"
output = "Damage"
priority = 0
child =
{
type = "condition_is_empty"
input = "Damage"
negated = 1
child =
{
type = "action_aim"
input = "Damage"
acquire_only = 1
}
}
},
// Else: attack if we see an enemy
{
type = "subtree"
file = "scripts/ai/modules/bt_attack.kv3"
name = "Attack"
},
{
type = "subtree"
file = "scripts/ai/modules/bt_heal_if_needed.kv3"
name = "HealIfNeeded"
},
// Else: investigate the closest memorized event
{
type = "subtree"
file = "scripts/ai/modules/bt_investigate_closest_memorized_event.kv3"
name = "InvestigateClosestMemorizedEvent"
},
// Else: hunt
{
// sequencer: evaluate first to last child, in order
type = "sequencer"
children =
[
{
type = "action_equip_weapon"
weapon = "BEST"
},
{
type = "decorator_random_int"
min = 0
max = 1
output = "BombSiteIndex"
child =
{
type = "action_choose_bomb_site_area"
input = "BombSiteIndex"
output = "HuntAreas"
}
},
{
type = "action_choose_team_spawn_area"
output = "HuntAreas"
},
{
type = "action_choose_random_waypoint"
input = "HuntAreas"
output = "TargetHuntArea"
},
{
type = "action_move_to"
destination = "TargetHuntArea"
movement_type = "BT_ACTION_MOVETO_RUN"
route_type = "BT_ACTION_MOVETO_FASTEST_ROUTE"
}
]
}
]
}
}
]
}
}
}
}