#using scripts\codescripts\struct;

#using scripts\shared\abilities\_ability_player;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\damagefeedback_shared;
#using scripts\shared\hostmigration_shared;
#using scripts\shared\hud_shared;
#using scripts\shared\hud_message_shared;
#using scripts\shared\hud_util_shared;
#using scripts\shared\killstreaks_shared;
#using scripts\shared\popups_shared;
#using scripts\shared\system_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons_shared;
#using scripts\shared\weapons\_weaponobjects;
#using scripts\shared\weapons\_weapons;

#using scripts\mp\_teamops;
#using scripts\mp\_util;
#using scripts\mp\gametypes\_battlechatter;
#using scripts\mp\gametypes\_globallogic_audio;
#using scripts\mp\gametypes\_globallogic_score;
#using scripts\mp\gametypes\_globallogic_utils;
#using scripts\mp\gametypes\_hostmigration;
#using scripts\mp\gametypes\_hud_message;
#using scripts\mp\gametypes\_loadout;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\killstreaks\_ai_tank;
#using scripts\mp\killstreaks\_airsupport;
#using scripts\mp\killstreaks\_combat_robot;
#using scripts\mp\killstreaks\_counteruav;
#using scripts\mp\killstreaks\_dart;
#using scripts\mp\killstreaks\_dogs;
#using scripts\mp\killstreaks\_drone_strike;
#using scripts\mp\killstreaks\_emp;
#using scripts\mp\killstreaks\_flak_drone;
#using scripts\mp\killstreaks\_helicopter;
#using scripts\mp\killstreaks\_helicopter_gunner;
#using scripts\mp\killstreaks\_killstreak_bundles;
#using scripts\mp\killstreaks\_killstreak_detect;
#using scripts\mp\killstreaks\_killstreak_hacking;
#using scripts\mp\killstreaks\_killstreakrules;
#using scripts\mp\killstreaks\_killstreaks;
#using scripts\mp\killstreaks\_microwave_turret;
#using scripts\mp\killstreaks\_planemortar;
#using scripts\mp\killstreaks\_qrdrone;
#using scripts\mp\killstreaks\_raps;
#using scripts\mp\killstreaks\_rcbomb;
#using scripts\mp\killstreaks\_remote_weapons;
#using scripts\mp\killstreaks\_remotemissile;
#using scripts\mp\killstreaks\_satellite;
#using scripts\mp\killstreaks\_sentinel;
#using scripts\mp\killstreaks\_supplydrop;
#using scripts\mp\killstreaks\_turret;
#using scripts\mp\killstreaks\_uav;

  
    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
                                             
   	                                                         	                                                                                                                                               	                                                      	                                                                              	                                                          	                                    	                                     	                                                     	                                                                                      	                                                             	                                                                                                    	                                             	                                     	                          	                                                                 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	   	                                                                                                                                                                                                                                  	                                                                                                                                                                                                                                                                                                                                                                                                                                                   
                                                                                                                                                                                       	   	                                                                      	  	  	


	
#precache( "string", "MP_KILLSTREAK_N" );	

#namespace killstreaks;

function autoexec __init__sytem__() {     system::register("killstreaks",&__init__,undefined,undefined);    }
	
function __init__()
{
	level.killstreaks = [];
	level.killstreakWeapons = [];
	level.dropLocations = [];
	level.zOffsetCounter = 0;
	
	clientfield::register( "vehicle", "timeout_beep", 1, 2, "int" );
	
	callback::on_start_gametype( &init );
}	
	
function init()
{
/#
	level.killstreak_init_start_time = GetMillisecondsRaw();
	thread debug_ricochet_protection();
#/
	
	if ( GetDvarString( "scr_allow_killstreak_building") == "" )
	{
		SetDvar( "scr_allow_killstreak_building", "0" );
	}	
	
	level.menuReferenceForKillStreak = [];
	level.numKillstreakReservedObjectives = 0;
	level.killstreakCounter = 0;
	level.play_killstreak_firewall_being_hacked_dialog = &play_killstreak_firewall_being_hacked_dialog;
	level.play_killstreak_firewall_hacked_dialog = &play_killstreak_firewall_hacked_dialog;
	level.play_killstreak_being_hacked_dialog = &play_killstreak_being_hacked_dialog;
	level.play_killstreak_hacked_dialog = &play_killstreak_hacked_dialog;
	
	if( !isdefined(level.roundStartKillstreakDelay) )
	{
		level.roundStartKillstreakDelay = 0;
	}
	
	level.isKillstreakWeapon =&killstreaks::is_killstreak_weapon;
	
	level.killstreakCoreBundle = struct::get_script_bundle( "killstreak", "killstreak_core" );

	remote_weapons::init();
	
	ai_tank::init();
	airsupport::init();
	combat_robot::init();
	counteruav::init();
	dart::init();
	drone_strike::init();	
	emp::init();
	flak_drone::init();
	helicopter::init();
	helicopter_gunner::init();
	killstreakrules::init();
	microwave_turret::init();
	planemortar::init();
	qrdrone::init();
	raps_mp::init();
	rcbomb::init();
	remotemissile::init();
	satellite::init();
	sentinel::init();
	turret::init();
	uav::init();
	
	supplydrop::init();

/#
	level.killstreak_init_end_time = GetMillisecondsRaw();

	elapsed_time = level.killstreak_init_end_time - level.killstreak_init_start_time;
	PrintLn ("Total killstreaks init time: " + elapsed_time + " ms" );
#/	
		
	callback::on_spawned( &on_player_spawned );
	callback::on_joined_team( &on_joined_team );

/#
	level thread killstreak_debug_think();
#/
	if( GetDvarint( "teamOpsEnabled" ) == 1 )
	{
		level teamops::main();
	}
}

function register( killstreakType, 			// killstreak name	
				   killstreakWeaponName, 	// weapon name associated with deploying this killstreak
				   killstreakMenuName,		// killstreak name from the cac loadout (could be merged with the type name)
				   killstreakUsageKey,		// variable that shows the usage for the killstreak ( could be merged with type name )
				   killstreakUseFunction,	// function that gets called when the killstreak gets activated	
				   killstreakDelayStreak,	// weather or not to delay the killstreak at round start
				   weaponHoldAllowed = false,		// if this killstreak weapon can be held by the player, as opposed to activate and remove (i.e. UAV)
				   killstreakStatsName = undefined,		// Stats name for killstreak weapons (optional)
				   registerDvars = true,
				   registerInventory = true )
{
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( !isdefined(level.killstreaks[killstreakType]), "Killstreak " + killstreakType + " already registered");
	assert( isdefined(killstreakUseFunction), "No use function defined for killstreak " + killstreakType);
		
	level.killstreaks[killstreakType] = SpawnStruct();
	
	statsTableName = util::getStatsTableName();

	// number of kills required to achieve killstreak
	level.killstreaks[killstreakType].killstreakLevel = int( tablelookup( statsTableName, 4, killstreakMenuName, 5 ) );
	level.killstreaks[killstreakType].momentumCost = int( tablelookup( statsTableName, 4, killstreakMenuName, 16 ) );
	level.killstreaks[killstreakType].iconMaterial = tablelookup( statsTableName, 4, killstreakMenuName, 6 );
	level.killstreaks[killstreakType].quantity = int( tablelookup( statsTableName, 4, killstreakMenuName, 5 ) );
	level.killstreaks[killstreakType].usageKey = killstreakUsageKey;
	level.killstreaks[killstreakType].useFunction = killstreakUseFunction;
	level.killstreaks[killstreakType].menuName = killstreakMenuName; 
	level.killstreaks[killstreakType].delayStreak = killstreakDelayStreak; 
	level.killstreaks[killstreakType].allowAssists = false;
	level.killstreaks[killstreakType].overrideEntityCameraInDemo = false;
	level.killstreaks[killstreakType].teamKillPenaltyScale = 1.0;
/#
	level.killstreaks[killstreakType].uiName = ( tablelookup( statsTableName, 4, killstreakMenuName, 3 ) );
	if ( level.killstreaks[killstreakType].uiName == "" )
	{
		level.killstreaks[killstreakType].uiName = killstreakMenuName;
	}
#/
	if ( isdefined( killstreakWeaponName ) )
	{
		killstreakWeapon = GetWeapon( killstreakWeaponName );
		assert( killstreakWeapon != level.weaponNone );
		assert( !isdefined(level.killstreakWeapons[killstreakWeapon]), "Can not have a weapon associated with multiple killstreaks.");
		level.killstreaks[killstreakType].weapon = killstreakWeapon;
		level.killstreakWeapons[killstreakWeapon] = killstreakType;
	}

	if( isdefined( killstreakStatsName ) )
	{
		level.killstreaks[killstreakType].killstreakStatsName = killstreakStatsName;
	}

	level.killstreaks[killstreakType].weaponHoldAllowed = weaponHoldAllowed;

	if( ( isdefined( registerInventory ) && registerInventory ) )
	{
		level.menuReferenceForKillStreak[killstreakMenuName] = killstreakType;
		killstreak_bundles::register_killstreak_bundle( killstreakType );
	}
	
	if( ( isdefined( registerInventory ) && registerInventory ) )
	{
		if( ( isdefined( registerDvars ) && registerDvars ) )
			register_dev_dvars( killstreakType );
		
		register( "inventory_" +  killstreakType,
				  "inventory_" +  killstreakWeaponName,
				   killstreakMenuName,
				   killstreakUsageKey,
				   killstreakUseFunction,
				   killstreakDelayStreak,
				   weaponHoldAllowed,
				   killstreakStatsName,
				   registerDvars,
				   false );
	}
}

function is_registered(killstreakType)
{
	return isdefined(level.killstreaks[killstreakType]);
}

function register_strings( killstreakType, receivedText, notUsableText, inboundText, inboundNearPlayerText, hackedText, utilizesAirspace = true, isInventory = false ) 
{
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_strings.");
	
	level.killstreaks[killstreakType].receivedText = receivedText;
	level.killstreaks[killstreakType].notAvailableText = notUsableText;
	level.killstreaks[killstreakType].inboundText = inboundText;
	level.killstreaks[killstreakType].inboundNearPlayerText = inboundNearPlayerText;
	level.killstreaks[killstreakType].hackedText = hackedText;
	level.killstreaks[killstreakType].utilizesAirspace = utilizesAirspace; // does the killstreak utilize airspace when deployed or while active?
	
	if( !( isdefined( isInventory ) && isInventory ) )
		register_strings( "inventory_" + killstreakType, receivedText, notUsableText, inboundText, inboundNearPlayerText, hackedText, utilizesAirspace, true );
}

function register_dialog( 
							killstreakType,
							informDialog,
							taacomDialogBundleKey,
							pilotDialogArrayKey,
							startDialogKey, 				// Commander
							enemyStartDialogKey,			// Commander
							enemyStartMultipleDialogKey,	// Commander							
							hackedDialogKey,				// Commander
							hackedStartDialogKey,			// Commander				
							requestDialogKey,		// Player
							threatDialogKey,		// Player
							isInventory
						)
{
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_dialog.");

	level.killstreaks[killstreakType].informDialog = informDialog;
	
	level.killstreaks[killstreakType].taacomDialogBundleKey = taacomDialogBundleKey;
	
	level.killstreaks[killstreakType].startDialogKey = startDialogKey;
	level.killstreaks[killstreakType].enemyStartDialogKey = enemyStartDialogKey;
	level.killstreaks[killstreakType].enemyStartMultipleDialogKey = enemyStartMultipleDialogKey;
	
	level.killstreaks[killstreakType].hackedDialogKey = hackedDialogKey;
	level.killstreaks[killstreakType].hackedStartDialogKey = hackedStartDialogKey;
	
	level.killstreaks[killstreakType].requestDialogKey = requestDialogKey;
	level.killstreaks[killstreakType].threatDialogKey = threatDialogKey;
	
	if ( isdefined( pilotDialogarrayKey ) )
	{
		// Set up Pilot Dialog Arrays
		taacomBundles = struct::get_script_bundles( "mpdialog_taacom" );
		
		foreach ( bundle in taacomBundles )
		{
			if ( !isdefined( bundle.pilotBundles ) )
			{
				bundle.pilotBundles = [];
			}
			
			bundle.pilotBundles[killstreakType] = [];
		
			i = 0;
			field = pilotDialogArrayKey + i;
			fieldValue = GetStructField( bundle, field );
			
			while ( isdefined( fieldValue ) )
			{
				bundle.pilotBundles[killstreakType][i] = fieldValue;
				
				i++;
				field = pilotDialogArrayKey + i;
				fieldValue = GetStructField( bundle, field );
			}
		}
	}
	
	if( !( isdefined( isInventory ) && isInventory ) )
		register_dialog( 
							"inventory_" + killstreakType,
							informDialog,
							taacomDialogBundleKey,
							pilotDialogArrayKey,
							startDialogKey,
							enemyStartDialogKey,
							enemyStartMultipleDialogKey,
							hackedDialogKey,
							hackedStartDialogKey,
							requestDialogKey,
							threatDialogKey,
							true );
		
}

