2024-12-11 11:28:08 +01:00

3766 lines
104 KiB
Plaintext

#include common_scripts\utility;
//#include common_scripts\shared;
#include maps\mp\_utility;
#include maps\mp\gametypes\_gamelogic;
#include maps\mp\bots\_bots_ks;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_strategy;
#include maps\mp\bots\_bots_personality;
#include maps\mp\bots\_bots_fireteam;
//========================================================
// main
//========================================================
main()
{
if ( is_aliens() )
return;
if( IsDefined( level.createFX_enabled ) && level.createFX_enabled )
return;
if ( level.script == "mp_character_room" )
return;
// This is called directly from native code on game startup
// The particular gametype's main() is called from native code afterward
setup_callbacks();
setup_personalities();
// Enable badplaces in destructibles
level.badplace_cylinder_func = ::badplace_cylinder;
level.badplace_delete_func = ::badplace_delete;
// Init bot killstreak script
maps\mp\bots\_bots_ks::bot_killstreak_setup();
// Init bot loadout data
// Needs to be after _bots_ks::bot_killstreak_setup
maps\mp\bots\_bots_loadout::init();
// Needs to be after _bots_loadout::init()
level thread init();
/*
/#
maps\mp\bots\_bots_gametype_dom::empty_function_to_force_script_dev_compile();
maps\mp\bots\_bots_gametype_sr::empty_function_to_force_script_dev_compile();
maps\mp\bots\_bots_gametype_mugger::empty_function_to_force_script_dev_compile();
maps\mp\bots\_bots_gametype_conf::empty_function_to_force_script_dev_compile();
maps\mp\bots\_bots_gametype_sd::empty_function_to_force_script_dev_compile();
maps\mp\bots\_bots_gametype_blitz::empty_function_to_force_script_dev_compile();
#/
*/
}
//========================================================
// setup_callbacks
//========================================================
setup_callbacks()
{
// Setup level.bot_funcs callback function table
level.bot_funcs = [];
// Bot System functions
level.bot_funcs["bots_spawn"] = ::spawn_bots;
level.bot_funcs["bots_add_scavenger_bag"] = ::bot_add_scavenger_bag;
level.bot_funcs["bots_add_to_level_targets"] = ::bot_add_to_bot_level_targets;
level.bot_funcs["bots_remove_from_level_targets"] = ::bot_remove_from_bot_level_targets;
level.bot_funcs["bots_make_entity_sentient"] = ::bot_make_entity_sentient;
// Bot entity functions
level.bot_funcs["think"] = ::bot_think;
level.bot_funcs["on_killed"] = ::on_bot_killed;
level.bot_funcs["should_do_killcam"] = ::bot_should_do_killcam;
level.bot_funcs["get_attacker_ent"] = ::bot_get_known_attacker;
level.bot_funcs["should_pickup_weapons"] = ::bot_should_pickup_weapons;
level.bot_funcs["on_damaged"] = ::bot_damage_callback;
level.bot_funcs["gametype_think"] = ::default_gametype_think;
level.bot_funcs["leader_dialog"] = ::bot_leader_dialog;
level.bot_funcs["player_spawned"] = ::bot_player_spawned;
level.bot_funcs["should_start_cautious_approach"] = ::should_start_cautious_approach_default;
level.bot_funcs["know_enemies_on_start"] = ::bot_know_enemies_on_start;
level.bot_funcs["bot_get_rank_xp"] = ::bot_get_rank_xp;
level.bot_funcs["ai_3d_sighting_model"] = ::bot_3d_sighting_model;
level.bot_funcs["dropped_weapon_think"] = ::bot_think_seek_dropped_weapons;
level.bot_funcs["dropped_weapon_cancel"] = ::should_stop_seeking_weapon;
level.bot_funcs["crate_can_use"] = ::crate_can_use_always;
level.bot_funcs["crate_low_ammo_check"] = ::crate_low_ammo_check;
level.bot_funcs["crate_should_claim"] = ::crate_should_claim;
level.bot_funcs["crate_wait_use"] = ::crate_wait_use;
level.bot_funcs["crate_in_range"] = ::crate_in_range;
level.bot_funcs["post_teleport"] = ::bot_post_teleport;
level.bot_random_path_function = [];
level.bot_random_path_function["allies"] = ::bot_random_path_default;
level.bot_random_path_function["axis"] = ::bot_random_path_default;
level.bot_can_use_box_by_type["deployable_vest"] = ::bot_should_use_ballistic_vest_crate;
level.bot_can_use_box_by_type["deployable_ammo"] = ::bot_should_use_ammo_crate;
level.bot_can_use_box_by_type["scavenger_bag"] = ::bot_should_use_scavenger_bag;
level.bot_can_use_box_by_type["deployable_grenades"]= ::bot_should_use_grenade_crate;
level.bot_can_use_box_by_type["deployable_juicebox"]= ::bot_should_use_juicebox_crate;
level.bot_pre_use_box_of_type["deployable_ammo"] = ::bot_pre_use_ammo_crate;
level.bot_post_use_box_of_type["deployable_ammo"] = ::bot_post_use_ammo_crate;
level.bot_find_defend_node_func["capture"] = ::find_defend_node_capture;
level.bot_find_defend_node_func["capture_zone"] = ::find_defend_node_capture_zone;
level.bot_find_defend_node_func["protect"] = ::find_defend_node_protect;
level.bot_find_defend_node_func["bodyguard"] = ::find_defend_node_bodyguard;
level.bot_find_defend_node_func["patrol"] = ::find_defend_node_patrol;
// War (TDM) gametype serves as the default, so we use it to setup default gametype callbacks
maps\mp\bots\_bots_gametype_war::setup_callbacks();
// Should be after everything else, so fireteam mode can override specific callbacks if necessary
if ( bot_is_fireteam_mode() )
{
bot_fireteam_setup_callbacks();
}
}
//========================================================
// CodeCallback_LeaderDialog
//========================================================
CodeCallback_LeaderDialog( dialog, location )
{
if ( IsDefined( level.bot_funcs ) && IsDefined( level.bot_funcs["leader_dialog"] ) )
{
self [[ level.bot_funcs["leader_dialog"] ]]( dialog, location );
}
}
//========================================================
// init
//========================================================
init()
{
thread monitor_smoke_grenades();
thread bot_triggers();
initBotLevelVariables();
/#
level thread bot_debug_drawing();
#/
if( !shouldSpawnBots() )
return;
refresh_existing_bots();
botAutoConnectValue = BotAutoConnectEnabled();
if ( botAutoConnectValue > 0 )
{
setMatchData( "hasBots", true );
if ( bot_is_fireteam_mode() )
{
// Fireteam mode : spawn bots on each team with specific loadouts
level thread bot_fireteam_init();
// Init Fireteam commander logic - this needs to be called after Callback_StartGameType's init() functions.
level thread maps\mp\bots\_bots_fireteam_commander::init();
}
else// if ( botAutoConnectValue == 1 )
{
// "fill_open" : drop and spawn bots as needed
level thread bot_connect_monitor();
}
}
}
//========================================================
// initVariables
//========================================================
initBotLevelVariables()
{
if ( !IsDefined(level.crateOwnerUseTime) )
{
level.crateOwnerUseTime = 500;
}
if ( !IsDefined(level.crateNonOwnerUseTime) )
{
level.crateNonOwnerUseTime = 3000;
}
// Time it takes (after losing track of an enemy) for a bot to consider himself "out of combat"
level.bot_out_of_combat_time = 3000;
// Weapon that bots will use to shoot down helicopters
level.bot_respawn_launcher_name = "iw6_panzerfaust3";
// Weapon to use as absolute fallback
level.bot_fallback_weapon = "iw6_knifeonly";
level.zoneCount = GetZoneCount();
initBotMapExtents();
}
initBotMapExtents()
{
if ( IsDefined(level.teleportGetActiveNodesFunc) )
all_nodes = [[level.teleportGetActiveNodesFunc]]();
else
all_nodes = GetAllNodes();
level.bot_map_min_x = 0;
level.bot_map_max_x = 0;
level.bot_map_min_y = 0;
level.bot_map_max_y = 0;
level.bot_map_min_z = 0;
level.bot_map_max_z = 0;
if ( all_nodes.size > 1 )
{
level.bot_map_min_x = all_nodes[0].origin[0];
level.bot_map_max_x = all_nodes[0].origin[0];
level.bot_map_min_y = all_nodes[0].origin[1];
level.bot_map_max_y = all_nodes[0].origin[1];
level.bot_map_min_z = all_nodes[0].origin[2];
level.bot_map_max_z = all_nodes[0].origin[2];
for ( i = 1; i < all_nodes.size; i++ )
{
node_origin = all_nodes[i].origin;
if ( node_origin[0] < level.bot_map_min_x )
level.bot_map_min_x = node_origin[0];
if ( node_origin[0] > level.bot_map_max_x )
level.bot_map_max_x = node_origin[0];
if ( node_origin[1] < level.bot_map_min_y )
level.bot_map_min_y = node_origin[1];
if ( node_origin[1] > level.bot_map_max_y )
level.bot_map_max_y = node_origin[1];
if ( node_origin[2] < level.bot_map_min_z )
level.bot_map_min_z = node_origin[2];
if ( node_origin[2] > level.bot_map_max_z )
level.bot_map_max_z = node_origin[2];
}
}
level.bot_map_center = ( (level.bot_map_min_x + level.bot_map_max_x)/2 , (level.bot_map_min_y + level.bot_map_max_y)/2 , (level.bot_map_min_z + level.bot_map_max_z)/2 );
// Needs to be the last line of initBotLevelVariables
level.bot_variables_initialized = true;
}
bot_post_teleport()
{
level.bot_variables_initialized = undefined;
level.bot_initialized_remote_vehicles = undefined;
initBotMapExtents();
maps\mp\bots\_bots_ks_remote_vehicle::remote_vehicle_setup();
}
//========================================================
// shouldSpawnBots
//========================================================
shouldSpawnBots()
{
return true;
}
refresh_existing_bots()
{
wait 1; // give level.players a chance to initialize between rounds
// If we switched sides, the bots will still exist in game but will have lost their think threads. So we need to restart them
foreach ( player in level.players )
{
if ( IsBot( player ) )
{
player.equipment_enabled = true;
player.bot_team = player.team;
player.bot_spawned_before = true;
player thread [[ level.bot_funcs["think"] ]]();
}
}
}
/#
bot_debug_drawing()
{
level endon( "game_ended" );
while( !bot_bots_enabled_or_added() )
wait(1.0);
level.defense_debug_structs = [];
for( i=0; i<10; i++ )
{
level.defense_debug_structs[level.defense_debug_structs.size] = SpawnStruct();
}
level.cur_num_defense_debug_structs = 0;
SetDevDvarIfUninitialized("bot_DebugPathToPlayer","");
for(;;)
{
draw_debug_type = GetDvar("bot_DrawDebugSpecial");
clear_defense_draw();
if ( draw_debug_type == "defend" )
{
bot_debug_draw_defense();
}
else if ( draw_debug_type == "entrances" )
{
bot_debug_draw_watch_nodes();
}
else if ( draw_debug_type == "defend_with_entrances" )
{
bot_debug_draw_defense();
bot_debug_draw_watch_nodes();
}
else if ( draw_debug_type == "key_entry_points" )
{
bot_debug_draw_cached_entrances();
}
else if ( draw_debug_type == "key_cached_paths" )
{
bot_debug_draw_cached_paths();
}
if ( GetDvar( "bot_DebugPathToPlayer" ) != "" && level.gameType == "war" )
{
SetDevDvar( "bot_DebugPathToPlayer", "" );
human = get_all_humans()[0];
if ( IsAlive(human) && human.team != "spectator" )
{
level.bot_debug_force_path_location = human.origin;
}
}
/*
// For testing bullet trace collision
if ( IsDefined(level.players[0]) && IsAlive(level.players[0]) )
{
start = level.players[0] GetEye();
player_forward = AnglesToForward(level.players[0] GetPlayerAngles());
player_right = AnglesToRight(level.players[0] GetPlayerAngles());
player_up = AnglesToUp(level.players[0] GetPlayerAngles());
end = start + player_forward * 100;
traceResult = BulletTrace( start, end, false, level.players[0], false, false, true );
bot_draw_cylinder(end - (0,0,2.5), 4, 5, 0.05, undefined, (0,1,0), true, 4);
// draw line from start to frac as blue, and frac to end as red
line_start = start - player_up;
line_end = end - player_up;
line_midpoint = line_start + (line_end - line_start) * traceResult["fraction"];
Line( line_start, line_midpoint, (0,0,1), 1.0, false );
Line( line_midpoint, line_end, (1,0,0), 1.0, false );
}
*/
wait(0.05);
}
}
clear_defense_draw()
{
for( i=0; i<level.cur_num_defense_debug_structs; i++ )
{
struct = level.defense_debug_structs[i];
if ( IsDefined(struct.defense_trigger) && struct.defense_trigger.classname != "trigger_radius" )
BotDebugDrawTrigger( false, level.defense_debug_structs[i].defense_trigger );
}
level.cur_num_defense_debug_structs = 0;
}
bot_debug_draw_defense()
{
assert(level.cur_num_defense_debug_structs == 0);
foreach( player in level.participants )
{
if ( !IsDefined( player.team ) )
continue;
if ( player.health > 0 && IsAI( player ) && player bot_is_defending() )
{
struct_for_defense = undefined;
for( i=0; i<level.cur_num_defense_debug_structs; i++ )
{
struct = level.defense_debug_structs[i];
same_defense_type = struct.defense_type == player.bot_defending_type;
same_defense_radius = IsDefined(struct.defense_radius) && IsDefined(player.bot_defending_radius) && (struct.defense_radius == player.bot_defending_radius);
same_defense_trigger = IsDefined(struct.defense_trigger) && IsDefined(player.bot_defending_trigger) && (struct.defense_trigger == player.bot_defending_trigger);
if ( same_defense_type && (same_defense_radius || same_defense_trigger) )
{
if ( bot_vectors_are_equal( struct.defense_center, player.bot_defending_center ) )
{
struct_for_defense = struct;
break;
}
}
}
if ( IsDefined(struct_for_defense) )
{
if ( struct_for_defense.defense_team != player.team )
{
struct_for_defense.cylinder_color = (1,0,1);
}
struct_for_defense.bots = array_add( struct_for_defense.bots, player );
}
else
{
new_defense_struct = level.defense_debug_structs[level.cur_num_defense_debug_structs];
level.cur_num_defense_debug_structs++;
// Create a struct for this defense type
if ( player.team == "allies" )
{
new_defense_struct.cylinder_color = (0,0,1); // allies are blue
}
else if ( player.team == "axis" )
{
new_defense_struct.cylinder_color = (1,0,0); // axis are red
}
new_defense_struct.defense_team = player.team;
new_defense_struct.defense_type = player.bot_defending_type;
new_defense_struct.defense_radius = player.bot_defending_radius;
new_defense_struct.defense_trigger = player.bot_defending_trigger;
new_defense_struct.defense_center = player.bot_defending_center;
new_defense_struct.defense_nodes = player.bot_defending_nodes;
new_defense_struct.bots = [];
new_defense_struct.bots = array_add( new_defense_struct.bots, player );
}
}
}
for( i=0; i<level.cur_num_defense_debug_structs; i++ )
{
struct = level.defense_debug_structs[i];
if ( IsDefined(struct.defense_nodes) )
{
// Draw nodes
foreach( node in struct.defense_nodes )
bot_draw_cylinder(node.origin, 10, 10, 0.05, undefined, struct.cylinder_color, true, 4);
}
if ( IsDefined(struct.defense_trigger) )
{
// Draw trigger
if ( struct.defense_trigger.classname != "trigger_radius" )
BotDebugDrawTrigger(true, level.defense_debug_structs[i].defense_trigger, struct.cylinder_color, true);
else
bot_draw_cylinder(struct.defense_center, struct.defense_trigger.radius, struct.defense_trigger.height, 0.05, undefined, struct.cylinder_color, true);
}
if ( IsDefined(struct.defense_radius) )
{
// Draw cylinder
bot_draw_cylinder(struct.defense_center, struct.defense_radius, 75, 0.05, undefined, struct.cylinder_color, true);
}
foreach( bot in struct.bots )
{
lineStart = struct.defense_center + (0,0,25);
lineEnd = bot.origin + (0,0,25);
line_color = undefined;
if ( bot.team == "allies" )
line_color = (0,0,1);
else if ( bot.team == "axis" )
line_color = (1,0,0);
line( lineStart, lineEnd, line_color, 1, true );
if ( IsDefined(bot.bot_defending_override_origin_node) )
{
node_line_color = (max(line_color[0],0.3),max(line_color[1],0.3),max(line_color[2],0.3));
bot_draw_cylinder(bot.bot_defending_override_origin_node.origin, 10, 10, 0.05, undefined, node_line_color, true, 4);
line( bot.bot_defending_override_origin_node.origin, lineEnd, node_line_color, 1, true );
}
}
}
}
bot_debug_draw_watch_nodes()
{
foreach( player in level.participants )
{
if ( player.health > 0 && IsAI( player ) && IsDefined(player.watch_nodes) )
{
node_offset = (0,0,player GetPlayerViewHeight());
foreach( node in player.watch_nodes )
{
// green means "high priority watch"
// white means "low priority watch"
// color lerps between the two
entrance_color = (1 - node.watch_node_chance[player.entity_number], 1, 1 - node.watch_node_chance[player.entity_number]);
line( player.origin + node_offset, node.origin + (0,0,30), entrance_color, 1, true );
bot_draw_cylinder(node.origin + (0,0,30), 10, 10, 0.05, undefined, entrance_color, true, 4);
}
}
}
}
bot_debug_draw_cached_entrances()
{
if ( !IsDefined( level.entrance_indices ) || !IsDefined(level.entrance_points) )
return;
node_offset = (0,0,11);
standing_offset = (0,0,55);
crouching_offset = (0,0,40);
prone_offset = (0,0,15);
color_visible = (0,1,0);
color_not_visible = (1,0,0);
node_height = 13;
for ( i = 0; i < level.entrance_indices.size; i++ )
{
entrance_collection = level.entrance_points[level.entrance_indices[i]];
for ( j = 0; j < entrance_collection.size; j++ )
{
bot_draw_cylinder(entrance_collection[j].origin + node_offset, 10, node_height, 0.05, undefined, (0,1,0), true, 4);
// standing
line( level.entrance_origin_points[i] + standing_offset, entrance_collection[j].origin + node_offset + (0,0,node_height/2), color_visible, 1, true );
// crouching
color_to_use = color_not_visible;
if ( entrance_collection[j].crouch_visible_from[level.entrance_indices[i]] )
color_to_use = color_visible;
line( level.entrance_origin_points[i] + crouching_offset, entrance_collection[j].origin + node_offset + (0,0,node_height/2), color_to_use, 1, true );
// prone
color_to_use = color_not_visible;
if ( entrance_collection[j].prone_visible_from[level.entrance_indices[i]] )
color_to_use = color_visible;
line( level.entrance_origin_points[i] + prone_offset, entrance_collection[j].origin + node_offset + (0,0,node_height/2), color_to_use, 1, true );
}
// Note: this white cylinder is the botTarget, if it exists
bot_draw_cylinder(level.entrance_origin_points[i], 10, 75, 0.05, undefined, (1,1,1), true, 8);
}
}
bot_debug_draw_cached_paths()
{
if ( !IsDefined( level.entrance_indices ) )
return;
node_colors = [ (1,0,0), (0,1,0), (0,0,1), (1,0,1), (1,1,0), (0,1,1), (1,0.6,0.6), (0.6,1,0.6), (0.6,0.6,1), (0.1,0.1,0.1) ];
if ( !IsDefined(level.next_path_time) )
{
level.bot_debug_cur_first_index = level.entrance_indices.size - 2;
level.bot_debug_cur_second_index = level.entrance_indices.size - 1;
level.next_path_time = GetTime() - 1;
}
if ( GetTime() > level.next_path_time )
{
keep_trying = true;
while(keep_trying)
{
if ( level.bot_debug_cur_second_index == level.entrance_indices.size - 1 )
{
if ( level.bot_debug_cur_first_index == level.entrance_indices.size - 2 )
{
level.bot_debug_cur_first_index = 0;
level.bot_debug_cur_node_color = -1;
}
else
{
level.bot_debug_cur_first_index++;
}
level.bot_debug_cur_second_index = level.bot_debug_cur_first_index + 1;
}
else
{
level.bot_debug_cur_second_index++;
}
keep_trying = !IsDefined(level.precalculated_paths[level.entrance_indices[level.bot_debug_cur_first_index]][level.entrance_indices[level.bot_debug_cur_second_index]]);
}
level.bot_debug_cur_node_color = (level.bot_debug_cur_node_color + 1) % node_colors.size;
level.next_path_time = GetTime() + (15 * 1000);
}
// Note: these white cylinders are the botTargets, if they exist
bot_draw_cylinder(level.entrance_origin_points[level.bot_debug_cur_first_index], 10, 75, 0.05, undefined, (1,1,1), true, 8);
bot_draw_cylinder(level.entrance_origin_points[level.bot_debug_cur_second_index], 10, 75, 0.05, undefined, (1,1,1), true, 8);
foreach( node in level.precalculated_paths[level.entrance_indices[level.bot_debug_cur_first_index]][level.entrance_indices[level.bot_debug_cur_second_index]] )
{
bot_draw_cylinder(node.origin, 10, 13, 0.05, undefined, node_colors[level.bot_debug_cur_node_color], true, 4);
}
}
#/
//========================================================
// bot_player_spawned
//========================================================
bot_player_spawned()
{
self bot_set_loadout_class();
if( IsDefined( self.prev_personality ) )
{
self bot_set_personality( self.prev_personality );
self.prev_personality = undefined;
}
}
bot_set_loadout_class()
{
if ( !IsDefined( self.bot_class ) )
{
if ( !bot_gametype_chooses_class() )
{
while( !IsDefined(level.bot_loadouts_initialized) )
wait(0.05);
/#
debugClass = GetDvar( "bot_debugClass", "" );
if ( IsDefined( debugClass ) && debugClass != "" )
{
self.bot_class = debugClass;
return;
}
#/
if ( IsDefined( self.override_class_function ) )
{
self.bot_class = [[ self.override_class_function ]]();
}
else
{
self.bot_class = bot_setup_callback_class();
}
}
else
{
self.bot_class = self.class;
}
}
}
watch_players_connecting()
{
while(1)
{
level waittill("connected", player);
if ( !IsAI( player ) && (level.players.size > 0) ) // If playing a local listen server, ignore this for the first player to join
{
level.players_waiting_to_join = array_add(level.players_waiting_to_join, player);
childthread bots_notify_on_spawn(player);
childthread bots_notify_on_disconnect(player);
childthread bots_remove_from_array_on_notify(player);
}
}
}
bots_notify_on_spawn(player)
{
player endon("bots_human_disconnected");
while( !array_contains(level.players,player) )
{
wait(0.05);
}
player notify("bots_human_spawned");
}
bots_notify_on_disconnect(player)
{
player endon("bots_human_spawned");
player waittill("disconnect");
player notify("bots_human_disconnected");
}
bots_remove_from_array_on_notify(player)
{
player waittill_any("bots_human_spawned","bots_human_disconnected");
level.players_waiting_to_join = array_remove(level.players_waiting_to_join,player);
}
monitor_pause_spawning()
{
// The purpose of this function (and all the childthreads) is to pause bot spawning while a human player is in the process of joining the game
// So when a human connects, we add him to the "waiting" array, and he stays in there until he spawns in the game or disconnects
// As long as there are any humans in the queue, we don't want bots to be spawning and taking up the human players' spots
level.players_waiting_to_join = [];
childthread watch_players_connecting();
while(1)
{
if ( level.players_waiting_to_join.size > 0 )
level.pausing_bot_connect_monitor = true;
else
level.pausing_bot_connect_monitor = false;
wait(0.5);
}
}
//========================================================
// bot_can_join_team
//========================================================
bot_can_join_team( team )
{
// Checks ahead of time if _menus.gsc::setTeam() is going to approve of the bot's team choice
// Only necessary to return false if we are in a private match with team balancing (or SS/SL) and game mode has team limits active
if ( matchMakingGame() )
return true;
if( GetDvar( "squad_vs_squad" ) == "1" )
return true;
if ( !level.teamBased )
return true;
if ( maps\mp\gametypes\_teams::getJoinTeamPermissions( team ) )
return true;
return false;
}
//========================================================
// bot_connect_monitor
//========================================================
bot_connect_monitor( num_ally_bots, num_enemy_bots )
{
level endon( "game_ended" );
self notify( "bot_connect_monitor" );
self endon( "bot_connect_monitor" );
level.pausing_bot_connect_monitor = false;
childthread monitor_pause_spawning();
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 0.5 );
bot_connect_monitor_update_time = 1.5;
if ( !IsDefined( level.bot_cm_spawned_bots ) )
level.bot_cm_spawned_bots = false;
if ( !IsDefined( level.bot_cm_waited_players_time ) )
level.bot_cm_waited_players_time = 0;
if ( !IsDefined( level.bot_cm_human_picked ) )
level.bot_cm_human_picked = false;
if( ( GetDvar( "squad_vs_squad" ) == "1" ) )
{
while ( !( isDefined( level.squad_vs_squad_allies_client ) && isDefined( level.squad_vs_squad_axis_client ) ) )
{
wait 0.05;
}
wait 2.0; // wait a bit for additional humans to connect
}
if( ( GetDvar( "squad_match" ) == "1" ) )
{
while ( !isDefined( level.squad_match_client ) )
{
wait 0.05;
}
wait 2.0; // wait a bit for additional humans to connect
}
if( ( GetDvar( "squad_use_hosts_squad" ) == "1" ) )
{
while ( !isDefined( level.wargame_client ) )
{
wait 0.05;
}
wait 2.0; // wait a bit for additional humans to connect
}
for(;;)
{
if ( level.pausing_bot_connect_monitor )
{
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( bot_connect_monitor_update_time );
continue;
}
ignore_team_balance = IsDefined(level.bots_ignore_team_balance) || !level.teamBased;
// NOTE: if level.bots_ignore_team_balance is defined, these variables don't control the number of bots per team,
// but combined they do control the absolute number of bots. For example if level.bots_ignore_team_balance is defined, and
// max_ally_bots_absolute is 2 and max_enemy_bots_absolute is 8, the total number of bots you can have in the match is 10, but the
// ally team could have all 10 of them.
max_ally_bots_absolute = BotGetTeamLimit( 0 );
max_enemy_bots_absolute = BotGetTeamLimit( 1 );
difficultyAlly = BotGetTeamDifficulty( 0 );
difficultyEnemy = BotGetTeamDifficulty( 1 );
/#
// dont kick/spawn bots for team balance while this is on (for example if test clients have been added via the scr_testclients dvar)
if ( GetDvarInt("bot_DisableAutoConnect") )
{
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( bot_connect_monitor_update_time );
continue;
}
#/
team_ally = "allies"; // team_ally is the team that the human player is on
team_enemy = "axis"; // team_enemy is the enemy team (opposed to the team the human player is on)
clientCounts = bot_client_counts();
numHumans = cat_array_get( clientCounts, "humans" );
if ( numHumans > 1 )
{
hostPlayerTeam = bot_get_host_team();
if ( !matchMakingGame() && IsDefined(hostPlayerTeam) && hostPlayerTeam != "spectator" )
{
team_ally = hostPlayerTeam;
team_enemy = getOtherTeam( hostPlayerTeam );
}
else
{
cur_num_allies_players = cat_array_get( clientCounts, "humans_" + "allies");
cur_num_axis_players = cat_array_get( clientCounts, "humans_" + "axis");
if ( cur_num_axis_players > cur_num_allies_players )
{
// If there are more axis players, consider that to be team_ally (team that human players are on)
team_ally = "axis";
team_enemy = "allies";
}
}
}
else
{
humanPlayer = get_human_player();
if ( IsDefined( humanPlayer ) )
{
humanPlayer_team = humanPlayer bot_get_player_team();
if ( IsDefined(humanPlayer_team) && humanPlayer_team != "spectator" )
{
team_ally = humanPlayer_team;
team_enemy = getOtherTeam( humanPlayer_team );
}
}
}
// Count the max size of each team (in terms of client limits)
ally_team_size = bot_get_team_limit();
enemy_team_size = bot_get_team_limit();
if ( ally_team_size + enemy_team_size < bot_get_client_limit() )
{
// The client limit is odd, so add 1 to a team so we are still at the client limit
if ( ally_team_size < max_ally_bots_absolute )
ally_team_size++;
else if ( enemy_team_size < max_enemy_bots_absolute )
enemy_team_size++;
}
// Count current number of humans
cur_num_ally_humans = cat_array_get( clientCounts, "humans_" + team_ally);
cur_num_enemy_humans = cat_array_get( clientCounts, "humans_" + team_enemy);
cur_num_humans = cur_num_ally_humans + cur_num_enemy_humans;
// Count current number of spectators and try predict which team they will join
cur_num_spectators = cat_array_get( clientCounts, "spectator" );
cur_num_ally_spectators = 0;
cur_num_enemy_spectators = 0;
while( cur_num_spectators > 0 )
{
ally_team_has_room_for_another_spectator = (cur_num_ally_humans + cur_num_ally_spectators + 1 <= ally_team_size);
enemy_team_has_room_for_another_spectator = (cur_num_enemy_humans + cur_num_enemy_spectators + 1 <= enemy_team_size);
if ( ally_team_has_room_for_another_spectator && !enemy_team_has_room_for_another_spectator )
{
cur_num_ally_spectators++;
}
else if ( !ally_team_has_room_for_another_spectator && enemy_team_has_room_for_another_spectator )
{
cur_num_enemy_spectators++;
}
else if ( ally_team_has_room_for_another_spectator && enemy_team_has_room_for_another_spectator )
{
if ( (cur_num_spectators % 2) == 1 )
cur_num_ally_spectators++;
else
cur_num_enemy_spectators++;
}
cur_num_spectators--;
}
// Count current number of bots
cur_num_ally_bots = cat_array_get( clientCounts, "bots_" + team_ally);
cur_num_enemy_bots = cat_array_get( clientCounts, "bots_" + team_enemy);
cur_num_bots = cur_num_ally_bots + cur_num_enemy_bots;
if ( cur_num_bots > 0 )
level.bot_cm_spawned_bots = true;
waitingForHumanToChoose = false;
if ( !level.bot_cm_human_picked )
{
waitingForHumanToChoose = !bot_get_human_picked_team();
/#
// Exception for developer dvar bot_autoconnectdefault which is used to get default BotAutoConnectEnabled() in command line runs
if ( GetDvarInt( "bot_autoconnectdefault" ) && waitingForHumanToChoose )
waitingForHumanToChoose = false;
#/
if ( !waitingForHumanToChoose )
level.bot_cm_human_picked = true;
}
if ( waitingForHumanToChoose )
{
// In split screen mode (Local Play as its called now) never spawn in until a human has made a choice
splitScreenWait = !getDvarInt( "systemlink" ) && !getDvarInt( "onlinegame" );
// With lopsided team sizes dont spawn ANY bots until a human has picked their team or 10 seconds has passed and prematch is over
lopsidedWait = !ignore_team_balance && (max_enemy_bots_absolute != max_ally_bots_absolute) && !level.bot_cm_spawned_bots && ((level.bot_cm_waited_players_time < 10) || !gameFlag("prematch_done"));
if ( splitScreenWait || lopsidedWait )
{
level.bot_cm_waited_players_time += bot_connect_monitor_update_time;
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( bot_connect_monitor_update_time );
continue;
}
}
// Open bot slots per team is either the number of available spots (team size - number of humans) or the hard limit, whichever is lower
ally_bot_slots = INT(min(ally_team_size - cur_num_ally_humans - cur_num_ally_spectators,max_ally_bots_absolute));
enemy_bot_slots = INT(min(enemy_team_size - cur_num_enemy_humans - cur_num_enemy_spectators,max_enemy_bots_absolute));
// Make sure we are not leaving one team short because of reduced absolute limits being imbalanced
fillTeam = 1;
slotCount = (ally_bot_slots + enemy_bot_slots + numHumans);
slotLimit = (max_ally_bots_absolute + max_enemy_bots_absolute + numHumans);
lastSlotCount = [-1, -1];
while ( (slotCount < bot_get_client_limit()) && (slotCount < slotLimit) )
{
if ( fillTeam && (ally_bot_slots < max_ally_bots_absolute) && bot_can_join_team( team_ally ) )
ally_bot_slots++;
else if ( !fillTeam && (enemy_bot_slots < max_enemy_bots_absolute) && bot_can_join_team( team_enemy ) )
enemy_bot_slots++;
slotCount = (ally_bot_slots + enemy_bot_slots + numHumans);
// if we have come back to the same team again and not added any new slots we are done
if ( lastSlotCount[fillTeam] == slotCount )
break;
lastSlotCount[fillTeam] = slotCount;
fillTeam = !fillTeam;
}
// When teams are not deliberately unbalanced by request, keep waiting up to 10 seconds for human to pick a team before spawning last bot when there is only one human playing
if ( (max_ally_bots_absolute == max_enemy_bots_absolute) && !ignore_team_balance && (cur_num_ally_spectators == 1) && (cur_num_enemy_spectators == 0) && (enemy_bot_slots > 0) )
{
if ( !IsDefined( level.bot_prematchDoneTime ) && gameFlag("prematch_done") )
level.bot_prematchDoneTime = GetTime();
if ( waitingForHumanToChoose && (!IsDefined( level.bot_prematchDoneTime ) || ((GetTime() - level.bot_prematchDoneTime) < 10000)) )
enemy_bot_slots--;
}
// Number of bots wanted right now is the number of available slots (from above) minus the number of bots currently in the game
ally_bots_wanted = ally_bot_slots - cur_num_ally_bots;
enemy_bots_wanted = enemy_bot_slots - cur_num_enemy_bots;
need_to_spawn_or_drop = true;
if ( ignore_team_balance )
{
// Don't move bots between teams, but maybe spawn or drop them if necessary
total_team_size = ally_team_size + enemy_team_size;
max_total_bots_absolute = max_ally_bots_absolute + max_enemy_bots_absolute;
cur_num_total_humans = cur_num_ally_humans + cur_num_enemy_humans;
cur_num_total_bots = cur_num_ally_bots + cur_num_enemy_bots;
total_bot_slots_open = INT(min(total_team_size - cur_num_total_humans,max_total_bots_absolute));
total_num_bots_wanted = total_bot_slots_open - cur_num_total_bots;
if ( total_num_bots_wanted == 0 )
{
// No changes needed
need_to_spawn_or_drop = false;
}
else if ( total_num_bots_wanted > 0 )
{
// Need to add bots. Just even them out between teams (doesn't really matter though)
ally_bots_wanted = INT(total_num_bots_wanted/2) + (total_num_bots_wanted % 2 );
enemy_bots_wanted = INT(total_num_bots_wanted/2);
}
else if ( total_num_bots_wanted < 0 )
{
// Need to remove bots. First try to remove them from the ally team, then if that doesn't do it, the enemy team as well
num_of_bots_to_drop = total_num_bots_wanted * -1;
ally_bots_wanted = -1 * INT(min(num_of_bots_to_drop,cur_num_ally_bots));
enemy_bots_wanted = -1 * (num_of_bots_to_drop + ally_bots_wanted);
}
}
else if ( !matchMakingGame() && (ally_bots_wanted * enemy_bots_wanted < 0 && gameFlag("prematch_done") && !IsDefined(level.bots_disable_team_switching)) )
{
// ally_bots_wanted and enemy_bots_wanted are both nonzero and have opposite signs.
// This means one team needs to gain players and one needs to lose them. So move bots from one team to the other.
difference = INT(min(abs(ally_bots_wanted),abs(enemy_bots_wanted)));
if ( ally_bots_wanted > 0 )
move_bots_from_team_to_team( difference, team_enemy, team_ally, difficultyAlly );
else if ( enemy_bots_wanted > 0 )
move_bots_from_team_to_team( difference, team_ally, team_enemy, difficultyEnemy );
need_to_spawn_or_drop = false;
}
if ( need_to_spawn_or_drop )
{
// Spawn or drop bots for teams that are under / over the limit
if ( enemy_bots_wanted < 0 )
drop_bots( enemy_bots_wanted * -1, team_enemy );
if ( ally_bots_wanted < 0 )
drop_bots( ally_bots_wanted * -1, team_ally );
if ( enemy_bots_wanted > 0 )
level thread spawn_bots( enemy_bots_wanted, team_enemy, undefined, undefined, "spawned_enemies", difficultyEnemy );
if ( ally_bots_wanted > 0 )
level thread spawn_bots( ally_bots_wanted, team_ally, undefined, undefined, "spawned_allies", difficultyAlly );
if ( enemy_bots_wanted > 0 && ally_bots_wanted > 0 )
level waittill_multiple( "spawned_enemies", "spawned_allies" );
else if ( enemy_bots_wanted > 0 )
level waittill( "spawned_enemies" );
else if ( ally_bots_wanted > 0 )
level waittill( "spawned_allies" );
}
if ( difficultyEnemy != difficultyAlly )
{
bots_update_difficulty( team_enemy, difficultyEnemy );
bots_update_difficulty( team_ally, difficultyAlly );
}
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( bot_connect_monitor_update_time );
}
}
bot_get_player_team()
{
if ( IsDefined(self.team) )
return self.team;
if ( IsDefined(self.pers["team"]) )
return self.pers["team"];
return undefined;
}
bot_get_host_team()
{
foreach( player in level.players )
{
if ( !isAI( player ) && player isHost() )
return player bot_get_player_team();
}
return "spectator";
}
bot_get_human_picked_team()
{
haveHost = false;
humanChose = false;
hostChose = false;
foreach ( player in level.players )
{
if ( !isAI( player ) )
{
if ( player isHost() )
haveHost = true;
if ( player_picked_team( player ) )
{
humanChose = true;
if ( player IsHost() )
hostChose = true;
}
}
}
return ( hostChose || (humanChose && !haveHost) );
}
player_picked_team( player )
{
if ( IsDefined( player.team ) && player.team != "spectator" )
return true;
if ( IsDefined( player.spectating_actively ) && player.spectating_actively )
return true;
if ( player IsMLGSpectator() && IsDefined( player.team ) && player.team == "spectator" )
return true;
return false;
}
bot_client_counts()
{
clientCounts = [];
for ( i = 0; i < level.players.size; i++ )
{
player = level.players[i];
if ( IsDefined(player) && IsDefined(player.team) )
{
clientCounts = cat_array_add( clientCounts, "all" );
clientCounts = cat_array_add( clientCounts, player.team );
if ( IsBot( player ) )
{
clientCounts = cat_array_add( clientCounts, "bots" );
clientCounts = cat_array_add( clientCounts, "bots_" + player.team );
}
else
{
clientCounts = cat_array_add( clientCounts, "humans" );
clientCounts = cat_array_add( clientCounts, "humans_" + player.team );
}
}
}
return clientCounts;
}
cat_array_add( arrayCounts, category )
{
if ( !IsDefined( arrayCounts ) )
{
arrayCounts = [];
}
if ( !IsDefined( arrayCounts[ category ] ) )
{
arrayCounts[ category ] = 0;
}
arrayCounts[ category ] = arrayCounts[ category ] + 1;
return arrayCounts;
}
cat_array_get( arrayCounts, category )
{
if ( !IsDefined( arrayCounts ) )
{
return 0;
}
if ( !IsDefined( arrayCounts[ category ] ) )
{
return 0;
}
return arrayCounts[ category ];
}
//========================================================
// move_bots_from_team_to_team
//========================================================
move_bots_from_team_to_team( count, teamFrom, teamTo, difficulty )
{
foreach ( player in level.players )
{
if ( !IsDefined( player.team ) )
continue;
if ( IsDefined( player.connected ) && player.connected && IsBot( player ) && player.team == teamFrom )
{
player.bot_team = teamTo;
if ( IsDefined( difficulty ) )
player bot_set_difficulty( difficulty );
player notify( "luinotifyserver", "team_select", bot_lui_convert_team_to_int(teamTo) );
wait(0.05); // Wait for the team change
player notify( "luinotifyserver", "class_select", player.bot_class );
count--;
if ( count <= 0 )
break;
else
wait(0.1);
}
}
}
//========================================================
// bots_update_difficulty
//========================================================
bots_update_difficulty( team, difficulty )
{
foreach ( player in level.players )
{
if ( !IsDefined( player.team ) )
continue;
if ( IsDefined( player.connected ) && player.connected && IsBot( player ) && player.team == team )
{
if ( difficulty != (player BotGetDifficulty()) )
player bot_set_difficulty( difficulty );
}
}
}
//========================================================
// bot_drop
//========================================================
bot_drop() // self = bot
{
assert( isBot( self ) );
assert( self.connected );
kick( self.entity_number, "EXE_PLAYERKICKED_BOT_BALANCE" );
wait 0.1;
}
//========================================================
// drop_bots
//========================================================
drop_bots( count, team )
{
bots = [];
foreach ( player in level.players )
{
if ( IsDefined( player.connected ) && player.connected && IsBot( player ) && (!IsDefined( team ) || (IsDefined( player.team ) && player.team == team)) )
bots[bots.size] = player;
}
// First try to drop any bots who are dead (to avoid distrupting a game of S&R for example)
for ( i = bots.size - 1; i >= 0; i-- )
{
if ( count <= 0 )
break;
if ( !isReallyAlive( bots[i] ) )
{
bots[i] bot_drop();
bots = array_remove( bots, bots[i] );
count--;
}
}
// Then drop any who are still remaining
for ( i = bots.size - 1; i >= 0; i-- )
{
if ( count <= 0 )
break;
bots[i] bot_drop();
count--;
}
}
bot_lui_convert_team_to_int( team_name )
{
if ( team_name == "axis" )
return 0;
else if ( team_name == "allies" )
return 1;
else if ( team_name == "autoassign" || team_name == "random" )
return 2;
else // if ( team_name == "spectator" )
return 3;
}
spawn_bot_latent( team, botCallback, connecting )
{
function_wait_time = GetTime() + 60000;
// Wait for spawn to be allowed. This is typically an indication that the models need to be streamed in.
// This loop could be enhanced by adding all the test clients first, then waiting on the spawn, or threading off the wait
while ( !self CanSpawnTestClient() )
{
// This could block forever in cases when some client's can't sync. Ideally the client itself would be
// dropped, this is temporary until such a system is implemented.
if ( GetTime() >= function_wait_time )
{
kick( self.entity_number, "EXE_PLAYERKICKED_BOT_BALANCE" );
connecting.abort = true;
return;
}
wait( 0.05 );
// Entity may have been removed in the meantime
if ( !IsDefined( self ) )
{
connecting.abort = true;
return;
}
}
// Randomize a bit to simulate bot selecting team/loadout
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause ( RandomFloatRange( 0.25, 2.0 ) );
if ( !IsDefined( self ) )
{
connecting.abort = true;
return;
}
self SpawnTestClient();
self.pers["isBot"] = true;
self.equipment_enabled = true;
self.bot_team = team;
if ( IsDefined( connecting.difficulty ) )
self bot_set_difficulty( connecting.difficulty );
if( IsDefined( botCallback ) )
{
self [[botCallback]]();
}
self thread [[ level.bot_funcs["think"] ]]();
connecting.ready = true;
}
find_squad_member_index( client, team )
{
active_squad_member = client GetRankedPlayerData( "activeSquadMember" );
found = false;
count = 0;
while ( count < 10 ) //this needs to be kept in sync with max squads in player data
{
found = false;
index = client GetRankedPlayerData( "squadHQ", "aiSquadMembers", count );
if ( index == active_squad_member )
{
count++;
continue;
}
if ( !IsDefined( level.human_team_bot_added ) || !IsDefined( level.human_team_bot_added[ index ] ) || level.human_team_bot_added[ index ] == false )
{
return index;
}
count++;
}
return -1;
}
//========================================================
// spawn_bots
//========================================================
spawn_bots( num_bots, team, botCallback, haltWhenFull, notifyWhenDone, difficulty )
{
function_wait_time = GetTime() + 10000;
connectingArray = [];
squad_index = connectingArray.size;
// First get all the bots connected
while ( (level.players.size < bot_get_client_limit()) && (connectingArray.size < num_bots) && (GetTime() < function_wait_time) ) // don't want to be stuck in this function forever
{
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 0.05 );
bot = undefined;
if( ( GetDvar( "squad_vs_squad" ) == "1" ) )
{
leader = level.squad_vs_squad_axis_client;
if ( team == "allies" )
{
leader = level.squad_vs_squad_allies_client;
}
if ( IsDefined( leader ) )
{
index_needed = squad_index;
active_squad_member = leader GetRankedPlayerData( "activeSquadMember" );
slot = 0;
index = 0;
for ( squad_member = 0; squad_member < bot_get_team_limit(); squad_member++ )
{
index = leader GetRankedPlayerData( "squadHQ", "aiSquadMembers", squad_member );
if ( index == active_squad_member )
continue;
if ( index_needed == slot )
break;
slot++;
}
name = leader GetRankedPlayerData( "squadMembers", index, "name" );
head = leader GetRankedPlayerData( "squadMembers", index, "head" );
body = leader GetRankedPlayerData( "squadMembers", index, "body" );
helmet = leader GetRankedPlayerData( "squadMembers", index, "helmet" );
bot = AddBot( name, head, body, helmet );
if ( IsDefined( bot ) )
{
bot.pers[ "squadSlot" ] = index;
}
}
else
{
if ( !IsDefined( level.squad_vs_squad_has_forfeited ) )
{
if ( IsDefined( level.squad_vs_squad_axis_client ) )
{
level.finalKillCam_winner = "axis";
thread maps\mp\gametypes\_gamelogic::endGame( "axis", game[ "end_reason" ][ "allies_forfeited" ] );
}
else
{
level.finalKillCam_winner = "allies";
thread maps\mp\gametypes\_gamelogic::endGame( "allies", game[ "end_reason" ][ "axis_forfeited" ] );
}
level.squad_vs_squad_has_forfeited = true;
}
}
}
else if( ( GetDvar( "squad_use_hosts_squad" ) == "1" ) )
{
if ( level.wargame_client.team == team )
{
if ( matchMakingGame() )
{
index = find_squad_member_index( level.wargame_client, team );
name = level.wargame_client GetRankedPlayerData( "squadMembers", index, "name" );
head = level.wargame_client GetRankedPlayerData( "squadMembers", index, "head" );
body = level.wargame_client GetRankedPlayerData( "squadMembers", index, "body" );
helmet = level.wargame_client GetRankedPlayerData( "squadMembers", index, "helmet" );
bot = AddBot( name, head, body, helmet );
if ( IsDefined( bot ) )
{
level.human_team_bot_added[ index ] = true;
bot.pers[ "squadSlot" ] = index;
}
}
else
{
index_needed = squad_index;
active_squad_member = level.wargame_client GetPrivatePlayerData( "privateMatchActiveSquadMember" );
slot = 0;
index = 0;
for ( squad_member = 0; squad_member < bot_get_team_limit(); squad_member++ )
{
index = squad_member;
if ( squad_member == active_squad_member )
continue;
if ( index_needed == slot )
break;
slot++;
}
name = level.wargame_client GetPrivatePlayerData( "privateMatchSquadMembers", index, "name" );
head = level.wargame_client GetPrivatePlayerData( "privateMatchSquadMembers", index, "head" );
body = level.wargame_client GetPrivatePlayerData( "privateMatchSquadMembers", index, "body" );
helmet = level.wargame_client GetPrivatePlayerData( "privateMatchSquadMembers", index, "helmet" );
bot = AddBot( name, head, body, helmet );
if ( IsDefined( bot ) )
{
bot.pers[ "squadSlot" ] = index;
}
}
}
else
{
bot = AddBot( "", 0, 0, 0 );
}
}
else if( ( GetDvar( "squad_match" ) == "1" ) )
{
if ( team == "axis" )
{
index = GetEnemySquadData( "squadHQ", "aiSquadMembers", squad_index );
name = GetEnemySquadData( "squadMembers", index, "name" );
head = GetEnemySquadData( "squadMembers", index, "head" );
body = GetEnemySquadData( "squadMembers", index, "body" );
helmet = GetEnemySquadData( "squadMembers", index, "helmet" );
bot = AddBot( name, head, body, helmet );
if ( IsDefined( bot ) )
{
bot.pers[ "squadSlot" ] = index;
}
}
else
{
index = find_squad_member_index( level.squad_match_client, team );
if ( index > -1 ) // no spare slots at the moment
{
name = level.squad_match_client GetRankedPlayerData( "squadMembers", index, "name" );
head = level.squad_match_client GetRankedPlayerData( "squadMembers", index, "head" );
body = level.squad_match_client GetRankedPlayerData( "squadMembers", index, "body" );
helmet = level.squad_match_client GetRankedPlayerData( "squadMembers", index, "helmet" );
bot = AddBot( name, head, body, helmet );
if ( IsDefined( bot ) )
{
level.human_team_bot_added[ index ] = true;
bot.pers[ "squadSlot" ] = index;
}
}
}
}
else
{
bot = AddBot( "", 0, 0, 0 );
}
if ( !IsDefined( bot ) )
{
if ( IsDefined( haltWhenFull ) && haltWhenFull )
{
if ( IsDefined( notifyWhenDone ) )
self notify( notifyWhenDone );
return;
}
maps\mp\gametypes\_hostmigration::waitLongDurationWithHostMigrationPause( 1 );
continue;
}
else
{
connecting = SpawnStruct();
connecting.bot = bot;
connecting.ready = false;
connecting.abort = false;
connecting.index = squad_index;
connecting.difficulty = difficulty;
connectingArray[connectingArray.size] = connecting;
connecting.bot thread spawn_bot_latent( team, botCallback, connecting );
squad_index++;
}
}
// Wait for all the bots to complete their spawn process before returning
connectedComplete = 0;
function_wait_time = GetTime() + 60000;
while ( (connectedComplete < connectingArray.size) && (GetTime() < function_wait_time) )
{
connectedComplete = 0;
foreach ( connecting in connectingArray )
{
if ( connecting.ready || connecting.abort )
connectedComplete++;
}
wait 0.05;
}
if ( IsDefined( notifyWhenDone ) )
self notify( notifyWhenDone );
}
bot_gametype_chooses_team()
{
if ( !level.teamBased )
return true;
if ( IsDefined(level.bots_gametype_handles_team_choice) && level.bots_gametype_handles_team_choice )
return true;
return false;
}
bot_gametype_chooses_class()
{
return ( IsDefined(level.bots_gametype_handles_class_choice) && level.bots_gametype_handles_class_choice );
}
//========================================================
// bot_think
//========================================================
bot_think( )
{
self notify( "bot_think" );
self endon( "bot_think" );
self endon( "disconnect" );
while( !IsDefined( self.pers["team"] ) )
{
wait( 0.05 );
}
level.hasbots = true;
if ( bot_gametype_chooses_team() )
self.bot_team = self.pers["team"];
team = self.bot_team;
if ( !IsDefined( team ) )
team = self.pers["team"];
maps\mp\bots\_bots_ks::bot_killstreak_setup();
self.entity_number = self GetEntityNumber();
firstSpawn = false;
if ( !isDefined( self.bot_spawned_before ) )
{
firstSpawn = true;
self.bot_spawned_before = true;
if ( !bot_gametype_chooses_team() )
{
self notify( "luinotifyserver", "team_select", bot_lui_convert_team_to_int(team) );
wait( 0.5 );
// if we are still on team spectator something is preventing us from joining the team we want to be on. Drop and try to reconnect again later.
if ( self.pers["team"] == "spectator" )
{
self bot_drop();
return;
}
}
}
while( true )
{
// Make sure we pick a difficulty if its set to "default"
self bot_set_difficulty( self BotGetDifficulty() );
// Balance personalities unless we are restricting them based on difficulty
allowAdvPersonality = self BotGetDifficultySetting( "advancedPersonality" );
if ( firstSpawn && IsDefined( allowAdvPersonality ) && allowAdvPersonality != 0 )
self bot_balance_personality();
/#
self bot_set_personality_from_dev_dvar();
#/
self bot_assign_personality_functions();
if ( firstSpawn )
{
self bot_set_loadout_class();
if ( !bot_gametype_chooses_class() )
self notify( "luinotifyserver", "class_select", self.bot_class );
if ( self.health == 0 )
self waittill( "spawned_player" ); // Don't wait here if we have health (i.e. we've already spawned)
if ( IsDefined( level.bot_funcs ) && IsDefined( level.bot_funcs["know_enemies_on_start"] ) )
self thread [[ level.bot_funcs["know_enemies_on_start"] ]]();
firstSpawn = false;
}
self bot_restart_think_threads();
wait( 0.10 );
self waittill( "death" );
self respawn_watcher();
self waittill( "spawned_player" );
}
}
/#
bot_set_personality_from_dev_dvar()
{
debug_personality = GetDvar( "bot_debugPersonality", "default" );
if( debug_personality != "default" )
self bot_set_personality( debug_personality );
}
#/
respawn_watcher()
{
self endon( "started_spawnPlayer" ); // If for whatever reason spawnPlayer starts, then immediately end this
// First wait till the bot is actually waiting to spawn
while( !self.waitingToSpawn )
wait(0.05);
// Now that he is waiting to spawn, push the Respawn button if necessary
if ( self maps\mp\gametypes\_playerlogic::needsButtonToRespawn() )
{
while( self.waitingToSpawn )
{
if ( self.sessionstate == "spectator" )
{
// Can attempt to spawn if the gamemode has unlimited lives or if the bot has lives remaining
if ( GetDvarInt("numlives") == 0 || self.pers["lives"] > 0 )
self BotPressButton( "use", 0.5 );
}
wait(1.0);
}
}
}
bot_get_rank_xp()
{
if ( self bot_israndom() == false )
{
if ( !IsDefined(self.pers[ "rankxp" ]) )
self.pers[ "rankxp" ] = 0;
return self.pers[ "rankxp" ];
}
difficulty = self BotGetDifficulty();
persName = "bot_rank_" + difficulty;
if ( IsDefined(self.pers[persName]) && self.pers[persName] > 0 )
return self.pers[persName];
desiredRanks = bot_random_ranks_for_difficulty( difficulty );
rank = desiredRanks[ "rank" ];
prestige = desiredRanks[ "prestige" ];
minXP = maps\mp\gametypes\_rank::getRankInfoMinXP( rank );
maxXP = minXP + maps\mp\gametypes\_rank::getRankInfoXPAmt( rank ) ;
rankXP = RandomIntRange( minXP, maxXP + 1 );
self.pers[persName] = rankXP;
return rankXP;
}
bot_3d_sighting_model( associatedEnt )
{
self thread bot_3d_sighting_model_thread( associatedEnt );
}
bot_3d_sighting_model_thread( associatedEnt )
{
associatedEnt endon("disconnect");
self endon( "disconnect" );
level endon( "game_ended" );
while( 1 )
{
if ( IsAlive( self ) && !(self BotCanSeeEntity( associatedEnt )) && within_fov( self.origin, self.angles, associatedEnt.origin, self BotGetFovDot() ) )
self BotGetImperfectEnemyInfo( associatedEnt, associatedEnt.origin );
wait 0.1;
}
}
bot_random_ranks_for_difficulty( difficulty )
{
result = [];
result["rank"] = 0;
result["prestige"] = 0;
if ( difficulty == "default" )
return result;
// Rank: 1 - 8, 10 - 38, 40 - 58, 60 (never set to N9 so there is no chance of jumping bracket by gained XP during the match)
if ( !isDefined( level.bot_rnd_rank ) )
{
level.bot_rnd_rank = [];
level.bot_rnd_rank["recruit"][0] = 0;
level.bot_rnd_rank["recruit"][1] = 7;
level.bot_rnd_rank["regular"][0] = 9;
level.bot_rnd_rank["regular"][1] = 37;
level.bot_rnd_rank["hardened"][0] = 39;
level.bot_rnd_rank["hardened"][1] = 57;
level.bot_rnd_rank["veteran"][0] = 59;
level.bot_rnd_rank["veteran"][1] = 59;
}
// Prestige: 2 - 9 only at veteran
if ( !isDefined( level.bot_rnd_prestige ) )
{
level.bot_rnd_prestige = [];
level.bot_rnd_prestige["recruit"][0] = 0;
level.bot_rnd_prestige["recruit"][1] = 0;
level.bot_rnd_prestige["regular"][0] = 0;
level.bot_rnd_prestige["regular"][1] = 0;
level.bot_rnd_prestige["hardened"][0] = 0;
level.bot_rnd_prestige["hardened"][1] = 0;
level.bot_rnd_prestige["veteran"][0] = 0;
level.bot_rnd_prestige["veteran"][1] = 9;
}
if ( IsDefined( level.bot_rnd_rank[difficulty][0] ) && IsDefined( level.bot_rnd_rank[difficulty][1] ) )
result["rank"] = RandomIntRange( level.bot_rnd_rank[difficulty][0], level.bot_rnd_rank[difficulty][1] + 1 );
if ( IsDefined( level.bot_rnd_prestige[difficulty][0] ) && IsDefined( level.bot_rnd_prestige[difficulty][1] ) )
result["prestige"] = RandomIntRange( level.bot_rnd_prestige[difficulty][0], level.bot_rnd_prestige[difficulty][1] + 1 );
return result;
}
crate_can_use_always( crate )
{
// Agents can only pickup boxes normally
if ( IsAgent(self) && !IsDefined( crate.boxType ) )
return false;
return true;
}
//========================================================
// get_human_player
//========================================================
get_human_player()
{
result = undefined;
players = getEntArray( "player", "classname" );
if ( IsDefined( players ) )
{
for( index = 0; index < players.size; index++ )
{
if( IsDefined( players[index] ) && IsDefined( players[index].connected ) && players[index].connected &&
!IsAI( players[index] ) && (!IsDefined( result ) || result.team == "spectator") )
{
result = players[index];
}
}
}
return result;
}
/#
get_all_humans()
{
humans = [];
foreach ( player in level.players )
{
if ( player.connected && !IsAI( player ) )
humans = array_add( humans, player );
}
return humans;
}
spectators_exist()
{
humans = get_all_humans();
foreach( player in humans )
{
if ( player.team == "spectator" )
return true;
}
return false;
}
#/
//========================================================
// bot_damage_callback
//========================================================
bot_damage_callback( eAttacker, iDamage, sMeansOfDeath, sWeapon, eInflictor, sHitLoc )
{
if( !IsDefined( self ) || !IsAlive( self ) )
{
return;
}
if( sMeansOfDeath == "MOD_FALLING" || sMeansOfDeath == "MOD_SUICIDE" )
{
return;
}
if( iDamage <= 0 )
{
return;
}
if ( !IsDefined( eInflictor ) )
{
if ( !IsDefined( eAttacker ) )
return;
eInflictor = eAttacker;
}
if ( IsDefined( eInflictor ) )
{
if ( level.teamBased )
{
if ( IsDefined( eInflictor.team ) && eInflictor.team == self.team )
return;
else if ( IsDefined( eAttacker ) && IsDefined( eAttacker.team ) && eAttacker.team == self.team )
return;
}
attacker_ent = bot_get_known_attacker( eAttacker, eInflictor );
if ( IsDefined(attacker_ent) )
self BotSetAttacker( attacker_ent );
}
}
//========================================================
// on_bot_killed
//========================================================
on_bot_killed( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, killId )
{
self BotClearScriptEnemy();
self BotClearScriptGoal();
attacker_ent = bot_get_known_attacker( attacker, eInflictor );
if ( !bot_is_fireteam_mode() && GetDvar( "squad_match" ) != "1" && GetDvar( "squad_vs_squad" ) != "1" && IsDefined(attacker_ent) && attacker_ent.classname == "script_vehicle" && IsDefined(attacker_ent.helitype) )
{
respawn_chance = self BotGetDifficultySetting("launcherRespawnChance");
if ( RandomFloat(1.0) < respawn_chance )
self.respawn_with_launcher = true;
}
}
//========================================================
// bot_should_do_killcam
//========================================================
bot_should_do_killcam()
{
/#
if ( GetDvar("scr_game_spectatetype") == "2" )
{
if ( spectators_exist() )
{
return false;
}
}
#/
if ( bot_is_fireteam_mode() )
return false;
skip_killcam_chance = 0.0;
bot_difficulty = self BotGetDifficulty();
if ( bot_difficulty == "recruit" )
{
skip_killcam_chance = 0.1;
}
else if ( bot_difficulty == "regular" )
{
skip_killcam_chance = 0.4;
}
else if ( bot_difficulty == "hardened" )
{
skip_killcam_chance = 0.7;
}
else if ( bot_difficulty == "veteran" )
{
skip_killcam_chance = 1.0;
}
return (RandomFloat(1.0) < (1.0-skip_killcam_chance));
}
//========================================================
// bot_should_pickup_weapons
//========================================================
bot_should_pickup_weapons()
{
if ( self isJuggernaut() )
return false;
return true;
}
//========================================================
// bot_restart_think_threads
//========================================================
bot_restart_think_threads()
{
self thread bot_think_watch_enemy();
self thread bot_think_tactical_goals();
self thread [[ level.bot_funcs["dropped_weapon_think"] ]]();
self thread bot_think_level_actions();
self thread bot_think_crate();
self thread bot_think_crate_blocking_path();
//self thread bot_think_revive();
self thread bot_think_killstreak();
self thread bot_think_watch_aerial_killstreak();
self thread bot_think_gametype();
/#
self thread bot_think_debug();
#/
}
/#
bot_think_debug()
{
self notify( "bot_think_debug" );
self endon( "bot_think_debug" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while(1)
{
if ( IsDefined(level.bot_debug_force_path_location) )
{
self bot_disable_tactical_goals();
self.ignoreall = true;
self BotSetStance("stand");
if ( !self BotHasScriptGoal() )
{
self BotSetScriptGoal(level.bot_debug_force_path_location,0,"tactical");
}
else
{
goal = self BotGetScriptGoal();
if ( !bot_vectors_are_equal(goal, level.bot_debug_force_path_location) )
self BotSetScriptGoal(level.bot_debug_force_path_location,0,"tactical");
}
}
wait(0.05);
}
}
#/
//========================================================
// bot_think_watch_enemy
//========================================================
bot_think_watch_enemy( bEndOnDeath )
{
endMessage = "spawned_player";
if( IsDefined(bEndOnDeath) && bEndOnDeath )
endMessage = "death";
self notify( "bot_think_watch_enemy" );
self endon( "bot_think_watch_enemy" );
self endon( endMessage );
self endon( "disconnect" );
level endon( "game_ended" );
// This function is for any logic that needs to be updated each frame regarding the enemy
self.last_enemy_sight_time = GetTime();
while( true )
{
if ( IsDefined( self.enemy ) )
{
if ( self BotCanSeeEntity( self.enemy ) )
{
self.last_enemy_sight_time = GetTime();
}
}
wait(0.05);
}
}
//========================================================
// bot_think_dropped_weapons
//========================================================
bot_think_seek_dropped_weapons()
{
self notify( "bot_think_seek_dropped_weapons" );
self endon( "bot_think_seek_dropped_weapons" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
throwing_knife_name = "throwingknife_mp";
throwing_knife_jugg_name = "throwingknifejugg_mp";
while( true )
{
still_seeking_weapon = false;
if ( self bot_out_of_ammo() && self [[level.bot_funcs["should_pickup_weapons"]]]() && !self bot_is_remote_or_linked() )
{
dropped_weapons = GetEntArray("dropped_weapon","targetname");
dropped_weapons_sorted = get_array_of_closest(self.origin,dropped_weapons);
if ( dropped_weapons_sorted.size > 0 )
{
dropped_weapon = dropped_weapons_sorted[0];
self bot_seek_dropped_weapon( dropped_weapon );
}
}
if ( !self bot_in_combat() && !self bot_is_remote_or_linked() && self BotGetDifficultySetting("strategyLevel") > 0 )
{
has_knife_normal = self HasWeapon( throwing_knife_name );
has_knife_jugg = self HasWeapon( throwing_knife_jugg_name );
knife_thrown = (has_knife_normal && self GetAmmoCount( throwing_knife_name ) == 0) || (has_knife_jugg && self GetAmmoCount( throwing_knife_jugg_name ) == 0);
if ( knife_thrown )
{
if ( IsDefined(self.going_for_knife) )
{
wait(5.0); // Already set a knife destination
continue;
}
dropped_knives = GetEntArray("dropped_knife","targetname");
dropped_knives_sorted = get_array_of_closest(self.origin,dropped_knives);
foreach( knife in dropped_knives_sorted )
{
if ( !IsDefined(knife) )
{
// May have been deleted while we were waiting for the bot_queued_process below on the previous knife in the array
continue;
}
if ( !IsDefined(knife.calculated_closest_point) )
{
result = bot_queued_process( "BotGetClosestNavigablePoint", ::func_bot_get_closest_navigable_point, knife.origin, 32, self );
// since bot_queued_process waits, it's possible that the knife gets removed
if ( IsDefined( knife ) )
{
knife.closest_point_on_grid = result;
knife.calculated_closest_point = true;
}
else
{
continue;
}
}
if ( IsDefined(knife.closest_point_on_grid) )
{
self.going_for_knife = true;
self bot_seek_dropped_weapon( knife );
}
}
}
else if ( has_knife_normal || has_knife_jugg )
{
// Has a knife and has not thrown it yet
self.going_for_knife = undefined;
}
}
wait( RandomFloatRange(0.25, 0.75) );
}
}
bot_seek_dropped_weapon( dropped_weapon )
{
if ( self bot_has_tactical_goal( "seek_dropped_weapon", dropped_weapon ) == false )
{
action_thread = undefined;
if ( dropped_weapon.targetname == "dropped_weapon" )
{
needs_to_pickup_weapon = true;
heldweapons = self GetWeaponsListPrimaries();
foreach ( held_weapon in heldweapons )
{
if ( dropped_weapon.model == GetWeaponModel(held_weapon) )
needs_to_pickup_weapon = false;
}
if ( needs_to_pickup_weapon )
action_thread = ::bot_pickup_weapon;
}
extra_params = SpawnStruct();
extra_params.object = dropped_weapon;
extra_params.script_goal_radius = 12;
extra_params.should_abort = level.bot_funcs["dropped_weapon_cancel"];
extra_params.action_thread = action_thread;
self bot_new_tactical_goal( "seek_dropped_weapon", dropped_weapon.origin, 100, extra_params );
}
}
bot_pickup_weapon( goal )
{
self BotPressButton( "use", 2 );
wait(2);
}
should_stop_seeking_weapon( goal )
{
// goal.object is the dropped weapon
if ( !IsDefined( goal.object ) )
return true;
if ( goal.object.targetname == "dropped_weapon" )
{
if ( self bot_get_total_gun_ammo() > 0 )
return true;
}
else if ( goal.object.targetname == "dropped_knife" )
{
if ( self bot_in_combat() )
{
self.going_for_knife = undefined;
return true;
}
}
return false;
}
//========================================================
// bot_think_level_actions
//========================================================
bot_think_level_actions( override_radius )
{
self notify( "bot_think_level_actions" );
self endon( "bot_think_level_actions" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( true )
{
waittill_notify_or_timeout( "calculate_new_level_targets", randomfloatrange( 2, 10 ) );
if ( !IsDefined(level.level_specific_bot_targets) || level.level_specific_bot_targets.size == 0 )
continue;
if ( self bot_has_tactical_goal( "map_interactive_object" ) )
continue;
if ( self bot_in_combat() || self bot_is_remote_or_linked() )
continue;
target_picked = undefined;
foreach( level_target in level.level_specific_bot_targets )
{
if ( array_contains( level_target.high_priority_for, self ) )
{
target_picked = level_target;
break;
}
}
if ( !IsDefined(target_picked) )
{
if ( RandomInt(100) > 25 )
continue;
level_triggers_sorted = get_array_of_closest( self.origin, level.level_specific_bot_targets );
max_dist = 256;
if ( IsDefined(override_radius) )
max_dist = override_radius;
else if ( self BotGetScriptGoalType() == "hunt" && self BotPursuingScriptGoal() )
max_dist = 512;
// If bot is not hunting, or bot is hunting but has an enemy targeted,
// then only use one of these if the bot is relatively close to it
if ( DistanceSquared( self.origin, level_triggers_sorted[0].origin ) > max_dist*max_dist )
continue;
target_picked = level_triggers_sorted[0];
}
assert( IsDefined(target_picked) );
should_melee_target = false;
if ( target_picked.bot_interaction_type == "damage" )
{
should_melee_target = self bot_should_melee_level_damage_target( target_picked );
if ( should_melee_target )
{
height_diff_to_node_0 = target_picked.origin[2] - (target_picked.bot_targets[0].origin[2] + 55);
height_diff_to_node_1 = target_picked.origin[2] - (target_picked.bot_targets[1].origin[2] + 55);
if ( (height_diff_to_node_0 > 55 && height_diff_to_node_1 > 55) )
{
if ( array_contains( target_picked.high_priority_for, self ) )
target_picked.high_priority_for = array_remove(target_picked.high_priority_for, self);
continue;
}
}
weapon_class = WeaponClass( self GetCurrentWeapon() );
if ( weapon_class == "spread" )
{
vec_to_node_0 = target_picked.bot_targets[0].origin - target_picked.origin;
vec_to_node_1 = target_picked.bot_targets[1].origin - target_picked.origin;
dist_to_node_0_sq = LengthSquared(vec_to_node_0);
dist_to_node_1_sq = LengthSquared(vec_to_node_1);
if ( (dist_to_node_0_sq > 150*150 && dist_to_node_1_sq > 150*150) )
{
if ( array_contains( target_picked.high_priority_for, self ) )
target_picked.high_priority_for = array_remove(target_picked.high_priority_for, self);
continue;
}
}
}
extra_params = SpawnStruct();
extra_params.object = target_picked;
if ( target_picked.bot_interaction_type == "damage" )
{
if ( should_melee_target )
extra_params.should_abort = ::level_trigger_should_abort_melee;
else
extra_params.should_abort = ::level_trigger_should_abort_ranged;
}
if ( target_picked.bot_interaction_type == "use" )
{
extra_params.action_thread = ::use_use_trigger;
extra_params.should_abort = ::level_trigger_should_abort;
extra_params.script_goal_yaw = VectorToAngles(target_picked.origin - target_picked.bot_target.origin)[1];
self bot_new_tactical_goal( "map_interactive_object", target_picked.bot_target.origin, 10, extra_params );
}
else if ( target_picked.bot_interaction_type == "damage" )
{
Assert(target_picked.bot_targets.size == 2);
if ( should_melee_target )
{
extra_params.action_thread = ::melee_damage_trigger;
extra_params.script_goal_radius = 20;
}
else
{
extra_params.action_thread = ::attack_damage_trigger;
extra_params.script_goal_radius = 50;
}
node_target = undefined;
path_dist_0 = bot_queued_process( "GetPathDistLevelAction", ::func_get_path_dist, self.origin, target_picked.bot_targets[0].origin );
path_dist_1 = bot_queued_process( "GetPathDistLevelAction", ::func_get_path_dist, self.origin, target_picked.bot_targets[1].origin );
if ( !IsDefined(target_picked) )
continue; // Could have gone undefined during the queued process wait
if ( path_dist_0 <= 0 && path_dist_1 <= 0 )
continue;
if ( path_dist_0 > 0 )
{
Assert(IsDefined(target_picked.bot_targets[0]));
if ( path_dist_1 < 0 || path_dist_0 <= path_dist_1 )
node_target = target_picked.bot_targets[0];
}
if ( path_dist_1 > 0 )
{
Assert(IsDefined(target_picked.bot_targets[1]));
if ( path_dist_0 < 0 || path_dist_1 <= path_dist_0 )
node_target = target_picked.bot_targets[1];
}
Assert(IsDefined(node_target));
/#
if ( !IsDefined(node_target) )
continue; // In non-ship, bail to avoid SRE spam
#/
if ( !should_melee_target )
self childthread monitor_node_visible( node_target );
self bot_new_tactical_goal( "map_interactive_object", node_target.origin, 10, extra_params );
}
}
}
bot_should_melee_level_damage_target( level_target )
{
Assert(level_target.bot_interaction_type == "damage" );
current_weapon = self GetCurrentWeapon();
should_melee_target = self bot_out_of_ammo() || self.hasRiotShieldEquipped || ( IsDefined( self.isJuggernautManiac ) && self.isJuggernautManiac == true )
|| WeaponClass(current_weapon) == "grenade" || current_weapon == "iw6_knifeonly_mp" || current_weapon == "iw6_knifeonlyfast_mp";
/#
should_melee_target = should_melee_target || (GetDvarInt("bot_SimulateNoAmmo") == 1);
#/
return should_melee_target;
}
monitor_node_visible( node_target )
{
self endon("goal");
wait(0.1); // give two frames for the tactical goal to start before we are allowed to notify "goal"
while(1)
{
if ( WeaponClass( self GetCurrentWeapon() ) == "spread" )
{
if ( DistanceSquared(self.origin,node_target.origin) > 300 * 300 )
{
wait(0.05);
continue;
}
}
nearest_node_self = self GetNearestNode();
if ( IsDefined(nearest_node_self) )
{
if ( NodesVisible( nearest_node_self, node_target ) )
{
if ( SightTracePassed( self.origin + (0,0,55), node_target.origin + (0,0,55), false, self ) )
self notify("goal");
}
}
wait(0.05);
}
}
SCR_CONST_DAMAGE_TRIGGER_TIMEOUT_TIME = 5000;
attack_damage_trigger( goal )
{
// goal.object is the trigger
Assert(goal.object.bot_interaction_type == "damage" );
if ( goal.object.origin[2] - self GetEye()[2] > 55 )
{
// Object is a lot higher than me, don't target it if I end up directly under it
if ( Distance2DSquared(goal.object.origin, self.origin) < 15*15 )
return;
}
self BotSetFlag("disable_movement", true);
self look_at_damage_trigger( goal.object, 0.30 );
self BotPressButton( "ads", 0.30 );
wait(0.25);
time_started = GetTime();
while( IsDefined( goal.object ) && !IsDefined(goal.object.already_used) && GetTime() - time_started < SCR_CONST_DAMAGE_TRIGGER_TIMEOUT_TIME )
{
self look_at_damage_trigger( goal.object, 0.15 );
self BotPressButton( "ads", 0.15 );
self BotPressButton( "attack" );
wait(0.1);
}
self BotSetFlag("disable_movement", false);
}
melee_damage_trigger( goal )
{
// goal.object is the trigger
Assert(goal.object.bot_interaction_type == "damage" );
self BotSetFlag("disable_movement", true);
self look_at_damage_trigger( goal.object, 0.30 );
wait(0.25);
time_started = GetTime();
while( IsDefined( goal.object ) && !IsDefined(goal.object.already_used) && GetTime() - time_started < SCR_CONST_DAMAGE_TRIGGER_TIMEOUT_TIME )
{
self look_at_damage_trigger( goal.object, 0.15 );
self BotPressButton( "melee" );
wait(0.1);
}
self BotSetFlag("disable_movement", false);
}
look_at_damage_trigger( damage_trigger, time )
{
look_origin = damage_trigger.origin;
if ( Distance2DSquared( self.origin, look_origin ) < 10*10 )
look_origin = ( look_origin[0], look_origin[1], self GetEye()[2] ); // Ensure we're still looking forward when really close to the object, so we don't try to look directly up or down
self BotLookAtPoint( look_origin, time, "script_forced" );
}
use_use_trigger( goal )
{
// goal.object is the trigger
Assert(goal.object.bot_interaction_type == "use" );
if ( IsAgent(self) )
{
self _enableUsability();
goal.object EnablePlayerUse( self );
wait(0.05);
}
time = goal.object.use_time;
self BotPressButton( "use", time );
wait( time );
if ( IsAgent(self) )
{
self _disableUsability();
if ( IsDefined(goal.object) )
goal.object DisablePlayerUse( self );
}
}
level_trigger_should_abort_melee( goal )
{
// goal.object is the damage_trigger
Assert( !IsDefined( goal.object ) || goal.object.bot_interaction_type == "damage" );
if ( level_trigger_should_abort(goal) )
return true;
if ( !self bot_should_melee_level_damage_target(goal.object) )
return true;
return false;
}
level_trigger_should_abort_ranged( goal )
{
// goal.object is the damage_trigger
Assert( !IsDefined( goal.object ) || goal.object.bot_interaction_type == "damage" );
if ( level_trigger_should_abort(goal) )
return true;
if ( self bot_should_melee_level_damage_target(goal.object) )
return true;
return false;
}
level_trigger_should_abort( goal )
{
// goal.object is the trigger
if ( !IsDefined( goal.object ) )
return true;
if ( IsDefined( goal.object.already_used ) )
return true;
if ( self bot_in_combat() )
return true;
return false;
}
crate_in_range( crate )
{
if ( !IsDefined( crate.owner ) || (crate.owner != self) )
{
// I didn't call in this crate...
// Ignore it if it is greater than 2048 distance away
if ( DistanceSquared( self.origin, crate.origin ) > 2048 * 2048 )
return false;
}
return true;
}
bot_crate_valid( crate )
{
if ( !IsDefined( crate ) )
return false;
if ( !(self [[ level.bot_funcs["crate_can_use"] ]]( crate )) )
return false;
if ( !crate_landed_and_on_path_grid( crate ) )
return false;
// Ignore any crate that is a trap for the other team
if ( level.teamBased && IsDefined( crate.bomb ) && IsDefined( crate.team ) && (crate.team == self.team) )
return false;
if ( !( self [[ level.bot_funcs["crate_in_range"] ]]( crate ) ) )
{
return false;
}
if ( IsDefined(crate.boxType) )
{
if ( IsDefined( level.boxSettings[crate.boxType] ) && ![[level.boxSettings[crate.boxType].canUseCallback]]() )
return false;
if ( IsDefined( crate.disabled_use_for ) && IsDefined(crate.disabled_use_for[self GetEntityNumber()]) && crate.disabled_use_for[self GetEntityNumber()] )
return false;
/#
if ( !IsDefined(level.bot_can_use_box_by_type[crate.boxType]) )
{
AssertMsg( "Crate type <" + crate.boxType + "> is not supported for bots" );
return false;
}
#/
if ( !self [[level.bot_can_use_box_by_type[crate.boxType]]]( crate ) )
return false;
}
return isDefined( crate );
}
crate_landed_and_on_path_grid( crate )
{
Assert(IsDefined(crate));
if ( !crate_has_landed(crate) )
return false;
Assert(IsDefined(crate));
if ( !crate_is_on_path_grid(crate) )
return false;
return isDefined( crate );
}
crate_has_landed( crate )
{
if ( IsDefined(crate.boxType) )
{
return ( GetTime() > (crate.birthtime + 1000) );
}
else
{
return (IsDefined(crate.droppingToGround) && !crate.droppingToGround);
}
}
crate_is_on_path_grid( crate )
{
Assert( crate_has_landed(crate) ); // This can't be called on a crate still in the air
if ( !IsDefined(crate.on_path_grid) )
crate_calculate_on_path_grid( crate );
return (IsDefined(crate) && crate.on_path_grid);
}
node_within_use_radius_of_crate( node, crate )
{
if ( IsDefined(crate.boxtype) && crate.boxtype == "scavenger_bag" )
{
return ( abs(node.origin[0] - crate.origin[0]) < 36 && abs(node.origin[0] - crate.origin[0]) < 36 && abs(node.origin[0] - crate.origin[0]) < 18 );
}
else
{
player_use_radius = GetDvarFloat( "player_useRadius" );
dist_to_nearest_node_sq = DistanceSquared(crate.origin, node.origin + (0,0,40)); // Assume crouched at node
return ( dist_to_nearest_node_sq <= (player_use_radius*player_use_radius) );
}
}
crate_calculate_on_path_grid( crate )
{
Assert(!IsDefined(crate.nearest_nodes));
Assert(!IsDefined(crate.on_path_grid));
crate thread crate_monitor_position();
crate.on_path_grid = false;
prev_forceDisconnectUntil = undefined;
time_to_disconnect_until = undefined;
if ( IsDefined(crate.forceDisconnectUntil) )
{
prev_forceDisconnectUntil = crate.forceDisconnectUntil;
time_to_disconnect_until = GetTime() + 30000;
crate.forceDisconnectUntil = time_to_disconnect_until;
crate notify( "path_disconnect" );
}
wait(0.05); // Wait for the disconnect to happen
if ( !IsDefined( crate ) )
return;
nearest_nodes = crate_get_nearest_valid_nodes( crate );
if ( !IsDefined( crate ) )
return;
if ( IsDefined(nearest_nodes) && nearest_nodes.size > 0 )
{
crate.nearest_nodes = nearest_nodes;
crate.on_path_grid = true;
}
else
{
player_use_radius = GetDvarFloat( "player_useRadius" );
closest_node = GetNodesInRadiusSorted( crate.origin, player_use_radius * 2, 0 )[0];
crate_loc_on_ground = crate GetPointInBounds(0,0,-1); // get the point on the ground (where the pathnode origin would be)
nearest_point = undefined;
if ( IsDefined(crate.boxtype) && crate.boxtype == "scavenger_bag" )
{
if ( bot_point_is_on_pathgrid(crate.origin) )
nearest_point = crate.origin;
}
else
{
nearest_point = BotGetClosestNavigablePoint(crate.origin, player_use_radius);
}
if ( IsDefined(closest_node) && !closest_node NodeIsDisconnected() && IsDefined(nearest_point) && abs(closest_node.origin[2] - crate_loc_on_ground[2]) < 30 )
{
crate.nearest_points = [nearest_point];
crate.nearest_nodes = [closest_node]; // Needed for vis checks even though its not our destination
crate.on_path_grid = true;
}
}
if ( IsDefined(crate.forceDisconnectUntil) )
{
if ( crate.forceDisconnectUntil == time_to_disconnect_until )
crate.forceDisconnectUntil = prev_forceDisconnectUntil;
}
}
crate_get_nearest_valid_nodes( crate )
{
nodes = GetNodesInRadiusSorted( crate.origin, 256, 0 );
for ( i = nodes.size; i > 0; i-- )
nodes[i] = nodes[i-1];
nodes[0] = GetClosestNodeInSight( crate.origin );
all_nodes = undefined;
if ( IsDefined(crate.forceDisconnectUntil) )
all_nodes = GetAllNodes();
nodes_to_return = [];
nodes_wanted = 1;
if ( !IsDefined(crate.boxType) )
nodes_wanted = 2;
for ( i = 0; i < nodes.size; i++ )
{
node = nodes[i];
if ( !IsDefined(node) || !IsDefined( crate ) )
continue;
if ( node NodeIsDisconnected() )
continue;
if ( !node_within_use_radius_of_crate( node, crate ) )
{
// If i > 0, then we know the nodes are sorted by distance, so if this node is not within the radius than none of the rest will be either
if ( i == 0 )
continue;
else
break;
}
wait(0.05); // Wait a frame in between sight traces
if ( !IsDefined( crate ) )
break;
if ( SightTracePassed( crate.origin, node.origin + (0,0,55), false, crate ) )
{
wait(0.05); // Wait a frame in between path generations
if ( !IsDefined( crate ) )
break;
if ( !IsDefined(crate.forceDisconnectUntil) )
{
// If this is not defined, then this crate doesn't disconnect paths and we don't need to check for that
nodes_to_return[nodes_to_return.size] = node;
if ( nodes_to_return.size == nodes_wanted )
return nodes_to_return;
else
continue;
}
// Find another node relatively far away and test path to that node
other_node_to_test = undefined;
num_tested = 0;
while( !IsDefined(other_node_to_test) && num_tested < 100 )
{
num_tested++;
node_trying = Random(all_nodes);
if ( DistanceSquared( node.origin, node_trying.origin ) > 500 * 500 )
other_node_to_test = node_trying;
}
if ( IsDefined(other_node_to_test) )
{
path = bot_queued_process( "GetNodesOnPathCrate", ::func_get_nodes_on_path, node.origin, other_node_to_test.origin );
if ( IsDefined(path) )
{
nodes_to_return[nodes_to_return.size] = node;
if ( nodes_to_return.size == nodes_wanted )
return nodes_to_return;
else
continue;
}
}
}
}
return undefined;
}
crate_get_bot_target( crate )
{
if ( IsDefined(crate.nearest_points) )
return crate.nearest_points[0];
// ESU - 5/9/14 - If only one node exists it will cause an assert, since BotNodeScoreMultiple will return undefined. So we just return that single node origin directly.
if ( IsDefined(crate.nearest_nodes) )
{
if ( crate.nearest_nodes.size > 1 )
{
nodes_sorted = array_reverse(self BotNodeScoreMultiple( crate.nearest_nodes, "node_exposed" ));
return random_weight_sorted(nodes_sorted).origin;
}
else
return crate.nearest_nodes[0].origin;
}
AssertMsg("unreachable");
}
crate_get_bot_target_check_distance( crate, use_radius )
{
crateStandPos = crate_get_bot_target( crate );
testRadiusSq = use_radius * 0.9;
testRadiusSq *= testRadiusSq;
if ( DistanceSquared( crate.origin, crateStandPos ) <= testRadiusSq )
return crateStandPos;
else
return undefined;
}
//========================================================
// bot_think_crate
//========================================================
bot_think_crate()
{
self notify( "bot_think_crate" );
self endon( "bot_think_crate" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
player_use_radius = GetDvarFloat( "player_useRadius" );
while( true )
{
wait_time = RandomFloatRange(2,4);
self waittill_notify_or_timeout("new_crate_to_take", wait_time);
if ( IsDefined( self.boxes ) && self.boxes.size == 0 )
self.boxes = undefined;
all_crates = level.carePackages;
if ( !(self bot_in_combat()) && IsDefined(self.boxes) )
all_crates = array_combine(all_crates, self.boxes);
if ( IsDefined( level.bot_scavenger_bags ) && self _hasPerk( "specialty_scavenger" ) )
all_crates = array_combine(all_crates, level.bot_scavenger_bags);
// Early out if we didn't find any crates
all_crates = array_removeUndefined( all_crates );
if ( all_crates.size == 0 )
continue;
if ( bot_has_tactical_goal( "airdrop_crate" ) || self BotGetScriptGoalType() == "tactical" || self bot_is_remote_or_linked() )
{
continue;
}
all_valid_crates = [];
foreach ( crate in all_crates )
{
if ( self bot_crate_valid( crate ) )
all_valid_crates[all_valid_crates.size] = crate;
}
// bot_crate_valid() has a wait in it and crates could have been removed after being added to all_valid_crates
all_valid_crates = array_remove_duplicates( all_valid_crates );
// We didn't find any valid crates to take
if ( all_valid_crates.size == 0 )
continue;
// Sort the array
all_valid_crates = get_array_of_closest( self.origin, all_valid_crates );
// First check for the closest crate the bot can see (ignoring current FOV)
nearest_node_bot = self GetNearestNode();
if ( !IsDefined(nearest_node_bot) )
continue;
ammoLow = self [[ level.bot_funcs["crate_low_ammo_check"] ]]();
can_take_any_crate_on_radar = (ammoLow || (RandomInt(100) < 50)) && !(self isEMPed());
crate_to_take = undefined;
foreach ( crate in all_valid_crates )
{
player_has_claimed_crate = false;
if ( ( !IsDefined(crate.owner) || crate.owner != self ) && !IsDefined(crate.boxType) )
{
human_allies_near_crate = [];
foreach( player in level.players )
{
if ( !IsDefined( player.team ) )
continue;
if ( !IsAI(player) && level.teamBased && player.team == self.team )
{
if ( DistanceSquared(player.origin,crate.origin) < 700*700 )
human_allies_near_crate[human_allies_near_crate.size] = player;
}
}
if ( human_allies_near_crate.size > 0 )
{
// Check if the human ally has a line of sight to the crate
nearest_node_human = human_allies_near_crate[0] GetNearestNode();
if ( IsDefined(nearest_node_human) )
{
player_has_claimed_crate = false;
foreach ( node in crate.nearest_nodes )
{
player_has_claimed_crate = player_has_claimed_crate | NodesVisible( nearest_node_human, node, true );
}
}
}
}
if ( !player_has_claimed_crate )
{
bot_has_claimed_crate = IsDefined( crate.bots ) && IsDefined( crate.bots[self.team] ) && crate.bots[self.team] > 0;
i_can_see_crate = false;
foreach ( node in crate.nearest_nodes )
{
i_can_see_crate = i_can_see_crate | NodesVisible( nearest_node_bot, node, true );
}
// Either take a crate that I can see, or 50% chance to take the closest one pointed out on HUD to me that isnt claimed
if ( i_can_see_crate || (can_take_any_crate_on_radar && !bot_has_claimed_crate) )
{
crate_to_take = crate;
break;
}
}
}
if ( IsDefined( crate_to_take ) )
{
// Claim this crate
if ( self [[ level.bot_funcs["crate_should_claim"] ]] () )
{
if ( !IsDefined(crate_to_take.boxType) )
{
if ( !IsDefined( crate_to_take.bots ) )
{
crate_to_take.bots = [];
}
crate_to_take.bots[self.team] = 1;
}
}
extra_params = SpawnStruct();
extra_params.object = crate_to_take;
extra_params.start_thread = ::watch_bot_died_during_crate;
extra_params.should_abort = ::crate_picked_up;
crate_dest = undefined;
if ( IsDefined(crate_to_take.boxType) )
{
if ( IsDefined( crate_to_take.boxTouchOnly ) && crate_to_take.boxTouchOnly )
{
extra_params.script_goal_radius = 16;
extra_params.action_thread = undefined;
crate_dest = crate_to_take.origin;
}
else
{
extra_params.script_goal_radius = 50;
extra_params.action_thread = ::use_box;
vec_crate_to_nearest_node = self crate_get_bot_target_check_distance( crate_to_take, player_use_radius );
if ( !IsDefined( vec_crate_to_nearest_node ) )
{
continue;
}
vec_crate_to_nearest_node -= crate_to_take.origin;
scale = Length(vec_crate_to_nearest_node) * RandomFloat(1.0);
crate_dest = (crate_to_take.origin + VectorNormalize(vec_crate_to_nearest_node) * scale) + (0,0,12);
}
}
else
{
extra_params.action_thread = ::use_crate;
extra_params.end_thread = ::stop_using_crate;
crate_dest = self crate_get_bot_target_check_distance(crate_to_take, player_use_radius );
if ( !IsDefined( crate_dest ) )
{
continue;
}
extra_params.script_goal_radius = (player_use_radius - Distance(crate_to_take.origin, crate_dest + (0,0,40)));
crate_dest = crate_dest + (0,0,24);
}
if ( IsDefined(extra_params.script_goal_radius) )
Assert(extra_params.script_goal_radius >= 0);
crate_to_take notify( "path_disconnect" );
wait 0.05;
if ( !IsDefined( crate_to_take ) )
continue;
self bot_new_tactical_goal( "airdrop_crate", crate_dest, 30, extra_params );
}
}
}
bot_should_use_ballistic_vest_crate( crate )
{
return true;
}
crate_should_claim()
{
return true;
}
crate_low_ammo_check()
{
return false;
}
bot_should_use_ammo_crate( crate )
{
if ( self GetCurrentWeapon() == level.boxSettings[crate.boxType].minigunWeapon )
return false;
return true;
}
bot_pre_use_ammo_crate( crate )
{
self SwitchToWeapon(self.secondaryWeapon);
wait(1.0);
}
bot_post_use_ammo_crate( crate )
{
self SwitchToWeapon("none");
self.secondaryWeapon = self GetCurrentWeapon(); // Make sure we're only ever switching out our secondary from gun boxes, not our primary
}
bot_should_use_scavenger_bag( crate )
{
if ( self bot_get_low_on_ammo( 0.66 ) )
{
// Scavenger bag must be in sight
nearest_node_bot = self GetNearestNode();
if ( IsDefined( crate.nearest_nodes ) && IsDefined( crate.nearest_nodes[0] ) && IsDefined( nearest_node_bot ) )
{
if ( NodesVisible(nearest_node_bot, crate.nearest_nodes[0], true) )
{
if ( within_fov( self.origin, self.angles, crate.origin, self BotGetFovDot() ) )
return true;
}
}
}
return false;
}
bot_should_use_grenade_crate( crate )
{
offhand_list = self GetWeaponsListOffhands();
foreach( weapon in offhand_list )
{
if ( self GetWeaponAmmoStock(weapon) == 0 )
return true;
}
return false;
}
bot_should_use_juicebox_crate( crate )
{
return true;
}
crate_monitor_position()
{
self notify("crate_monitor_position");
self endon("crate_monitor_position");
self endon("death");
level endon("game_ended");
while(1)
{
lastPos = self.origin;
wait(0.5);
if ( !IsAlive( self ) )
return;
if ( !bot_vectors_are_equal( self.origin, lastPos ) )
{
self.on_path_grid = undefined;
self.nearest_nodes = undefined;
self.nearest_points = undefined;
}
}
}
crate_wait_use()
{
}
crate_picked_up( goal )
{
// goal.object is the crate
if ( !IsDefined( goal.object ) )
return true;
return false;
}
use_crate( goal )
{
// goal.object is the crate
if ( IsAgent(self) )
{
self _enableUsability();
goal.object EnablePlayerUse( self );
wait(0.05);
}
self [[ level.bot_funcs["crate_wait_use"] ]]();
// crate.owner doesn't have to exist. But if it does, and this bot is the owner, use the shorter amount of time
if ( IsDefined(goal.object.owner) && goal.object.owner == self )
{
time = level.crateOwnerUseTime / 1000 + 0.5;
}
else
{
time = level.crateNonOwnerUseTime / 1000 + 1.0;
}
self BotPressButton( "use", time );
wait( time );
if ( IsAgent(self) )
{
self _disableUsability();
if ( IsDefined(goal.object) )
goal.object DisablePlayerUse( self );
}
if( isDefined( goal.object ) )
{
if ( !IsDefined( goal.object.bots_used ) )
goal.object.bots_used = [];
goal.object.bots_used[goal.object.bots_used.size] = self;
}
}
use_box( goal )
{
// goal.object is the box
if ( IsAgent(self) )
{
self _enableUsability();
goal.object EnablePlayerUse( self );
wait(0.05);
}
if ( isDefined( goal.object ) && isDefined( goal.object.boxType ) )
{
boxType = goal.object.boxType;
if ( IsDefined(level.bot_pre_use_box_of_type[boxType]) )
self [[ level.bot_pre_use_box_of_type[boxType] ]](goal.object);
if ( IsDefined(goal.object) ) // Might have been picked up during a wait in the pre_use function
{
time = (level.boxSettings[goal.object.boxType].useTime / 1000) + 0.5;
self BotPressButton( "use", time );
wait( time );
if ( IsDefined(level.bot_post_use_box_of_type[boxType]) )
self [[ level.bot_post_use_box_of_type[boxType] ]](goal.object);
}
}
if ( IsAgent(self) )
{
self _disableUsability();
if ( IsDefined(goal.object) )
goal.object DisablePlayerUse( self );
}
}
watch_bot_died_during_crate( goal )
{
// goal.object is the crate
self thread bot_watch_for_death( goal.object );
}
stop_using_crate( goal )
{
// goal.object is the crate
if ( IsDefined( goal.object ) )
{
goal.object.bots[self.team] = 0;
}
}
//========================================================
// bot_watch_for_death
//========================================================
bot_watch_for_death( object )
{
object endon( "death" );
object endon( "revived" );
object endon( "disconnect" );
level endon( "game_ended" );
prev_team = self.team;
self waittill_any( "death", "disconnect" );
if ( IsDefined(object) )
{
object.bots[prev_team] = 0;
}
}
//========================================================
// bot_think_crate_blocking_path
//========================================================
bot_think_crate_blocking_path()
{
self notify( "bot_think_crate_blocking_path" );
self endon( "bot_think_crate_blocking_path" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
radius = GetDvarFloat( "player_useRadius" );
// ensure bots don't get stuck on crates
while( true )
{
wait( 3 );
if( self UseButtonPressed() )
{
continue;
}
if ( self isUsingRemote() )
continue;
crates = level.carePackages;
for ( i = 0; i < crates.size; i++ )
{
crate = crates[i];
if ( !IsDefined( crate ) )
continue;
if( DistanceSquared( self.origin, crate.origin ) < radius * radius )
{
if ( crate.owner == self )
{
self BotPressButton( "use", level.crateOwnerUseTime / 1000 + 0.5 );
}
else
{
self BotPressButton( "use", level.crateNonOwnerUseTime / 1000 + 0.5 );
}
}
}
}
}
//========================================================
// bot_think_revive
//========================================================
bot_think_revive()
{
self notify( "bot_think_revive" );
self endon( "bot_think_revive" );
self endon( "death" );
self endon( "disconnect" );
level endon ( "game_ended" );
if( !level.teamBased )
{
return;
}
while( true )
{
waitTime = 2.0;
revive_triggers = GetEntArray( "revive_trigger", "targetname" );
if( revive_triggers.size > 0 )
waitTime = 0.05;
level waittill_notify_or_timeout( "player_last_stand", waitTime );
if( !self bot_can_revive() )
{
continue;
}
revive_triggers = GetEntArray( "revive_trigger", "targetname" );
// sort the players in last stand
if( revive_triggers.size > 1 )
{
revive_triggers = SortByDistance( revive_triggers, self.origin );
// put the agent's owner at the begining of the list
if( IsDefined(self.owner) )
{
for( i = 0; i < revive_triggers.size; i++ )
{
if( revive_triggers[i].owner != self.owner )
continue;
// agent's owner is already at the front of the array
if( i == 0 )
break;
agent_owner_trigger = revive_triggers[i];
revive_triggers[i] = revive_triggers[0];
revive_triggers[0] = agent_owner_trigger;
break;
}
}
}
for( i = 0; i < revive_triggers.size; i++ )
{
revive_trigger = revive_triggers[i];
player = revive_trigger.owner;
if( !IsDefined( player) )
{
continue;
}
if( player == self )
{
continue;
}
if( !IsAlive( player ) )
{
continue;
}
if( player.team != self.team )
{
continue;
}
if( !IsDefined( player.inLastStand ) || !player.inLastStand )
{
continue;
}
if ( IsDefined( player.bots ) && IsDefined( player.bots[self.team] ) && player.bots[self.team] > 0 )
{
continue;
}
if( DistanceSquared( self.origin, player.origin ) < 2048 * 2048 )
{
extra_params = SpawnStruct();
extra_params.object = revive_trigger;
extra_params.script_goal_radius = 64;
if ( IsDefined(self.last_revive_fail_time) && GetTime() - self.last_revive_fail_time < 1000 )
extra_params.script_goal_radius = 32;
extra_params.start_thread = ::watch_bot_died_during_revive;
extra_params.end_thread = ::stop_reviving;
extra_params.should_abort = ::player_revived_or_dead;
extra_params.action_thread = ::revive_player;
self bot_new_tactical_goal( "revive", player.origin, 60, extra_params );
break;
}
}
}
}
watch_bot_died_during_revive( goal )
{
// goal.object is the revive trigger of the player to revive
self thread bot_watch_for_death( goal.object.owner );
}
stop_reviving( goal )
{
// goal.object is the revive trigger of the player to revive
if ( IsDefined( goal.object.owner ) )
{
goal.object.owner.bots[self.team] = 0;
}
}
player_revived_or_dead( goal )
{
// goal.object is the revive trigger of the player to revive
if ( !IsDefined( goal.object.owner ) || goal.object.owner.health <= 0 )
return true;
if ( !IsDefined( goal.object.owner.inLastStand ) || !goal.object.owner.inLastStand )
return true;
return false;
}
revive_player( goal )
{
// goal.object is the revive trigger of the player to revive
if ( DistanceSquared(self.origin,goal.object.owner.origin) > 64 * 64 )
{
self.last_revive_fail_time = GetTime();
return; // player crawled away, try again
}
if ( IsAgent(self) )
{
self _enableUsability();
goal.object EnablePlayerUse( self );
wait(0.05);
}
prev_team = self.team;
self BotPressButton( "use", level.lastStandUseTime / 1000 + 0.5 );
wait( level.lastStandUseTime / 1000 + 1.5 );
if ( IsDefined(goal.object.owner) )
goal.object.bots[prev_team] = 0;
if ( IsAgent(self) )
{
self _disableUsability();
if ( IsDefined(goal.object) )
goal.object DisablePlayerUse( self );
}
}
//========================================================
// bot_can_revive
//========================================================
bot_can_revive()
{
if ( IsDefined( self.laststand ) && self.laststand == true )
return false;
if ( self bot_has_tactical_goal( "revive" ) )
return false;
if ( self bot_is_remote_or_linked() )
return false;
// Bodyguards can always revive no matter what their goal type is
if ( self bot_is_bodyguarding() )
return true;
goalType = self BotGetScriptGoalType();
if ( goalType == "none" || goalType == "hunt" || goalType == "guard" )
return true;
return false;
}
//========================================================
// revive_watch_for_finished
//========================================================
revive_watch_for_finished( player )
{
self endon( "death" );
self endon( "disconnect" );
self endon( "bad_path" );
self endon( "goal" );
player waittill_any( "death", "revived" );
self notify( "bad_path" );
}
//========================================================
// bot_know_enemies_on_start
//========================================================
bot_know_enemies_on_start()
{
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
// Wait till grace period is over, then let this bot know where enemies are
// (this is intended for the beginning of a match to get them seeking out enemies based on "knowledge" of the map start spots)
if ( GetTime() > 15000 )
return;
while ( !gameHasStarted() || !gameFlag( "prematch_done" ) )
{
wait 0.05;
}
chosenEnemy = undefined;
chosenEnemyKnowSelf = undefined;
for ( enemyIdx = 0; enemyIdx < level.players.size; enemyIdx++ )
{
otherPlayer = level.players[enemyIdx];
if ( IsDefined( otherPlayer ) && IsDefined( self.team ) && IsDefined( otherPlayer.team ) && IsEnemyTeam( self.team, otherPlayer.team ) )
{
if ( !IsDefined( otherPlayer.bot_start_known_by_enemy ) )
chosenEnemy = otherPlayer;
if ( IsAI( otherPlayer ) && !IsDefined( otherPlayer.bot_start_know_enemy ) )
chosenEnemyKnowSelf = otherPlayer;
}
}
if ( IsDefined( chosenEnemy ) )
{
self.bot_start_know_enemy = true;
chosenEnemy.bot_start_known_by_enemy = true;
self GetEnemyInfo( chosenEnemy );
}
if ( IsDefined( chosenEnemyKnowSelf ) )
{
chosenEnemyKnowSelf.bot_start_know_enemy = true;
self.bot_start_known_by_enemy = true;
chosenEnemyKnowSelf GetEnemyInfo( self );
}
}
//========================================================
// bot_make_entity_sentient
//========================================================
bot_make_entity_sentient( team, expendable )
{
if ( IsDefined(expendable) )
return self MakeEntitySentient( team, expendable );
else
return self MakeEntitySentient( team );
}
//========================================================
// bot_think_gametype
//========================================================
bot_think_gametype()
{
self notify( "bot_think_gametype" );
self endon( "bot_think_gametype" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
gameFlagWait( "prematch_done" );
self thread [[ level.bot_funcs["gametype_think"] ]]();
}
default_gametype_think()
{
// do nothing
}
monitor_smoke_grenades()
{
while(1)
{
level waittill("smoke", smoke_grenade, smoke_grenade_weaponName);
if ( smoke_grenade_weaponName == "smoke_grenade_mp" || smoke_grenade_weaponName == "smoke_grenadejugg_mp" || smoke_grenade_weaponName == "odin_projectile_smoke_mp" )
smoke_grenade thread handle_smoke( 9.0 );
else if ( smoke_grenade_weaponName == "odin_projectile_large_rod_mp" )
smoke_grenade thread handle_smoke( 2.5 );
}
}
handle_smoke( final_wait_time )
{
self waittill("explode", explosion_location );
new_sight_clip_origin = spawn_tag_origin();
new_sight_clip_origin show();
new_sight_clip_origin.origin = explosion_location;
next_wait_time = 0.8;
wait(next_wait_time);
next_wait_time = 0.5;
smoke_sight_clip_collision_64_short = GetEnt( "smoke_grenade_sight_clip_64_short", "targetname" );
if ( IsDefined(smoke_sight_clip_collision_64_short) )
{
new_sight_clip_origin CloneBrushmodelToScriptmodel( smoke_sight_clip_collision_64_short );
//draw_entity_bounds( new_sight_clip_origin, next_wait_time, (1,0,0) );
}
wait(next_wait_time);
next_wait_time = 0.6;
smoke_sight_clip_collision_64_tall = GetEnt( "smoke_grenade_sight_clip_64_tall", "targetname" );
if ( IsDefined(smoke_sight_clip_collision_64_tall) )
{
new_sight_clip_origin CloneBrushmodelToScriptmodel( smoke_sight_clip_collision_64_tall );
//draw_entity_bounds( new_sight_clip_origin, next_wait_time, (1,0,0) );
}
wait(next_wait_time);
next_wait_time = final_wait_time;
smoke_sight_clip_collision_256 = GetEnt( "smoke_grenade_sight_clip_256", "targetname" );
if ( IsDefined(smoke_sight_clip_collision_256) )
{
new_sight_clip_origin CloneBrushmodelToScriptmodel( smoke_sight_clip_collision_256 );
//draw_entity_bounds( new_sight_clip_origin, next_wait_time, (1,0,0) );
}
wait(next_wait_time);
new_sight_clip_origin delete();
}
bot_add_scavenger_bag( dropBag )
{
added = false;
dropBag.boxType = "scavenger_bag";
dropBag.boxTouchOnly = true;
if ( !IsDefined( level.bot_scavenger_bags ) )
level.bot_scavenger_bags = [];
// First fill any empty slot found
foreach( index, existingBag in level.bot_scavenger_bags )
{
if ( !IsDefined( existingBag ) )
{
added = true;
level.bot_scavenger_bags[index] = dropBag;
break;
}
}
if ( !added )
level.bot_scavenger_bags[level.bot_scavenger_bags.size] = dropBag;
// Notify all scavengers that this bag is now available
foreach( participant in level.participants )
{
if ( isAI( participant ) && participant _hasPerk( "specialty_scavenger" ) )
participant notify( "new_crate_to_take" );
}
}
//========================================================
// bot_triggers
//========================================================
bot_triggers()
{
bot_flag_set_triggers = GetEntArray("bot_flag_set", "targetname");
foreach(trigger in bot_flag_set_triggers)
{
if(!IsDefined(trigger.script_noteworthy))
{
AssertMsg("Bot Flag trigger at " + trigger.origin + " is missing script_noteworthy flag name.");
continue;
}
trigger thread bot_flag_trigger(trigger.script_noteworthy);
}
}
bot_flag_trigger(flag_name)
{
self endon("death");
while(1)
{
self waittill("trigger", bot);
if(IsAIGameParticipant(bot))
{
bot notify("flag_trigger_set_" + flag_name);
bot BotSetFlag(flag_name, true);
bot thread bot_flag_trigger_clear(flag_name);
}
}
}
bot_flag_trigger_clear(flag_name)
{
self endon("flag_trigger_set_" + flag_name);
self endon("death");
self endon("disconnect");
level endon("game_ended");
waitframe();
waittillframeend;
self BotSetFlag(flag_name, false);
}