#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\bots\_bots;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_ks_remote_vehicle;
#include maps\mp\bots\_bots_sentry;

//========================================================
//				bot_killstreak_setup
//========================================================

bot_killstreak_setup()
{
	time_started = GetTime();
	
 	if ( !IsDefined( level.killstreak_botfunc ) )
	{
		thread bot_setup_map_specific_killstreaks();
		
		// *******************************
		// *    BLACKSMITH KILLSTREAKS   *
		// *******************************
		
		// Simple Use ===========================================================================
		
		bot_register_killstreak_func( "uav",									::bot_killstreak_simple_use );
		bot_register_killstreak_func( "orbital_carepackage",					::bot_killstreak_simple_use );
		bot_register_killstreak_func( "heavy_exosuit",							::bot_killstreak_simple_use );
		bot_register_killstreak_func( "nuke",									::bot_killstreak_simple_use );
		
		// Simple Use with test =================================================================
		
		bot_register_killstreak_func( "emp",									::bot_killstreak_simple_use,	::bot_can_use_emp );
		bot_register_killstreak_func( "remote_mg_sentry_turret",				::bot_killstreak_sentry, 		::bot_can_use_sentry_only_ai_version, "turret" );
		bot_register_killstreak_func( "assault_ugv", 							::bot_killstreak_simple_use,	::bot_can_use_assault_ugv_only_ai_version );
		bot_register_killstreak_func( "warbird",								::bot_killstreak_simple_use,	::bot_can_use_warbird_only_ai_version );
		
		// Aerial Targeting =====================================================================
		
		bot_register_killstreak_func( "strafing_run_airstrike",					::bot_killstreak_choose_loc_enemies, ::bot_can_use_strafing_run );
		
		// Not hooked up yet ====================================================================
		
		bot_register_killstreak_func( "orbitalsupport",							::bot_killstreak_never_use,		::bot_killstreak_do_not_use );
		bot_register_killstreak_func( "recon_ugv",								::bot_killstreak_never_use,		::bot_killstreak_do_not_use );
		bot_register_killstreak_func( "orbital_strike_laser",					::bot_killstreak_never_use,		::bot_killstreak_do_not_use );
		bot_register_killstreak_func( "missile_strike",							::bot_killstreak_never_use, 	::bot_killstreak_do_not_use );
		
		/#
		thread bot_validate_killstreak_funcs();
		#/
	}
 	
 	thread remote_vehicle_setup();
 	
 	Assert(time_started == GetTime());
}

bot_setup_map_specific_killstreaks()
{
	wait(0.5);	// Give time for the level.mapCustomBotKillstreakFunc to be defined
	if(IsDefined(level.mapCustomBotKillstreakFunc))
		[[ level.mapCustomBotKillstreakFunc ]]();
	else if ( IsDefined(level.mapKillStreak) )
		bot_register_killstreak_func( level.mapKillStreak, maps\mp\bots\_bots_ks::bot_killstreak_never_use, maps\mp\bots\_bots_ks::bot_killstreak_do_not_use );
}

//========================================================
//				bot_register_killstreak_func 
//========================================================
bot_register_killstreak_func( name, func, can_use, optionalParam )
{
 	if ( !IsDefined( level.killstreak_botfunc ) )
		level.killstreak_botfunc = [];

	level.killstreak_botfunc[name] = func;
	
	if ( !IsDefined( level.killstreak_botcanuse ) )
		level.killstreak_botcanuse = [];
	
	level.killstreak_botcanuse[name] = can_use;
	
 	if ( !IsDefined( level.killstreak_botparm ) )
		level.killstreak_botparm = [];

	level.killstreak_botparm[name] = optionalParam;

 	if ( !IsDefined( level.bot_supported_killstreaks ) )
		level.bot_supported_killstreaks = [];
	level.bot_supported_killstreaks[level.bot_supported_killstreaks.size] = name;
}


/#
assert_streak_valid_for_bots_in_general(streak)
{
	if ( !bot_killstreak_valid_for_bots_in_general(streak) )
	{
		AssertMsg( "Bots do not support killstreak <" + streak + ">" );
	}
}