// additional weapons associated with this killstreak
function register_alt_weapon( killstreakType, weaponName, isInventory )
{
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_alt_weapon.");

	weapon = GetWeapon( weaponName );
	
	if( weapon == level.weaponNone )
		return;
	
	if ( level.killstreaks[killstreakType].weapon == weapon )
	{
		return;
	}

	if ( !isdefined( level.killstreaks[killstreakType].altWeapons ) )
	{
		level.killstreaks[killstreakType].altWeapons = [];
	}

	if( !isdefined( level.killstreakWeapons[weapon] ) )
	{
		level.killstreakWeapons[weapon] = killstreakType;
	}
	level.killstreaks[killstreakType].altWeapons[level.killstreaks[killstreakType].altWeapons.size] = weapon;
	
	if( !( isdefined( isInventory ) && isInventory ) )
		register_alt_weapon( "inventory_" + killstreakType, weaponName, true );
}

// remote override weapons associated with this killstreak
function register_remote_override_weapon( killstreakType, weaponName, isInventory )
{
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_remote_override_weapon.");

	weapon = GetWeapon( weaponName );
	if ( level.killstreaks[killstreakType].weapon == weapon )
	{
		return;
	}
		
	if ( !isdefined( level.killstreaks[killstreakType].remoteOverrideWeapons ) )
	{
		level.killstreaks[killstreakType].remoteOverrideWeapons = [];
	}

	if( !isdefined( level.killstreakWeapons[weapon] ) )
	{
		level.killstreakWeapons[weapon] = killstreakType;
	}
	level.killstreaks[killstreakType].remoteOverrideWeapons[level.killstreaks[killstreakType].remoteOverrideWeapons.size] = weapon;
	
	if( !( isdefined( isInventory ) && isInventory ) )
		register_remote_override_weapon( "inventory_" + killstreakType, weaponName, true );
}

function is_remote_override_weapon( killstreakType, weapon )
{
	if ( isdefined( level.killstreaks[killstreakType].remoteOverrideWeapons ) )
	{
		for ( i=0; i<level.killstreaks[killstreakType].remoteOverrideWeapons.size; i++)
		{
			if ( level.killstreaks[killstreakType].remoteOverrideWeapons[i] == weapon)
			{
				return true;	
			}
		}
	}
	return false;
}

function register_dev_dvars( killstreakType )
{
/#
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_dev_dvar.");
	
	level.killstreaks[killstreakType].devDvar = "scr_" + killstreakType + "_give";
	level.killstreaks[killstreakType].devEnemyDvar = "scr_" + killstreakType + "_giveenemy";
	level.killstreaks[killstreakType].devTimeoutDvar = "scr_" + killstreakType + "_notimeout";

	// must create this one because the toggle command will not create it
	SetDvar( level.killstreaks[killstreakType].devTimeoutDvar, 0 );
	
	level thread register_devgui( killstreakType );
#/
}

function register_dev_debug_dvar( killstreakType )
{
/#
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_dev_dvar.");

	level.killstreaks[killstreakType].devDebugDvar = "scr_" + killstreakType + "_debug";

	devgui_scorestreak_command_debugdvar( killstreakType, level.killstreaks[killstreakType].devDebugDvar );
#/
}

/#

function register_devgui( killstreakType )
{
	level endon( "game_ended" );

	wait( RandomIntRange( 2, 20 ) * .05 ); // to avoid input buffer overflow

	give_type_all = "Give";
	give_type_enemy = "Give Enemy";

	if ( isdefined(level.killstreaks[killstreakType].devDvar) )
	{
		devgui_scorestreak_command_givedvar( killstreakType, give_type_all, level.killstreaks[killstreakType].devDvar );
	}
	if ( isdefined(level.killstreaks[killstreakType].devEnemyDvar) )
	{
		devgui_scorestreak_command_givedvar( killstreakType, give_type_enemy, level.killstreaks[killstreakType].devEnemyDvar );
	}
	if ( isdefined(level.killstreaks[killstreakType].devTimeoutDvar) )
	{
		devgui_scorestreak_command_timeoutdvar( killstreakType, level.killstreaks[killstreakType].devTimeoutDvar );
	}
}

function devgui_scorestreak_command_givedvar( killstreakType, give_type, dvar )
{
	devgui_scorestreak_command( killstreakType, give_type, "set "+dvar+" 1" );
}

function devgui_scorestreak_command_timeoutdvar( killstreakType, dvar )
{
	devgui_scorestreak_dvar_toggle( killstreakType, "Time Out", dvar );
}

function devgui_scorestreak_command_debugdvar( killstreakType, dvar )
{
	devgui_scorestreak_dvar_toggle( killstreakType, "Debug", dvar );
}
#/

function devgui_scorestreak_dvar_toggle( killstreakType, title, dvar )
{
	// must create this one because the toggle command will not create it
	SetDvar( dvar, 0 );
	
	devgui_scorestreak_command( killstreakType, "Toggle " + title, "toggle "+dvar+" 1 0" );
}

function devgui_scorestreak_command( killstreakType, title, command )
{
/#
	assert( isdefined(killstreakType), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling register_dev_dvar.");
	
	root = "devgui_cmd \"MP/Scorestreaks/";
	user_name = MakeLocalizedString(level.killstreaks[killstreakType].uiName);
	AddDebugCommand( root + user_name + " (" + killstreakType + ")/"+ title + "\" \"" + command + "\" \n");
#/
}

function should_draw_debug( killstreak )
{
/#
	assert( isdefined(killstreak), "Can not register a killstreak without a valid type name.");

	if ( isdefined( level.killstreaks[killstreak] ) && isdefined( level.killstreaks[killstreak].devDebugDvar ) )
			return GetDvarInt( level.killstreaks[killstreak].devDebugDvar );
#/

	return false;	
}


function register_tos_dvar(dvar)
{
	level.teamops_dvar = dvar;
}

function allow_assists( killstreakType, allow )
{
	level.killstreaks[killstreakType].allowAssists = allow;	
}

function set_team_kill_penalty_scale( killstreakType, scale, isInventory )
{
	level.killstreaks[killstreakType].teamKillPenaltyScale = scale;	
	if( !( isdefined( isInventory ) && isInventory ) )
		set_team_kill_penalty_scale( "inventory_" + killstreakType, scale, true );
}

function override_entity_camera_in_demo( killstreakType, value, isInventory )
{
	level.killstreaks[killstreakType].overrideEntityCameraInDemo = value;
	if( !( isdefined( isInventory ) && isInventory ) )
		override_entity_camera_in_demo( "inventory_" + killstreakType, value, true );
}

function is_available( killstreak )
{
	if ( isdefined( level.menuReferenceForKillStreak[killstreak] ) )
	{
		return true;
	}
	else
	{
		return false;
	}
}

function get_by_menu_name( killstreak )
{
	return level.menuReferenceForKillStreak[killstreak];
}

function get_menu_name( killstreakType )
{
	Assert( isdefined(level.killstreaks[killstreakType] ) );
	return level.killstreaks[killstreakType].menuName;
}

function get_level( index, killstreak )
{
	killstreakLevel = level.killstreaks[ get_by_menu_name( killstreak ) ].killstreakLevel;
	if( GetDvarInt( "custom_killstreak_mode" ) == 2 )
	{
		if ( isdefined( self.killstreak[ index ] ) && ( killstreak == self.killstreak[ index ] ) )
		{
			killsRequired = GetDvarInt( "custom_killstreak_" + index + 1 + "_kills" );
			if ( killsRequired )
			{
				killstreakLevel = GetDvarInt( "custom_killstreak_" + index + 1 + "_kills" );
			}
		}
	}
	return killstreakLevel;
}

function give_if_streak_count_matches( index, killstreak, streakCount )
{
	pixbeginevent( "giveKillstreakIfStreakCountMatches" );
	
	/#
	if(!isdefined( killstreak ) )
	{
		println( "Killstreak Undefined.\n" );
	}
	if( isdefined( killstreak ) )
	{
		println( "Killstreak listed as."+killstreak+"\n" );
	}
	if( !is_available(killstreak) )
	{
		println( "Killstreak Not Available.\n" );
	}
	#/

	if( self.pers["killstreaksEarnedThisKillstreak"] > index && util::isRoundBased() )
	{
		hasAlreadyEarnedKillstreak = true;
	}
	else
	{
		hasAlreadyEarnedKillstreak = false;
	}

	if ( isdefined( killstreak ) && is_available(killstreak) && !hasAlreadyEarnedKillstreak )
	{
		killstreakLevel = get_level( index, killstreak );

		if ( self HasPerk( "specialty_killstreak" ) )
		{
			reduction = GetDvarint( "perk_killstreakReduction" );
			killstreakLevel -= reduction;

			// a fix for custom game types being able to adjust the killstreak reduction perk
			if( killstreakLevel <= 0 )
			{
				killstreakLevel = 1;
			}
		}
		
		if ( killstreakLevel == streakCount )
		{
			self give( get_by_menu_name( killstreak ), streakCount );
			self.pers["killstreaksEarnedThisKillstreak"] = index + 1;
			pixendevent();
			return true;
		}
	}

	pixendevent();
	return false;
}

//Self is the player. This function looks at the player current killstreak and decides if he should be award a killstreak reward.
//It also manages the prompt that appears when the player  gets killstreaks at intervals of 5 kills once they reach 10 kills. -Leif
function give_for_streak()
{
	if ( !util::isKillStreaksEnabled() )
	{
		return;
	}

	//Equals total kills within one life
	if( !isdefined(self.pers["totalKillstreakCount"]) )
	{
		self.pers["totalKillstreakCount"] = 0;
	}
	
	// send the running tally to see what kill streak we should get
	given = false;
	
	for ( i = 0; i < self.killstreak.size; i++ )
	{
		given |= give_if_streak_count_matches( i, self.killstreak[i], self.pers["cur_kill_streak"] );
	}
}

function is_an_a_killstreak()
{
	onKillstreak = false;
	if( !isdefined( self.pers["kill_streak_before_death"] ) )
	{
		self.pers["kill_streak_before_death"] = 0;
	}
	
	streakPlusOne = self.pers["kill_streak_before_death"] + 1;
	
	if ( self.pers["kill_streak_before_death"] >= 5 ) 
	{
		onKillstreak = true;
	}


	return onKillstreak;
}

function give( killstreakType, streak, suppressNotification, noXP, toBottom )
{
	pixbeginevent( "giveKillstreak" );
	self endon("disconnect");
	level endon( "game_ended" );
	
	had_to_delay = false;
	
	killstreakGiven = false;
	if( isdefined( noXP ) )
	{
		if ( self give_internal( killstreakType, undefined, noXP, toBottom ) )
		{
			killstreakGiven = true;
			if ( self.just_given_new_inventory_killstreak === true )
			{
				self add_to_notification_queue( level.killstreaks[killstreakType].menuname, streak, killstreakType, noXP );
			}
		}
	}
	else if ( self give_internal( killstreakType, noXP ) )
	{
		killstreakGiven = true;
		if ( self.just_given_new_inventory_killstreak === true )
		{
			self add_to_notification_queue( level.killstreaks[killstreakType].menuname, streak, killstreakType, noXP );
		}
	}
	pixendevent(); //  "giveKillstreak"
}

function take( killstreak )
{
	self endon( "disconnect" );
	
	killstreak_weapon = get_killstreak_weapon( killstreak );
	remove_used_killstreak( killstreak );
	
	if ( self GetInventoryWeapon() == killstreak_weapon )
	{
		self SetInventoryWeapon( level.weaponNone );
	}

	waittillframeend;
	
	currentWeapon = self GetCurrentWeapon();
	if( currentWeapon != killstreak_weapon || killstreak_weapon.isCarriedKillstreak )
	{ 
		return;
	}
	
	killstreaks::switch_to_last_non_killstreak_weapon();
	activate_next();
}

function remove_oldest()
{
	if( isdefined( self.pers["killstreaks"][0] ) )
	{
		currentWeapon = self getCurrentWeapon();

		if( currentWeapon == get_killstreak_weapon( self.pers["killstreaks"][0] ) )
		{
			primaries = self GetWeaponsListPrimaries();

			if( primaries.size > 0 )
			{
				self SwitchToWeapon( primaries[0] );
			}
		}

		self notify("oldest_killstreak_removed", self.pers["killstreaks"][0], self.pers["killstreak_unique_id"][0] ); 
		self remove_used_killstreak( self.pers["killstreaks"][0], self.pers["killstreak_unique_id"][0], false );
	}
}

function give_internal( killstreakType, do_not_update_death_count, noXP, toBottom )
{
	self.just_given_new_inventory_killstreak = undefined;

	if ( level.gameEnded )
	{
		return false;
	}
		
	if ( !util::isKillStreaksEnabled() )
	{
		return false;
	}
		
	if ( !isdefined( level.killstreaks[killstreakType] ) )
	{
		return false;
	}

	if ( !isdefined( self.pers["killstreaks"] ) )
	{
		self.pers["killstreaks"] = [];
	}
	if( !isdefined( self.pers["killstreak_has_been_used"] ) )
	{
		self.pers["killstreak_has_been_used"] = [];
	}
	if( !isdefined( self.pers["killstreak_unique_id"] ) )
	{
		self.pers["killstreak_unique_id"] = [];
	}
	if( !isdefined( self.pers["killstreak_ammo_count"] ) )
	{
		self.pers["killstreak_ammo_count"] = [];
	}
	
	just_max_stack_removed_inventory_killstreak = undefined;

	if( isdefined( toBottom ) && toBottom )
	{
		size = self.pers["killstreaks"].size;
		
		if( self.pers["killstreaks"].size >= level.maxInventoryScoreStreaks )
		{
			self remove_oldest();
			just_max_stack_removed_inventory_killstreak = self.just_removed_used_killstreak;
		}		
		
		for( i = size; i > 0; i-- )
		{
			self.pers["killstreaks"][i] = self.pers["killstreaks"][i - 1];
			self.pers["killstreak_has_been_used"][i] = self.pers["killstreak_has_been_used"][i - 1];
			self.pers["killstreak_unique_id"][i] = self.pers["killstreak_unique_id"][i - 1];
			self.pers["killstreak_ammo_count"][i] = self.pers["killstreak_ammo_count"][i - 1];
		}
		self.pers["killstreaks"][0] = killstreakType;
		self.pers["killstreak_unique_id"][0] = level.killstreakCounter;
		level.killstreakCounter++;
		
		if( isdefined(noXP) )
		{
			self.pers["killstreak_has_been_used"][0] = noXP;
		}
		else
		{
			self.pers["killstreak_has_been_used"][0] = false;
		}
		
		
		if( size == 0 )
		{
			weapon = get_killstreak_weapon( killstreakType );
			ammoCount = give_weapon( weapon, true );
		}
	
		self.pers["killstreak_ammo_count"][0] = 0;
	}
	else
	{
		self.pers["killstreaks"][self.pers["killstreaks"].size] = killstreakType;
		self.pers["killstreak_unique_id"][self.pers["killstreak_unique_id"].size] = level.killstreakCounter;
		level.killstreakCounter++;
	
		if( self.pers["killstreaks"].size > level.maxInventoryScoreStreaks )
		{
			self remove_oldest();
			just_max_stack_removed_inventory_killstreak = self.just_removed_used_killstreak;
		}
		
		if( isdefined(noXP) )
		{
			self.pers["killstreak_has_been_used"][self.pers["killstreak_has_been_used"].size] = noXP;
		}
		else
		{
			self.pers["killstreak_has_been_used"][self.pers["killstreak_has_been_used"].size] = false;
		}
		
		weapon = get_killstreak_weapon( killstreakType );
		
		ammoCount = give_weapon( weapon, true );
	
		self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size] = ammoCount;
	}

	self.just_given_new_inventory_killstreak = ( killstreakType !== just_max_stack_removed_inventory_killstreak );

	return true;
}

