Counter-Strike: Global Offensive/Bot Behavior Trees: Difference between revisions
Bezimienny (talk | contribs) (Added / fixed info about few nodes based on my personal testing) |
m (Setting bug notice hidetested=1 param on page where the bug might not need tested in param specified) |
||
(16 intermediate revisions by 8 users not shown) | |||
Line 1: | Line 1: | ||
{{ | {{LanguageBar}} | ||
{{Delisted|csgo}} | |||
{{update}} | {{update}} | ||
{{back|Counter-Strike: Global Offensive Level Creation}} | {{back|Counter-Strike: Global Offensive Level Creation}} | ||
In {{ | In {{csgo|4}}, [[bot]]s 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. | ||
Behavior trees are text files which use [[Valve]]'s proprietary [[ | Behavior trees are text files which use [[Valve]]'s proprietary [[KeyValues3]] format (<code>.kv3</code>), which is a text-based format with somewhat strict syntax. They typically use the <code>bt_</code> prefix, to signify that file is a <u>B</u>ehavior <u>T</u>ree. They are stored in <code>csgo/scripts/ai</code>. One can create and use their own behavior trees, see below. | ||
Officially, behavior trees are used for the game modes {{csgo mode|Coop}}, {{csgo mode|Gd}} and {{csgo mode|Dm}}. | Officially, behavior trees are used for the game modes {{csgo mode|Coop}}, {{csgo mode|Gd}} and {{csgo mode|Dm}}. | ||
Line 12: | Line 13: | ||
== Using Behavior Trees == | == Using Behavior Trees == | ||
There are some ways to use behavior tree files in-game. | There are some ways to use behavior tree files in-game. | ||
* The [[ConVar]] <code>mp_bot_ai_bt</code> determines a behavior tree that all respawning bots on the server (both T and CT!) will use. Its value is a string representing the path to a .kv3 file, starting from <code>csgo/</code>. If there are player respawns, killing a bot is sufficient to make them use a newly set file, otherwise restarting the game or round with <code>mp_restartgame 1</code> or <code>endround</code> should also do it. If a behavior tree file has been modified and if a bot had already loaded it, it is also necessary to flush the loaded files using <code>mp_bot_ai_bt_clear_cache</code> to make changes apply. As soon as a bot (re)spawns, there will be error messages containing <code>[AI BT]</code> if any used file has errors and the bot does not use any behavior tree. {{ | * The [[ConVar]] <code>mp_bot_ai_bt</code> determines a behavior tree that all respawning bots on the server (both T and CT!) will use. Its value is a string representing the path to a .kv3 file, starting from <code>csgo/</code>. If there are player respawns, killing a bot is sufficient to make them use a newly set file, otherwise restarting the game or round with <code>mp_restartgame 1</code> or <code>endround</code> should also do it. If a behavior tree file has been modified and if a bot had already loaded it, it is also necessary to flush the loaded files using <code>mp_bot_ai_bt_clear_cache</code> to make changes apply. As soon as a bot (re)spawns, there will be error messages containing <code>[AI BT]</code> if any used file has errors and the bot does not use any behavior tree. {{ModernExample|The following line is used for official Deathmatch and can be found in <code>csgo/cfg/gamemode_deathmatch.cfg</code>: {{pre|mp_bot_ai_bt "scripts/ai/deathmatch/bt_default.kv3"}}}} | ||
* For {{csgo mode|Coop}}, {{ent|info_enemy_terrorist_spawn}} entities have the KeyValue <code>behavior_tree_file</code> that can be used to specify a behavior tree file only for bots spawning at this entity. | * For {{csgo mode|Coop}}, {{ent|info_enemy_terrorist_spawn}} entities have the KeyValue <code>behavior_tree_file</code> that can be used to specify a behavior tree file only for bots spawning at this entity. | ||
Line 113: | Line 114: | ||
== Bot Configuration == | == Bot Configuration == | ||
{{ | {{todo|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, elite, default'' ), like a botprofile.db. | These parameters define general bot reaction and combat behavior for every bot skill ( ''low, fair, normal, tough, hard, very_hard, expert, elite, default'' ), like a botprofile.db. | ||
Line 248: | Line 247: | ||
{{warning|For each node, the game will ignore all parameters with no warning if they don't belong to it; it will only regard some specificly named parameters and those names depend on its type. While testing, this can make it hard to spot parameter names that sound plausible but don't actually have an effect (like <code>source/input/input_location</code> but only one of them is an actual parameter) or ones with typos within their names.}} | {{warning|For each node, the game will ignore all parameters with no warning if they don't belong to it; it will only regard some specificly named parameters and those names depend on its type. While testing, this can make it hard to spot parameter names that sound plausible but don't actually have an effect (like <code>source/input/input_location</code> but only one of them is an actual parameter) or ones with typos within their names.}} | ||
Most nodes can have the following parameters that are omitted in the following lists: | Most nodes can have the following parameters that are omitted in the following lists: | ||
* <code>child</code> - Its value should be another node which will or will not be executed, depending on node type and context. | * <code>child</code> - Supported by [[#Decorators]] and [[#Conditions]]. Its value should be another node which will or will not be executed, depending on node type and context. By default most decorators execute their child function automatically, unless otherwise specified. | ||
* <code>children</code> - Its value should be an array of nodes, similar to <code>child</code>. | * <code>children</code> - Supported by [[#Combinators]] (Except <code>subtree</code>!). Its value should be an array of nodes, similar to <code>child</code>. | ||
* <code>negated</code> - Set to 1 to invert its return value (success or not), for example a node of the type <code>condition_is_empty</code> will succeed if something does not exist; If this node has the parameter <code>negated = 1</code>, it will instead succeed if something does exist. | * <code>negated</code> - Supported by [[#Decorators]] and [[#Conditions]]. Set to 1 to invert its return value (success or not), for example a node of the type <code>condition_is_empty</code> will succeed if something does not exist; If this node has the parameter <code>negated = 1</code>, it will instead succeed if something does exist. | ||
{{ | {{todo|'' | ||
* Complete node parameters and descriptions, check for inaccuracies. | * Complete node parameters and descriptions, check for inaccuracies. | ||
* Define variable types for parameters. | * Define variable types for parameters. | ||
* Explain all parameters. | * Explain all parameters. | ||
''}} | |||
=== Combinators === | === Combinators === | ||
Line 295: | Line 294: | ||
| <code>decorator_buy_service</code> | | <code>decorator_buy_service</code> | ||
| <code>array output</code> | | <code>array output</code> | ||
| Returns array of items that bot will buy | | Returns array of items that bot will buy. | ||
|- | |- | ||
| <code>decorator_dec_global_counter</code> | | <code>decorator_dec_global_counter</code> | ||
| <code>variable input_name</code> | | <code>variable input_name</code> | ||
| Decreases input variable by 1 | | Decreases input variable by 1. | ||
|- | |- | ||
| <code>decorator_find_utility_strat</code> | | <code>decorator_find_utility_strat</code> | ||
Line 307: | Line 306: | ||
| <code>decorator_game_event</code> | | <code>decorator_game_event</code> | ||
| | | | ||
| {{ | | {{warning|Release Notes for 21/10/2021 say about it but it doesn't exist in code?}} | ||
|- | |- | ||
| <code>decorator_hiding_spot_service</code> | | <code>decorator_hiding_spot_service</code> | ||
| <code>variable? domain (GroupID or AllBots), vector output_hiding_spot, int distance_threshold, int expiration_time</code> | | <code>variable? domain (GroupID or AllBots), vector output_hiding_spot, int distance_threshold, int expiration_time</code> | ||
| Keeps track of visible hiding spots. Used in conjunction with | | Keeps track of visible hiding spots. Used in conjunction with <code>action_move_to</code> to make a bot check hiding spots as they move throughout the level. {{note|Also keeps track of checked hiding spots in <code>domain</code>. Bots sharing the same domain will not check hiding spots for some time if another bot has already checked them.}} | ||
|- | |- | ||
| <code>decorator_invert</code> | | <code>decorator_invert</code> | ||
| | | | ||
| Unused. Valve scripts usually use <code>negated = 1</code> flag in node parameters. | | Unused. Valve scripts usually use <code>negated = 1</code> flag in node parameters. However, <code>action</code> nodes don't support <code>negated</code>, so this node can be used as a workaround. | ||
|- | |- | ||
| <code>decorator_maybe</code> | | <code>decorator_maybe</code> | ||
| <code>float chance</code> | | <code>float chance</code> | ||
| Executes child | | Executes child function with specified <code>chance</code> (1 - 100%, 0.7 - 70% etc). | ||
|- | |- | ||
| <code>decorator_memory</code> | | <code>decorator_memory</code> | ||
| <code>vector? input, memory? output, string output_domain</code> | | <code>vector? input, memory? output, string output_domain</code> | ||
| Saves input | | Saves entities in the <code>input</code> array to <code>output</code> array. | ||
|- | |- | ||
| <code>decorator_need_healing</code> | | <code>decorator_need_healing</code> | ||
| <code>int health_threshold</code> | | <code>int health_threshold</code> | ||
| Executes child | | Executes child function if bot health is lower than <code>health_threshold</code>. | ||
|- | |- | ||
| <code>decorator_picker_blocked_by_smoke</code> | | <code>decorator_picker_blocked_by_smoke</code> | ||
| <code>vector? input, int distance_threshold</code> | | <code>vector? input, int distance_threshold</code> | ||
| Removes entities from the | | Removes entities from the <code>input</code> array that are not covered by smokes. | ||
|- | |- | ||
| <code>decorator_picker_dedup</code> | | <code>decorator_picker_dedup</code> | ||
| <code>vector? input, memory? against, int distance_threshold</code> | | <code>vector? input, memory? against, int distance_threshold</code> | ||
| Removes entities from the | | Removes entities from the <code>input</code> array that are within <code>distance_threshold</code> to entities in <code>against</code> array. | ||
|- | |- | ||
| <code>decorator_picker_grenade_type</code> | | <code>decorator_picker_grenade_type</code> | ||
| <code>array input, array types (EXPLOSIVE, FLASH, FIRE, DECOY, SMOKE, SENSOR, SNOWBALL)</code> | | <code>array input, array types (EXPLOSIVE, FLASH, FIRE, DECOY, SMOKE, SENSOR, SNOWBALL)</code> | ||
| Removes grenade entities from the | | Removes grenade entities from the <code>input</code> array that are not in <code>types</code> array. | ||
|- | |- | ||
| <code>decorator_picker_max_score</code> | | <code>decorator_picker_max_score</code> | ||
| <code>array input</code> | | <code>array input</code> | ||
| | | Picks the first entity in the <code>input</code> array. By default entities are sorted by the order they appear on the server. {{note|<code>decorator_ranker_dist</code> can be used before this node to pick the closest entity instead.}} | ||
|- | |- | ||
| <code>decorator_picker_nearby</code> | | <code>decorator_picker_nearby</code> | ||
| <code>array input, int cutoff_distance</code> | | <code>array input, int cutoff_distance</code> | ||
| | | Removes entities from the <code>input</code> array that are beyond the <code>cutoff_distance</code>. | ||
|- | |- | ||
| <code>decorator_picker_random_by_distance</code> | | <code>decorator_picker_random_by_distance</code> | ||
| <code>array input, int distance_min, int distance_max</code> | | <code>array input, int distance_min, int distance_max</code> | ||
| Removes entities from the | | Removes entities from the <code>input</code> array based on the distance distribution. Entities closer to <code>distance_min</code> are less likely to get removed, while entities closer to <code>distance_max</code> are more likely to get removed. | ||
|- | |- | ||
| <code>decorator_picker_reaction_time</code> | | <code>decorator_picker_reaction_time</code> | ||
| <code>variable? input_domain (GroupID or AllBots), memory? input, vector? output</code> | | <code>variable? input_domain (GroupID or AllBots), memory? input, vector? output</code> | ||
| Executes child | | Executes child function after delay, that depends of bot skill reaction time? | ||
|- | |- | ||
| <code>decorator_picker_visible</code> | | <code>decorator_picker_visible</code> | ||
| <code>array input, int_flag? check_fov</code> | | <code>array input, int_flag? check_fov</code> | ||
| Removes obscured entities from | | Removes obscured entities from <code>input</code> array. Will check bot FOV only, unless <code>check_fov</code> is set to 0. | ||
|- | |- | ||
| <code>decorator_picker_weight_as_distance</code> | | <code>decorator_picker_weight_as_distance</code> | ||
| <code>array input</code> | | <code>array input</code> | ||
| Removes the entities whose distance to the bot running the tree is greater than the entities "weight". "weight" is an internal notion mostly used for hearing sounds. It | | Removes the entities whose distance to the bot running the tree is greater than the entities "weight". "weight" is an internal notion mostly used for hearing sounds. It should only be used when sensing "NOISE". | ||
|- | |- | ||
| <code>decorator_random_approach_point</code> | | <code>decorator_random_approach_point</code> | ||
| <code>vector output</code> | | <code>vector output</code> | ||
| Returns a random visible point in navmesh that's an "entrance" to the area the bot is currently in. {{tip|Can be used to make a bot look around randomly, as seen in "...\modules\bt_look_around.kv3" | | Returns a random visible point in navmesh that's ideally an "entrance" to the area the bot is currently in. {{tip|Can be used to make a bot look around randomly, as seen in <code>"...\modules\bt_look_around.kv3"</code>.}} | ||
|- | |- | ||
| <code>decorator_random_int</code> | | <code>decorator_random_int</code> | ||
| <code>int min, int max, int output</code> | | <code>int min, int max, int output</code> | ||
| Returns a random int the range of min to max. | | Returns a random int in the range of <code>min</code> to <code>max</code>. | ||
|- | |- | ||
| <code>decorator_ranker_dist</code> | | <code>decorator_ranker_dist</code> | ||
| <code>array input</code> | | <code>array input</code> | ||
| Sorts entities | | Sorts entities in the <code>input</code> array based on distance. | ||
|- | |- | ||
| <code>decorator_remove</code> | | <code>decorator_remove</code> | ||
| <code>variable? input_domain (GroupID or AllBots), memory? input, array? remove</code> | | <code>variable? input_domain (GroupID or AllBots), memory? input, array? remove</code> | ||
| Removes selected entities from input memory. | | Removes selected entities from <code>input</code> memory. | ||
|- | |- | ||
| <code>decorator_remove_key</code> | | <code>decorator_remove_key</code> | ||
| <code>variable? input</code> | | <code>variable? input</code> | ||
| Removes input variable from script scope. | | Removes <code>input</code> variable from script scope. | ||
|- | |- | ||
| <code>decorator_repeat</code> | | <code>decorator_repeat</code> | ||
| | | | ||
| Executes child | | Executes child function in loop. Stops looping if child function fails. | ||
|- | |- | ||
| <code>decorator_route_service</code> | | <code>decorator_route_service</code> | ||
Line 395: | Line 394: | ||
| <code>decorator_run_once</code> | | <code>decorator_run_once</code> | ||
| <code>int max_attempts = 1, variable? domain (e.g. domain = "'CoordinatedBuy'")</code> | | <code>int max_attempts = 1, variable? domain (e.g. domain = "'CoordinatedBuy'")</code> | ||
| Executes child | | Executes child function limited amount of times. <code>domain</code> can be set to make only one bot run it. | ||
|- | |- | ||
| <code>decorator_sensor</code> | | <code>decorator_sensor</code> | ||
| <code>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</code> | | <code>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</code> | ||
| Returns | | Returns entities detected by sensor. {{bug|hidetested=1|When sensing grenades with <code>team_filter</code> set to "ENEMY" it incorrectly returns terrorist grenades, regardless of team the bot is on. "OPPOSITE" filter can be used as a workaround.}} | ||
|- | |- | ||
| <code>decorator_set_barrier</code> | | <code>decorator_set_barrier</code> | ||
Line 407: | Line 406: | ||
| <code>decorator_set_reaction_time</code> | | <code>decorator_set_reaction_time</code> | ||
| <code>array? input</code> | | <code>array? input</code> | ||
| Executes child | | Executes child function after delay, that depends of bot skill reaction time? | ||
|- | |- | ||
| <code>decorator_succeed</code> | | <code>decorator_succeed</code> | ||
| | | | ||
| Node succeeds (returns true condition) even if child | | Node succeeds (returns true condition) even if child function failed. | ||
|- | |- | ||
| <code>decorator_tag_entity</code> | | <code>decorator_tag_entity</code> | ||
| <code>array input, array output, flag operation_type (BT_DECORATOR_TAG_ENTITY_CLEAR, BT_DECORATOR_TAG_ENTITY_SET), int expiration_time</code> | | <code>array input, array output, flag operation_type (BT_DECORATOR_TAG_ENTITY_CLEAR, BT_DECORATOR_TAG_ENTITY_SET), int expiration_time</code> | ||
| Adds or removes the first entity in | | Adds or removes the first entity in <code>input</code> array to <code>output</code> array for specified amount of seconds. <code>input</code> entity will be tagged as long as the child function is being executed. | ||
|- | |- | ||
| <code>decorator_tag_threshold</code> | | <code>decorator_tag_threshold</code> | ||
| <code>array entity_input, array tagged_entities_input, int amount, flag check_type (BT_DECORATOR_TAG_THRESHOLD_AT_MOST, BT_DECORATOR_TAG_THRESHOLD_AT_LEAST)</code> | | <code>array entity_input, array tagged_entities_input, int amount, flag check_type (BT_DECORATOR_TAG_THRESHOLD_AT_MOST, BT_DECORATOR_TAG_THRESHOLD_AT_LEAST)</code> | ||
| Compares entities in | | Compares entities in <code>entity_input</code> against entities in <code>tagged_entities_input</code> using <code>check_type</code>. If it passes, executes child function. (If at least/most <code>x</code> entities from <code>entity_input</code> are in <code>tagged_entities_input</code>, execute child function) | ||
|- | |- | ||
| <code>decorator_token_service</code> | | <code>decorator_token_service</code> | ||
Line 427: | Line 426: | ||
| <code>decorator_try_lock</code> | | <code>decorator_try_lock</code> | ||
| <code>variable? domain (e.g. domain = "'DefuseIfCovered'")</code> | | <code>variable? domain (e.g. domain = "'DefuseIfCovered'")</code> | ||
| Locks the domain for the duration of executing the child function, meaning only one bot at a time will be executing this. | | Locks the <code>domain</code> for the duration of executing the child function, meaning only one bot at a time will be executing this. | ||
|- | |- | ||
| <code>decorator_wait_success</code> | | <code>decorator_wait_success</code> | ||
| <code>int timeout</code> | | <code>int timeout</code> | ||
| Waits for child to return true or finish executing. If child never returns true, stops waiting after | | Waits for child to return true or finish executing. If child never returns true, stops waiting after <code>timeout</code> seconds. | ||
|} | |} | ||
Line 447: | Line 446: | ||
| <code>condition_distance_less</code> | | <code>condition_distance_less</code> | ||
| <code>vector input, int distance_threshold_min, int distance_threshold_max</code> | | <code>vector input, int distance_threshold_min, int distance_threshold_max</code> | ||
| Returns true, if distance to input position is less than somewhere between the | | Returns true, if distance to <code>input</code> position is less than somewhere between the <code>distance_threshold_min</code> and <code>distance_threshold_max</code>. | ||
|- | |- | ||
| <code>condition_has_parachute</code> | | <code>condition_has_parachute</code> | ||
Line 467: | Line 466: | ||
| <code>condition_is_empty</code> | | <code>condition_is_empty</code> | ||
| <code>int_flag? global, any input</code> | | <code>int_flag? global, any input</code> | ||
| Returns true, if input variable is empty or doesn't exist. | | Returns true, if <code>input</code> variable is empty or doesn't exist. | ||
|- | |- | ||
| <code>condition_is_equal</code> | | <code>condition_is_equal</code> | ||
| <code>any source, any destination</code> | | <code>any source, any destination</code> | ||
| Returns true, if source variable is equal to | | Returns true, if source variable is equal to <code>destination</code> input. | ||
|- | |- | ||
| <code>condition_is_greater</code> | | <code>condition_is_greater</code> | ||
| <code>any source, any destination</code> | | <code>any source, any destination</code> | ||
| Unused. Returns true, if source variable is greater than | | Unused. Returns true, if source variable is greater than <code>destination</code> input. | ||
|- | |- | ||
| <code>condition_is_greater_equal</code> | | <code>condition_is_greater_equal</code> | ||
| <code>any source, any destination</code> | | <code>any source, any destination</code> | ||
| Unused. Returns true, if source variable is equal or greater than | | Unused. Returns true, if source variable is equal or greater than <code>destination</code> input. | ||
|- | |- | ||
| <code>condition_is_inv_slot_empty</code> | | <code>condition_is_inv_slot_empty</code> | ||
Line 487: | Line 486: | ||
| <code>condition_is_less</code> | | <code>condition_is_less</code> | ||
| <code>any source, any destination</code> | | <code>any source, any destination</code> | ||
| Unused. Returns true, if source variable is less than | | Unused. Returns true, if source variable is less than <code>destination</code> input. | ||
|- | |- | ||
| <code>condition_is_less_equal</code> | | <code>condition_is_less_equal</code> | ||
| <code>any source, any destination</code> | | <code>any source, any destination</code> | ||
| Unused. Returns true, if source variable is equal or less than | | Unused. Returns true, if source variable is equal or less than <code>destination</code> input. | ||
|- | |- | ||
| <code>condition_is_reloading</code> | | <code>condition_is_reloading</code> | ||
Line 499: | Line 498: | ||
| <code>condition_is_weapon_equipped</code> | | <code>condition_is_weapon_equipped</code> | ||
| <code>string weapon</code> | | <code>string weapon</code> | ||
| Returns true, if bot has active weapon with | | Returns true, if bot has active weapon with <code>weapon</code> classname. | ||
|- | |- | ||
| <code>condition_is_weapon_suitable</code> | | <code>condition_is_weapon_suitable</code> | ||
| <code>string weapon</code> | | <code>vector input, string weapon</code> | ||
| Unused. | | Unused. Returns true, if weapon with <code>weapon</code> classname is suitable at the range to the <code>input</code> entity. Snipers are not considered suitable below 160.0 units, while shotguns are not considered suitable past 600.0 units. {{bug|hidetested=1|Works exactly the same as <code>condition_is_weapon_equipped</code>.{{confirm}}}} | ||
|- | |- | ||
| <code>condition_out_of_ammo</code> | | <code>condition_out_of_ammo</code> | ||
Line 510: | Line 509: | ||
|- | |- | ||
| <code>condition_owns_item</code> | | <code>condition_owns_item</code> | ||
| <code>string item</code> | | <code>string item, array items_one_of</code> | ||
| Returns true, if bot has item with input classname. | | Returns true, if bot has item with <code>input</code> classname. | ||
|} | |} | ||
Line 527: | Line 526: | ||
| <code>action_aim</code> | | <code>action_aim</code> | ||
| <code>vector input, int_flag? acquire_only, flag? ready</code> | | <code>vector input, int_flag? acquire_only, flag? ready</code> | ||
| Rotates bot towards input point. Uses aim penalties set in the bot config. Can overshoot the | | Rotates bot towards input point. Uses aim penalties set in the bot config. Can overshoot the target. Forces the bot to scope with scoped weapons. {{note|<code>acquire_only</code> should be set to 1 if you don't intend to track your target.}} {{warning|Bot will never unscope unless they switch weapons. Equipping the same weapon as the one bot is currently holding will also reset the scope.}} | ||
|- | |- | ||
| <code>action_aim_projectile</code> | | <code>action_aim_projectile</code> | ||
| <code>vector input, vector output</code> | | <code>vector input, vector output</code> | ||
| Calculates angles for throwing at input position? {{bug|Doesn't actually take the grenade trajectory into account.}} | | Calculates angles for throwing at input position? {{bug|hidetested=1|Doesn't actually take the grenade trajectory into account.}} | ||
|- | |- | ||
| <code>action_assign_guardian_loadout</code> | | <code>action_assign_guardian_loadout</code> | ||
Line 539: | Line 538: | ||
| <code>action_attack</code> | | <code>action_attack</code> | ||
| <code>vector input, flag? output (e.g. output = "Attacking"), flag? ready (e.g. ready = "AimReady")</code> | | <code>vector input, flag? output (e.g. output = "Attacking"), flag? ready (e.g. ready = "AimReady")</code> | ||
| Forces bot to fire | | Forces bot to fire. Uses spray duration settings from bot config. {{note|<code>ready</code> flag usually should be controlled automatically with <code>action_aim</code> node.}} | ||
|- | |- | ||
| <code>action_buy</code> | | <code>action_buy</code> | ||
| | | | ||
| Bot buys weapons. | | Bot buys random weapons. | ||
|- | |- | ||
| <code>action_choose_bomb_site_area</code> | | <code>action_choose_bomb_site_area</code> | ||
Line 567: | Line 566: | ||
| <code>action_combat_positioning</code> | | <code>action_combat_positioning</code> | ||
| <code>vector input, flag? is_attacking</code> | | <code>vector input, flag? is_attacking</code> | ||
| | | Bot side steps to dodge attacks or closes distance on the enemy. Uses settings from the bot config. {{note|<code>is_attacking</code> flag usually should be controlled automatically with <code>action_attack</code> node.}} | ||
|- | |- | ||
| <code>action_commit_suicide</code> | | <code>action_commit_suicide</code> | ||
Line 575: | Line 574: | ||
| <code>action_compare_global_counter</code> | | <code>action_compare_global_counter</code> | ||
| <code>variable? input_name (e.g. input_name = "'Test'"), int input_value</code> | | <code>variable? input_name (e.g. input_name = "'Test'"), int input_value</code> | ||
| Compares value from variable | | Compares value from variable <code>input_name</code> to <code>input_value</code>. | ||
|- | |- | ||
| <code>action_coordinated_buy</code> | | <code>action_coordinated_buy</code> | ||
| <code>flag team_filter (ANY, CT, TERRORIST, SAME, OPPOSITE, ENEMY),<br>int save_threshold,<br>flag? id_no_purchase (e.g. id_no_purchase = "DidNotPurchase"),<br>array purchases(array items, string id)</code> | | <code>flag team_filter (ANY, CT, TERRORIST, SAME, OPPOSITE, ENEMY),<br>int save_threshold,<br>flag? id_no_purchase (e.g. id_no_purchase = "DidNotPurchase"),<br>array purchases(array items, string id)</code> | ||
| | | Forces all of the bots that match the <code>team_filter</code> to buy items from <code>purchases</code> array and assigns appropriate <code>id</code> to them. Node will return false and be skipped if there's not enough bots in the specified team to fulfill all <code>purchases</code>. Bots that do not make a purchase are assigned with <code>id_no_purchase</code>. You can check what <code>id</code> bots are assigned with by using <code>condition_is_empty</code>. {{warning|One bot using this node will make all of the bots that match the specified <code>team_filter</code> make purchases! Use in conjunction with <code>decorator_run_once</code> and <code>domain</code> set to something.}} {{bug|hidetested=1|Bots that did not match the <code>team_filter</code> are still assigned "id_no_purchase" erroneously.}} {{bug|hidetested=1|Bots further down the <code>purchases</code> array inherit items to purchase from bots before them. See [[#Coordinated Buy (Alternative Buying Method)]]}} | ||
|- | |- | ||
| <code>action_crouch</code> | | <code>action_crouch</code> | ||
Line 607: | Line 606: | ||
| <code>action_hide</code> | | <code>action_hide</code> | ||
| <code>float max_range, vector output</code> | | <code>float max_range, vector output</code> | ||
| Unused. Returns a random hiding spot within search radius. {{ | | Unused. Returns a random hiding spot within search radius. {{warning|Does NOT check if it's already occupied!}} | ||
|- | |- | ||
| <code>action_inspect_current_weapon</code> | | <code>action_inspect_current_weapon</code> | ||
Line 623: | Line 622: | ||
| <code>action_move_to</code> | | <code>action_move_to</code> | ||
| <code>vector destination,<br>flag movement_type (BT_ACTION_MOVETO_RUN, BT_ACTION_MOVETO_WALK),<br>flag route_type (BT_ACTION_MOVETO_FASTEST_ROUTE,BT_ACTION_MOVETO_SAFEST_ROUTE),<br>vector? hiding_spot,<br>vector? threat,<br>int damaging_areas_penalty_cost,<br>int nearest_area_distance_threshold,<br>int hiding_spot_check_distance_threshold,<br>int arrival_epsilon,<br>float additional_arrival_epsilon_2d,<br>int_flag? auto_look_adjust</code> | | <code>vector destination,<br>flag movement_type (BT_ACTION_MOVETO_RUN, BT_ACTION_MOVETO_WALK),<br>flag route_type (BT_ACTION_MOVETO_FASTEST_ROUTE,BT_ACTION_MOVETO_SAFEST_ROUTE),<br>vector? hiding_spot,<br>vector? threat,<br>int damaging_areas_penalty_cost,<br>int nearest_area_distance_threshold,<br>int hiding_spot_check_distance_threshold,<br>int arrival_epsilon,<br>float additional_arrival_epsilon_2d,<br>int_flag? auto_look_adjust</code> | ||
| Forces bot to move at destination point. {{bug|Move type will reset to running after jumping.}} | | Forces bot to move at destination point. {{bug|hidetested=1|Move type will reset to running after jumping.}} {{bug|hidetested=1|When teammate collision is on, bots will not try to repath around their teammates if they block their way. Instead they'll constantly try to path through their teammate until they move out of their way.}} | ||
|- | |- | ||
| <code>action_parachute_positioning</code> | | <code>action_parachute_positioning</code> | ||
Line 651: | Line 650: | ||
| <code>action_set_global_counter</code> | | <code>action_set_global_counter</code> | ||
| <code>variable? input_name, int input_value</code> | | <code>variable? input_name, int input_value</code> | ||
| Sets input value to variable | | Sets input value to variable <code>input_name</code>. | ||
|- | |- | ||
| <code>action_set_global_flag</code> | | <code>action_set_global_flag</code> | ||
| <code>variable? name, int expiration_time_min, int expiration_time_max</code> | | <code>variable? name, int expiration_time_min, int expiration_time_max</code> | ||
| Sets global variable | | Sets global variable <code>name</code> to 1 for specified time? | ||
|- | |- | ||
| <code>action_set_value_float</code> | | <code>action_set_value_float</code> | ||
| <code>variable? key, float value</code> | | <code>variable? key, float value</code> | ||
| Sets float value to variable | | Sets float value to variable <code>key</code>. | ||
|- | |- | ||
| <code>action_set_value_vector</code> | | <code>action_set_value_vector</code> | ||
| <code>variable? key, vector value</code> | | <code>variable? key, vector value</code> | ||
| Sets vector value to variable | | Sets vector value to variable <code>key</code>. | ||
|- | |- | ||
| <code>action_standup</code> | | <code>action_standup</code> | ||
Line 787: | Line 786: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Coordinated Buy === | |||
This simple script showcases how to use <code>action_coordinated_buy</code> node to make your bots strategize. | |||
{{Expand| | |||
<source lang="javascript"><!-- 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_repeat" | |||
child = | |||
{ | |||
type = "selector" | |||
children = | |||
[ | |||
{ | |||
// with the "domain" set only one bot on the server will run this per round. | |||
type = "decorator_run_once" | |||
domain = "'CoordinatedBuy'" | |||
child = | |||
{ | |||
type = "sequencer" | |||
children = | |||
[ | |||
{ | |||
// set a global variable to make our teammates wait for the coordinated buy. | |||
type = "action_set_global_counter" | |||
input_name = "'WaitForCoordinatedBuy'" | |||
input_value = 1 | |||
}, | |||
{ | |||
type = "action_wait" | |||
wait_time_min = 3 | |||
wait_time_max = 3 | |||
}, | |||
{ | |||
// continue with the sequencer even if we weren't able to coordinate any buy. | |||
type = "decorator_succeed" | |||
child = | |||
{ | |||
// when used in a selector coordinated buys that can't be fulfilled will be skipped. | |||
type = "selector" | |||
children = | |||
[ | |||
{ | |||
type = "action_coordinated_buy" | |||
// team that should coordinte a buy. Works the same as in "decorator_sensor". | |||
team_filter = "SAME" | |||
// all of the bots on the specified team must have at least this much money. | |||
// should be set to the minimum amount of money required to purchase the needed items | |||
// in this case the minimum amount of money would be 500, as that's needed | |||
// to buy the "SetupSmokeAndFlashbang1". | |||
save_threshold = 2000 | |||
// advised to only be used in fully pve modes, | |||
// due to being assigned to the other team as well. | |||
id_no_purchase = "DidNotPurchase" | |||
// array with purchases. | |||
// in this case it needs at least 4 bots on the team in order to go through. | |||
purchases = | |||
[ | |||
{ | |||
// items to buy. | |||
// (WARNING: due to the previously mentioned bug it is | |||
// highly advised to use alternative methods for buying items). | |||
items = [ "weapon_flashbang" ] | |||
// a variable with this name will be saved on that bot. | |||
// check with "condition_is_empty". | |||
id = "SetupFlashbang1" | |||
}, | |||
{ | |||
items = [ "weapon_smokegrenade", "weapon_flashbang" ] | |||
id = "SetupSmokeAndFlashbang1" | |||
}, | |||
{ | |||
items = [ "weapon_smokegrenade" ] | |||
id = "SetupSmoke1" | |||
}, | |||
{ | |||
items = [ "weapon_smokegrenade" ] | |||
id = "SetupSmoke2" | |||
} | |||
] | |||
}, | |||
{ | |||
// this node needs less bots and less money to go through. | |||
type = "action_coordinated_buy" | |||
team_filter = "SAME" | |||
save_threshold = 1000 | |||
id_no_purchase = "DidNotPurchase" | |||
purchases = | |||
[ | |||
{ | |||
items = [ "weapon_flashbang" ] | |||
id = "SetupFlashbang1" | |||
}, | |||
{ | |||
items = [ "weapon_smokegrenade", "weapon_flashbang" ] | |||
id = "SetupSmokeAndFlashbang1" | |||
} | |||
] | |||
}, | |||
{ | |||
// this node will always go through as long as there's at least 2 bots on the team. | |||
type = "action_coordinated_buy" | |||
team_filter = "SAME" | |||
save_threshold = 0 | |||
id_no_purchase = "DidNotPurchase" | |||
purchases = | |||
[ | |||
{ | |||
// items array can be completely empty, id is still going to be assigned. | |||
items = [ ] | |||
// ids don't need to be unique, they are saved as a local variable on | |||
// the bot that bought them. | |||
id = "SetupNoGrenades" | |||
}, | |||
{ | |||
items = [ ] | |||
id = "SetupNoGrenades" | |||
} | |||
] | |||
} | |||
] | |||
} | |||
}, | |||
{ | |||
type = "action_set_global_counter" | |||
input_name = "'WaitForCoordinatedBuy'" | |||
input_value = 0 | |||
} | |||
] | |||
} | |||
}, | |||
// Wait 4 seconds for the coordinated buy to be done. | |||
{ | |||
type = "decorator_run_once" | |||
child = | |||
{ | |||
type = "decorator_wait_success" | |||
timeout = 4 | |||
child = | |||
{ | |||
type = "action_compare_global_counter" | |||
input_name = "'WaitForCoordinatedBuy'" | |||
input_value = 0 | |||
} | |||
} | |||
}, | |||
// now check for the ids with "condition_is_empty" | |||
// to make specific bots do the setups you want! | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupFlashbang1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 -4.096003 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmokeAndFlashbang1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 -175.519958 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmoke1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 -89.543930 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmoke2" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 90.496025 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupNoGrenades" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_jump" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "DidNotPurchase" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_crouch" | |||
} | |||
}, | |||
// be sure to make a backup behavior | |||
// if there's a possibility that none of the coodrinated buys can be fulfilled | |||
{ | |||
type = "subtree" | |||
file = "scripts/ai/modules/bt_look_around.kv3" | |||
name = "LookAround" | |||
} | |||
] | |||
} | |||
} | |||
} | |||
</source> | |||
}} | |||
=== Coordinated Buy (Alternative Buying Method) === | |||
Alternative buying method that avoids the duplicated item buying bug. | |||
{{Expand| | |||
<source lang="javascript"><!-- 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_repeat" | |||
child = | |||
{ | |||
type = "selector" | |||
children = | |||
[ | |||
{ | |||
// with the "domain" set only one bot on the server will run this per round. | |||
type = "decorator_run_once" | |||
domain = "'CoordinatedBuy'" | |||
child = | |||
{ | |||
type = "sequencer" | |||
children = | |||
[ | |||
{ | |||
// set a global variable to make our teammates wait for the coordinated buy. | |||
type = "action_set_global_counter" | |||
input_name = "'WaitForCoordinatedBuy'" | |||
input_value = 1 | |||
}, | |||
{ | |||
type = "action_wait" | |||
wait_time_min = 3 | |||
wait_time_max = 3 | |||
}, | |||
{ | |||
// continue with the sequencer even if we weren't able to coordinate any buy. | |||
type = "decorator_succeed" | |||
child = | |||
{ | |||
// when used in a selector coordinated buys that can't be fulfilled will be skipped. | |||
type = "selector" | |||
children = | |||
[ | |||
{ | |||
type = "action_coordinated_buy" | |||
// team that should coordinte a buy. Works the same as in "decorator_sensor". | |||
team_filter = "SAME" | |||
// all of the bots on the specified team must have at least this much money. | |||
// should be set to the minimum amount of money required to purchase the needed items | |||
// in this case the minimum amount of money would be 500, as that's needed | |||
// to buy the "SetupSmokeAndFlashbang1". | |||
save_threshold = 2000 | |||
// advised to only be used in fully pve modes, | |||
// due to being assigned to the other team as well. | |||
id_no_purchase = "DidNotPurchase" | |||
// array with purchases. | |||
// in this case it needs at least 4 bots on the team in order to go through. | |||
purchases = | |||
[ | |||
{ | |||
// instead of setting up the items here | |||
// we'll buy them later | |||
items = [ ] | |||
// a variable with this name will be saved on that bot. | |||
// check with "condition_is_empty". | |||
id = "SetupFlashbang1" | |||
}, | |||
{ | |||
items = [ ] | |||
id = "SetupSmokeAndFlashbang1" | |||
}, | |||
{ | |||
items = [ ] | |||
id = "SetupSmoke1" | |||
}, | |||
{ | |||
items = [ ] | |||
id = "SetupSmoke2" | |||
} | |||
] | |||
}, | |||
{ | |||
// this node needs less bots and less money to go through. | |||
type = "action_coordinated_buy" | |||
team_filter = "SAME" | |||
save_threshold = 1000 | |||
id_no_purchase = "DidNotPurchase" | |||
purchases = | |||
[ | |||
{ | |||
items = [ ] | |||
id = "SetupFlashbang1" | |||
}, | |||
{ | |||
items = [ ] | |||
id = "SetupSmokeAndFlashbang1" | |||
} | |||
] | |||
}, | |||
{ | |||
// this node will always go through as long as there's at least 2 bots on the team. | |||
type = "action_coordinated_buy" | |||
team_filter = "SAME" | |||
save_threshold = 0 | |||
id_no_purchase = "DidNotPurchase" | |||
purchases = | |||
[ | |||
{ | |||
items = [ ] | |||
// ids don't need to be unique, they are saved as a local variable on | |||
// the bot that bought them. | |||
id = "SetupNoGrenades" | |||
}, | |||
{ | |||
items = [ ] | |||
id = "SetupNoGrenades" | |||
} | |||
] | |||
} | |||
] | |||
} | |||
}, | |||
{ | |||
type = "action_set_global_counter" | |||
input_name = "'WaitForCoordinatedBuy'" | |||
input_value = 0 | |||
} | |||
] | |||
} | |||
}, | |||
// Wait 4 seconds for the coordinated buy to be done. | |||
{ | |||
type = "decorator_run_once" | |||
child = | |||
{ | |||
type = "decorator_wait_success" | |||
timeout = 4 | |||
child = | |||
{ | |||
type = "action_compare_global_counter" | |||
input_name = "'WaitForCoordinatedBuy'" | |||
input_value = 0 | |||
} | |||
} | |||
}, | |||
// alternative method for buying items that avoids the "duplicate buy" bug | |||
{ | |||
type = "decorator_run_once" | |||
child = | |||
{ | |||
type = "selector" | |||
children = | |||
[ | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupFlashbang1" | |||
negated = 1 | |||
child = | |||
{ | |||
// bot will try to buy all of the items in this node | |||
type = "action_custom_buy" | |||
item_aliases = | |||
[ | |||
"flashbang" | |||
] | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmokeAndFlashbang1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_custom_buy" | |||
item_aliases = | |||
[ | |||
"smokegrenade", | |||
"flashbang" | |||
] | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmoke1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_custom_buy" | |||
item_aliases = | |||
[ | |||
"smokegrenade" | |||
] | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmoke2" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_custom_buy" | |||
item_aliases = | |||
[ | |||
"smokegrenade" | |||
] | |||
} | |||
} | |||
] | |||
} | |||
}, | |||
// now check for the ids with "condition_is_empty" | |||
// to make specific bots do the setups you want! | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupFlashbang1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 -4.096003 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmokeAndFlashbang1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 -175.519958 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmoke1" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 -89.543930 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupSmoke2" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_look_at" | |||
input_angles = "0.000000 90.496025 0.000000" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "SetupNoGrenades" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_jump" | |||
} | |||
}, | |||
{ | |||
type = "condition_is_empty" | |||
input = "DidNotPurchase" | |||
negated = 1 | |||
child = | |||
{ | |||
type = "action_crouch" | |||
} | |||
}, | |||
// be sure to make a backup behavior | |||
// if there's a possibility that none of the coodrinated buys can be fulfilled | |||
{ | |||
type = "subtree" | |||
file = "scripts/ai/modules/bt_look_around.kv3" | |||
name = "LookAround" | |||
} | |||
] | |||
} | |||
} | |||
} | |||
</source> | |||
}} | |||
=== Default Deathmatch Behavior Tree === | === Default Deathmatch Behavior Tree === | ||
Line 972: | Line 1,474: | ||
}} | }} | ||
{{csgo-navbox}} | |||
[[Category:Counter-Strike: Global Offensive]] | [[Category:Counter-Strike: Global Offensive]] |
Latest revision as of 07:18, 20 May 2025


It is covered here for historical and technical reference.

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.
Behavior trees are text files which 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 their 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 Behavior Trees
There are some ways to use behavior tree files in-game.
- The ConVar
mp_bot_ai_bt
determines a behavior tree that all respawning bots on the server (both T and CT!) will use. Its value is a string representing the path to a .kv3 file, starting fromcsgo/
. If there are player respawns, killing a bot is sufficient to make them use a newly set file, otherwise restarting the game or round withmp_restartgame 1
orendround
should also do it. If a behavior tree file has been modified and if a bot had already loaded it, it is also necessary to flush the loaded files usingmp_bot_ai_bt_clear_cache
to make changes apply. As soon as a bot (re)spawns, there will be error messages containing[AI BT]
if any used file has errors and the bot does not use any behavior tree.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
Co-op Strike, info_enemy_terrorist_spawn entities have the KeyValue
behavior_tree_file
that can be used to specify a behavior tree file 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 (see above). |
mp_bot_ai_bt_clear_cache
|
ConCommand | Clears the cache for behavior tree files. |
File Format
To briefly summarize the file syntax:
- The game ignores consecutive whitespace characters and does not differentiate between them, so a file can be written in one line, but please don't. Newlines are used to increase readability, see the examples below.
- KeyValues are limited by curly brackets
{ }
and contain key-value pairs with the formatkey = value key = value ...
where values can have different data types, such as integer (1
), float (1.0
), string ("1"
) and especially KeyValues themselves. - Arrays are limited by squared brackets
[ ]
and their elements (no matter the data type) are separated by commas,
, for example:[ {...}, {...}, {...} ]
- Inline comments start with
//
, multiline comments start with/*
and end with*/
.
The first line of a .kv3
file is always a header specifying the KV3 version. For , use this header:
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
The rest of the file is one KeyValues, enclosed by curly brackets { }
. For Behavior Tree files, this KeyValues contains the keys config
and root
.
The config
key determines a general bot configuration with the bot's "skill". Its value should contain the full path to such a file, including the file extension.
Valve has already provided three such config files, namely
"scripts/ai/deathmatch/bt_config.kv3"
,"scripts/ai/guardian/bt_config.kv3"
and"scripts/ai/coop/bt_config.kv3"
.
Generally speaking, we must add the following:
<!-- 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"
}
Next, add the root
key with a Behavior Node as value. The game "executes" the root node regularly.[Clarify]
<!-- 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 Nodes are once again KeyValues but with a type
key whose value determines its node type. There are different types of nodes, namely
- direct #Actions that the bot will perform, such as
type = "action_pull_trigger", "action_use", "action_jump"
or"action_wait"
, - four #Combinators that determine how multiple nodes are executed, for example in a
sequence
orparallel
, - #Decorators mostly for generating/getting/storing data, such as
decorator_sensor
,decorator_random_int
ordecorator_memory
. - #Conditions for comparing data and branching.
<!-- 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>
}
}
}
The format of each Behavior Node depends on its type, because most Behavior Nodes use their specific parameter names, value types and uses.

Some nodes allow to define new global variables. Their input names should contain both double and single quotes:
type = "action_set_global_counter"
input_name = "'Test'" // Note those quotation marks.
input_value = 1
Some nodes support convars as input. Their names should start with @
symbol:
type = "action_choose_bomb_site_area"
input = "@mp_guardian_target_site"
output = "BombSiteArea"
Bot Configuration
These parameters define general bot reaction and combat behavior for every bot skill ( low, fair, normal, tough, hard, very_hard, expert, elite, 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.
Every node has a type
parameter which determines its functionality and which other parameters it considers. Parameters might have a default value, in which case it is optional to specify them; if a parameter doesn't have a default value and if it is not specified, there will be an error message in the console.

source/input/input_location
but only one of them is an actual parameter) or ones with typos within their names.Most nodes can have the following parameters that are omitted in the following lists:
child
- Supported by #Decorators and #Conditions. Its value should be another node which will or will not be executed, depending on node type and context. By default most decorators execute their child function automatically, unless otherwise specified.children
- Supported by #Combinators (Exceptsubtree
!). Its value should be an array of nodes, similar tochild
.negated
- Supported by #Decorators and #Conditions. Set to 1 to invert its return value (success or not), for example a node of the typecondition_is_empty
will succeed if something does not exist; If this node has the parameternegated = 1
, it will instead succeed if something does exist.
- Complete node parameters and descriptions, check for inaccuracies.
- Define variable types for parameters.
- Explain all parameters.
Combinators
Type | Parameters | Description |
---|---|---|
parallel
|
int_flag? succeed_after_first
|
Executes all child functions in one script tick until a child function returns false condition or until all child functions return true condition. "succeed_after_first" ends execution after first child function returns true condition. |
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, array params
|
Executes behavior tree from file. "params" array goes in "key", "value" format in brackets. e.g.
|
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. |
decorator_dec_global_counter
|
variable input_name
|
Decreases input variable by 1. |
decorator_find_utility_strat
|
vector input_location, int distance_threshold, vector output_location, vector output_angles, string output_weapon
|
Unused. |
decorator_game_event
|
![]() | |
decorator_hiding_spot_service
|
variable? domain (GroupID or AllBots), vector output_hiding_spot, int distance_threshold, int expiration_time
|
Keeps track of visible hiding spots. Used in conjunction with action_move_to to make a bot check hiding spots as they move throughout the level. ![]() domain . Bots sharing the same domain will not check hiding spots for some time if another bot has already checked them. |
decorator_invert
|
Unused. Valve scripts usually use negated = 1 flag in node parameters. However, action nodes don't support negated , so this node can be used as a workaround.
| |
decorator_maybe
|
float chance
|
Executes child function with specified chance (1 - 100%, 0.7 - 70% etc).
|
decorator_memory
|
vector? input, memory? output, string output_domain
|
Saves entities in the input array to output array.
|
decorator_need_healing
|
int health_threshold
|
Executes child function if bot health is lower than health_threshold .
|
decorator_picker_blocked_by_smoke
|
vector? input, int distance_threshold
|
Removes entities from the input array that are not covered by smokes.
|
decorator_picker_dedup
|
vector? input, memory? against, int distance_threshold
|
Removes entities from the input array that are within distance_threshold to entities in against array.
|
decorator_picker_grenade_type
|
array input, array types (EXPLOSIVE, FLASH, FIRE, DECOY, SMOKE, SENSOR, SNOWBALL)
|
Removes grenade entities from the input array that are not in types array.
|
decorator_picker_max_score
|
array input
|
Picks the first entity in the input array. By default entities are sorted by the order they appear on the server. ![]() decorator_ranker_dist can be used before this node to pick the closest entity instead. |
decorator_picker_nearby
|
array input, int cutoff_distance
|
Removes entities from the input array that are beyond the cutoff_distance .
|
decorator_picker_random_by_distance
|
array input, int distance_min, int distance_max
|
Removes entities from the input array based on the distance distribution. Entities closer to distance_min are less likely to get removed, while entities closer to distance_max are more likely to get removed.
|
decorator_picker_reaction_time
|
variable? input_domain (GroupID or AllBots), memory? input, vector? output
|
Executes child function after delay, that depends of bot skill reaction time? |
decorator_picker_visible
|
array input, int_flag? check_fov
|
Removes obscured entities from input array. Will check bot FOV only, unless check_fov is set to 0.
|
decorator_picker_weight_as_distance
|
array input
|
Removes the entities whose distance to the bot running the tree is greater than the entities "weight". "weight" is an internal notion mostly used for hearing sounds. It should only be used when sensing "NOISE". |
decorator_random_approach_point
|
vector output
|
Returns a random visible point in navmesh that's ideally an "entrance" to the area the bot is currently in. ![]() "...\modules\bt_look_around.kv3" . |
decorator_random_int
|
int min, int max, int output
|
Returns a random int in the range of min to max .
|
decorator_ranker_dist
|
array input
|
Sorts entities in the input array based 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 function in loop. Stops looping if child function fails. | |
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, variable? domain (e.g. domain = "'CoordinatedBuy'")
|
Executes child function limited amount of times. domain can be set to make only one bot run it.
|
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 entities detected by sensor. ![]() team_filter set to "ENEMY" it incorrectly returns terrorist grenades, regardless of team the bot is on. "OPPOSITE" filter can be used as a workaround. |
decorator_set_barrier
|
domain input_domain, variable input_name, navzone? input_location
|
Unused. |
decorator_set_reaction_time
|
array? input
|
Executes child function after delay, that depends of bot skill reaction time? |
decorator_succeed
|
Node succeeds (returns true condition) even if child function failed. | |
decorator_tag_entity
|
array input, array output, flag operation_type (BT_DECORATOR_TAG_ENTITY_CLEAR, BT_DECORATOR_TAG_ENTITY_SET), int expiration_time
|
Adds or removes the first entity in input array to output array for specified amount of seconds. input entity will be tagged as long as the child function is being executed.
|
decorator_tag_threshold
|
array entity_input, array tagged_entities_input, int amount, flag check_type (BT_DECORATOR_TAG_THRESHOLD_AT_MOST, BT_DECORATOR_TAG_THRESHOLD_AT_LEAST)
|
Compares entities in entity_input against entities in tagged_entities_input using check_type . If it passes, executes child function. (If at least/most x entities from entity_input are in tagged_entities_input , execute child function)
|
decorator_token_service
|
variable? domain, string output_token_name, string output_token_domain, function config(array tokens, array assignments)
|
In ![]() |
decorator_try_lock
|
variable? domain (e.g. domain = "'DefuseIfCovered'")
|
Locks the domain for the duration of executing the child function, meaning only one bot at a time will be executing this.
|
decorator_wait_success
|
int timeout
|
Waits for child to return true or finish executing. If child never returns true, stops waiting after timeout seconds.
|
Conditions
Type | Parameters | Description |
---|---|---|
condition_barrier
|
domain input_domain, variable input_name, navzone? input_location
|
Unused. |
condition_distance_less
|
vector input, int distance_threshold_min, int distance_threshold_max
|
Returns true, if distance to input position is less than somewhere between the distance_threshold_min and distance_threshold_max .
|
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 weapon classname.
|
condition_is_weapon_suitable
|
vector input, string weapon
|
Unused. Returns true, if weapon with weapon classname is suitable at the range to the input entity. Snipers are not considered suitable below 160.0 units, while shotguns are not considered suitable past 600.0 units. ![]() condition_is_weapon_equipped .[confirm] |
condition_out_of_ammo
|
Returns true, if active weapon is out of ammo. | |
condition_owns_item
|
string item, array items_one_of
|
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, e.g. { type = "action_acquire_items" items = [ "weapon_awp" ] } .
|
action_aim
|
vector input, int_flag? acquire_only, flag? ready
|
Rotates bot towards input point. Uses aim penalties set in the bot config. Can overshoot the target. Forces the bot to scope with scoped weapons. ![]() acquire_only should be set to 1 if you don't intend to track your target.![]() |
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 ![]() |
action_attack
|
vector input, flag? output (e.g. output = "Attacking"), flag? ready (e.g. ready = "AimReady")
|
Forces bot to fire. Uses spray duration settings from bot config. ![]() ready flag usually should be controlled automatically with action_aim node. |
action_buy
|
Bot buys random 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 a random navmesh tile from the input navzones and returns its center as a vector position. |
action_choose_random_waypoint_within_radius
|
vector origin, vector output, float radius
|
Chooses a random navmesh tile within specified origin and radius and returns a random point within it as a vector position. |
action_choose_team_spawn_area
|
navzone? output
|
Gets all navzones with spawnpoints? |
action_combat_positioning
|
vector input, flag? is_attacking
|
Bot side steps to dodge attacks or closes distance on the enemy. Uses settings from the bot config. ![]() is_attacking flag usually should be controlled automatically with action_attack node. |
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),
|
Forces all of the bots that match the team_filter to buy items from purchases array and assigns appropriate id to them. Node will return false and be skipped if there's not enough bots in the specified team to fulfill all purchases . Bots that do not make a purchase are assigned with id_no_purchase . You can check what id bots are assigned with by using condition_is_empty . ![]() team_filter make purchases! Use in conjunction with decorator_run_once and domain set to something.![]() team_filter are still assigned "id_no_purchase" erroneously.![]() purchases array inherit items to purchase from bots before them. See #Coordinated Buy (Alternative Buying Method) |
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
|
float max_range, vector output
|
Unused. Returns a random hiding spot within search radius. ![]() |
action_inspect_current_weapon
|
Unused. Forces bot to play inspect animation. | |
action_jump
|
Forces bot to jump. | |
action_look_at
|
vector input_angles, vector input_location
|
Rotates the bot to specified input_angle , like setang. If omitted, rotates the bot so that it looks to the coordinates input_location . Otherwise, an error occurs.
|
action_move_to
|
vector destination,
|
Forces bot to move at destination point. ![]() ![]() |
action_parachute_positioning
|
Forces bot to move diagonally ignoring the navmesh. Changes direction every few seconds. | |
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
|
float wait_time_min, float 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 ![]() |
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 ![]() |
Vector LastCoopSpawnPointLocation
|
Origin point of last spawn point spawned that bot in ![]() |
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
To get these behavior trees running, see #Using Behavior Trees.
Simple Actions
One of the simplest behavior trees that you can give a bot is the following. When the bot spawns, it will keep jumping on the same spot and not react on anything. Like in the old days.
<!-- 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 = "action_jump"
}
}
To make it a bit more useful, the bot should shoot at the same time. This can be done with a parallel
node:
<!-- 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 = "parallel"
children =
[
{
type = "action_pull_trigger"
},
{
type = "action_jump"
}
]
}
}
But this bot still won't stop pulling the trigger even though it is out of ammunition. To address this, we can add a condition_out_of_ammo
node. The following bot will jump and shoot continuously but stop as soon as it has no more ammunition:
<!-- 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 = "condition_out_of_ammo"
negated = 1
child =
{
type = "parallel"
children =
[
{
type = "action_pull_trigger"
},
{
type = "action_jump"
}
]
}
}
}
Coordinated Buy
This simple script showcases how to use action_coordinated_buy
node to make your bots strategize.
<!-- 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_repeat"
child =
{
type = "selector"
children =
[
{
// with the "domain" set only one bot on the server will run this per round.
type = "decorator_run_once"
domain = "'CoordinatedBuy'"
child =
{
type = "sequencer"
children =
[
{
// set a global variable to make our teammates wait for the coordinated buy.
type = "action_set_global_counter"
input_name = "'WaitForCoordinatedBuy'"
input_value = 1
},
{
type = "action_wait"
wait_time_min = 3
wait_time_max = 3
},
{
// continue with the sequencer even if we weren't able to coordinate any buy.
type = "decorator_succeed"
child =
{
// when used in a selector coordinated buys that can't be fulfilled will be skipped.
type = "selector"
children =
[
{
type = "action_coordinated_buy"
// team that should coordinte a buy. Works the same as in "decorator_sensor".
team_filter = "SAME"
// all of the bots on the specified team must have at least this much money.
// should be set to the minimum amount of money required to purchase the needed items
// in this case the minimum amount of money would be 500, as that's needed
// to buy the "SetupSmokeAndFlashbang1".
save_threshold = 2000
// advised to only be used in fully pve modes,
// due to being assigned to the other team as well.
id_no_purchase = "DidNotPurchase"
// array with purchases.
// in this case it needs at least 4 bots on the team in order to go through.
purchases =
[
{
// items to buy.
// (WARNING: due to the previously mentioned bug it is
// highly advised to use alternative methods for buying items).
items = [ "weapon_flashbang" ]
// a variable with this name will be saved on that bot.
// check with "condition_is_empty".
id = "SetupFlashbang1"
},
{
items = [ "weapon_smokegrenade", "weapon_flashbang" ]
id = "SetupSmokeAndFlashbang1"
},
{
items = [ "weapon_smokegrenade" ]
id = "SetupSmoke1"
},
{
items = [ "weapon_smokegrenade" ]
id = "SetupSmoke2"
}
]
},
{
// this node needs less bots and less money to go through.
type = "action_coordinated_buy"
team_filter = "SAME"
save_threshold = 1000
id_no_purchase = "DidNotPurchase"
purchases =
[
{
items = [ "weapon_flashbang" ]
id = "SetupFlashbang1"
},
{
items = [ "weapon_smokegrenade", "weapon_flashbang" ]
id = "SetupSmokeAndFlashbang1"
}
]
},
{
// this node will always go through as long as there's at least 2 bots on the team.
type = "action_coordinated_buy"
team_filter = "SAME"
save_threshold = 0
id_no_purchase = "DidNotPurchase"
purchases =
[
{
// items array can be completely empty, id is still going to be assigned.
items = [ ]
// ids don't need to be unique, they are saved as a local variable on
// the bot that bought them.
id = "SetupNoGrenades"
},
{
items = [ ]
id = "SetupNoGrenades"
}
]
}
]
}
},
{
type = "action_set_global_counter"
input_name = "'WaitForCoordinatedBuy'"
input_value = 0
}
]
}
},
// Wait 4 seconds for the coordinated buy to be done.
{
type = "decorator_run_once"
child =
{
type = "decorator_wait_success"
timeout = 4
child =
{
type = "action_compare_global_counter"
input_name = "'WaitForCoordinatedBuy'"
input_value = 0
}
}
},
// now check for the ids with "condition_is_empty"
// to make specific bots do the setups you want!
{
type = "condition_is_empty"
input = "SetupFlashbang1"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 -4.096003 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupSmokeAndFlashbang1"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 -175.519958 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupSmoke1"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 -89.543930 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupSmoke2"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 90.496025 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupNoGrenades"
negated = 1
child =
{
type = "action_jump"
}
},
{
type = "condition_is_empty"
input = "DidNotPurchase"
negated = 1
child =
{
type = "action_crouch"
}
},
// be sure to make a backup behavior
// if there's a possibility that none of the coodrinated buys can be fulfilled
{
type = "subtree"
file = "scripts/ai/modules/bt_look_around.kv3"
name = "LookAround"
}
]
}
}
}
|
Coordinated Buy (Alternative Buying Method)
Alternative buying method that avoids the duplicated item buying bug.
<!-- 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_repeat"
child =
{
type = "selector"
children =
[
{
// with the "domain" set only one bot on the server will run this per round.
type = "decorator_run_once"
domain = "'CoordinatedBuy'"
child =
{
type = "sequencer"
children =
[
{
// set a global variable to make our teammates wait for the coordinated buy.
type = "action_set_global_counter"
input_name = "'WaitForCoordinatedBuy'"
input_value = 1
},
{
type = "action_wait"
wait_time_min = 3
wait_time_max = 3
},
{
// continue with the sequencer even if we weren't able to coordinate any buy.
type = "decorator_succeed"
child =
{
// when used in a selector coordinated buys that can't be fulfilled will be skipped.
type = "selector"
children =
[
{
type = "action_coordinated_buy"
// team that should coordinte a buy. Works the same as in "decorator_sensor".
team_filter = "SAME"
// all of the bots on the specified team must have at least this much money.
// should be set to the minimum amount of money required to purchase the needed items
// in this case the minimum amount of money would be 500, as that's needed
// to buy the "SetupSmokeAndFlashbang1".
save_threshold = 2000
// advised to only be used in fully pve modes,
// due to being assigned to the other team as well.
id_no_purchase = "DidNotPurchase"
// array with purchases.
// in this case it needs at least 4 bots on the team in order to go through.
purchases =
[
{
// instead of setting up the items here
// we'll buy them later
items = [ ]
// a variable with this name will be saved on that bot.
// check with "condition_is_empty".
id = "SetupFlashbang1"
},
{
items = [ ]
id = "SetupSmokeAndFlashbang1"
},
{
items = [ ]
id = "SetupSmoke1"
},
{
items = [ ]
id = "SetupSmoke2"
}
]
},
{
// this node needs less bots and less money to go through.
type = "action_coordinated_buy"
team_filter = "SAME"
save_threshold = 1000
id_no_purchase = "DidNotPurchase"
purchases =
[
{
items = [ ]
id = "SetupFlashbang1"
},
{
items = [ ]
id = "SetupSmokeAndFlashbang1"
}
]
},
{
// this node will always go through as long as there's at least 2 bots on the team.
type = "action_coordinated_buy"
team_filter = "SAME"
save_threshold = 0
id_no_purchase = "DidNotPurchase"
purchases =
[
{
items = [ ]
// ids don't need to be unique, they are saved as a local variable on
// the bot that bought them.
id = "SetupNoGrenades"
},
{
items = [ ]
id = "SetupNoGrenades"
}
]
}
]
}
},
{
type = "action_set_global_counter"
input_name = "'WaitForCoordinatedBuy'"
input_value = 0
}
]
}
},
// Wait 4 seconds for the coordinated buy to be done.
{
type = "decorator_run_once"
child =
{
type = "decorator_wait_success"
timeout = 4
child =
{
type = "action_compare_global_counter"
input_name = "'WaitForCoordinatedBuy'"
input_value = 0
}
}
},
// alternative method for buying items that avoids the "duplicate buy" bug
{
type = "decorator_run_once"
child =
{
type = "selector"
children =
[
{
type = "condition_is_empty"
input = "SetupFlashbang1"
negated = 1
child =
{
// bot will try to buy all of the items in this node
type = "action_custom_buy"
item_aliases =
[
"flashbang"
]
}
},
{
type = "condition_is_empty"
input = "SetupSmokeAndFlashbang1"
negated = 1
child =
{
type = "action_custom_buy"
item_aliases =
[
"smokegrenade",
"flashbang"
]
}
},
{
type = "condition_is_empty"
input = "SetupSmoke1"
negated = 1
child =
{
type = "action_custom_buy"
item_aliases =
[
"smokegrenade"
]
}
},
{
type = "condition_is_empty"
input = "SetupSmoke2"
negated = 1
child =
{
type = "action_custom_buy"
item_aliases =
[
"smokegrenade"
]
}
}
]
}
},
// now check for the ids with "condition_is_empty"
// to make specific bots do the setups you want!
{
type = "condition_is_empty"
input = "SetupFlashbang1"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 -4.096003 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupSmokeAndFlashbang1"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 -175.519958 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupSmoke1"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 -89.543930 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupSmoke2"
negated = 1
child =
{
type = "action_look_at"
input_angles = "0.000000 90.496025 0.000000"
}
},
{
type = "condition_is_empty"
input = "SetupNoGrenades"
negated = 1
child =
{
type = "action_jump"
}
},
{
type = "condition_is_empty"
input = "DidNotPurchase"
negated = 1
child =
{
type = "action_crouch"
}
},
// be sure to make a backup behavior
// if there's a possibility that none of the coodrinated buys can be fulfilled
{
type = "subtree"
file = "scripts/ai/modules/bt_look_around.kv3"
name = "LookAround"
}
]
}
}
}
|
Default Deathmatch Behavior Tree
Found in csgo/scripts/ai/bt_default.kv3
. This file is a good base to write an own bot.
<!-- 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"
}
]
}
]
}
}
]
}
}
}
}
|
|