assert_streak_valid_for_specific_bot(streak, bot)
{
	if ( !bot_killstreak_valid_for_specific_bot(streak, bot) )
	{
		AssertMsg( "Bot <" + bot.name + "> does not support killstreak <" + streak + ">" );
	}
}

//========================================================
//				bot_validate_killstreak_funcs 
//========================================================
bot_validate_killstreak_funcs()
{
	// Give a second for other killstreaks to be included before testing
	// Needed because for example, init() in _dog_killstreak.gsc is called AFTER this function
	wait(1);
	
	errors = [];
	foreach( streakName in level.bot_supported_killstreaks )
	{
		if ( !bot_killstreak_valid_for_humans(streakName) )
		{
			error( "bot_validate_killstreak_funcs() invalid killstreak: " + streakName );		
			errors[errors.size] = streakName;
		}
	}
	if ( errors.size )
	{
		temp = level.killstreakFuncs;
		level.killStreakFuncs = [];
		foreach( streakName in temp )
		{
			if ( !array_contains( errors, streakName ) )
				level.killStreakFuncs[streakName] = temp[streakName];
		}
	}
}

bot_killstreak_valid_for_humans(streakName)
{
	// Checks if the specified killstreak is valid for human players (i.e. set up correctly, human players can use it, etc)
	return bot_killstreak_is_valid_internal(streakName, "humans");
}

bot_killstreak_valid_for_bots_in_general(streakName)
{
	// Checks if the specified killstreak is valid for bot players.  This means it is set up correctly for humans, AND bots also know how to use it
	return bot_killstreak_is_valid_internal(streakName, "bots");
}

bot_killstreak_valid_for_specific_bot(streakName, bot)
{
	// Checks if the specified killstreak is valid for bot players.  This means it is set up correctly for humans, AND bots also know how to use it,
	// and this specific bot can use it
	return bot_killstreak_is_valid_internal(streakName, "bots", bot);
}
#/

bot_is_valid_killstreak(streakName, assertIt)
{
	// Checks if the specified killstreak is valid for bot players.  This means it is set up correctly for humans, AND bots also know how to use it.
	if ( bot_killstreak_is_valid_internal(streakName, "bots", undefined) )
	{
		return true;
	}
	else if ( assertIt )
	{
		AssertMsg( "Bots do not support killstreak <" + streakName + ">" );
	}

	return false;
}

bot_killstreak_is_valid_internal(streakName, who_to_check, optional_bot )
{
	if ( !bot_killstreak_is_valid_single(streakName, who_to_check) )
	{
		// Either bots or human players don't have a function handle this killstreak
		return false;
	}
	
	return true;
}

bot_killstreak_is_valid_single(streakName, who_to_check)
{
	if ( who_to_check == "humans" )
	{
		return ( IsDefined( level.killstreakFuncs[streakName] ) && getKillstreakIndex( streakName ) != -1 );
	}
	else if ( who_to_check == "bots" )
	{
		return IsDefined( level.killstreak_botfunc[streakName] );
	}
	
	AssertMsg("Unreachable");
}
	