function add_to_notification_queue( menuName, streakCount, hardpointType, noNotify )
{
	killstreakTableNumber = level.killStreakIndices[ menuName ];

	if ( !isdefined( killstreakTableNumber ) )
	{
		return;
	}
	
	if( isdefined( noNotify ) && noNotify )
	{
		return;
	}

	informDialog = get_killstreak_inform_dialog( hardpointType );
	
	if( GetDvarInt( "teamOpsEnabled" ) == 0 )
	{	
		self thread play_killstreak_ready_dialog( hardpointType, 2.4 );
		self thread play_killstreak_ready_sfx ( hardpointType );
		self LUINotifyEvent( &"killstreak_received", 2, killstreakTableNumber, istring( informDialog ) );
		self LUINotifyEventToSpectators( &"killstreak_received", 2, killstreakTableNumber, istring( informDialog ) );
	}
	
}


function has_equipped( )
{
	currentWeapon = self getCurrentWeapon();

	keys = getarraykeys( level.killstreaks );
	for ( i = 0; i < keys.size; i++ )
	{
		if ( level.killstreaks[keys[i]].weapon == currentWeapon )
		{
			return true;
		}
	}

	return false;
}

function _get_from_weapon( weapon ) 
{
	keys = getarraykeys( level.killstreaks );

	foreach( key in keys )
	{
		killstreak = level.killstreaks[ key ];
		
		if ( killstreak.weapon == weapon )
		{
			return key;
		}
		
		if ( isdefined( killstreak.altweapons ) )
		{
			foreach( altweapon in killstreak.altweapons )
			{
				if ( altweapon == weapon ) 
				{
					return key;
				}
			}
		}
			
		if ( isdefined( killstreak.remoteoverrideweapons ) )
		{
			foreach( remoteOverrideWeapon in killstreak.remoteoverrideweapons )
			{
				if( remoteOverrideWeapon == weapon )
				{
					return key;
				}
			}	
		}
	}

	return undefined;
}


function get_from_weapon( weapon ) 
{
	if( weapon == level.weaponNone )
	{
		return undefined;
	}
	
	res = _get_from_weapon( weapon );
	if( !isdefined( res ) )
		return _get_from_weapon( weapon.rootweapon );
	else
		return res;
}

// dont need the isinventory it will be inventory they all are
function give_weapon( weapon, isinventory, useStoredAmmo )
{	
	currentWeapon = self GetCurrentWeapon();
	
	if ( currentWeapon != level.weaponNone && !( isdefined( level.usingMomentum ) && level.usingMomentum ) )
	{
		weaponsList = self GetWeaponsList();
		for( idx = 0; idx < weaponsList.size; idx++ )
		{
		 	carriedWeapon = weaponsList[idx];
		 	
		 	if ( currentWeapon == carriedWeapon )
		 	{
		 		continue;
		 	}

			// special case weapons that are killstreak weapons but shouldn't be taken from the player
			switch ( carriedWeapon.name )
			{
			case "minigun":
			case "m32":
				continue;
			}
		 		
			if ( killstreaks::is_killstreak_weapon( carriedWeapon ) )
		 	{
		 		self TakeWeapon( carriedWeapon );
		 	}
		}
	}
	
	// take the weapon in-case we already have it.  
	// otherwise giveweapon will not give the weapon or ammo
	if( currentWeapon != weapon && ( self hasWeapon(weapon) == false ) )
	{
		self TakeWeapon( weapon );
		self GiveWeapon( weapon );
	}
	
	if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) )
	{
		self SetInventoryWeapon( weapon );

		if( weapon.isCarriedKillstreak )
		{
			if( !isdefined( self.pers["held_killstreak_ammo_count"][weapon] ) )
			{
				self.pers["held_killstreak_ammo_count"][weapon] = 0;
			}

			if( !isdefined( self.pers["held_killstreak_clip_count"][weapon] ) )
			{
				self.pers["held_killstreak_clip_count"][weapon] = weapon.clipSize;
			}

			if( !isdefined( self.pers["killstreak_quantity"][weapon] ) )
			{
				self.pers["killstreak_quantity"][weapon] = 0;
			}

			if( currentWeapon == weapon && !killstreaks::isHeldInventoryKillstreakWeapon( weapon ) )
			{
				return weapon.maxAmmo;
			}
			else if( ( isdefined( useStoredAmmo ) && useStoredAmmo ) && self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1] > 0 )
			{
				switch( weapon.name )
				{
				case "inventory_minigun":
					if( ( isdefined( self.minigunActive ) && self.minigunActive ) )
					{
						return self.pers["held_killstreak_ammo_count"][weapon];
					}
					break;
				case "inventory_m32":
					if( ( isdefined( self.m32Active ) && self.m32Active ) )
					{
						return self.pers["held_killstreak_ammo_count"][weapon];
					}
					break;
				default:
					break;
				}
				self.pers["held_killstreak_ammo_count"][weapon] = self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1];
				self loadout::setWeaponAmmoOverall( weapon, self.pers["killstreak_ammo_count"][self.pers["killstreak_ammo_count"].size - 1] );
			}
			else
			{
				self.pers["held_killstreak_ammo_count"][weapon] = weapon.maxAmmo;
				self.pers["held_killstreak_clip_count"][weapon] = weapon.clipSize;
				self loadout::setWeaponAmmoOverall( weapon, self.pers["held_killstreak_ammo_count"][weapon] );
			}
			return self.pers["held_killstreak_ammo_count"][weapon];
		}
		else
		{
			switch ( weapon.name )
			{
			case "inventory_minigun_drop":
			case "inventory_m32_drop":
			case "inventory_missile_drone":
					
			case "combat_robot_marker":
			case "inventory_combat_robot_marker":
					
			case "dart":
			case "inventory_dart":
					
			case "ai_tank_marker":
			case "inventory_ai_tank_marker":
					
			case "supplydrop_marker":
			case "inventory_supplydrop_marker":
				delta = 1;
				break;
			default:
				delta = 0;
				break;
			}
		
			return change_killstreak_quantity( weapon, delta );	
		}
	}
	else
	{
		self setActionSlot( 4, "weapon", weapon );
		return 1;
	}
}

function activate_next( do_not_update_death_count )
{
	if ( level.gameEnded )
	{
		return false;
	}

	if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) )
	{
		self SetInventoryWeapon( level.weaponNone );
	}
	else
	{
		self setActionSlot( 4, "" );
	}

 	if ( !isdefined( self.pers["killstreaks"] ) || self.pers["killstreaks"].size == 0 )
 	{
 		return false;
 	}
 	
	killstreakType = self.pers["killstreaks"][self.pers["killstreaks"].size - 1];

	if ( !isdefined( level.killstreaks[killstreakType] ) )
	{
		return false;
	}
	
	weapon = level.killstreaks[killstreakType].weapon;
	{wait(.05);};
	
	ammoCount = give_weapon( weapon, false, true );

	//Set the ammo now so we don't get a flash on the HUD when we use this weapon later
	if( weapon.isCarriedKillstreak )
	{
		self setWeaponAmmoClip( weapon, self.pers["held_killstreak_clip_count"][weapon] );
		self setWeaponAmmoStock( weapon, ammoCount - self.pers["held_killstreak_clip_count"][weapon] );
	}

	if ( !isdefined( do_not_update_death_count ) || do_not_update_death_count != false )
	{
		self.pers["killstreakItemDeathCount"+killstreakType] = self.deathCount;
	}	
	
	return true;
}

function give_owned()
{
	if ( isdefined( self.pers["killstreaks"] ) && self.pers["killstreaks"].size > 0 )
	{
		self activate_next( false );
	}
}

function get_killstreak_quantity( killstreakWeapon )
{
	return (isdefined(self.pers["killstreak_quantity"][killstreakWeapon])?self.pers["killstreak_quantity"][killstreakWeapon]:0);
}

function change_killstreak_quantity( killstreakWeapon, delta )
{
	quantity = get_killstreak_quantity( killstreakWeapon );
	
	previousQuantity = quantity;
	quantity += delta;

	if ( quantity > level.scoreStreaksMaxStacking )
	{
		quantity = level.scoreStreaksMaxStacking;
	}
	
	// take the weapon in-case we already have it.  
	// otherwise giveweapon will not give the weapon or ammo
	if(self hasWeapon( killstreakWeapon ) == false )
	{
		self TakeWeapon( killstreakWeapon );
		self GiveWeapon( killstreakWeapon );
		self SetEverHadWeaponAll( true );
	}

	self.pers["killstreak_quantity"][killstreakWeapon] = quantity;
	self SetWeaponAmmoClip( killstreakWeapon, quantity );
	return quantity;
}

function has_killstreak_in_class( killstreakMenuName )
{
	foreach ( equippedKillstreak in self.killstreak )
	{
		if ( equippedKillstreak == killstreakMenuName )
		{
			return true;
		}
	}
	return false;
}

function has_killstreak( killstreak )
{
	player = self;
	
	if( !isdefined( killstreak ) || !isdefined( player.pers["killstreaks"] ) )
		return false;
		
	for( i = 0; i < self.pers["killstreaks"].size; i++ )
	{
		if( player.pers["killstreaks"][i] == killstreak )
			return true;
	}
	return false;
}

function RecordKillstreakBeginDirect(recordStreakIndex)
{
	player = self;
	if(!isPlayer(player) || !isDefined(recordstreakindex))
	{
		return;
	}
	
	if( !isdefined(self.killstreakEvents) )
		player.killstreakEvents = associativeArray();
	
	// Already defined means the End happened first, so lets call both start and end.
	// Note that in this case, the killstreakEvents is storing the number of kills
	if(isDefined(self.killstreakEvents[recordStreakIndex]))
	{
		kills = player.killstreakEvents[recordStreakIndex];
		eventIndex = player RecordKillStreakEvent( recordStreakIndex );
		player killstreakrules::RecordKillstreakEndDirect(eventIndex, recordStreakIndex, kills);
		
		player.killstreakEvents[recordStreakIndex] = undefined;
	}
	else
	{
		// Should be called in correct order
		eventIndex = player RecordKillStreakEvent( recordStreakIndex );
		player.killstreakEvents[recordStreakIndex] = eventIndex;
	}
}

function remove_when_done( killstreak, hasKillstreakBeenUsed, isFromInventory )
{
	self endon( "disconnect" );
	
	continue_wait = true;
	
	while( continue_wait )
	{
		self waittill( "killstreak_done", successful, killstreakType );
		
		if ( killstreakType == killstreak )
			continue_wait = false;
	}
	
	if ( successful )
	{	
		/#print( "killstreak: " + get_menu_name( killstreak ) );#/
		
		// good place to hook into killstreak usage
		killstreak_weapon = get_killstreak_weapon( killstreak );
		recordStreakIndex = undefined;
		if( isdefined( level.killstreaks[killstreak].menuname ) )
		{
			recordStreakIndex = level.killstreakindices[level.killstreaks[killstreak].menuname];
			self RecordKillstreakBeginDirect(recordStreakIndex);
		}
		
		if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) )
		{
			if ( ( isdefined( isFromInventory ) && isFromInventory ) )
			{
				remove_used_killstreak( killstreak );
				if ( self GetInventoryWeapon() == killstreak_weapon )
				{
					self SetInventoryWeapon( level.weaponNone );
				}
			}
			else
			{
				self change_killstreak_quantity( killstreak_weapon, -1 );
			}
		}
		else if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) )
		{
			if ( ( isdefined( isFromInventory ) && isFromInventory ) && ( self GetInventoryWeapon() == killstreak_weapon ) )
			{
				remove_used_killstreak( killstreak );
				self SetInventoryWeapon( level.weaponNone );
			}
			else
			{
				globallogic_score::_setPlayerMomentum( self, self.momentum - level.killstreaks[killstreakType].momentumCost );
			}
		}
		else
		{
			remove_used_killstreak( killstreak );
		}

		if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) )
		{
			self setActionSlot( 4, "" );
		}
	
		success = true;
	}

	waittillframeend;

	// each killstreak should hide the compass via this clientfield if so desired
	self unhide_compass();
	
	currentWeapon = self GetCurrentWeapon();
	killstreak_weapon = get_killstreak_weapon( killstreakType );
	if( currentWeapon == killstreak_weapon && killstreak_weapon.isCarriedKillstreak )
	{ 
		return;
	}
	
	if ( successful && ( !self has_killstreak_in_class( get_menu_name( killstreak ) ) || ( isdefined( isFromInventory ) && isFromInventory ) ) )
	{
		killstreaks::switch_to_last_non_killstreak_weapon();
	}
	else
	{
		// the killstreak could have failed because we switched to another killstreak weapon
		killstreakForCurrentWeapon = get_from_weapon( currentWeapon );
		
		if ( currentWeapon.isGameplayWeapon )
		{
			if ( ( isdefined( self.isPlanting ) && self.isPlanting ) || ( isdefined( self.isDefusing ) && self.isDefusing ) )
			{
				return;
			}
		}
		
		// not sure why we would switch when !isdefined( killstreakForCurrentWeapon ) so this is so we don't when we have switched to a HeroWeapon
		if ( !isdefined( killstreakForCurrentWeapon ) && currentWeapon.isHeroWeapon )
		{
			return;
		}		
		
		if ( successful || !isdefined( killstreakForCurrentWeapon ) || killstreakForCurrentWeapon == killstreak )
		{
			killstreaks::switch_to_last_non_killstreak_weapon();
		}
	}

	if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) || ( isdefined( isFromInventory ) && isFromInventory ) )
	{
		if ( successful )
		{
			activate_next();
		}
	}
}

function useKillstreak( killstreak, isFromInventory )
{
	hasKillstreakBeenUsed = get_if_top_killstreak_has_been_used();
	
	if ( isdefined( self.selectingLocation ) )
	{
		return;
	}

	self thread remove_when_done( killstreak, hasKillstreakBeenUsed, isFromInventory );
	self thread trigger_killstreak( killstreak, isFromInventory );
}

function remove_used_killstreak( killstreak, killstreakId, take_weapon_after_use = true )
{
	self.just_removed_used_killstreak = undefined;

	if( !isdefined( self.pers["killstreaks"] ) )
		return;

	// the killstreak stack is a lifo stack
	// find the top most killstreak in the list 
	// remove it 
	killstreakIndex = undefined;
	
	for ( i = self.pers["killstreaks"].size - 1; i >= 0; i-- )
	{
		if ( self.pers["killstreaks"][i] == killstreak )
		{
			if( isdefined( killstreakId ) && self.pers["killstreak_unique_id"][i] != killstreakId )
			{
				continue;
			}
	  		
			killstreakIndex = i;
			break;
		}
	}

	if ( !isdefined(killstreakIndex) )
	{
		return false;
	}

	self.just_removed_used_killstreak = killstreak;

	if( take_weapon_after_use && !self has_killstreak_in_class( get_menu_name( killstreak ) ) )
	{
		self thread take_weapon_after_use( get_killstreak_weapon( killstreak ) );
	}
	
	arraySize = self.pers["killstreaks"].size;
	for ( i = killstreakIndex; i < arraySize - 1; i++ )
	{
		self.pers["killstreaks"][i] = self.pers["killstreaks"][i + 1];
		self.pers["killstreak_has_been_used"][i] = self.pers["killstreak_has_been_used"][i + 1];
		self.pers["killstreak_unique_id"][i] = self.pers["killstreak_unique_id"][i + 1];
		self.pers["killstreak_ammo_count"][i] = self.pers["killstreak_ammo_count"][i + 1];
	}
	
	self.pers["killstreaks"][arraySize-1] = undefined;
	self.pers["killstreak_has_been_used"][arraySize-1] = undefined;
	self.pers["killstreak_unique_id"][arraySize-1] = undefined;
	self.pers["killstreak_ammo_count"][arraySize-1] = undefined;
	
	return true;
}

function take_weapon_after_use( killstreakWeapon )
{
	self endon("disconnect");
	self endon("death");
	self endon( "joined_team" );
	self endon( "joined_spectators" );

	self waittill( "weapon_change" );
	
	inventoryWeapon = self GetInventoryWeapon();
 	if ( inventoryWeapon != killstreakWeapon ) 
	{
		self TakeWeapon( killstreakWeapon );
	}
}

function get_top_killstreak()
{
	if ( self.pers["killstreaks"].size == 0 )
	{
		return undefined;
	}
		
	return self.pers["killstreaks"][self.pers["killstreaks"].size-1];
}

function get_if_top_killstreak_has_been_used()
{
	if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) )
	{
		if ( self.pers["killstreak_has_been_used"].size == 0 )
		{
			return undefined;
		}
		
		return self.pers["killstreak_has_been_used"][self.pers["killstreak_has_been_used"].size-1];
	}
}

function get_top_killstreak_unique_id()
{
	if ( self.pers["killstreak_unique_id"].size == 0 )
	{
		return undefined;
	}
		
	return self.pers["killstreak_unique_id"][self.pers["killstreak_unique_id"].size-1];
}

function get_killstreak_index_by_id( killstreakId )
{
	for( index = self.pers["killstreak_unique_id"].size - 1; index >= 0; index-- )
	{
		if( self.pers["killstreak_unique_id"][index] == killstreakId )
		{
			return index;
		}
	}

	return undefined;
}


function get_killstreak_momentum_cost( killstreak )
{
	if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) )
	{
		return 0;
	}

	if ( !isdefined( killstreak ) )
	{
		return 0;
	}

	Assert( isdefined(level.killstreaks[killstreak]) );
	
	return level.killstreaks[killstreak].momentumCost;
}

function get_killstreak_for_weapon( weapon )
{
	if( isdefined( level.killstreakWeapons[weapon] ) )
		return level.killstreakWeapons[weapon];
	else
		return level.killstreakWeapons[weapon.rootweapon];
}

function get_killstreak_for_weapon_for_stats( weapon )
{
	prefix = "inventory_";
	
	killstreak = get_killstreak_for_weapon( weapon );
	
	if ( isdefined( killstreak ) )
	{
		if ( StrStartsWith( killstreak, prefix ) )
			killstreak = getSubStr( killstreak, prefix.size );
	}
	
	return killstreak;
}

function is_killstreak_weapon_assist_allowed( weapon )
{
	killstreak = get_killstreak_for_weapon( weapon );

	if ( !isdefined( killstreak ) )
	{
		return false;
	}
		
	if ( level.killstreaks[killstreak].allowAssists )
	{
		return true;
	}
		
	return false;
}

function get_killstreak_team_kill_penalty_scale( weapon )
{
	killstreak = get_killstreak_for_weapon( weapon );

	if ( !isdefined( killstreak ) )
	{
		return 1.0;
	}
		
	return level.killstreaks[killstreak].teamKillPenaltyScale;
}

function should_override_entity_camera_in_demo( player, weapon )
{
	killstreak = get_killstreak_for_weapon( weapon );

	if ( !isdefined( killstreak ) )
	{
		return false;
	}
		
	if ( level.killstreaks[killstreak].overrideEntityCameraInDemo )
	{
		return true;
	}
	
	if ( isdefined( player.remoteWeapon ) && ( isdefined( player.remoteWeapon.controlled ) && player.remoteWeapon.controlled ) )
	{
		return true;
	}
			
	return false;
}

function wait_till_hero_weapon_is_fully_on( weapon )
{
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "weapon_change" );
	
	slot = self GadgetGetSlot( weapon );

	while (1)
	{
		if ( self ability_player::gadget_is_in_use( slot ) )
		{
			self.lastNonKillstreakWeapon = weapon;
			return;
		}
		{wait(.05);};
	}
}

function track_weapon_usage()
{
	self endon( "death" );
	self endon( "disconnect" );

	self.lastNonKillstreakWeapon = self GetCurrentWeapon();
	lastValidPimary = self GetCurrentWeapon();
	if ( self.lastNonKillstreakWeapon == level.weaponNone )
	{
		weapons = self GetWeaponsListPrimaries();
		if ( weapons.size > 0 )
		{
			self.lastNonKillstreakWeapon = weapons[0];
		}
		else
		{
			self.lastNonKillstreakWeapon = level.weaponBaseMelee;
		}
	}
	Assert( self.lastNonKillstreakWeapon != level.weaponNone );
	
	for ( ;; )
	{
		currentWeapon = self GetCurrentWeapon();
		self waittill( "weapon_change", weapon );
		
		if ( weapons::is_primary_weapon( weapon ) )
		{
			lastValidPimary = weapon;
		}

		if ( weapon == self.lastNonKillstreakWeapon || weapon == level.weaponNone || weapon == level.weaponBaseMelee )
		{
			continue;
		}

		if ( weapon.isGameplayWeapon )
		{
			continue;
		}
				
		if( isdefined( self.resurrect_weapon ) && ( weapon == self.resurrect_weapon ) )
		{
			continue;
		}

		name = get_killstreak_for_weapon( weapon );

		if ( isdefined( name ) && !weapon.isCarriedKillstreak )
		{
			killstreak = level.killstreaks[ name ];
			continue;
		}

		if ( currentWeapon.isEquipment )
		{
			if ( self.lastNonKillstreakWeapon.isCarriedKillstreak )
			{
				self.lastNonKillstreakWeapon = lastValidPimary;
			}
			continue;
		}

		if ( weapon.isHeroWeapon )
		{
			if ( weapon.gadget_heroversion_2_0 )
			{
				if ( weapon.isGadget && self GetAmmoCount(weapon ) > 0 )
				{
					self thread wait_till_hero_weapon_is_fully_on( weapon );
					continue;
				}
			}
		}
		
	
		self.lastNonKillstreakWeapon = weapon;
	}
}