//========================================================
//				bot_think_killstreak 
//========================================================
bot_think_killstreak()
{
	self notify( "bot_think_killstreak" );
	self endon(  "bot_think_killstreak" );

	self endon( "death" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	while( !IsDefined(level.killstreak_botfunc) )
		wait(0.05);
	
	//self childthread bot_start_aa_launcher_tracking();
	
	for(;;)
	{
		if ( self bot_allowed_to_use_killstreaks() )
		{
			killstreaks_array = self.pers["killstreaks"];
			if ( IsDefined( killstreaks_array ) )
			{
				restart_loop = false;
				for ( ksi = 0; ksi < killstreaks_array.size && !restart_loop; ksi++ )
				{
					killstreak_info = killstreaks_array[ksi];
					
					if ( IsDefined( killstreak_info.streakName ) && IsDefined( self.bot_killstreak_wait ) && IsDefined( self.bot_killstreak_wait[killstreak_info.streakName] ) && GetTime() < self.bot_killstreak_wait[killstreak_info.streakName] )
						continue;
					
					if ( killstreak_info.available )
					{
						tableName = killstreak_info.streakName;

						killstreak_info.weapon = getKillstreakWeapon( killstreak_info.streakName, killstreak_info.modules );
						
						can_use_killstreak_function = level.killstreak_botcanuse[ tableName ];
						if ( IsDefined(can_use_killstreak_function) && !self [[ can_use_killstreak_function ]](killstreak_info) )
							continue;
						
						if ( !self validateUseStreak( killstreak_info.streakName, true ) )
							continue;
						
						bot_killstreak_func = level.killstreak_botfunc[ tableName ];
						
						if ( IsDefined( bot_killstreak_func ) )
					    {
							// Call the function (do NOT thread it)
							// This way its easy to manage one killstreak working at a time
							// and all these functions will end when any of the above endon conditions are met
							result = self [[bot_killstreak_func]]( killstreak_info, killstreaks_array, can_use_killstreak_function, level.killstreak_botparm[ killstreak_info.streakName ] );
							if ( !IsDefined( result ) || result == false )
							{
								// killstreak cannot be used yet, stop trying for a bit and come back to it later
								if ( !isdefined( self.bot_killstreak_wait ) )
									self.bot_killstreak_wait = [];
								self.bot_killstreak_wait[killstreak_info.streakName] = GetTime() + 5000;
							}
					    }
						else
						{
							// Bots dont know how to use this killstreak, just get rid of it so we can use something else on the stack
							AssertMsg("Bots do not know how to use killstreak <" + killstreak_info.streakName + ">");
							killstreak_info.available = false;
							self maps\mp\killstreaks\_killstreaks::updateKillstreaks( false );
						}
						
						restart_loop = true;
					}
				}
			}
		}
		
		wait( RandomFloatRange( 2.0, 4.0 ) );
	}
}

bot_killstreak_never_use()
{
	// This function needs to exist because every killstreak for bots needs a function, but it should never be called
	AssertMsg( "bot_killstreak_never_use() was somehow called" );
}

bot_killstreak_do_not_use( killstreak_info )
{
	return false;
}

bot_killstreak_can_use_weapon_version( killstreak_info )
{
	// For now bots can use fire-and-forget killstreaks, not controllable killstreaks ("killstreak_predator_missile_mp")
	return ( killstreak_info.weapon == "killstreak_uav_mp" );
}

bot_can_use_warbird_only_ai_version( killstreak_info )
{
	if ( !bot_killstreak_can_use_weapon_version( killstreak_info ) )
		return false;
	
	if ( !bot_can_use_warbird( killstreak_info ) )
		return false;
	
	return true;
}

bot_can_use_warbird( killstreak_info )
{
	if ( !self maps\mp\killstreaks\_warbird::CanUseWarbird() )
		return false;
	
	if ( vehicle_would_exceed_limit() )
		return false;
	
	return true;
}

bot_can_use_assault_ugv_only_ai_version( killstreak_info )
{
	if ( !bot_killstreak_can_use_weapon_version( killstreak_info ) )
		return false;
	
	if ( !bot_can_use_assault_ugv( killstreak_info ) )
		return false;
	
	return true;
}

bot_can_use_assault_ugv( killstreak_info )
{
	if ( vehicle_would_exceed_limit() )
		return false;
	
	return true;
}

vehicle_would_exceed_limit()
{
	return ( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + 1 >= maxVehiclesAllowed() );
}

bot_can_use_emp( killstreak_info )
{
	// is there already an active EMP?
	if ( IsDefined( level.empPlayer ) )
		return false;
	
	otherTeam = level.otherTeam[self.team];	
	if ( isdefined( level.teamEMPed ) && IsDefined( level.teamEMPed[ otherTeam ] ) && level.teamEMPed[ otherTeam ] ) 
		return false;
	
	return true;
}

bot_can_use_strafing_run( killstreak_info )
{
	if ( IsDefined( level.strafing_run_airstrike ) )
		return false;	
	
	return true;
}

//========================================================
//				bot_killstreak_simple_use 
//========================================================
bot_killstreak_simple_use( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
	wait( RandomIntRange( 3, 5 ) );
		
	if ( !self bot_allowed_to_use_killstreaks() )
	{
		// This may have become false during the wait, like an enemy appeared while we were waiting
		return true;
	}
	
	if ( IsDefined( canUseFunc ) && !self [[canUseFunc]]( killstreak_info ) )
		return false;
	
	bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
	
	return true;
}

//========================================================
//				bot_killstreak_drop_anywhere
//========================================================
bot_killstreak_drop_anywhere( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
	bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, "anywhere" );
}
	