function killstreak_waiter()
{
	self endon ( "death" );
	self endon ( "disconnect" );
	level endon ( "game_ended" );
	
	self thread track_weapon_usage();
	
	self give_owned();
	
	for ( ;; )
	{
		self waittill( "weapon_change", weapon );
		
		if( !killstreaks::is_killstreak_weapon( weapon ) )
		{
			continue;
		}
		
		killstreak = get_killstreak_for_weapon( weapon );
				
		if ( !( isdefined( level.usingMomentum ) && level.usingMomentum ) )
		{
			killstreak = get_top_killstreak();
			if( weapon != get_killstreak_weapon(killstreak) )
				continue;
		}
		
		if( is_remote_override_weapon( killstreak, weapon ) )
		{
			continue;
		}
		
		inventoryButtonPressed = ( self InventoryButtonPressed() ) || ( isdefined( self.pers["isBot"] ) );

		waittillframeend;

		if( ( isdefined( self.usingKillstreakHeldWeapon ) && self.usingKillstreakHeldWeapon ) && weapon.isCarriedKillstreak )
		{
			continue;
		}
		
		isFromInventory = undefined;
		
		if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) )
		{		
			if ( ( weapon == self GetInventoryWeapon() ) )
			{
				isFromInventory = true;
			}
			else if (( self GetAmmoCount( weapon ) <= 0 ) && (weapon.name != "killstreak_ai_tank"))
			{
				self killstreaks::switch_to_last_non_killstreak_weapon();
				continue;
			}
		}
		else if ( ( isdefined( level.usingMomentum ) && level.usingMomentum ) )
		{
			if ( ( weapon == self GetInventoryWeapon() ) && inventoryButtonPressed )
			{
				isFromInventory = true;
			}
			else if ( self.momentum < level.killstreaks[killstreak].momentumCost )
			{
				self killstreaks::switch_to_last_non_killstreak_weapon();
				continue;
			}
		}
		
		// this catches the between round cases
		if ( !isdefined( level.startTime ) && ( level.roundStartKillstreakDelay > 0 ) )
		{
			display_unavailable_time();
			continue;
		}
			
		thread useKillstreak( killstreak, isFromInventory );
	}
}

function should_delay_killstreak( killstreakType )
{
	if( !isdefined(level.startTime) )
	{
		return false;
	}

	if( level.roundStartKillstreakDelay < ( ( ( gettime() - level.startTime ) - level.discardTime ) / 1000 ) )
	{
		return false;
	}

	if( !is_delayable_killstreak(killstreakType) )
	{
		return false;
	}

	killstreakWeapon = get_killstreak_weapon( killstreakType );
	if( killstreakWeapon.isCarriedKillstreak )
	{
		return false;
	}

	if ( util::isFirstRound() || util::isOneRound() )
	{
		return false;
	}

	return true;
}

//check if this is a killstreak we want to delay at the start of a round
function is_delayable_killstreak( killstreakType )
{
	if( isdefined( level.killstreaks[killstreakType] ) && ( isdefined( level.killstreaks[killstreakType].delayStreak ) && level.killstreaks[killstreakType].delayStreak ) )
	{
		return true;
	}

	return false;
}

function get_xp_amount_for_killstreak( killstreakType )
{
	// looks like only the rcxd does this
	// all killstreaks need this?
	xpAmount = 0;
	switch( level.killstreaks[killstreakType].killstreakLevel )
	{
	case 1:
	case 2:
	case 3:
	case 4:
		xpAmount = 100;
		break;
	case 5:
		xpAmount = 150;
		break;
	case 6:
	case 7:
		xpAmount = 200;
		break;
	case 8:
		xpAmount = 250;
		break;
	case 9:
		xpAmount = 300;
		break;
	case 10:
	case 11:
		xpAmount = 350;
		break;
	case 12:
	case 13:
	case 14:
	case 15:
		xpAmount = 500;
		break;
	}

	return xpAmount;
}

function display_unavailable_time()
{
	timeLeft = Int( level.roundStartKillstreakDelay - (globallogic_utils::getTimePassed() / 1000) );
	
	if ( timeLeft <= 0 )
	{
		timeLeft = 1;
	}

	self iPrintLnBold( &"MP_UNAVAILABLE_FOR_N", " " + timeLeft + " ", &"EXE_SECONDS" );	
}

function trigger_killstreak( killstreakType, isFromInventory )
{
	assert( isdefined(level.killstreaks[killstreakType].useFunction), "No use function defined for killstreak " + killstreakType);
	
	self.usingKillstreakFromInventory = isFromInventory;

	if ( level.inFinalKillcam )
	{
		return false;
	}

	if( should_delay_killstreak( killstreakType ) )
	{
		display_unavailable_time();
	}
	else if ( [[level.killstreaks[killstreakType].useFunction]](killstreakType) )
	{
		//Killstreak of 3-4:+100, 5: +150, 6-7 +200, 8: +250, 9: +300, 11: +350, Above: +500

		if ( isdefined( self ) )
		{
			//bbPrint( "mpkillstreakuses", "gametime %d spawnid %d name %s", getTime(), getplayerspawnid( self ), killstreakType );

			if ( !isdefined( self.pers[level.killstreaks[killstreakType].usageKey] ) )
			{
				self.pers[level.killstreaks[killstreakType].usageKey] = 0;
			}
			
			self.pers[level.killstreaks[killstreakType].usageKey]++;
			self notify( "killstreak_used", killstreakType );
			self notify( "killstreak_done", true, killstreakType );
		}
		
		self.usingKillstreakFromInventory = undefined;
		
		return true;
	}
	
	self.usingKillstreakFromInventory = undefined;
	
	if ( isdefined( self ) )
	{
		self notify( "killstreak_done", false, killstreakType );
	}
	return false;
}

function add_to_killstreak_count( weapon )
{
	if ( !isdefined( self.pers["totalKillstreakCount"] ) )
	{
		self.pers["totalKillstreakCount"] = 0;
	}
		
// The check is now done further up the stack to see if this should be counted
		self.pers["totalKillstreakCount"]++;
}

function get_first_valid_killstreak_alt_weapon( killstreakType )
{
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak not registered.");

	if( isdefined( level.killstreaks[killstreakType].altWeapons ) )
	{
		for( i = 0; i < level.killstreaks[killstreakType].altWeapons.size; i++ )
		{
			if( isdefined( level.killstreaks[killstreakType].altWeapons[i] ) )
			{
				return level.killstreaks[killstreakType].altWeapons[i];
			}
		}
	}
	
	return level.weaponNone;
}

function should_give_killstreak( weapon ) 
{
	if( GetDvarInt( "teamOpsEnabled" ) == 1 )
		return false;
	
	killstreakBuilding = GetDvarint( "scr_allow_killstreak_building" );
	
	if ( killstreakBuilding == 0 )
	{
		if ( killstreaks::is_weapon_associated_with_killstreak(weapon) )
		{
			return false;
		}
	}
	
	return true;
}

function point_is_in_danger_area( point, targetpos, radius )
{
	return distance2d( point, targetpos ) <= radius * 1.25;
}

function print_killstreak_start_text( killstreakType, owner, team, targetpos, dangerRadius )
{
	if ( !isdefined( level.killstreaks[killstreakType] ) )
	{
		return;
	}
	
	if ( level.teambased )
	{
		players = level.players;
		if ( !level.hardcoreMode && isdefined(level.killstreaks[killstreakType].inboundNearPlayerText))
		{
			for(i = 0; i < players.size; i++)
			{
				if(isalive(players[i]) && (isdefined(players[i].pers["team"])) && (players[i].pers["team"] == team)) 
				{
					if ( point_is_in_danger_area( players[i].origin, targetpos, dangerRadius ) )
					{
						players[i] iprintlnbold(level.killstreaks[killstreakType].inboundNearPlayerText);
					}
				}
			}
		}
		
		if ( isdefined(level.killstreaks[killstreakType]) )
		{
			for ( i = 0; i < level.players.size; i++ )
			{
				player = level.players[i];
				playerteam = player.pers["team"];
				if ( isdefined( playerteam ) )
				{
					if ( playerteam == team )
					{
						player iprintln( level.killstreaks[killstreakType].inboundText, owner );
					}
				}
			}
		}
	}
	else
	{
		if ( !level.hardcoreMode && isdefined(level.killstreaks[killstreakType].inboundNearPlayerText) )
		{
			if ( point_is_in_danger_area( owner.origin, targetpos, dangerRadius ) )
			{
				owner iprintlnbold(level.killstreaks[killstreakType].inboundNearPlayerText);
			}
		}
	}
}

function play_killstreak_firewall_being_hacked_dialog( killstreakType, killstreakId )
{
	if ( self globallogic_audio::killstreak_dialog_queued( "firewallBeingHacked", killstreakType, killstreakId ) )
	{
		return;
	}
	
	self globallogic_audio::play_taacom_dialog( "firewallBeingHacked", killstreakType, killstreakId );	
}

function play_killstreak_firewall_hacked_dialog( killstreakType, killstreakId )
{
	if ( self globallogic_audio::killstreak_dialog_queued( "firewallHacked", killstreakType, killstreakId ) )
	{
		return;
	}
	
	self globallogic_audio::play_taacom_dialog( "firewallHacked", killstreakType, killstreakId );	
}

function play_killstreak_being_hacked_dialog( killstreakType, killstreakId )
{
	if ( self globallogic_audio::killstreak_dialog_queued( "beingHacked", killstreakType, killstreakId ) )
	{
		return;
	}
	
	self globallogic_audio::play_taacom_dialog( "beingHacked", killstreakType, killstreakId );	
}

function play_killstreak_hacked_dialog( killstreakType, killstreakId, hacker )
{
	self globallogic_audio::flush_killstreak_dialog_on_player( killstreakId );
	self globallogic_audio::play_taacom_dialog( "hacked", killstreakType );
	
	excludeSelf = [];
	excludeSelf[0] = self;
	
	if ( level.teambased )
	{
		globallogic_audio::leader_dialog( level.killstreaks[killstreakType].hackedDialogKey, self.team, excludeSelf );
		globallogic_audio::leader_dialog_for_other_teams( level.killstreaks[killstreakType].hackedStartDialogKey, self.team, undefined, killstreakId );
	}
	else
	{
		self globallogic_audio::leader_dialog_on_player( level.killstreaks[killstreakType].hackedDialogKey );
		hacker globallogic_audio::leader_dialog_on_player( level.killstreaks[killstreakType].hackedStartDialogKey );
	}
}

function play_killstreak_start_dialog( killstreakType, team, killstreakId )
{
	if ( !isdefined( killstreakType ) || 
	     !isdefined( killstreakId ) )
	{
		return;
	}
	
	// Kill any waiting 'scorestreak ready' taacom threads
	self notify ( "killstreak_start_" + killstreakType );
	self notify ( "killstreak_start_inventory_" + killstreakType );
	
	dialogKey = level.killstreaks[killstreakType].requestDialogKey;
	
	if ( !isdefined( self.currentKillstreakDialog ) && isdefined( dialogKey ) && isdefined( level.heroPlayDialog ) )
    {		
		self thread [[level.heroPlayDialog]]( dialogKey );
    }
	
	excludeSelf = [];
	excludeSelf[0] = self;
	
	if ( level.teambased )
	{
		// Don't play the friendly incoming audio over your own request
		globallogic_audio::leader_dialog( level.killstreaks[killstreakType].startDialogKey, team, excludeSelf, undefined, killstreakId );
		
		globallogic_audio::leader_dialog_for_other_teams( level.killstreaks[killstreakType].enemyStartDialogKey, team, undefined, killstreakId );
	}
	else
	{
		globallogic_audio::leader_dialog( level.killstreaks[killstreakType].enemyStartDialogKey, undefined, excludeSelf, undefined, killstreakId  );
	}
}

function play_killstreak_ready_sfx (killstreaktype)
{
	if ( !isdefined( level.gameEnded ) || !level.gameEnded )
	{
		ready_sfx_alias = "mpl_killstreak_" + killstreaktype;
		
		if ( isdefined (ready_sfx_alias))
		{
			self playsoundtoplayer (ready_sfx_alias, self );
		}
	}
}	

function play_killstreak_ready_dialog( killstreakType, taacomWaitTime )
{
	self notify( "killstreak_ready_" + killstreakType );
	
	self endon( "death" );
	self endon( "killstreak_start_" + killstreakType );
	self endon( "killstreak_ready_" + killstreakType );

	level endon( "game_ended" );
	
	if ( isdefined( level.gameEnded ) && level.gameEnded )
	{
		return;
	}
	
	if ( globallogic_audio::killstreak_dialog_queued( "ready", killstreakType ) )
	{
		return;
	}
	
	if ( isdefined( taacomWaitTime ) )
	{
		wait ( taacomWaitTime );
	}
	
	self globallogic_audio::play_taacom_dialog( "ready", killstreakType );
}