//========================================================
//				bot_killstreak_drop_outside
//========================================================
bot_killstreak_drop_outside( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
	bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, "outside" );
}

//========================================================
//				bot_killstreak_drop_hidden
//========================================================
bot_killstreak_drop_hidden( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
	bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, "hidden" );
}

//========================================================
//				bot_killstreak_drop
//========================================================
bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, drop_where )
{
	wait( RandomIntRange( 2, 4 ) );
	
	if ( !isDefined( drop_where ) ) 
		drop_where = "anywhere";
	
	if ( !self bot_allowed_to_use_killstreaks() )
	{
		// This may have become false during the wait, like an enemy appeared while we were waiting
		return true;
	}
	
	if ( IsDefined( canUseFunc ) && !self [[canUseFunc]]( killstreak_info ) )
		return false;
	
	ammo = self GetWeaponAmmoClip( killstreak_info.weapon ) + self GetWeaponAmmoStock( killstreak_info.weapon );
	if ( ammo == 0 )
	{
		// Trying to use an airdrop but we don't have any ammo
		foreach( streak in killstreaks_array )
		{
			if ( IsDefined(streak.streakName) && streak.streakName == killstreak_info.streakName )
				streak.available = 0;
		}
		self maps\mp\killstreaks\_killstreaks::updateKillstreaks( false );
		return true;
	}

	node_target = undefined;
	if ( drop_where == "outside" )
	{
		outside_nodes = [];
		nodes_in_cone = self bot_get_nodes_in_cone( 0, 750, 0.6, true );
		foreach ( node in nodes_in_cone )
		{
			if ( NodeExposedToSky( node ) )
				outside_nodes = array_add( outside_nodes, node );
		}
		
		if ( (nodes_in_cone.size > 5) && (outside_nodes.size > nodes_in_cone.size * 0.6) )
		{
			outside_nodes_sorted = get_array_of_closest( self.origin, outside_nodes, undefined, undefined, undefined, 150 );
			if ( outside_nodes_sorted.size > 0 )
				node_target = random(outside_nodes_sorted);
			else
				node_target = random(outside_nodes);
		}
	}
	else if ( drop_where == "hidden" )
	{
		nodes_in_radius = GetNodesInRadius( self.origin, 256, 0, 40 );
		node_nearest_bot = self GetNearestNode();
		if ( IsDefined(node_nearest_bot) )
		{
			visible_nodes_in_radius = [];
			foreach( node in nodes_in_radius )
			{
				if ( NodesVisible( node_nearest_bot, node, true ) )
					visible_nodes_in_radius = array_add(visible_nodes_in_radius,node);
			}
			
			node_target = self BotNodePick( visible_nodes_in_radius, 1, "node_hide" );
		}
	}
	
	if ( IsDefined(node_target) || drop_where == "anywhere" )
	{
		self BotSetFlag( "disable_movement", true );
		
		if ( IsDefined(node_target) )
			self BotLookAtPoint( node_target.origin, 1.5+0.95, "script_forced" );
		
		bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
		wait(2.0);
		self BotPressButton( "attack" );
		wait(1.5);
		self SwitchToWeapon( "none" );	// clears scripted weapon for bots
		self BotSetFlag( "disable_movement", false );
	}
	
	return true;
}

bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, weapon_name )
{
	self bot_notify_streak_used( killstreak_info, killstreaks_array );
	wait(0.05);	// Wait for the notify to be received and self.killstreakIndexWeapon to be set
	self SwitchToWeapon( weapon_name );
}