// Self is killstreak
function play_destroyed_dialog_on_owner( killstreakType, killstreakId )
{
	if ( !isdefined( self.owner ) ||
	     !isdefined( self.team )  ||
	     self.team != self.owner.team )
	{
		return;
	}
	
	self.owner globallogic_audio::flush_killstreak_dialog_on_player( killstreakId );
	
	self.owner globallogic_audio::play_taacom_dialog( "destroyed", killstreakType );
}

// Self is killstreak
function play_taacom_dialog_on_owner( dialogKey, killstreakType, killstreakId )
{
	if ( !isdefined( self.owner ) ||
	     !isdefined( self.team )  ||
	     self.team != self.owner.team )
	{
		return;
	}
	
	self.owner globallogic_audio::play_taacom_dialog( dialogKey, killstreakType, killstreakId );
}

// Self is killstreak
function play_pilot_dialog_on_owner( dialogKey, killstreakType, killstreakId )
{
	if ( !isdefined( self.owner ) ||
	     !isdefined( self.owner.team ) ||
	     !isdefined( self.team ) ||
	     self.team != self.owner.team )
	{
		return;
	}
		
	self.owner play_pilot_dialog( dialogKey, killstreakType, killstreakId, self.pilotIndex );
}

// self is player
function play_pilot_dialog( dialogKey, killstreakType, killstreakId, pilotIndex )
{	
	if ( !isdefined( killstreakType ) || 
	     !isdefined( pilotIndex ) )
	{
		return;
	}
	
	self globallogic_audio::killstreak_dialog_on_player( dialogKey, killstreakType, killstreakId, pilotIndex );
}

// Self is killstreak
function play_taacom_dialog_response_on_owner( dialogKey, killstreakType, killstreakId )
{
	assert( isdefined( dialogKey ) );
	assert( isdefined( killstreakType ) );
	
	if ( !isdefined( self.owner ) ||
	     !isdefined( self.team ) ||
	     self.team != self.owner.team )
	{
		return;
	}
	
	self.owner play_taacom_dialog_response( dialogKey, killstreakType, killstreakId, self.pilotIndex );
}

// self is player
function play_taacom_dialog_response( dialogKey, killstreakType, killstreakId, pilotIndex )
{
	assert( isdefined( dialogKey ) );
	assert( isdefined( killstreakType ) );
	
	if ( !isdefined( pilotIndex ) )
	{
		return;
	}
	
	self globallogic_audio::play_taacom_dialog( dialogKey + pilotIndex, killstreakType, killstreakId );	
}


// Self is player
function get_random_pilot_index( killstreakType )
{
	if ( !isdefined( killstreakType ) )
	{
		return undefined;
	}
	
	taacomBundle = struct::get_script_bundle( "mpdialog_taacom", self.pers["mptaacom"] );
	
	if( !isdefined( taacomBundle.pilotBundles[killstreakType] ) )
	{
		return undefined;
	}
	
	numPilots = taacomBundle.pilotBundles[killStreakType].size;
	
	if ( numPilots <= 0 )
	{
		return undefined;
	}
	
	return RandomInt( numPilots );
}

// Self is killstreak
function player_killstreak_threat_tracking( killstreakType )
{
	assert( isdefined( killstreakType ) );
	
	self endon ( "death" );
	self endon ( "delete" );
	self endon ( "leaving" );
	level endon( "game_ended" );
	
	while( 1 )
	{
		if ( !isdefined( self.owner ) )
		{
			return;
		}
		
		players =  self.owner battlechatter::get_enemy_players();
		players = array::randomize( players );
		
		foreach( player in players )
		{
			if ( !player battlechatter::can_play_dialog( true ) )
			{
				continue;
			}
			
			lookAngles = player GetPlayerAngles();
			
			if ( lookAngles[0] < 270 || lookAngles[0] > 330 )
			{
				continue;
			}
			
			lookDir = AnglesToForward( lookAngles );
			eyePoint = player getEye();
			
			streakDir = VectorNormalize( self.origin - eyePoint );
			
			dot = VectorDot( streakDir, lookDir );
			
			if ( dot < 0.94 )
			{
				continue;
			}
			
			traceResult = BulletTrace( eyePoint, self.origin, true, player );
			if ( traceResult["fraction"] >= 1.0 || traceResult["entity"] === self )
			{
				if ( battlechatter::dialog_chance( "killstreakSpotChance" ) ) 
				{
					player battlechatter::play_killstreak_threat( killstreakType );
				}
				wait ( battlechatter::mpdialog_value( "killstreakSpotDelay", 0 ) );
				break;
			}
		}
		
		wait ( battlechatter::mpdialog_value( "killstreakSpotInterval", .05 ) );
	}
}

function get_killstreak_inform_dialog( killstreakType )
{
	// please add inform dialog to killstreak
	//assert( isdefined ( level.killstreaks[killstreakType].informDialog ) );
	
	if ( isdefined( level.killstreaks[killstreakType].informDialog ) )
	{
		return level.killstreaks[killstreakType].informDialog;
	}
	return "";
}

function get_killstreak_usage_by_killstreak(killstreakType)
{
	assert( isdefined(level.killstreaks[killstreakType]), "Killstreak needs to be registered before calling get_killstreak_usage.");
	
	return get_killstreak_usage( level.killstreaks[killstreakType].usageKey );
}

function get_killstreak_usage(usageKey)
{
	if ( !isdefined( self.pers[usageKey] ) )
	{
		return 0;
	}
	
	return self.pers[usageKey];
}

function on_player_spawned()
{
	self endon("disconnect");

	pixbeginevent("_killstreaks.gsc/onPlayerSpawned");
	
	give_owned();
	
	if ( !isdefined( self.pers["killstreaks"] ) )
	{
		self.pers["killstreaks"] = [];
	}
	if ( !isdefined( self.pers["killstreak_has_been_used"] ) )
	{
		self.pers["killstreak_has_been_used"] = [];
	}
	if ( !isdefined( self.pers["killstreak_unique_id"] ) )
	{
		self.pers["killstreak_unique_id"] = [];
	}
	if( !isdefined( self.pers["killstreak_ammo_count"] ) )
	{
		self.pers["killstreak_ammo_count"] = [];
	}

	size = self.pers["killstreaks"].size;

	if ( size > 0 )
	{
		self thread play_killstreak_ready_dialog( self.pers["killstreaks"][size - 1] );
	}
	
	self.killcamKilledByEnt = undefined;
	
	pixendevent();
}

function on_joined_team()
{
	self endon("disconnect");
	
	self SetInventoryWeapon( level.weaponNone );
	self.pers["cur_kill_streak"] = 0;
	self.pers["cur_total_kill_streak"] = 0;		
	self setplayercurrentstreak( 0 );
	self.pers["totalKillstreakCount"] = 0;
	self.pers["killstreaks"] = [];
	self.pers["killstreak_has_been_used"] = [];
	self.pers["killstreak_unique_id"] = [];
	self.pers["killstreak_ammo_count"] = [];
	
	if ( ( isdefined( level.usingScoreStreaks ) && level.usingScoreStreaks ) )
	{
		self.pers["killstreak_quantity"] = [];
		self.pers["held_killstreak_ammo_count"] = [];
		self.pers["held_killstreak_clip_count"] = [];
	}
}

function init_ride_killstreak( streak, always_allow = false )
{
	self disableUsability();
	result = self init_ride_killstreak_internal( streak, always_allow );

	if ( isdefined( self ) )
	{
		self enableUsability();
	}
		
	return result;
}

function watch_for_remove_remote_weapon()
{
	self endon( "endWatchForRemoveRemoteWeapon" );
	for ( ;; )
	{
		self waittill( "remove_remote_weapon" );
		self killstreaks::switch_to_last_non_killstreak_weapon();
		self enableUsability();
	}
}

function init_ride_killstreak_internal( streak, always_allow )
{	
	if ( isdefined( streak ) && ( ( streak == "qrdrone" ) || ( streak == "dart" ) || ( streak == "killstreak_remote_turret" ) || ( streak == "killstreak_ai_tank" ) || (streak == "qrdrone") || (streak == "sentinel") ) )
	{
		laptopWait = "timeout";
	}
	else
	{
		laptopWait = self util::waittill_any_timeout( 0.6, "disconnect", "death", "weapon_switch_started" );
	}
		
	hostmigration::waitTillHostMigrationDone();

	if ( laptopWait == "weapon_switch_started" )
	{
		return ( "fail" );
	}

	if ( !isAlive( self ) && !always_allow )
	{
		return "fail";
	}

	if ( laptopWait == "disconnect" || laptopWait == "death" )
	{
		if ( laptopWait == "disconnect" )
		{
			return ( "disconnect" );
		}

		if ( self.team == "spectator" )
		{
			return "fail";
		}

		return ( "success" );		
	}
	
	if ( self IsEMPJammed() && !( isdefined( self.ignoreEMPJammed ) && self.ignoreEMPJammed ) )
	{
		return ( "fail" );
	}
	
	if ( self is_interacting_with_object() )
	{
		return "fail";
	}
	
	self thread hud::fade_to_black_for_x_sec( 0, 0.2, 0.4, 0.25 );
	self thread watch_for_remove_remote_weapon();
	blackOutWait = self util::waittill_any_timeout( 0.60, "disconnect", "death" );
	self notify( "endWatchForRemoveRemoteWeapon" );

	hostmigration::waitTillHostMigrationDone();

	if ( blackOutWait != "disconnect" ) 
	{
		self thread clear_ride_intro( 1.0 );
		
		if ( self.team == "spectator" )
		{
			return "fail";
		}
	}
	
	if ( always_allow )
	{
		if ( blackOutWait == "disconnect" )
		{
			return ( "disconnect" );
		}
		else
		{
			return ( "success" );		
		}
	}

	if ( self isOnLadder() )
	{
		return "fail";	
	}

	if ( !isAlive( self ) )
	{
		return "fail";
	}

	if ( self IsEMPJammed() && !( isdefined( self.ignoreEMPJammed ) && self.ignoreEMPJammed ) )
	{
		return ( "fail" );
	}
	
	if ( ( isdefined( self.laststand ) && self.laststand ) )
	{
		return "fail";
	}
	
	if ( self is_interacting_with_object() )
	{
		return "fail";
	}
	
	if ( blackOutWait == "disconnect" )
	{
		return ( "disconnect" );
	}
	else
	{
		return ( "success" );		
	}
}

function clear_ride_intro( delay )
{
	self endon( "disconnect" );

	if ( isdefined( delay ) )
		wait( delay );

	//self util::freeze_player_controls( false );
	
	self thread hud::screen_fade_in( 0 );
}

/#

function killstreak_debug_think()
{
	SetDvar( "debug_killstreak", "" );

	for( ;; )
	{
		cmd = GetDvarString( "debug_killstreak" );

		switch( cmd )
		{
		case "data_dump":	
			killstreak_data_dump();
			break;
		}

		if ( cmd != "" )
		{
			SetDvar( "debug_killstreak", "" );
		}

		wait( 0.5 );
	}
}

function killstreak_data_dump()
{
	iprintln( "Killstreak Data Sent to Console" );
	println( "##### Killstreak Data #####");
	println( "killstreak,killstreaklevel,weapon,altweapon1,altweapon2,altweapon3,altweapon4,type1,type2,type3,type4" );
	
	keys = GetArrayKeys( level.killstreaks );
		
	for( i = 0; i < keys.size; i++ )
	{
		data = level.killstreaks[ keys[i] ];
		type_data = level.killstreaktype[ keys[i] ];
		
		print( keys[i] + "," );
		print( data.killstreaklevel + "," );
		print( data.weapon.name + "," );

		alt = 0;

		if ( isdefined( data.altweapons ) )
		{
			assert( data.altweapons.size <= 4 );

			for ( alt = 0; alt < data.altweapons.size; alt++ )
			{
				print( data.altweapons[alt].name + "," );
			}
		}

		for ( ; alt < 4; alt++ )
		{
			print( "," );
		}

		type = 0;

		if ( isdefined( type_data ) )
		{
			assert( type_data.size < 4 );
			type_keys = GetArrayKeys( type_data );

			for ( ; type < type_keys.size; type++ )
			{
				if ( type_data[ type_keys[type] ] == 1 )
				{
					print( type_keys[type] + "," );
				}
			}
		}

		for ( ; type < 4; type++ )
		{
			print( "," );
		}

		println( "" );
	}

	println( "##### End Killstreak Data #####");
}

#/


function is_interacting_with_object()
{
	if ( self isCarryingTurret() )
	{
		return true;
	}
	if ( ( isdefined( self.isPlanting ) && self.isPlanting ) )
	{
		return true;
	}
	if ( ( isdefined( self.isDefusing ) && self.isDefusing ) )
	{
		return true;
	}

	return false;
}