bot_notify_streak_used( killstreak_info, killstreaks_array )
{
	if ( IsDefined( killstreak_info.isgimme ) && killstreak_info.isgimme )
	{
		self notify("streakUsed1");
	}
	else
	{
		for ( index = 0; index < 3; index++ )
		{
			if ( IsDefined(killstreaks_array[index].streakName) )
			{
				if ( killstreaks_array[index].streakName == killstreak_info.streakname )
					break;
			}
		}
		self notify("streakUsed" + (index+1));
	}
}

//========================================================
//			bot_killstreak_choose_loc_enemies 
//========================================================
bot_killstreak_choose_loc_enemies( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
	wait( RandomIntRange( 3, 5 ) );
	
	if ( !self bot_allowed_to_use_killstreaks() )
	{
		// This may have become false during the wait, like an enemy appeared while we were waiting
		return;
	}
	
	zone_nearest_bot = GetZoneNearest( self.origin );
	if ( !IsDefined(zone_nearest_bot) )
		return;
	
	self BotSetFlag( "disable_movement", true );
	bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
	wait 2;
	
	if ( !IsDefined(self.selectingLocation) )
		return;

	zone_count = level.zoneCount;
	best_zone = -1;
	best_zone_count = 0;
	possible_fallback_zones = [];
	iterate_backwards = RandomFloat(100) > 50;	// randomly choose to iterate backwards
	for ( z = 0; z < zone_count; z++ )
	{
		if ( iterate_backwards )
			zone = zone_count - 1 - z;
		else
			zone = z;
		
		if ( (zone != zone_nearest_bot) && (BotZoneGetIndoorPercent( zone ) < 0.25) )
		{
			// This zone is not the current bot's zone, and it is mostly an outside zone
			enemies_in_zone = BotZoneGetCount( zone, self.team, "enemy_predict" );
			if ( enemies_in_zone > best_zone_count )
			{
				best_zone = zone;
				best_zone_count = enemies_in_zone;
			}
			
			possible_fallback_zones = array_add( possible_fallback_zones, zone );
		}
	}
	
	if ( best_zone >= 0 )
		zoneCenter = GetZoneOrigin( best_zone );
	else if ( possible_fallback_zones.size > 0 )
		zoneCenter = GetZoneOrigin( random(possible_fallback_zones) );
	else
		zoneCenter = GetZoneOrigin(RandomInt( level.zoneCount ));

	randomOffset = (RandomFloatRange(-500, 500), RandomFloatRange(-500, 500), 0);

	keep_trying_placement = true;
	while( keep_trying_placement )
	{
		self notify( "confirm_location", zoneCenter + randomOffset, RandomIntRange(0, 360) );
		
		result = self waittill_any_return( "location_selection_complete", "airstrikeShowBlockedHUD" );
		
		if ( result == "location_selection_complete" )
			keep_trying_placement = false;
		else
			wait(0.5);
	}
	
	wait( 1.0 );
	self BotSetFlag( "disable_movement", false );
}

SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS = 4000;
SCR_CONST_GLOBAL_BP_DURATION_S = 5.0;

//========================================================
//			bot_think_watch_aerial_killstreak 
//========================================================
bot_think_watch_aerial_killstreak()
{
	self notify( "bot_think_watch_aerial_killstreak" );
	self endon(  "bot_think_watch_aerial_killstreak" );

	self endon( "death" );
	self endon( "disconnect" );
	level endon ( "game_ended" );
	
	if ( !IsDefined(level.last_global_badplace_time) )
		level.last_global_badplace_time = -10000;
	
	if ( !IsDefined(level.killstreak_global_bp_exists_for) )
	{
		level.killstreak_global_bp_exists_for["allies"] = [];
		level.killstreak_global_bp_exists_for["axis"] = [];
	}
	
	if ( !IsDefined(level.aerial_danger_exists_for) )
	{
		level.aerial_danger_exists_for["allies"] = false;
		level.aerial_danger_exists_for["axis"] = false;
	}
	
	currently_hiding = false;
	next_wait_time = RandomFloatRange(0.05,4.0);
	while(1)
	{
		wait(next_wait_time);
		next_wait_time = RandomFloatRange(0.05,4.0);
		
		if ( self bot_is_remote_or_linked() )
			continue;
		
		if ( self BotGetDifficultySetting("strategyLevel") == 0 )
			continue;	// dumb bots don't try to avoid aerial danger
		
		needs_to_hide = false;
		
		// Enemy called in Warbird
		warbird = get_enemy_warbird( self.team );
		if ( IsDefined(warbird) )
		{
			needs_to_hide = true;
			if ( !self bot_is_monitoring_aerial_danger(warbird) )
				self childthread monitor_aerial_danger(warbird);
		}
		
		// Enemy called in Paladin
		if ( enemy_paladin_exists( self.team ) )
		{
			needs_to_hide = true;
			if ( !self bot_is_monitoring_aerial_danger(level.orbitalsupport_planemodel) )
				self childthread monitor_aerial_danger(level.orbitalsupport_planemodel);
		}
		
		// Enemy called in Orbital Laser
		if ( enemy_orbital_laser_exists( self.team ) )
		{
			self try_place_global_badplace( "orbital_laser", ::enemy_orbital_laser_exists );
			needs_to_hide = true;
		}
		
		// Enemy is using Missile Strike
		if ( enemy_missile_strike_exists( self.team ) )
		{
			self try_place_global_badplace( "missile_strike", ::enemy_missile_strike_exists );
			needs_to_hide = true;
		}
		
		// Enemy is using Strafing Run
		if ( enemy_strafing_run_exists( self.team ) )
		{
			self try_place_global_badplace( "missile_strike", ::enemy_strafing_run_exists );
			needs_to_hide = true;
		}
		
		/*
		// Enemy called in Attack Helicopter / Pave Low
		if ( IsDefined(level.chopper) && level.chopper.team != self.team )
			needs_to_hide = true;
		
		// Enemy controlling Heli Sniper
		if ( IsDefined(level.lbSniper) && level.lbSniper.team != self.team )
			needs_to_hide = true;
		
		// Enemy controlling Heli Pilot
		if ( IsDefined(level.heli_pilot) && IsDefined(level.heli_pilot[get_enemy_team(self.team)]) )
		    needs_to_hide = true;
		
		if ( enemy_mortar_strike_exists( self.team ) )
		{
			needs_to_hide = true;
			self try_place_global_badplace( "mortar_strike", ::enemy_mortar_strike_exists );
		}
				
		// Enemy is using Switchblade Cluster
		if ( enemy_switchblade_exists( self.team ) )
		{
			needs_to_hide = true;
			self try_place_global_badplace( "switchblade", ::enemy_switchblade_exists );
		}
		
		// Enemy is using Odin Assault
		if ( enemy_odin_assault_exists( self.team ) )
		{
			needs_to_hide = true;
			self try_place_global_badplace( "odin_assault", ::enemy_odin_assault_exists );
		}

		// Enemy is using Vanguard
		enemy_vanguard = self get_enemy_vanguard();
		if ( IsDefined(enemy_vanguard) )
		{
			botEye = self GetEye();
			if ( within_fov( botEye, self GetPlayerAngles(), enemy_vanguard.attackArrow.origin, self BotGetFovDot() ) )
			{
				if ( SightTracePassed( botEye, enemy_vanguard.attackArrow.origin, false, self, enemy_vanguard.attackArrow ) )
					BadPlace_Cylinder("vanguard_" + enemy_vanguard GetEntityNumber(), next_wait_time + 0.5, enemy_vanguard.attackArrow.origin, 200, 100, self.team );
			}
		}
		*/
		
		if ( !currently_hiding && needs_to_hide )
		{
			currently_hiding = true;
			self BotSetFlag( "hide_indoors", 1 );
		}
		if ( currently_hiding && !needs_to_hide )
		{
			currently_hiding = false;
			self BotSetFlag( "hide_indoors", 0 );
		}
		
		level.aerial_danger_exists_for[self.team] = needs_to_hide;
	}
}