function clear_using_remote( immediate, skipNotify )
{
	if ( !isdefined( self ) )
	{
		return;
	}
	
	self.dofutz = false;
	self.no_fade2black = false;
	self clientfield::set_to_player( "static_postfx", 0 );

	if ( isdefined( self.carryIcon ) )
	{
		self.carryIcon.alpha = 1;
	}

	self.usingRemote = undefined;
	self reset_killstreak_delay_killcam();
	self enableOffhandWeapons();
	self enableWeaponCycling();
	
	curWeapon = self getCurrentWeapon();
	
	if ( isalive( self ) )
	{
		self killstreaks::switch_to_last_non_killstreak_weapon( immediate );
	}
	
	if( !level.gameEnded )
		self util::freeze_player_controls( false );
	if( !( isdefined( skipNotify ) && skipNotify ))
		self notify( "stopped_using_remote" );
	
	thread hide_tablet();
}

function hide_tablet()
{
	self endon("disconnect");
	wait .2;
	self clientfield::set_player_uimodel( "hudItems.remoteKillstreakActivated", 0 );	
}

function set_killstreak_delay_killcam( killstreak_name )
{
	self.killstreak_delay_killcam = killstreak_name;
}

function reset_killstreak_delay_killcam() // self == player
{
	self.killstreak_delay_killcam = undefined;
}

function hide_compass()
{
	self clientfield::set( "killstreak_hides_compass", 1 );
}

function unhide_compass()
{
	self clientfield::set( "killstreak_hides_compass", 0 );
}

function setup_health( killstreak_ref, max_health, low_health )
{
	self.maxhealth = max_health;
	self.lowhealth = low_health;

	self.hackedHealthUpdateCallback = &defaultHackedHealthUpdateCallback;
	
	tableMaxHealth = killstreak_bundles::get_max_health( killstreak_ref );
	
	if ( isdefined( tableMaxHealth ) )
	{
		self.maxhealth = tableMaxHealth;
	}
	
	tableLowHealth = killstreak_bundles::get_low_health( killstreak_ref );
	
	if ( isdefined( tableLowHealth ) )
	{
		self.lowhealth = tableLowHealth;
	}	
	
	tableHackedHealth = killstreak_bundles::get_hacked_health( killstreak_ref );
	
	if ( isdefined( tableHackedHealth ) )
	{
		self.hackedHealth = tableHackedHealth;
	}	
	else
	{
		self.hackedHealth = self.maxhealth;
	}
}

function MonitorDamage( killstreak_ref, 
               			max_health, destroyed_callback, 
               			low_health, low_health_callback, 
               			emp_damage, emp_callback, 
               			allow_bullet_damage )
{
	self endon( "death" );
	self endon( "delete" );
	
	self.health = 9999999;
	self.damageTaken = 0;

	self setup_health( killstreak_ref, max_health, low_health );
	
	assert( ( !IsVehicle( self ) || !IsSentient( self ) ), "MonitorDamage should not be called on a sentient vehicle. For sentient vehicles, use overrideVehicleDamage instead.");

	while( true )
	{
		weapon_damage = undefined;
		// this damage is done to self.health which isnt used to determine the helicopter's health, damageTaken is.
		self waittill( "damage", damage, attacker, direction, point, type, tagName, modelName, partname, weapon, flags, inflictor, chargeLevel );		

		if( ( isdefined( self.invulnerable ) && self.invulnerable ) )
		{
			continue;
		}
		
		if( !isdefined( attacker ) || !isplayer( attacker ) )
		{
			continue;
		}
		
		friendlyfire = weaponobjects::friendlyFireCheck( self.owner, attacker );
		if( !friendlyfire )
		{
			continue;			
		}
			
		if(	isdefined( self.owner ) && attacker == self.owner )
		{
			continue;
		}
		
		isValidAttacker = true;
		if( level.teambased )
		{
			isValidAttacker = ( isdefined( attacker.team ) && attacker.team != self.team );
		}

		if( !isValidAttacker )
		{
			continue;
		}
		
		if ( isdefined( self.killstreakDamageModifier ) )
		{
			damage = [[self.killstreakDamageModifier]]( damage, attacker, direction, point, type, tagName, modelName, partname, weapon, flags, inflictor, chargeLevel );
			if ( damage <= 0 )
				continue;
		}

		if( weapon.isEmp && type == "MOD_GRENADE_SPLASH" )
		{			
			emp_damage_to_apply = killstreak_bundles::get_emp_grenade_damage( killstreak_ref, self.maxhealth );
			
			if ( !isdefined( emp_damage_to_apply ) )
				emp_damage_to_apply = ( isdefined( emp_damage ) ? emp_damage : 1 );
		
			if( isdefined( emp_callback ) && emp_damage_to_apply > 0 )
			{
				self [[ emp_callback ]]( attacker );
			}

			weapon_damage = emp_damage_to_apply;
		}
		
		if ( ( isdefined( self.selfDestruct ) && self.selfDestruct ) )
		{
			weapon_damage = self.maxhealth + 1;
		}
		
		if ( !isdefined( weapon_damage ) )
		{
			weapon_damage = killstreak_bundles::get_weapon_damage( killstreak_ref, self.maxhealth, attacker, weapon, type, damage, flags, chargeLevel );

			if ( !isdefined( weapon_damage ) )
			{			
				weapon_damage = get_old_damage( attacker, weapon, type, damage, allow_bullet_damage );
			}
		}		
		
		if ( weapon_damage > 0 )
		{
			if( damagefeedback::doDamageFeedback( weapon, attacker ) )
			{
				attacker thread damagefeedback::update( type );
			}
			
			self challenges::trackAssists( attacker, weapon_damage, false );
		}		
		
		self.damageTaken += weapon_damage;
		
		if ( !IsSentient( self ) && weapon_damage > 0 )
			self.attacker = attacker;
				
		if( self.damageTaken > self.maxhealth )
		{
			weaponStatName = "destroyed";
			switch( weapon.name )
			{
				case "auto_tow":
				case "tow_turret":
				case "tow_turret_drop":
					weaponStatName = "kills";
					break;
			}
			
			level.globalKillstreaksDestroyed++;
			attacker AddWeaponStat( GetWeapon( killstreak_ref ), "destroyed", 1 );
			
			if( isdefined( destroyed_callback ) )
			{
				self thread [[ destroyed_callback ]]( attacker, weapon );
			}
			
			return;
		}
		
		remaining_health = ( max_health - self.damageTaken );
		
		if( ( remaining_health < low_health ) && weapon_damage > 0 )
		{
			if( isdefined( low_health_callback ) && ( !isdefined( self.currentState ) || self.currentState != "damaged" ) )
			{
				self [[ low_health_callback ]]( attacker, weapon );
			}
			
			self.currentstate = "damaged";
		}
		
		if( isdefined( self.extra_low_health ) && ( remaining_health < self.extra_low_health ) && weapon_damage > 0 )
		{
			if( isdefined( self.extra_low_health_callback ) && ( !isdefined( self.extra_low_damage_notified ) ) )
			{
				self [[ self.extra_low_health_callback ]]( attacker, weapon );
				
				self.extra_low_damage_notified = true;
			}
		}
	}
}

function defaultHackedHealthUpdateCallback( hacker )
{
	killstreak = self;

	assert( isdefined( self.maxHealth ) );
	assert( isdefined( self.hackedHealth ) );
	assert( isdefined( self.damageTaken ) );
		
	damageAfterHacking = self.maxHealth - self.hackedHealth;
	if ( self.damageTaken < damageAfterHacking ) 
	{
		self.damageTaken = damageAfterHacking;
	}
}

function OnDamagePerWeapon( killstreak_ref, 
                  		attacker, damage, flags, type, weapon,
               			max_health, destroyed_callback, 
               			low_health, low_health_callback, 
               			emp_damage, emp_callback, 
               			allow_bullet_damage, chargeLevel )
{	
	self.maxhealth = max_health;
	self.lowhealth = low_health;
	
	tableHealth = killstreak_bundles::get_max_health( killstreak_ref );
	
	if ( isdefined( tableHealth ) )
	{
		self.maxhealth = tableHealth;
	}
	
	tableHealth = killstreak_bundles::get_low_health( killstreak_ref );
	
	if ( isdefined( tableHealth ) )
	{
		self.lowhealth = tableHealth;
	}	
	
	if( ( isdefined( self.invulnerable ) && self.invulnerable ) )
	{
		return 0;
	}
	
	if( !isdefined( attacker ) || !isplayer( attacker ) )
	{
		return get_old_damage( attacker, weapon, type, damage, allow_bullet_damage );
	}
	
	friendlyfire = weaponobjects::friendlyFireCheck( self.owner, attacker );
	if( !friendlyfire )
	{
		return 0;
	}	
	
	isValidAttacker = true;
	if( level.teambased )
	{
		isValidAttacker = ( isdefined( attacker.team ) && attacker.team != self.team );
	}

	if( !isValidAttacker )
	{
		return 0;
	}
	
	if( weapon.isEmp && type == "MOD_GRENADE_SPLASH" )
	{		
		emp_damage_to_apply = killstreak_bundles::get_emp_grenade_damage( killstreak_ref, self.maxhealth );
		
		if ( !isdefined( emp_damage_to_apply ) )
			emp_damage_to_apply = ( isdefined( emp_damage ) ? emp_damage : 1 );

		if( isdefined( emp_callback ) && emp_damage_to_apply > 0 )
		{
			self [[ emp_callback ]]( attacker, weapon );
		}
		
		return emp_damage_to_apply;
	}	

	weapon_damage = killstreak_bundles::get_weapon_damage( killstreak_ref, self.maxhealth, attacker, weapon, type, damage, flags, chargeLevel );

	if ( !isdefined( weapon_damage ) )
	{			
		weapon_damage = get_old_damage( attacker, weapon, type, damage, allow_bullet_damage );
	}
	
	if ( weapon_damage <= 0 )
	{
		return 0;
	}	
	
	iDamage = int( weapon_damage );
	if( iDamage > self.health )
	{
		if( isdefined( destroyed_callback ) )
		{
			self thread [[ destroyed_callback ]]( attacker, weapon );
		}
	}	
	
	return iDamage;
}

function get_old_damage( attacker, weapon, type, damage, allow_bullet_damage)
{
	switch( type )
	{
		case "MOD_RIFLE_BULLET":
		case "MOD_PISTOL_BULLET":
			{
				if( !allow_bullet_damage )
				{
					damage = 0;
					break;
				}
				
				if ( isdefined( attacker ) && isplayer( attacker ) )
				{
					hasFMJ = attacker HasPerk( "specialty_armorpiercing" );
				}				
	
				if ( ( isdefined( hasFMJ ) && hasFMJ ) )
				{
					damage = int( damage * level.cac_armorpiercing_data );
				}
			}
			break;
			
		case "MOD_PROJECTILE":
		case "MOD_EXPLOSIVE":
		case "MOD_PROJECTILE_SPLASH":
			if ( ( weapon.statIndex == level.weaponPistolEnergy.statIndex ) || ( weapon.statIndex != level.weaponShotgunEnergy.statIndex ) || ( weapon.statIndex == level.weaponSpecialCrossbow.statIndex ) )
				break;
			
			if( isdefined( self.remoteMissileDamage ) && isdefined( weapon ) && weapon.name == "remote_missile_missile")
			{
				 damage = self.remoteMissileDamage;
			}
			else if( isdefined( self.rocketDamage ) )
			{
				damage = self.rocketDamage;
			}
			break;
		default:			
			break;
	}
	
	return damage;
}



function configure_team( killstreakType, killstreakId, owner, influencerType, configureTeamPreFunction, configureTeamPostFunction, isHacked = false )
{
	killstreak = self;
	
	killstreak.killstreakType = killstreakType;
	killstreak.killstreakId = killstreakId;
	killstreak _setup_configure_team_callbacks( influencerType, configureTeamPreFunction, configureTeamPostFunction );
	killstreak configure_team_internal( owner, isHacked );

	owner thread trackActiveKillstreak( killstreak );
}


function trackActiveKillstreak( killstreak )
{
	self endon( "disconnect" );
	
	killstreakIndex = killstreak.killstreakID;
	if( isdefined( killstreakIndex ) )
	{
		self.pers["activeKillstreaks"][ killstreakIndex ] = killstreak;
		
		killstreak util::waittill_any( "killstreak_hacked", "death" );
	
		self.pers["activeKillstreaks"][ killstreakIndex ] = undefined;
	}
}

function getActiveKillstreaks()
{
	return self.pers["activeKillstreaks"];
}

function configure_team_internal( owner, isHacked )
{
	killstreak = self;
	if ( isHacked == false )
	{
		killstreak.originalOwner = owner;
		killstreak.originalteam = owner.team;	
/#
		killstreak thread killstreak_hacking::killstreak_switch_team( owner );
#/
	}
	else
	{
		assert( killstreak.killstreakTeamConfigured, "configure_team must be called before a killstreak can be hacked" );
	}
	
	if ( isdefined( killstreak.killstreakConfigureTeamPreFunction ) )
	{
		killstreak thread [[killstreak.killstreakConfigureTeamPreFunction]]( owner, ishacked );
	}

	if ( isdefined( killstreak.killstreakInfluencerType ) )
	{
		killstreak spawning::remove_influencers();
	}
	
	killstreak SetTeam( owner.team );
	killstreak.team = owner.team;
	if ( !IsAI( killstreak ) )
	{
		killstreak SetOwner( owner );
	}
	killstreak.owner = owner;
	killstreak.ownerEntnum = owner.entnum;

	killstreak.pilotIndex = killstreak.owner get_random_pilot_index( killstreak.killstreakType );
	
	if ( isdefined( killstreak.killstreakInfluencerType ) )
	{
		killstreak spawning::create_entity_enemy_influencer( killstreak.killstreakInfluencerType, owner.team );
	}
	
	if ( isdefined( killstreak.killstreakConfigureTeamPostFunction ) )
	{
		killstreak thread [[killstreak.killstreakConfigureTeamPostFunction]]( owner, ishacked );
	}
}

function private _setup_configure_team_callbacks( influencerType, configureTeamPreFunction, configureTeamPostFunction )
{
	killstreak = self;
	
	killstreak.killstreakTeamConfigured = true;
	killstreak.killstreakInfluencerType = influencerType;
	killstreak.killstreakConfigureTeamPreFunction = configureTeamPreFunction;
	killstreak.killstreakConfigureTeamPostFunction = configureTeamPostFunction;
}

	
function WatchTeamChange( teamChangeNotify )
{
	self notify( teamChangeNotify+ "_Singleton" );
	self endon ( teamChangeNotify+ "_Singleton" );
	
	killstreak = self;
	killstreak endon( "death" );
	
	killstreak endon( teamChangeNotify );
	killstreak.owner util::waittill_any( "joined_team", "disconnect", "joined_spectators", "emp_jammed" );
	killstreak notify( teamChangeNotify );
}

function should_not_timeout( killstreak )
{
/#
	assert( isdefined(killstreak), "Can not register a killstreak without a valid type name.");
	assert( isdefined(level.killstreaks[killstreak]), "Killstreak needs to be registered before calling register_dev_dvar.");

	if ( isdefined( level.killstreaks[killstreak].devTimeoutDvar ) )
			return GetDvarInt( level.killstreaks[killstreak].devTimeoutDvar );
#/

	return false;	
}

function WaitForTimeout( killstreak, duration, callback, endCondition1, endCondition2, endCondition3 )
{
/#
	if (killstreaks::should_not_timeout(killstreak))
	{
		return;
	}		
#/	
	self endon( "killstreak_hacked" );
	
	if( isdefined( endCondition1 ) )
		self endon( endCondition1 );
	if( isdefined( endCondition2 ) )
		self endon( endCondition2 );
	if( isdefined( endCondition3 ) )
		self endon( endCondition3 );
	
	self thread waitForTimeoutHacked( killstreak, callback, endCondition1, endCondition2, endCondition3 );
	
	killstreakBundle = level.killstreakBundle[self.killstreakType];
	self.killstreakEndTime = getTime() + duration;
	if ( isdefined( killstreakBundle ) && isdefined( killstreakBundle.ksTimeoutBeepDuration ) )
	{
		self WaitForTimeoutBeep( killstreakBundle, duration );
	}
	else
	{
		hostmigration::MigrationAwareWait( duration );	
	}
	
	self notify( "kill_WaitForTimeoutHacked_thread" );
	self.killstreakTimedOut = true;
	self.killstreakEndTime = 0;
	self notify( "timed_out" );
	self [[ callback ]]();
}


function WaitForTimeoutBeep( killstreakBundle, duration )
{
	self endon("death");
	beepDuration = killstreakBundle.ksTimeoutBeepDuration * 1000;
	hostmigration::MigrationAwareWait( max( duration - beepDuration, 0 ) );
	
	if ( IsVehicle( self ) )
	{
		self clientfield::set( "timeout_beep", 1 );
	}
	
	if ( isdefined( killstreakBundle.ksTimeoutFastBeepDuration ) )
	{
		fastBeepDuration = killstreakBundle.ksTimeoutFastBeepDuration * 1000;
		hostmigration::MigrationAwareWait( max( beepDuration - fastBeepDuration, 0 ) );
		
		if ( IsVehicle( self ) )
		{
			self clientfield::set( "timeout_beep", 2 );
		}
		
		hostmigration::MigrationAwareWait( fastBeepDuration );
	}

	if ( IsVehicle( self ) )
	{
		self clientfield::set( "timeout_beep", 0 );
	}
}
		

function WaitForTimeoutHacked( killstreak, callback, endCondition1, endCondition2, endCondition3 )
{
	self endon( "kill_WaitForTimeoutHacked_thread" );
	
	if( isdefined( endCondition1 ) )
		self endon( endCondition1 );
	if( isdefined( endCondition2 ) )
		self endon( endCondition2 );
	if( isdefined( endCondition3 ) )
		self endon( endCondition3 );
	
	self waittill( "killstreak_hacked" );
	
	hackedDuration = self killstreak_hacking::get_hacked_timeout_duration_ms();
	self.killstreakEndTime = getTime() + hackedDuration;
	hostmigration::MigrationAwareWait( hackedDuration );
	self.killstreakEndTime = 0;
	self notify( "timed_out" );
	self  [[ callback ]]();	
}

function update_player_threat( player )
{
	heli = self;
	
	player.threatlevel = 0;
	
	// distance factor
	dist = distance( player.origin, heli.origin );
	player.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range
	
	// behavior factor
	if( isdefined( heli.attacker ) && player == heli.attacker )
		player.threatlevel += 100;
	
	if( isdefined( player.carryObject ) )  //flag carrier
		player.threatlevel += 200;

	// player score factor
	if( isdefined( player.score ) )
		player.threatlevel += player.score * 2;
	
	if( player weapons::has_launcher() )
	{
		if( player weapons::has_lockon( heli ) )
			player.threatlevel += 1000;
		else 
			player.threatlevel += 500;
	}
	
	if( player weapons::has_hero_weapon() )
		player.threatlevel += 300;
	
	if( player weapons::has_lmg() )
		player.threatlevel += 200;
		
	if( isdefined( player.antithreat ) )
		player.threatlevel -= player.antithreat;
		
	if( player.threatlevel <= 0 )
		player.threatlevel = 1;
}

function update_non_player_threat( non_player )
{
	heli = self;
	
	non_player.threatlevel = 0;
	
	// distance factor
	dist = distance( non_player.origin, heli.origin );
	non_player.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range
			
	if( non_player.threatlevel <= 0 )
		non_player.threatlevel = 1;
}

function update_actor_threat( actor )
{
	heli = self;
	actor.threatlevel = 0;
	
	// distance factor
	dist = distance( actor.origin, heli.origin );
	actor.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range
	
	// player score factor
	if( isdefined( actor.owner ) )
    {
		// behavior factor
		if( isdefined( heli.attacker ) && actor.owner == heli.attacker )
			actor.threatlevel += 100;

		if( isdefined( actor.owner.carryObject ) )  //flag carrier
			actor.threatlevel += 200;

		if( isdefined( actor.owner.score ) )
			actor.threatlevel += actor.owner.score * 4;
	
		if( isdefined( actor.owner.antithreat ) )
			actor.threatlevel -= actor.owner.antithreat;
   }
		
	if( actor.threatlevel <= 0 )
		actor.threatlevel = 1;
}

function update_dog_threat( dog )
{
	heli = self;
	dog.threatlevel = 0;
	
	// distance factor
	dist = distance( dog.origin, heli.origin );
	dog.threatlevel += ( ( level.heli_visual_range - dist ) / level.heli_visual_range ) * 100; // inverse distance % with respect to helicopter targeting range
}

// check if missile is in hittable sight zone
function missile_valid_target_check( missiletarget )
{
	heli2target_normal = vectornormalize( missiletarget.origin - self.origin );
	heli2forward = anglestoforward( self.angles );
	heli2forward_normal = vectornormalize( heli2forward );

	heli_dot_target = vectordot( heli2target_normal, heli2forward_normal );
	
	if ( heli_dot_target >= level.heli_valid_target_cone )
	{
		return true;
	}
	return false;
}

function update_missile_player_threat( player )
{
	player.missilethreatlevel = 0;
	
	// distance factor
	dist = distance( player.origin, self.origin );
	player.missilethreatlevel += ( (level.heli_missile_range - dist)/level.heli_missile_range )*100; // inverse distance % with respect to helicopter targeting range
	
	
	if( self missile_valid_target_check( player ) == false )
	{
		player.missilethreatlevel = 1;
		return;
	}
		
	// behavior factor
	if ( isdefined( self.attacker ) && player == self.attacker )
		player.missilethreatlevel += 100;
	
	// player score factor
	player.missilethreatlevel += player.score*4;
		
	if( isdefined( player.antithreat ) )
		player.missilethreatlevel -= player.antithreat;
		
	if( player.missilethreatlevel <= 0 )
		player.missilethreatlevel = 1;
}

// threat missile factors
function update_missile_dog_threat( dog )
{
	dog.missilethreatlevel = 1;
}

function killstreak_assist(victim, assister, killstreak)
{
	victim RecordKillstreakAssist(victim, assister, killstreak);
}

function add_ricochet_protection( killstreak_id, owner, origin, ricochet_distance )
{
	testing = false;
	
/#
	testing = ( GetDvarInt( "scr_ricochet_protection_debug", 0 ) == 2 );
#/

	if ( !level.hardcoreMode && !testing )
		return;
	
	if ( !isdefined( ricochet_distance ) || ricochet_distance == 0 )
		return;

	if(!isdefined(owner.ricochet_protection))owner.ricochet_protection=[];
	
	owner.ricochet_protection[ killstreak_id ] = SpawnStruct();
	owner.ricochet_protection[ killstreak_id ].origin = origin;
	owner.ricochet_protection[ killstreak_id ].distanceSq = ( (ricochet_distance) * (ricochet_distance) );
}

function set_ricochet_protection_endtime( killstreak_id, owner, endTime )
{
	if ( !isdefined( owner ) || !isdefined( owner.ricochet_protection ) || !isdefined( killstreak_id ) )
		return;
	
	if ( !isdefined( owner.ricochet_protection[ killstreak_id ] ) )
	    return;

	owner.ricochet_protection[ killstreak_id ].endTime = endTime;
}

function remove_ricochet_protection( killstreak_id, owner )
{
	if ( !isdefined( owner ) || !isdefined( owner.ricochet_protection ) || !isdefined( killstreak_id ) )
		return;

	owner.ricochet_protection[ killstreak_id ] = undefined;
}

function is_ricochet_protected( player )
{
	if ( !isdefined( player ) || !isdefined( player.ricochet_protection ) )
		return false;
	
	foreach( protection in player.ricochet_protection )
	{
		if ( !isdefined( protection ) )
			continue;
		
		if ( isdefined( protection.endTime ) && protection.endTime < GetTime() )
			continue;

		if ( DistanceSquared( protection.origin, player.origin ) < protection.distanceSq )
			return true;
	}
		
	return false;
}

function is_killstreak_start_blocked()
{
	return ( isdefined( self.dart_thrown_time ) && ( GetTime() - self.dart_thrown_time < 1500 ) );
}

/#
function debug_ricochet_protection()
{
	debug_wait = 0.5;
	debug_frames = int( ( debug_wait / .05 )  ) + 1;
		
	while( 1 )
	{
		if ( GetDvarInt( "scr_ricochet_protection_debug", 0 ) == 0 )
		{
			wait 2.0;
			continue;
		}
		
		wait debug_wait;
		
		foreach( player in level.players )
		{
			if ( !isdefined( player ) )
				continue;
			
			if ( !isdefined( player.ricochet_protection ) )
				continue;
			
			foreach( protection in player.ricochet_protection )
			{
				if ( !isdefined( protection ) )
					continue;

				if ( isdefined( protection.endTime ) && protection.endTime < GetTime() )
					continue;
				
				radius = sqrt( protection.distanceSq );
				sphere( protection.origin, radius, ( 1, 1, 0 ), 0.25, false, 36, debug_frames );
				circle( protection.origin, radius, ( 1, .5, 0 ), false, true, debug_frames );
				circle( protection.origin + ( 0, 0, 2 ), radius, ( 1, .5, 0 ), false, true, debug_frames );
			}
		}
	}
}
#/