bot_is_monitoring_aerial_danger( aerial_ent )
{
	if ( !IsDefined(self.aerial_dangers_monitoring) )
		return false;
	
	return array_contains( self.aerial_dangers_monitoring, aerial_ent );
}

monitor_aerial_danger( aerial_ent )
{
	if ( !IsDefined(self.aerial_dangers_monitoring) )
		self.aerial_dangers_monitoring = [];
	
	Assert( !array_contains( self.aerial_dangers_monitoring, aerial_ent ) );
	self.aerial_dangers_monitoring[self.aerial_dangers_monitoring.size] = aerial_ent;
	
	checked_dir = VectorNormalize((aerial_ent.origin - self.origin) * (1,1,0));
	while( IsAlive(aerial_ent) )
	{
		new_checked_dir = VectorNormalize((aerial_ent.origin - self.origin) * (1,1,0));
		dot = VectorDot(checked_dir,new_checked_dir);
		if ( dot <= 0 )
		{
			checked_dir = new_checked_dir;
			self notify("defend_force_node_recalculation");
		}
		
		wait(0.05);
	}

	self.aerial_dangers_monitoring = array_remove( self.aerial_dangers_monitoring, aerial_ent );
}

try_place_global_badplace( killstreak_unique_name, killstreak_exists_func )
{
	if ( !IsDefined(level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name]) )
		level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name] = false;
	
	if ( !level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name] )
	{
		level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name] = true;
		level thread monitor_enemy_dangerous_killstreak(self.team, killstreak_unique_name, killstreak_exists_func);
	}
}

monitor_enemy_dangerous_killstreak( my_team, killstreak_unique_name, killstreak_exists_func )
{
	Assert( SCR_CONST_GLOBAL_BP_DURATION_S > (SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS / 1000) );
	wait_time = (SCR_CONST_GLOBAL_BP_DURATION_S - (SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS / 1000)) * 0.5;
	while( [[killstreak_exists_func]](my_team) )
	{
		if ( GetTime() > level.last_global_badplace_time + SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS )
		{
			BadPlace_Global( "", SCR_CONST_GLOBAL_BP_DURATION_S, my_team, "only_sky" );
			level.last_global_badplace_time = GetTime();
		}
		
		wait(wait_time);
	}
	
	level.killstreak_global_bp_exists_for[my_team][killstreak_unique_name] = false;
}

get_enemy_warbird( my_team )
{
	if ( IsDefined( level.SpawnedWarbirds ) )
	{
		foreach ( warbird in level.SpawnedWarbirds )
		{
			if ( !level.teamBased || (warbird.team != my_team) )
				return warbird;
		}	
	}
	
	return undefined;
}

enemy_orbital_laser_exists( my_team )
{
	if ( IsDefined( level.orbital_lasers ) )
	{
		foreach ( laser in level.orbital_lasers )
		{
			if ( !level.teamBased || (laser.team != my_team) )
				return true;
		}	
	}
	
	return false;
}

enemy_paladin_exists( my_team )
{
	if ( level.orbitalsupportInUse )
	{
		if ( IsDefined( level.orbitalsupport_planemodel ) && IsDefined( level.orbitalsupport_planemodel.owner ) )
		{
			if ( !level.teamBased || (level.orbitalsupport_planemodel.owner.team != my_team) )
				return true;
		}
	}
	
	return false;
}

enemy_missile_strike_exists( my_team )
{
	if ( IsDefined(level.remoteMissileInProgress) )
	{
		foreach ( rocket in level.rockets )
		{
			if ( rocket.type == "remote" && rocket.team != my_team )
				return true;
		}
	}
	
	return false;
}

enemy_strafing_run_exists( my_team )
{
	if ( IsDefined(level.artilleryDangerCenters) )
	{
		foreach ( artilleryDangerCenter in level.artilleryDangerCenters )
		{
			if ( isStrStart( artilleryDangerCenter.streakname, "strafing_run_airstrike" ) && artilleryDangerCenter.team != my_team )
				return true;
		}
	}
	
	return false;
}