#include common_scripts\utility;
#include maps\mp\gametypes\_hud_util;

MAX_VEHICLES = 8;

LIGHTWEIGHT_SCALAR = 1.07;

ATTACHMAP_TABLE					 = "mp/attachmentmap.csv";
ATTACHMAP_COL_CLASS_OR_WEAP_NAME = 0;
ATTACHMAP_ROW_ATTACH_BASE_NAME	 = 0;

ATTACHMENT_TABLE				= "mp/attachmentTable.csv";
ATTACHMENT_COL_INDEX			= 0;
ATTACHMENT_COL_GROUP			= 1;
ATTACHMENT_COL_REF_UNIQUE		= 3;
ATTACHMENT_COL_REF_BASE			= 4;
ATTACHMENT_COL_PERK				= 8;

exploder_sound()
{
	if ( isdefined( self.script_delay ) )
		wait self.script_delay;

	self playSound( level.scr_sound[ self.script_sound ] );
}

_beginLocationSelection ( streakName, selectorType, directionality , size )
{
	self BeginLocationSelection( selectorType, directionality, size );
	self SetClientOmnvar( "ui_map_location_selector", 1 );
	self.selectingLocation = true;
	self setblurforplayer( 10.3, 0.3 );
	
	self thread endSelectionOnAction( "cancel_location" );
	self thread endSelectionOnAction( "death" );
	self thread endSelectionOnAction( "disconnect" );
	self thread endSelectionOnAction( "used" );
	self thread endSelectionOnAction( "weapon_change" );
	
	self endon( "stop_location_selection" );
	self thread endSelectionOnEndGame();
	self thread endSelectionOnEMP();
	self thread endSelectionOHostMigration();
}

stopLocationSelection( disconnected, reason )
{
	if ( !IsDefined( reason ) )
		reason = "generic";
	
	if ( !disconnected )
	{
		self SetClientOmnvar( "ui_map_location_selector", 0 );
		self setblurforplayer( 0, 0.3 );
		self endLocationSelection();
		self.selectingLocation = undefined;
	}
	self notify( "stop_location_selection", reason );
}

endSelectionOnEMP()
{
	self endon( "stop_location_selection" );
	for ( ;; )
	{
		level waittill( "emp_update" );
	
		if ( !self isEMPed() )
			continue;
			
		self thread stopLocationSelection( false, "emp" );
		return;
	}
}

endSelectionOnAction( waitfor, numActions )
{
	self endon( "stop_location_selection" );
	if ( !IsDefined( numActions ) )
		numActions = 1;
	for( ; numActions > 0; numActions-- )
		self waittill( waitfor );
	self thread stopLocationSelection( (waitfor == "disconnect"), waitfor );
}

endSelectionOnEndGame()
{
	self endon( "stop_location_selection" );
	level waittill( "game_ended" );
	self thread stopLocationSelection( false, "end_game" );
}

endSelectionOHostMigration()
{
	self endon( "stop_location_selection" );
	level waittill( "host_migration_begin" );
	self thread stopLocationSelection( false, "hostmigrate" );
}

isAttachment( attachmentName )
{
	attachment = tableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_REF_UNIQUE, attachmentName, ATTACHMENT_COL_INDEX );
	
	if( IsDefined( attachment ) && attachment != "" )
		return true;
	else
		return false;	
}

getAttachmentType( attachmentName )
{
	attachmentType = tableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_REF_UNIQUE, attachmentName, ATTACHMENT_COL_GROUP );

	return attachmentType;		
}

/*
saveModel()
{
	info["model"] = self.model;
	info["viewmodel"] = self getViewModel();
	attachSize = self getAttachSize();
	info["attach"] = [];
	
	assert(info["viewmodel"] != ""); // No viewmodel was associated with the player's model
	
	for(i = 0; i < attachSize; i++)
	{
		info["attach"][i]["model"] = self getAttachModelName(i);
		info["attach"][i]["tag"] = self getAttachTagName(i);
		info["attach"][i]["ignoreCollision"] = self getAttachIgnoreCollision(i);
	}
	
	return info;
}

loadModel(info)
{
	self detachAll();
	self setModel(info["model"]);
	self setViewModel(info["viewmodel"]);

	attachInfo = info["attach"];
	attachSize = attachInfo.size;
    
	for(i = 0; i < attachSize; i++)
		self attach(attachInfo[i]["model"], attachInfo[i]["tag"], attachInfo[i]["ignoreCollision"]);
}
*/

/* 
============= 
///ScriptDocBegin
"Name: delayThread( <delay>, <function>, <arg1>, <arg2>, <arg3>, <arg4> )"
"Summary: Delaythread is cool! It saves you from having to write extra script for once off commands. Note you don’t have to thread it off. Delaythread is that smart!"
"Module: Utility"
"MandatoryArg: <delay> : The delay before the function occurs"
"MandatoryArg: <delay> : The function to run."
"OptionalArg: <arg1> : parameter 1 to pass to the process"
"OptionalArg: <arg2> : parameter 2 to pass to the process"
"OptionalArg: <arg3> : parameter 3 to pass to the process"
"OptionalArg: <arg4> : parameter 4 to pass to the process"
"OptionalArg: <arg5> : parameter 5 to pass to the process"
"Example: delayThread( 3, ::flag_set, "player_can_rappel" );
"SPMP: both"
///ScriptDocEnd
============= 
*/ 
delayThread( timer, func, param1, param2, param3, param4, param5 )
{
	// to thread it off
	thread delayThread_proc( func, timer, param1, param2, param3, param4, param5 );
}


delayThread_proc( func, timer, param1, param2, param3, param4, param5 )
{
	wait( timer );
	if ( !IsDefined( param1 ) )
	{
		assertex( !isdefined( param2 ), "Delaythread does not support vars after undefined." );
		assertex( !isdefined( param3 ), "Delaythread does not support vars after undefined." );
		assertex( !isdefined( param4 ), "Delaythread does not support vars after undefined." );
		assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." );
		thread [[ func ]]();
	}
	else
	if ( !IsDefined( param2 ) )
	{
		assertex( !isdefined( param3 ), "Delaythread does not support vars after undefined." );
		assertex( !isdefined( param4 ), "Delaythread does not support vars after undefined." );
		assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." );
		thread [[ func ]]( param1 );
	}
	else
	if ( !IsDefined( param3 ) )
	{
		assertex( !isdefined( param4 ), "Delaythread does not support vars after undefined." );
		assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." );
		thread [[ func ]]( param1, param2 );
	}
	else
	if ( !IsDefined( param4 ) )	
	{
		assertex( !isdefined( param5 ), "Delaythread does not support vars after undefined." );
		thread [[ func ]]( param1, param2, param3 );
	}
	else
	if ( !IsDefined( param5 ) )	
	{
		thread [[ func ]]( param1, param2, param3, param4 );
	}
	else
	{
		thread [[ func ]]( param1, param2, param3, param4, param5 );
	}
}

getPlant()
{
	start = self.origin + ( 0, 0, 10 );

	range = 11;
	forward = anglesToForward( self.angles );
	forward = ( forward * range );

	traceorigins[ 0 ] = start + forward;
	traceorigins[ 1 ] = start;

	trace = bulletTrace( traceorigins[ 0 ], ( traceorigins[ 0 ] + ( 0, 0, -18 ) ), false, undefined );
	if ( trace[ "fraction" ] < 1 )
	{
		//println("^6Using traceorigins[0], tracefraction is", trace["fraction"]);

		temp = spawnstruct();
		temp.origin = trace[ "position" ];
		temp.angles = orientToNormal( trace[ "normal" ] );
		return temp;
	}

	trace = bulletTrace( traceorigins[ 1 ], ( traceorigins[ 1 ] + ( 0, 0, -18 ) ), false, undefined );
	if ( trace[ "fraction" ] < 1 )
	{
		//println("^6Using traceorigins[1], tracefraction is", trace["fraction"]);

		temp = spawnstruct();
		temp.origin = trace[ "position" ];
		temp.angles = orientToNormal( trace[ "normal" ] );
		return temp;
	}

	traceorigins[ 2 ] = start + ( 16, 16, 0 );
	traceorigins[ 3 ] = start + ( 16, -16, 0 );
	traceorigins[ 4 ] = start + ( -16, -16, 0 );
	traceorigins[ 5 ] = start + ( -16, 16, 0 );

	besttracefraction = undefined;
	besttraceposition = undefined;
	for ( i = 0; i < traceorigins.size; i++ )
	{
		trace = bulletTrace( traceorigins[ i ], ( traceorigins[ i ] + ( 0, 0, -1000 ) ), false, undefined );

		//ent[i] = spawn("script_model",(traceorigins[i]+(0, 0, -2)));
		//ent[i].angles = (0, 180, 180);
		//ent[i] setmodel("105");

		//println("^6trace ", i ," fraction is ", trace["fraction"]);

		if ( !isdefined( besttracefraction ) || ( trace[ "fraction" ] < besttracefraction ) )
		{
			besttracefraction = trace[ "fraction" ];
			besttraceposition = trace[ "position" ];

			//println("^6besttracefraction set to ", besttracefraction, " which is traceorigin[", i, "]");
		}
	}

	if ( besttracefraction == 1 )
		besttraceposition = self.origin;

	temp = spawnstruct();
	temp.origin = besttraceposition;
	temp.angles = orientToNormal( trace[ "normal" ] );
	return temp;
}

orientToNormal( normal )
{
	hor_normal = ( normal[ 0 ], normal[ 1 ], 0 );
	hor_length = length( hor_normal );

	if ( !hor_length )
		return( 0, 0, 0 );

	hor_dir = vectornormalize( hor_normal );
	neg_height = normal[ 2 ] * - 1;
	tangent = ( hor_dir[ 0 ] * neg_height, hor_dir[ 1 ] * neg_height, hor_length );
	plant_angle = vectortoangles( tangent );

	//println("^6hor_normal is ", hor_normal);
	//println("^6hor_length is ", hor_length);
	//println("^6hor_dir is ", hor_dir);
	//println("^6neg_height is ", neg_height);
	//println("^6tangent is ", tangent);
	//println("^6plant_angle is ", plant_angle);

	return plant_angle;
}

deletePlacedEntity( entity )
{
	entities = getentarray( entity, "classname" );
	for ( i = 0; i < entities.size; i++ )
	{
		//println("DELETED: ", entities[i].classname);
		entities[ i ] delete();
	}
}

playSoundOnPlayers( sound, team, excludeList )
{
	assert( isdefined( level.players ) );

	if ( level.splitscreen )
	{
		if ( isdefined( level.players[ 0 ] ) )
			level.players[ 0 ] playLocalSound( sound );
	}
	else
	{
		if ( IsDefined( team ) )
		{
			if ( isdefined( excludeList ) )
			{
				for ( i = 0; i < level.players.size; i++ )
				{
					player = level.players[ i ];

					if( player isSplitscreenPlayer() && !player isSplitscreenPlayerPrimary() )
						continue;

					if ( isdefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) && !isExcluded( player, excludeList ) )
						player playLocalSound( sound );
				}
			}
			else
			{
				for ( i = 0; i < level.players.size; i++ )
				{
					player = level.players[ i ];

					if( player isSplitscreenPlayer() && !player isSplitscreenPlayerPrimary() )
						continue;

					if ( isdefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) )
						player playLocalSound( sound );
				}
			}
		}
		else
		{
			if ( isdefined( excludeList ) )
			{
				for ( i = 0; i < level.players.size; i++ )
				{
					player = level.players[ i ];
					
					if( player isSplitscreenPlayer() && !player isSplitscreenPlayerPrimary() )
						continue;

					if ( !isExcluded( player, excludeList ) )
						player playLocalSound( sound );
				}
			}
			else
			{
				for ( i = 0; i < level.players.size; i++ )
				{
					player = level.players[ i ];
					
					if( player isSplitscreenPlayer() && !player isSplitscreenPlayerPrimary() )
						continue;

					player playLocalSound( sound );
				}
			}
		}
	}
}


/* 
============= 
///ScriptDocBegin
"Name: playLoopSoundToPlayers( <alias> , <offset> )"
"Summary: Play loop sound alias on an entity to an optional list of players."
"Module: Sound"
"CallOn: An entity"
"MandatoryArg: <alias> : Sound alias to loop"
"OptionalArg: <offset> : Offset for sound origin relative to the world from the models origin."
"OptionalArg: <players> : List of players that should hear the sound."
"Example: vehicle thread playLoopSoundToPlayers( "engine_belt_run" );"
"SPMP: both"
///ScriptDocEnd
============= 
*/ 
playLoopSoundToPlayers( alias, offset, players )
{
	if ( !SoundExists( alias ) )
	{
		println( "Warning: playLoopSoundToPlayers() alias doesn't exist: " + alias );
		return;
	}
	
	org = Spawn( "script_origin", ( 0, 0, 0 ) );
	org endon( "death" );
	thread delete_on_death( org );
		
	if ( IsDefined( players ) )
	{
		org Hide();
		foreach( player in players )
			org ShowToPlayer( player );
	}
	if ( IsDefined( offset ) )
	{
		org.origin = self.origin + offset;
		org.angles = self.angles;
		org linktosynchronizedparent( self );
	}
	else
	{
		org.origin = self.origin;
		org.angles = self.angles;
		org linktosynchronizedparent( self );
	}
	
	org PlayLoopSound( alias );
// 	PrintLn( "playing loop sound ", alias, " on entity at origin ", self.origin, " at ORIGIN ", org.origin );

	self waittill( "stop sound" + alias );
	org StopLoopSound( alias );
	org Delete();
}


sortLowerMessages()
{
	for ( i = 1; i < self.lowerMessages.size; i++ )
	{
		message = self.lowerMessages[ i ];
		priority = message.priority;
		for ( j = i - 1; j >= 0 && priority > self.lowerMessages[ j ].priority; j -- )
			self.lowerMessages[ j + 1 ] = self.lowerMessages[ j ];
		self.lowerMessages[ j + 1 ] = message;
	}
}


addLowerMessage( name, text, time, priority, showTimer, shouldFade, fadeToAlpha, fadeToAlphaTime, hideWhenInDemo )
{
	newMessage = undefined;
	foreach ( message in self.lowerMessages )
	{
		if ( message.name == name )
		{
			if ( message.text == text && message.priority == priority )
				return;

			newMessage = message;
			break;
		}
	}

	if ( !IsDefined( newMessage ) )
	{
		newMessage = spawnStruct();
		self.lowerMessages[ self.lowerMessages.size ] = newMessage;
	}

	newMessage.name = name;
	newMessage.text = text;
	newMessage.time = time;
	newMessage.addTime = getTime();
	newMessage.priority = priority;
	newMessage.showTimer = showTimer;
	newMessage.shouldFade = shouldFade;
	newMessage.fadeToAlpha = fadeToAlpha;
	newMessage.fadeToAlphaTime = fadeToAlphaTime;
	newMessage.hideWhenInDemo = hideWhenInDemo;

	sortLowerMessages();
}


removeLowerMessage( name )
{
	if( IsDefined( self.lowerMessages ) )
	{
		// since we're changing the array in the loop, we should iterate backwards
		for ( i = self.lowerMessages.size; i > 0; i-- )
		{
			if ( self.lowerMessages[ i - 1 ].name != name )
				continue;

			message = self.lowerMessages[ i - 1 ];
			
			// now move every message down one to fill the empty space
			for( j = i; j < self.lowerMessages.size; j++ )
			{
				if ( IsDefined( self.lowerMessages[ j ] ) )
					self.lowerMessages[ j - 1 ] = self.lowerMessages[ j ];
			}

			// make the last one undefined because we filled the space above
			self.lowerMessages[ self.lowerMessages.size - 1 ] = undefined;
		}

		sortLowerMessages();
	}
}


getLowerMessage()
{
	return self.lowerMessages[ 0 ];
}


/*
============= 
///ScriptDocBegin
"Name: setLowerMessage( <name>, <text>, <time>, <priority>, <showTimer>, <shouldFade>, <fadeToAlpha>, <fadeToAlphaTime> )"
"Summary: Creates a message to show on the lower half of the screen."
"Module: HUD"
"CallOn: A player"
"MandatoryArg: <name> The name of the message."
"MandatoryArg: <text> The text of the message to display."
"OptionalArg: <time> How long the message will display (default 0 - infinite)."
"OptionalArg: <priority> The priority of the message to display (default 1)."
"OptionalArg: <showTimer> If you pass in time do you want the timer text to show, if not then lower message will fade over time (default false)."
"OptionalArg: <shouldFade> If you want the message to fade to an alpha after coming up the first time (default false)."
"OptionalArg: <fadeToAlpha> If you want the message to fade to an alpha after coming up the first time, pass in the value (default 0.85)."
"OptionalArg: <fadeToAlphaTime> The time that this message will fade to the alpha value passed in before this (default 3.0)."
"OptionalArg: <hideWhenInDemo> Show hud when in demo playback (default false)."
"Example: self setLowerMessage( "last_stand", &"PLATFORM_COWARDS_WAY_OUT", undefined, 50 );
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
setLowerMessage( name, text, time, priority, showTimer, shouldFade, fadeToAlpha, fadeToAlphaTime, hideWhenInDemo )
{
	if ( !IsDefined( priority ) )
		priority = 1;

	if ( !IsDefined( time ) )
		time = 0;

	if ( !IsDefined( showTimer ) )
		showTimer = false;

	if ( !IsDefined( shouldFade ) )
		shouldFade = false;

	if ( !IsDefined( fadeToAlpha ) )
		fadeToAlpha = 0.85;

	if ( !IsDefined( fadeToAlphaTime ) )
		fadeToAlphaTime = 3.0;
		
	if ( !IsDefined( hideWhenInDemo ) )
		hideWhenInDemo = false;

	self addLowerMessage( name, text, time, priority, showTimer, shouldFade, fadeToAlpha, fadeToAlphaTime, hideWhenInDemo );
	self updateLowerMessage();
	//self notify( "lower_message_set" );
}


updateLowerMessage()
{
	if ( !isdefined( self.lowerMessage ) )
		return;		// This can happen at the end of round in a tight timing case
	
	message = self getLowerMessage();

	if ( !IsDefined( message ) )
	{
		if ( isDefined ( self.lowerMessage ) && isDefined ( self.lowerTimer ) )
		{
			self.lowerMessage.alpha = 0;
			self.lowerTimer.alpha = 0;
		}
		return;
	}

	self.lowerMessage setText( message.text );
	self.lowerMessage.alpha = 0.85;
	self.lowerTimer.alpha = 1;
	
	self.lowerMessage.hideWhenInDemo = message.hideWhenInDemo;

	if( message.shouldFade )
	{
		self.lowerMessage FadeOverTime( min( message.fadeToAlphaTime, 60 ) );
		self.lowerMessage.alpha = message.fadeToAlpha;
	}

	if ( message.time > 0 && message.showTimer )
	{
		self.lowerTimer setTimer( max( message.time - ( ( getTime() - message.addTime ) / 1000 ), 0.1 ) );
	}
	else if( message.time > 0 && !message.showTimer )
	{
		self.lowerTimer setText( "" );
		self.lowerMessage FadeOverTime( min( message.time, 60 ) );
		self.lowerMessage.alpha = 0;
		self thread clearOnDeath( message );
		self thread clearAfterFade( message );
	}
	else
	{
		self.lowerTimer setText( "" );
	}
}

clearOnDeath( message )
{
	self notify( "message_cleared" );
	self endon( "message_cleared" );
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	self waittill( "death" );
	self clearLowerMessage( message.name );
}

clearAfterFade( message )
{
	wait( message.time );
	self clearLowerMessage( message.name );
	self notify( "message_cleared" );
}

/*
============= 
///ScriptDocBegin
"Name: clearLowerMessage( <name> )"
"Summary: Clears the message on the lower portion of the screen."
"Module: HUD"
"CallOn: A player"
"MandatoryArg: <name> The name of the message."
"Example: self clearLowerMessage( "last_stand" );
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
clearLowerMessage( name )
{
	self removeLowerMessage( name );
	self updateLowerMessage();
}

clearLowerMessages()
{
	for ( i = 0; i < self.lowerMessages.size; i++ )
		self.lowerMessages[ i ] = undefined;

	if ( !IsDefined( self.lowerMessage ) )
		return;

	self updateLowerMessage();
}

printOnTeam( printString, team )
{
	foreach ( player in level.players )
	{
		if ( player.team != team )
			continue;

		player iPrintLn( printString );
	}
}

printBoldOnTeam( text, team )
{
	assert( isdefined( level.players ) );
	for ( i = 0; i < level.players.size; i++ )
	{
		player = level.players[ i ];
		if ( ( isdefined( player.pers[ "team" ] ) ) && ( player.pers[ "team" ] == team ) )
			player iprintlnbold( text );
	}
}

printBoldOnTeamArg( text, team, arg )
{
	assert( isdefined( level.players ) );
	for ( i = 0; i < level.players.size; i++ )
	{
		player = level.players[ i ];
		if ( ( isdefined( player.pers[ "team" ] ) ) && ( player.pers[ "team" ] == team ) )
			player iprintlnbold( text, arg );
	}
}

printOnTeamArg( text, team, arg )
{
	assert( isdefined( level.players ) );
	for ( i = 0; i < level.players.size; i++ )
	{
		player = level.players[ i ];
		if ( ( isdefined( player.pers[ "team" ] ) ) && ( player.pers[ "team" ] == team ) )
			player iprintln( text, arg );
	}
}

printOnPlayers( text, team )
{
	players = level.players;
	for ( i = 0; i < players.size; i++ )
	{
		if ( IsDefined( team ) )
		{
			if ( ( isdefined( players[ i ].pers[ "team" ] ) ) && ( players[ i ].pers[ "team" ] == team ) )
				players[ i ] iprintln( text );
		}
		else
		{
			players[ i ] iprintln( text );
		}
	}
}

printAndSoundOnEveryone( team, otherteam, printFriendly, printEnemy, soundFriendly, soundEnemy, printarg )
{
	shouldDoSounds = IsDefined( soundFriendly );

	shouldDoEnemySounds = false;
	if ( IsDefined( soundEnemy ) )
	{
		assert( shouldDoSounds );// can't have an enemy sound without a friendly sound
		shouldDoEnemySounds = true;
	}

	if ( level.splitscreen || !shouldDoSounds )
	{
		for ( i = 0; i < level.players.size; i++ )
		{
			player = level.players[ i ];
			playerteam = player.team;
			if ( isdefined( playerteam ) )
			{
				if ( playerteam == team && isdefined( printFriendly ) )
					player iprintln( printFriendly, printarg );
				else if ( playerteam == otherteam && isdefined( printEnemy )  )
					player iprintln( printEnemy, printarg );
			}
		}
		if ( shouldDoSounds )
		{
			assert( level.splitscreen );
			level.players[ 0 ] playLocalSound( soundFriendly );
		}
	}
	else
	{
		assert( shouldDoSounds );
		if ( shouldDoEnemySounds )
		{
			for ( i = 0; i < level.players.size; i++ )
			{
				player = level.players[ i ];
				playerteam = player.team;
				if ( isdefined( playerteam ) )
				{
					if ( playerteam == team )
					{
						if ( isdefined( printFriendly ) )
							player iprintln( printFriendly, printarg );
						player playLocalSound( soundFriendly );
					}
					else if ( playerteam == otherteam )
					{
						if ( isdefined( printEnemy ) )
							player iprintln( printEnemy, printarg );
						player playLocalSound( soundEnemy );
					}
				}
			}
		}
		else
		{
			for ( i = 0; i < level.players.size; i++ )
			{
				player = level.players[ i ];
				playerteam = player.team;
				if ( isdefined( playerteam ) )
				{
					if ( playerteam == team )
					{
						if ( isdefined( printFriendly ) )
							player iprintln( printFriendly, printarg );
						player playLocalSound( soundFriendly );
					}
					else if ( playerteam == otherteam )
					{
						if ( isdefined( printEnemy ) )
							player iprintln( printEnemy, printarg );
					}
				}
			}
		}
	}
}

printAndSoundOnTeam( team, printString, soundAlias )
{
	foreach ( player in level.players )
	{
		if ( player.team != team )
			continue;

		player printAndSoundOnPlayer( printString, soundAlias );
	}
}

printAndSoundOnPlayer( printString, soundAlias )
{
	self iPrintLn( printString );
	self playLocalSound( soundAlias );
}

_playLocalSound( soundAlias )
{
	if ( level.splitscreen && self getEntityNumber() != 0 )
		return;

	self playLocalSound( soundAlias );
}

dvarIntValue( dVar, defVal, minVal, maxVal )
{
	dVar = "scr_" + level.gameType + "_" + dVar;
	if ( getDvar( dVar ) == "" )
	{
		setDvar( dVar, defVal );
		return defVal;
	}

	value = getDvarInt( dVar );

	if ( value > maxVal )
		value = maxVal;
	else if ( value < minVal )
		value = minVal;
	else
		return value;

	setDvar( dVar, value );
	return value;
}

dvarFloatValue( dVar, defVal, minVal, maxVal )
{
	dVar = "scr_" + level.gameType + "_" + dVar;
	if ( getDvar( dVar ) == "" )
	{
		setDvar( dVar, defVal );
		return defVal;
	}

	value = getDvarFloat( dVar );

	if ( value > maxVal )
		value = maxVal;
	else if ( value < minVal )
		value = minVal;
	else
		return value;

	setDvar( dVar, value );
	return value;
}

play_sound_on_tag( alias, tag )
{
	if ( isdefined( tag ) )
	{
		playsoundatpos( self getTagOrigin( tag ), alias );
	}
	else
	{
		playsoundatpos( self.origin, alias );
	}
}

getOtherTeam( team )
{
	if( level.multiTeamBased )
	{
		assertMsg( "getOtherTeam() should not be called in Multi Team Based gametypes" );
	}
	
	if ( team == "allies" )
		return "axis";
	else if ( team == "axis" )
		return "allies";
	else
		return "none";

	assertMsg( "getOtherTeam: invalid team " + team );
}

wait_endon( waitTime, endOnString, endonString2, endonString3 )
{
	self endon( endOnString );
	if ( IsDefined( endonString2 ) )
		self endon( endonString2 );
	if ( IsDefined( endonString3 ) )
		self endon( endonString3 );

	wait( waitTime );
}

initPersStat( dataName )
{
	if ( !IsDefined( self.pers[ dataName ] ) )
		self.pers[ dataName ] = 0;
}

getPersStat( dataName )
{
	return self.pers[ dataName ];
}

incPersStat( dataName, increment )
{
	if ( IsDefined( self ) && IsDefined( self.pers ) && IsDefined( self.pers[ dataName ] ) )
	{	
		self.pers[ dataName ] += increment;
		self maps\mp\gametypes\_persistence::statAdd( dataName, increment );
	}
}

setPersStat( dataName, value )
{
	assertEx( IsDefined( dataName ), "Called setPersStat with no dataName defined." );
	assertEx( IsDefined( value ), "Called setPersStat for " + dataName + " with no value defined." );
	
	self.pers[ dataName ] = value;
}

initPlayerStat( ref, defaultvalue )
{
	if ( !IsDefined( self.stats["stats_" + ref ] ) )
	{
		if ( !IsDefined( defaultvalue ) )
			defaultvalue = 0;
		
		self.stats["stats_" + ref ] = spawnstruct();
		self.stats["stats_" + ref ].value = defaultvalue;
		
		// clear the round stats
		if( self rankingEnabled() )
			self setCommonPlayerData( "round", "awards", ref, 0 );
	}
}

incPlayerStat( ref, increment )
{
	if ( IsAgent(self) )
		return;
	
	stat = self.stats["stats_" + ref ];
	stat.value += increment;
}

setPlayerStat( ref, value )
{
	stat = self.stats["stats_" + ref ];
	stat.value = value;
	stat.time = getTime();
}

getPlayerStat( ref )
{
	return self.stats["stats_" + ref ].value;
}

getPlayerStatTime( ref )
{
	return self.stats["stats_" + ref ].time;
}

setPlayerStatIfGreater( ref, newvalue )
{
	currentvalue = self getPlayerStat( ref );

	if ( newvalue > currentvalue )
		self setPlayerStat( ref, newvalue );
}

setPlayerStatIfLower( ref, newvalue )
{
	currentvalue = self getPlayerStat( ref );

	if ( newvalue < currentvalue )
		self setPlayerStat( ref, newvalue );
}

updatePersRatio( ratio, num, denom )
{
	if ( !self rankingEnabled() )
		return;

	numValue = self maps\mp\gametypes\_persistence::statGet( num );
	denomValue = self maps\mp\gametypes\_persistence::statGet( denom );
	if ( denomValue == 0 )
		denomValue = 1;

	self maps\mp\gametypes\_persistence::statSet( ratio, int( ( numValue * 1000 ) / denomValue ) );
}

updatePersRatioBuffered( ratio, num, denom )
{
	if ( !self rankingEnabled() )
		return;

	numValue = self maps\mp\gametypes\_persistence::statGetBuffered( num );
	denomValue = self maps\mp\gametypes\_persistence::statGetBuffered( denom );
	if ( denomValue == 0 )
		denomValue = 1;

	self maps\mp\gametypes\_persistence::statSetBuffered( ratio, int( ( numValue * 1000 ) / denomValue ) );
}


// to be used with things that are slow.
// unfortunately, it can only be used with things that aren't time critical.
WaitTillSlowProcessAllowed( allowLoop )
{
	// wait only a few frames if necessary
	// if we wait too long, we might get too many threads at once and run out of variables
	// i'm trying to avoid using a loop because i don't want any extra variables
	if ( level.lastSlowProcessFrame == gettime() )
	{
		if ( IsDefined( allowLoop ) && allowLoop )
		{
			while ( level.lastSlowProcessFrame == getTime() )
				wait( 0.05 );
		}
		else
		{
			wait .05;
			if ( level.lastSlowProcessFrame == gettime() )
			{
				wait .05;
				if ( level.lastSlowProcessFrame == gettime() )
				{
					wait .05;
					if ( level.lastSlowProcessFrame == gettime() )
					{
						wait .05;
					}
				}
			}
		}
	}

	level.lastSlowProcessFrame = getTime();
}


waitForTimeOrNotify( time, notifyname )
{
	self endon( notifyname );
	wait time;
}


waitForTimeOrNotifies( time, notifies )
{
	if ( IsDefined( notifies ) )
	{
		foreach ( notify_name in notifies )
		{
			self endon( notify_name );
		}
	}
	
	if ( IsDefined( time ) && time > 0 )
	{
		wait time;
	}
}


isExcluded( entity, entityList )
{
	for ( index = 0; index < entityList.size; index++ )
	{
		if ( entity == entityList[ index ] )
			return true;
	}
	return false;
}


leaderDialog( dialog, team, group, excludeList, location )
{
	if ( IsDefined( level.isZombieGame ) && level.isZombieGame )
		return;
	
	assert( isdefined( level.players ) );

	if ( dialog == "null" )
		return;

	if ( !IsDefined( team ) )
	{
		leaderDialogBothTeams( dialog, "allies", dialog, "axis", group, excludeList, location );
		return;
	}

	if ( IsDefined( excludeList ) )
	{
		for ( i = 0; i < level.players.size; i++ )
		{
			player = level.players[ i ];
			if ( ( IsDefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) ) && !isExcluded( player, excludeList ) )
			{
				if( player isSplitscreenPlayer() && ( !player isSplitscreenPlayerPrimary() || player IsSplitscreenOtherPlayerEnemy() ) )
					continue;

				player leaderDialogOnPlayer( dialog, group, undefined, location );
			}
		}
	}
	else
	{
		for ( i = 0; i < level.players.size; i++ )
		{
			player = level.players[ i ];
			if ( IsDefined( player.pers[ "team" ] ) && ( player.pers[ "team" ] == team ) )
			{
				if( player isSplitscreenPlayer() && ( !player isSplitscreenPlayerPrimary() || player IsSplitscreenOtherPlayerEnemy() ) )
					continue;

				player leaderDialogOnPlayer( dialog, group, undefined, location );
			}
		}
	}
}


leaderDialogBothTeams( dialog1, team1, dialog2, team2, group, excludeList, location )
{
	if ( IsDefined( level.isZombieGame ) && level.isZombieGame )
		return;

	assert( isdefined( level.players ) );

	if ( level.splitscreen )
		return;

	if ( level.splitscreen )
	{
		if ( level.players.size )
			level.players[ 0 ] leaderDialogOnPlayer( dialog1, group, undefined, location );
		return;
	}

	if ( IsDefined( excludeList ) )
	{
		for ( i = 0; i < level.players.size; i++ )
		{
			player = level.players[ i ];
			team = player.pers[ "team" ];

			if ( !IsDefined( team ) )
				continue;

			if ( isExcluded( player, excludeList ) )
				continue;

			if( player isSplitscreenPlayer() && ( !player isSplitscreenPlayerPrimary() || player IsSplitscreenOtherPlayerEnemy() ) )
					continue;

			if ( team == team1 )
				player leaderDialogOnPlayer( dialog1, group, undefined, location );
			else if ( team == team2 )
				player leaderDialogOnPlayer( dialog2, group, undefined, location );
		}
	}
	else
	{
		for ( i = 0; i < level.players.size; i++ )
		{
			player = level.players[ i ];
			team = player.pers[ "team" ];

			if ( !IsDefined( team ) )
				continue;

			if( player isSplitscreenPlayer() && ( !player isSplitscreenPlayerPrimary() || player IsSplitscreenOtherPlayerEnemy() ) )
					continue;

			if ( team == team1 )
				player leaderDialogOnPlayer( dialog1, group, undefined, location );
			else if ( team == team2 )
				player leaderDialogOnPlayer( dialog2, group, undefined, location );
		}
	}
}


leaderDialogOnPlayers( dialog, players, group, location )
{
	if ( IsDefined( level.isZombieGame ) && level.isZombieGame )
		return;

	foreach ( player in players )
		player leaderDialogOnPlayer( dialog, group, undefined, location );
}


leaderDialogOnPlayer( dialog, group, groupOverride, location )
{
	if ( IsDefined( level.isZombieGame ) && level.isZombieGame )
		return;

	if ( !IsDefined( groupOverride ) )
		groupOverride = false;
	
	team = self.pers[ "team" ];
	
	// in horde mode, only play VO on primary player to avoid stomping / overlap
	if ( isdefined ( level.isHorde ) && !self IsSplitscreenPlayerPrimary() )
		return;

	if ( IsDefined( level.announcerDisabled ) && level.announcerDisabled )
		return;

	if ( !IsDefined( team ) )
		return;

	if ( team != "allies" && team != "axis" )
		return;
	
	if ( self isSplitscreenPlayer() && self IsSplitscreenOtherPlayerEnemy() )
		return;
	
	if ( !IsDefined( location ) )
		location = (0, 0, 0);

	if ( IsDefined( group ) )
	{
		// ignore the message if one from the same group is already playing
		if ( self.leaderDialogGroup == group )
		{
			if ( groupOverride )
			{
				if ( self.leaderDialogActive != "" )
					self StopLocalSound( self.leaderDialogActive );
				self thread leaderDialogOnPlayer_internal( dialog, team, location );
			}
			
			return;
		}

		hadGroupDialog = IsDefined( self.leaderDialogGroups[ group ] );

		self.leaderDialogGroups[ group ] = dialog;
		dialog = group;

		// exit because the "group" dialog call is already in the queue
		if ( hadGroupDialog )
			return;
	}

	if ( self.leaderDialogActive == "" )
	{
		self thread leaderDialogOnPlayer_internal( dialog, team, location );
	}
	else
	{
		self.leaderDialogQueue[ self.leaderDialogQueue.size ] = dialog;
		self.leaderDialogLocQueue[ self.leaderDialogLocQueue.size ] = location;
	}
}

leaderDialog_TryLockout( dialog, player )
{
	lockoutTime = 2; // DEFAULT LOCK OUT
	if( IsDefined( game["dialog"]["lockouts"][dialog] ) )
	{
		lockoutTime = game["dialog"]["lockouts"][dialog];
		if( lockoutTime == 0 )
			return;
	}
	if( !IsDefined( player.active_vo_lockouts ) )
	{
		player.active_vo_lockouts = [];
	}
	player.active_vo_lockouts[dialog] = true;
	thread leaderDialog_LockoutClearDelayed( dialog, player, lockoutTime );
}

leaderDialog_LockoutClearDelayed( dialog, player, waittime )
{
	player endon( "disconnect" );
	wait( waittime );
	player.active_vo_lockouts[dialog] = undefined;
}

leaderDialog_IsLockedout( dialog, player )
{
	if( IsDefined( player.active_vo_lockouts ) )
	{
		if( IsDefined( player.active_vo_lockouts[dialog] ) )
		{
			if( IsDefined( player.active_vo_lockouts[dialog] == true ) )
			{
				return true;
			}
		}
	}
	return false;
}


leaderDialogOnPlayer_internal( dialog, team, location )
{

	self endon( "disconnect" );

	self notify ( "playLeaderDialogOnPlayer" );
	self endon ( "playLeaderDialogOnPlayer" );

	if ( IsDefined( self.leaderDialogGroups[ dialog ] ) )
	{
		group = dialog;
		dialog = self.leaderDialogGroups[ group ];
		self.leaderDialogGroups[ group ] = undefined;
		self.leaderDialogGroup = group;
	}
	
	// 2013-03-07 wallace. disable this assert until we get audio for ammo/grenade pickups?
	// assertEx( IsDefined( game["dialog"][ dialog ] ), "Dialog " + dialog + " was not defined in game[dialog] array." );
	if ( !IsDefined( game["dialog"][ dialog ] ) )
	{
		PrintLn( "Dialog " + dialog + " was not defined in game[dialog] array." );
		return;
	}

	if ( IsAI( self ) && IsDefined( level.bot_funcs ) && IsDefined( level.bot_funcs["leader_dialog"] ) )
		self [[ level.bot_funcs["leader_dialog"] ]]( dialog, location );

	if ( isSubStr( game["dialog"][ dialog ], "null" ) )
		return;

	//Force the alias for horde mode since we don't have "voices"
	if ( isdefined ( level.isHorde ) && level.isHorde )
	{
		if ( issubstr ( dialog, "coop_gdn" ) )
			alias = dialog; //skip creating the dialog string and just force the alias
		else
			alias = "AT_anr0_" + game[ "dialog" ][ dialog ]; //force Gideon voice for existing MP lines
	}
	else
		alias = game[ "voice" ][ team ] + game[ "dialog" ][ dialog ];
	
	if( SoundExists( alias ) )
	{
		if(leaderDialog_IsLockedout( game[ "dialog" ][ dialog ], self ))
		{
			// Line is locked out
			return;
		}
		self.leaderDialogActive = alias;
		self playLocalAnnouncerSound( alias );
		leaderDialog_TryLockout( game[ "dialog" ][ dialog ], self );
	}
	else
	{
		PrintLn( "Announcer Line Missing: " + alias );
	}
	
	wait( 2.0 );
	self.leaderDialogLocalSound = "";
	
	self.leaderDialogActive = "";
	self.leaderDialogGroup = "";

	if ( self.leaderDialogQueue.size > 0 )
	{
		assert( self.leaderDialogQueue.size == self.leaderDialogLocQueue.size );
		
		//if ( level.gameType == "dom" )
		//	self orderOnQueuedDialog();
		
		//nextDialog = getNextRelevantDialog();
		
		nextDialog = self.leaderDialogQueue[ 0 ];
		nextLoc = self.leaderDialogLocQueue[ 0 ];

		for ( i = 1; i < self.leaderDialogQueue.size; i++ )
			self.leaderDialogQueue[ i - 1 ] = self.leaderDialogQueue[ i ];
		for ( i = 1; i < self.leaderDialogLocQueue.size; i++ )
			self.leaderDialogLocQueue[ i - 1 ] = self.leaderDialogLocQueue[ i ];
		self.leaderDialogQueue[ i - 1 ] = undefined;
		self.leaderDialogLocQueue[ i - 1 ] = undefined;

		self thread leaderDialogOnPlayer_internal( nextDialog, team, nextLoc );
	}
}

//this removes irrelevent dom dialog.  Not sure if this is needed with proper queuing. We will wait and see
getNextRelevantDialog()
{
	for( i = 0; i < self.leaderDialogQueue.size; i++ )
	{
		if ( IsSubStr( self.leaderDialogQueue[i], "losing" ) )
		{
			if ( self.team == "allies" )
			{
				if ( isSubStr( level.axisCapturing, self.leaderDialogQueue[i] ) )
					return self.leaderDialogQueue[i];
				else
					array_remove( self.leaderDialogQueue, self.leaderDialogQueue[i] );
			}
			else
			{
				if ( isSubStr( level.alliesCapturing, self.leaderDialogQueue[i] ) )
				    return self.leaderDialogQueue[i];
				else
					array_remove( self.leaderDialogQueue, self.leaderDialogQueue[i] );				
			}
		}
		else
		{
			return level.alliesCapturing[self.leaderDialogQueue];
		}
			
	}
}

OrderOnQueuedDialog()
{
	self endon( "disconnect" );
	
	tempArray = [];
	tempArray = self.leaderDialogQueue;
	
	//This re-orders arrays to move "losing" dialog higher in priority
	for( i = 0; i < self.leaderDialogQueue.size; i++ )
	{
		if ( isSubStr( self.leaderDialogQueue[i], "losing") )
		{
			for( c = i; c >= 0; c-- )
			{
				if ( !IsSubStr( self.leaderDialogQueue[ c ], "losing" ) && c != 0 )
					continue;
				
				if ( c != i )
				{
					arrayInsertion( tempArray, self.leaderDialogQueue[i], c );
					array_remove( tempArray, self.leaderDialogQueue[i] );
					break;
				}
			}
		}
	}
	
	self.leaderDialogQueue = tempArray;
}


flushDialogOnPlayer()
{
	self.leaderDialogGroups = [];
	self.leaderDialogQueue = [];
	self.leaderDialogActive = "";
	self.currentLeaderDialogGroup = "";
	self notify( "flush_dialog" );
}


flushGroupDialog( group )
{
	foreach( player in level.players )
	{
		 player flushGroupDialogOnPlayer( group );
	}
}


ArrayRemoveValue( array, value )
{
	sourceIndex = 0;
	targetIndex = 0;
	
	while ( sourceIndex < array.size )
	{
		if ( array[sourceIndex] == value )
		{
			sourceIndex++;
		}
		else
		{
			if ( sourceIndex != targetIndex )
			{
				array[targetIndex] = array[sourceIndex];
			}
			
			sourceIndex++;
			targetIndex++;
		}
	}
	
	while ( targetIndex < array.size )
	{
		array[targetIndex] = undefined;
		targetIndex++;
	}
}


flushGroupDialogOnPlayer( group )
{
	self.leaderDialogGroups[group] = undefined;
	ArrayRemoveValue( self.leaderDialogQueue, group );

	if ( self.leaderDialogQueue.size == 0 )
	{
		self flushDialogOnPlayer();
	}
}


updateMainMenu()
{
	if (self.pers[ "team" ] == "spectator" )
	{
		self setClientDvar("g_scriptMainMenu", game["menu_team"]);
	}
	else
	{
		self setClientDvar( "g_scriptMainMenu", game[ "menu_class_" + self.pers["team"] ] );
	}
}


updateObjectiveText()
{
	if ( self.pers[ "team" ] == "spectator" )
	{
		self setClientDvar( "cg_objectiveText", "" );
		return;
	}

	if ( getWatchedDvar( "scorelimit" ) > 0 && !isObjectiveBased() )
	{
		if ( level.splitScreen )
			self setclientdvar( "cg_objectiveText", getObjectiveScoreText( self.pers[ "team" ] ) );
		else
			self setclientdvar( "cg_objectiveText", getObjectiveScoreText( self.pers[ "team" ] ), getWatchedDvar( "scorelimit" ) );
	}
	else
	{
		self setclientdvar( "cg_objectiveText", getObjectiveText( self.pers[ "team" ] ) );
	}
}


setObjectiveText( team, text )
{
	game[ "strings" ][ "objective_" + team ] = text;
}

setObjectiveScoreText( team, text )
{
	game[ "strings" ][ "objective_score_" + team ] = text;
}

setObjectiveHintText( team, text )
{
	game[ "strings" ][ "objective_hint_" + team ] = text;
}

getObjectiveText( team )
{
	return game[ "strings" ][ "objective_" + team ];
}

getObjectiveScoreText( team )
{
	return game[ "strings" ][ "objective_score_" + team ];
}

getObjectiveHintText( team )
{
	return game[ "strings" ][ "objective_hint_" + team ];
}

getTimePassed()
{
	if ( !IsDefined( level.startTime ) || !IsDefined( level.discardTime ) )
		return 0;
	
	if ( level.timerStopped )
		return( level.timerPauseTime - level.startTime ) - level.discardTime;
	else
		return( gettime() - level.startTime ) - level.discardTime;

}

// time passed from the beging of the match, not the current round or halftime
getUnpausedTimePassedRaw()
{
	if ( !IsDefined( level.matchDurationStartTime ) )
	{
		return 0;
	}
	
	return ( gettime() - level.matchDurationStartTime );
}

getGameTimePassedMS()
{
	gameLengthMS = GetMatchData( "gameLengthSeconds" ) * 1000;
	gameLengthMS = gameLengthMS + getUnpausedTimePassedRaw();
	return gameLengthMS;
}

getGameTimePassedSeconds()
{
	gameLengthMS = getGameTimePassedMS();
	gameLengthSeconds = int( gameLengthMS / 1000 );
	return gameLengthSeconds;
}

getTimePassedPercentage()
{
	return ( getTimePassed() / (getTimeLimit() * 60 * 1000) ) * 100;
}

getSecondsPassed()
{
	return (getTimePassed() / 1000);
}

getMinutesPassed()
{
	return (getSecondsPassed() / 60);
}

getTimeDeciSeconds()
{
	// NOT timePassed	
	return convertMillisecondsToDeciSecondsAndClampToShort( getTime() );	
}

getTimePassedDeciSeconds()
{	
	return convertMillisecondsToDeciSecondsAndClampToShort( getTimePassed() );	
}

getTimePassedDeciSecondsIncludingRounds()
{
	timePassedIncludingRoundsMs = getGameTimePassedMS();
	return convertMillisecondsToDeciSecondsAndClampToShort( timePassedIncludingRoundsMs );
}

convertMillisecondsToDeciSecondsAndClampToShort(value)
{
	return clampToShort( value / 100 );
}

clampToShort(value)
{
	// note: clamp test in code is testing for a signed short
	// using: 
	// #define SHRT_MIN    (-32768)        /* minimum (signed) short value */
 	// #define SHRT_MAX      32767         /* maximum (signed) short value */

	value = int(value);
	if( value > 32767 )
		value = 32767;
	if( value < -32768 )
		value = -32768;
	return value;	
}

clampToByte( value )
{
	// note: The clamp test in code is checking for an unsigned byte range
	// fortunately all uses current of the function need unsigned
	value = int(value);
	if( value > 255 )
		value = 255;
	if( value < 0 )
		value = 0;
	return value;		
}



ClearKillcamState()
{
	self.forcespectatorclient = -1;
	self.killcamentity = -1;
	self.archivetime = 0;
	self.psoffsettime = 0;
	self.spectatekillcam = false;
}

isInKillcam()
{
	ASSERT( self.spectatekillcam == ( self.forcespectatorclient != -1 || self.killcamentity != -1 ) );
	return self.spectatekillcam;
}

isValidClass( class )
{
	return IsDefined( class ) && class != "";
}



getValueInRange( value, minValue, maxValue )
{
	if ( value > maxValue )
		return maxValue;
	else if ( value < minValue )
		return minValue;
	else
		return value;
}

///#
logXPGains()
{
	if ( !IsDefined( self.pers["summary"] ) )
		return;
	
	if( IsAI(self) )
		return;
	
	timePlayed = 0;
	
	if( IsDefined(self.timePlayed["total"]) )
		timePlayed = self.timePlayed["total"];
	
	ReconEvent( "script_EarnedXP: totalXP %d, timeplayed %d, score %d, challenge %d, match %d, misc %d, gamemode %s", self.pers["summary"]["xp"], timePlayed, self.pers["summary"]["score"], self.pers["summary"]["challenge"], self.pers["summary"]["match"], self.pers["summary"]["misc"], level.gametype );
}
//#/

registerRoundSwitchDvar( dvarString, defaultValue, minValue, maxValue )
{
	registerWatchDvarInt( "roundswitch", defaultValue );

	dvarString = ( "scr_" + dvarString + "_roundswitch" );

	level.roundswitchDvar = dvarString;
	level.roundswitchMin = minValue;
	level.roundswitchMax = maxValue;
	level.roundswitch = getDvarInt( dvarString, defaultValue );
	
	if ( level.roundswitch < minValue )
		level.roundswitch = minValue;
	else if ( level.roundswitch > maxValue )
		level.roundswitch = maxValue;
}


registerRoundLimitDvar( dvarString, defaultValue )
{
	registerWatchDvarInt( "roundlimit", defaultValue );
}

registerNumTeamsDvar( dvarString, defaultValue )
{
	registerWatchDvarInt( "numTeams", defaultValue );
}


registerWinLimitDvar( dvarString, defaultValue )
{
	registerWatchDvarInt( "winlimit", defaultValue );
}


registerScoreLimitDvar( dvarString, defaultValue )
{
	registerWatchDvarInt( "scorelimit", defaultValue );
}


registerTimeLimitDvar( dvarString, defaultValue )
{
	registerWatchDvarFloat( "timelimit", defaultValue );
	SetDvar( "ui_timelimit", getTimeLimit() );
}

registerHalfTimeDvar( dvarString, defaultValue ) 
{
	registerWatchDvarInt( "halftime", defaultValue );
	SetDvar( "ui_halftime", getHalfTime() );
}

registerNumLivesDvar( dvarString, defaultValue )
{
	registerWatchDvarInt( "numlives", defaultValue );
}

setOverTimeLimitDvar( value )
{
	SetDvar( "overtimeTimeLimit", value );
}

get_damageable_player( player, playerpos )
{
	newent = spawnstruct();
	newent.isPlayer = true;
	newent.isADestructable = false;
	newent.entity = player;
	newent.damageCenter = playerpos;
	return newent;
}

get_damageable_sentry( sentry, sentryPos )
{
	newent = spawnstruct();
	newent.isPlayer = false;
	newent.isADestructable = false;
	newent.isSentry = true;
	newent.entity = sentry;
	newent.damageCenter = sentryPos;
	return newent;
}

get_damageable_grenade( grenade, entpos )
{
	newent = spawnstruct();
	newent.isPlayer = false;
	newent.isADestructable = false;
	newent.entity = grenade;
	newent.damageCenter = entpos;
	return newent;
}

get_damageable_mine( mine, entpos )
{
	newent = spawnstruct();
	newent.isPlayer = false;
	newent.isADestructable = false;
	newent.entity = mine;
	newent.damageCenter = entpos;
	return newent;
}

get_damageable_player_pos( player )
{
	return player.origin + ( 0, 0, 32 );
}

getStanceCenter()
{
	if ( self GetStance() == "crouch" )
		center = self.origin + ( 0, 0, 24 );
	else if ( self GetStance() == "prone" )
		center = self.origin + ( 0, 0, 10 );
	else
		center = self.origin + ( 0, 0, 32 );
	
	return center;
}

get_damageable_grenade_pos( grenade )
{
	return grenade.origin;
}

// this should be a code function.
getDvarVec( dvarName )
{
	dvarString = getDvar( dvarName );

	if ( dvarString == "" )
		return( 0, 0, 0 );

	dvarTokens = strTok( dvarString, " " );

	if ( dvarTokens.size < 3 )
		return( 0, 0, 0 );

	setDvar( "tempR", dvarTokens[ 0 ] );
	setDvar( "tempG", dvarTokens[ 1 ] );
	setDvar( "tempB", dvarTokens[ 2 ] );

	return( ( getDvarFloat( "tempR" ), getDvarFloat( "tempG" ), getDvarFloat( "tempB" ) ) );
}

strip_suffix( lookupString, stripString )
{
	if ( lookupString.size <= stripString.size )
		return lookupString;

	if ( getSubStr( lookupString, lookupString.size - stripString.size, lookupString.size ) == stripString )
		return getSubStr( lookupString, 0, lookupString.size - stripString.size );

	return lookupString;
}

_takeWeaponsExcept( saveWeapon )
{
	weaponsList = self GetWeaponsListAll();
	
	foreach ( weapon in weaponsList )
	{
		if ( weapon == saveWeapon )
		{
			continue;	
		}
		else
		{
			self takeWeapon( weapon );
		}
	}
}

saveData()
{
	saveData = spawnstruct();

	saveData.offhandClass = self getTacticalWeapon();
	saveData.actionSlots = self.saved_actionSlotData;

	saveData.currentWeapon = self getCurrentWeapon();

	weaponsList = self GetWeaponsListAll();
	saveData.weapons = [];
	foreach ( weapon in weaponsList )
	{
		if ( weaponInventoryType( weapon ) == "exclusive" )
			continue;
			
		if ( weaponInventoryType( weapon ) == "altmode" )
			continue;

		saveWeapon = spawnStruct();
		saveWeapon.name = weapon;
		saveWeapon.clipAmmoR = self getWeaponAmmoClip( weapon, "right" );
		saveWeapon.clipAmmoL = self getWeaponAmmoClip( weapon, "left" );
		saveWeapon.stockAmmo = self getWeaponAmmoStock( weapon );		
		/* save camo? */
		
		if ( IsDefined( self.throwingGrenade ) && self.throwingGrenade == weapon )
			saveWeapon.stockAmmo--;
		
		assert( saveWeapon.stockAmmo >= 0 );
		
		saveData.weapons[saveData.weapons.size] = saveWeapon;
	}
	
	self.script_saveData = saveData;
}


restoreData()
{
	saveData = self.script_saveData;

	self setTacticalWeapon( saveData.offhandClass );

	foreach ( weapon in saveData.weapons )
	{		
		//if ( weapon.name == self.loadoutPrimary + "_mp" )
			self _giveWeapon( weapon.name, int(tableLookup( "mp/camoTable.csv", 1, self.loadoutPrimaryCamo, 0 )) );
		//else
		//self _giveWeapon( weapon.name );
			
		self setWeaponAmmoClip( weapon.name, weapon.clipAmmoR, "right" );
		if ( isSubStr( weapon.name, "akimbo" ) )
			self setWeaponAmmoClip( weapon.name, weapon.clipAmmoL, "left" );

		self setWeaponAmmoStock( weapon.name, weapon.stockAmmo );
	}

	foreach ( slotID, actionSlot in saveData.actionSlots )
		self _setActionSlot( slotID, actionSlot.type, actionSlot.item );

	if ( self getCurrentWeapon() == "none" )
	{
		weapon = saveData.currentWeapon;

		if ( weapon == "none" )
			weapon = self getLastWeapon();
		
		// Can remove this when "spawn" isn't used after final stand
		self setSpawnWeapon( weapon );
		self switchToWeapon( weapon );
	}
}

setExtraScore0( newValue ) // self == player
{
	self.extrascore0 = newValue;
	self setPersStat( "extrascore0", newValue );
}

setExtraScore1( newValue ) // self == player
{
	self.extrascore1 = newValue;
	self setPersStat( "extrascore1", newValue );
}

_setActionSlot( slotID, type, item )
{
	self.saved_actionSlotData[slotID].type = type;
	self.saved_actionSlotData[slotID].item = item;

	self setActionSlot( slotID, type, item );
}


isFloat( value )
{
	if ( int( value ) != value )
		return true;

	return false;
}

registerWatchDvarInt( nameString, defaultValue )
{
	dvarString = "scr_" + level.gameType + "_" + nameString;

	level.watchDvars[ dvarString ] = spawnStruct();
	level.watchDvars[ dvarString ].value = getDvarInt( dvarString, defaultValue );
	level.watchDvars[ dvarString ].type = "int";
	level.watchDvars[ dvarString ].notifyString = "update_" + nameString;
}


registerWatchDvarFloat( nameString, defaultValue )
{
	dvarString = "scr_" + level.gameType + "_" + nameString;

	level.watchDvars[ dvarString ] = spawnStruct();
	level.watchDvars[ dvarString ].value = getDvarFloat( dvarString, defaultValue );
	level.watchDvars[ dvarString ].type = "float";
	level.watchDvars[ dvarString ].notifyString = "update_" + nameString;
}


registerWatchDvar( nameString, defaultValue )
{
	dvarString = "scr_" + level.gameType + "_" + nameString;

	level.watchDvars[ dvarString ] = spawnStruct();
	level.watchDvars[ dvarString ].value = getDvar( dvarString, defaultValue );
	level.watchDvars[ dvarString ].type = "string";
	level.watchDvars[ dvarString ].notifyString = "update_" + nameString;
}


setOverrideWatchDvar( dvarString, value )
{
	dvarString = "scr_" + level.gameType + "_" + dvarString;
	level.overrideWatchDvars[dvarString] = value;
}


getWatchedDvar( dvarString )
{
	dvarString = "scr_" + level.gameType + "_" + dvarString;
	
	if ( IsDefined( level.overrideWatchDvars ) && IsDefined( level.overrideWatchDvars[dvarString] ) )
	{
		return level.overrideWatchDvars[dvarString];
	}	
	
	return( level.watchDvars[ dvarString ].value );
}


updateWatchedDvars()
{
	while ( game[ "state" ] == "playing" )
	{
		watchDvars = getArrayKeys( level.watchDvars );

		foreach ( dvarString in watchDvars )
		{
			if ( level.watchDvars[ dvarString ].type == "string" )
				dvarValue = getProperty( dvarString, level.watchDvars[ dvarString ].value );
			else if ( level.watchDvars[ dvarString ].type == "float" )
				dvarValue = getFloatProperty( dvarString, level.watchDvars[ dvarString ].value );
			else
				dvarValue = getIntProperty( dvarString, level.watchDvars[ dvarString ].value );

			if ( dvarValue != level.watchDvars[ dvarString ].value )
			{
				level.watchDvars[ dvarString ].value = dvarValue;
				level notify( level.watchDvars[ dvarString ].notifyString, dvarValue );
			}
		}

		wait( 1.0 );
	}
}


isRoundBased()
{
	if ( !level.teamBased )
		return false;

	if ( getWatchedDvar( "winlimit" ) != 1 && getWatchedDvar( "roundlimit" ) != 1 )
		return true;

	return false;
}

isFirstRound()
{
	if ( !level.teamBased )
		return true;

	if ( getWatchedDvar( "roundlimit" ) > 1 && game[ "roundsPlayed" ] == 0 )
		return true;

	if ( getWatchedDvar( "winlimit" ) > 1 && game[ "roundsWon" ][ "allies" ] == 0 && game[ "roundsWon" ][ "axis" ] == 0 )
		return true;

	return false;
}

isLastRound()
{
	if ( !level.teamBased )
		return true;

	if ( getWatchedDvar( "roundlimit" ) > 1 && game[ "roundsPlayed" ] >= ( getWatchedDvar( "roundlimit" ) - 1 ) )
		return true;

	if ( getWatchedDvar( "winlimit" ) > 1 && game[ "roundsWon" ][ "allies" ] >= getWatchedDvar( "winlimit" ) - 1 && game[ "roundsWon" ][ "axis" ] >= getWatchedDvar( "winlimit" ) - 1 )
		return true;

	return false;
}


wasOnlyRound()
{
	if ( !level.teamBased )
		return true;
		
	if ( IsDefined( level.onlyRoundOverride ) )
		return false;

	if ( getWatchedDvar( "winlimit" ) == 1 && hitWinLimit() )
		return true;

	if ( getWatchedDvar( "roundlimit" ) == 1 )
		return true;

	return false;
}


wasLastRound()
{
	if ( level.forcedEnd )
		return true;

	if ( !level.teamBased )
		return true;

	if ( hitRoundLimit() || hitWinLimit() )
		return true;

	return false;
}


hitRoundLimit()
{
	if ( getWatchedDvar( "roundlimit" ) <= 0 )
		return false;

	return( game[ "roundsPlayed" ] >= getWatchedDvar( "roundlimit" ) );
}


hitScoreLimit()
{
	if ( isObjectiveBased()	 )
		return false;

	if ( getWatchedDvar( "scorelimit" ) <= 0 )
		return false;

	if ( level.teamBased )
	{
		if ( game[ "teamScores" ][ "allies" ] >= getWatchedDvar( "scorelimit" ) || game[ "teamScores" ][ "axis" ] >= getWatchedDvar( "scorelimit" ) )
			return true;
	}
	else
	{
		for ( i = 0; i < level.players.size; i++ )
		{
			player = level.players[ i ];
			if ( IsDefined( player.score ) && player.score >= getWatchedDvar( "scorelimit" ) )
				return true;
		}
	}
	return false;
}


hitWinLimit()
{
	if ( getWatchedDvar( "winlimit" ) <= 0 )
		return false;

	if ( !level.teamBased )
		return true;

	if ( getRoundsWon( "allies" ) >= getWatchedDvar( "winlimit" ) || getRoundsWon( "axis" ) >= getWatchedDvar( "winlimit" ) )
		return true;

	return false;
}


getScoreLimit()
{
	if ( isRoundBased() )
	{
		if ( getWatchedDvar( "roundlimit" ) )
			return ( getWatchedDvar( "roundlimit" ) );			
		else
			return ( getWatchedDvar( "winlimit" ) );
	}
	else
	{
		return ( getWatchedDvar( "scorelimit" ) );
	}	
}


getRoundsWon( team )
{
	return game[ "roundsWon" ][ team ];
}


isObjectiveBased()
{
	return level.objectiveBased;
}


getTimeLimit()
{
	if( inOvertime() )
	{
		timeLimit = float( getDvar( "overtimeTimeLimit" ) );
		
		if( !IsDefined( timeLimit ) )
			timeLimit = 1;
		
		return timeLimit;
	}

	return getWatchedDvar( "timelimit" );
}


getHalfTime()
{
	if( inOvertime() )
		return false;

	return getWatchedDvar( "halftime" );
}


inOvertime()
{
	return IsDefined( game["status"] ) && isOvertimeText( game["status"] );
}

isOvertimeText( text )
{
	return (text == "overtime") || (text == "overtime_halftime");
}

gameHasStarted()
{
	if( IsDefined(level.gameHasStarted) )
		return level.gameHasStarted;
		
	if( level.teamBased )
		return( level.hasSpawned[ "axis" ] && level.hasSpawned[ "allies" ] );
	
	return( level.maxPlayerCount > 1 );
}


getAverageOrigin( ent_array )
{
	avg_origin = ( 0, 0, 0 );

	if ( !ent_array.size )
		return undefined;

	foreach ( ent in ent_array )
		avg_origin += ent.origin;

	avg_x = int( avg_origin[ 0 ] / ent_array.size );
	avg_y = int( avg_origin[ 1 ] / ent_array.size );
	avg_z = int( avg_origin[ 2 ] / ent_array.size );

	avg_origin = ( avg_x, avg_y, avg_z );

	return avg_origin;
}


getLivingPlayers( team )
{
	player_array = [];

	foreach ( player in level.players )
	{
		if ( !isAlive( player ) )
			continue;

		if ( level.teambased && isdefined( team ) )
		{
			if ( team == player.pers[ "team" ] )
				player_array[ player_array.size ] = player;
		}
		else
		{
			player_array[ player_array.size ] = player;
		}
	}

	return player_array;
}


setUsingRemote( remoteName )
{
	if ( IsDefined( self.carryIcon) )
		self.carryIcon.alpha = 0;
	
	assert( !self isUsingRemote() );
	self.usingRemote = remoteName;

	self _disableOffhandWeapons();
	self notify( "using_remote" );
}

getRemoteName()
{
	assert( self isUsingRemote() );
	
	return self.usingRemote;	
}

freezeControlsWrapper( frozen )
{
	if ( IsDefined( level.hostMigrationTimer ) )
	{
		//println( "Migration Wrapper freezing controls for " + maps\mp\gametypes\_hostMigration::hostMigrationName( self ) + " with frozen = " + frozen );
		self freezeControls( true );
		return;
	}
	
	self freezeControls( frozen );
	self.controlsFrozen = frozen;
}

freezeControlsWrapperWithDelay( frozen, delay_time )
{
	wait( delay_time );
	
	if ( IsDefined( self ) )
		self freezeControlsWrapper( frozen );
}

clearUsingRemote()
{
	//if ( !isWeaponEnabled() )
	//	self disableWeapons();

	if ( IsDefined( self.carryIcon) )
		self.carryIcon.alpha = 1;

	self.usingRemote = undefined;
	self _enableOffhandWeapons();
	
	curWeapon = self getCurrentWeapon();
	
	if( curWeapon == "none" || isKillstreakWeapon( curWeapon ) )
		self switchToWeapon( self Getlastweapon() );
	
	self freezeControlsWrapper( false );
	
	self playerRemoteKillstreakShowHud();
	
	self notify( "stopped_using_remote" );
}

playerRemoteKillstreakHideHud()
{
	self SetClientOmnvar( "ui_killstreak_remote", true );
}

playerRemoteKillstreakShowHud()
{
	self SetClientOmnvar( "ui_killstreak_remote", false );
}


get_water_weapon()	//self == player
{
	if ( IsDefined( self.underwaterMotionType ) )
	{
		if ( self.underwaterMotionType == "shallow" && IsDefined( level.shallow_water_weapon ) )
			return level.shallow_water_weapon;
		if ( self.underwaterMotionType == "deep" && IsDefined( level.deep_water_weapon ) )
			return level.deep_water_weapon;
		if ( self.underwaterMotionType != "none" && IsDefined( level.shallow_water_weapon ) )
			return level.shallow_water_weapon;
	}
	return "none";
}

/*
============= 
///ScriptDocBegin
"Name: isUsingRemote()"
"Summary: Returns true if the player is using a remote to control something and isn't in their body."
"Module: Utility"
"CallOn: Player"
"Example: if( player isUsingRemote() )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isUsingRemote()
{
	return( IsDefined( self.usingRemote ) );
}

isInRemoteTransition()
{
	return ( IsDefined( self.remoteRideTransition ) );
}


isRocketCorpse()
{
	return ( IsDefined( self.isRocketCorpse ) && self.isRocketCorpse );
}


queueCreate( queueName )
{
	if ( !IsDefined( level.queues ) )
		level.queues = [];

	assert( !IsDefined( level.queues[ queueName ] ) );

	level.queues[ queueName ] = [];
}


queueAdd( queueName, entity )
{
	assert( IsDefined( level.queues[ queueName ] ) );
	level.queues[ queueName ][ level.queues[ queueName ].size ] = entity;
}


queueRemoveFirst( queueName )
{
	assert( IsDefined( level.queues[ queueName ] ) );

	first = undefined;
	newQueue = [];
	foreach ( element in level.queues[ queueName ] )
	{
		if ( !IsDefined( element ) )
			continue;

		if ( !IsDefined( first ) )
			first = element;
		else
			newQueue[ newQueue.size ] = element;
	}

	level.queues[ queueName ] = newQueue;

	return first;
}


_giveWeapon( weapon, variant, dualWieldOverRide )
{
	if ( !IsDefined(variant) )
		variant = -1;
	
	inHybridSight = false;
	if ( IsDefined( self.pers[ "toggleScopeStates" ] ) && IsDefined( self.pers[ "toggleScopeStates" ][ weapon ] ) )
	{
		inHybridSight = self.pers[ "toggleScopeStates" ][ weapon ];
	}
	
	if ( isSubstr( weapon, "_akimbo" ) || IsDefined(dualWieldOverRide) && dualWieldOverRide == true)
	{
		if ( IsAgent ( self ) )
			self giveWeapon(weapon, variant, true, -1, false);
		else
			self giveWeapon(weapon, variant, true, -1, false, self, inHybridSight);
	}
	else
	{
		if ( isAgent ( self ) )
			self giveWeapon(weapon, variant, false, -1, false);
		else
			self giveWeapon(weapon, variant, false, -1, false, self, inHybridSight);
	}
			
}

/*
============= 
///ScriptDocBegin
"Name: _hasPerk( <perkName> )"
"Summary: Does the player have this perk?"
"Module: Player"
"CallOn: A Player"
"MandatoryArg: <perkName> The of the perk from perkTable.csv."
"Example: if( self _hasPerk( "specialty_scavenger" ) )
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
_hasPerk( perkName )
{
	if ( IsDefined( self.perks ) && IsDefined( self.perks[ perkName ] ) )
		return true;
	
	return false;
}

/*
============= 
///ScriptDocBegin
"Name: givePerk( <perkName>, <useSlot> )"
"Summary: Gives the perk to the player."
"Module: Player"
"CallOn: A Player"
"MandatoryArg: <perkName> The of the perk from PerkTable.csv."
"MandatoryArg: <useSlot> Boolean, should we put this perk in a perk slot. This should be true if the perks are the CAC selected perks."
"OptionalArg: <setSlot> int(0-5), force the slot this perk will be set to (used for stacking perks)."
"Example: self givePerk( "specialty_pistoldeath", false );
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
givePerk( perkName, useSlot, setSlot )
{
	AssertEx( IsDefined( perkName ), "givePerk perkName not defined and should be" );
	AssertEx( IsDefined( useSlot ), "givePerk useSlot not defined and should be" );
	AssertEx( !IsSubStr( perkName, "specialty_null" ), "givePerk perkName shouldn't be specialty_null, use _clearPerks()s" );

	if( IsSubStr( perkName, "_mp" ) )
	{
		self _giveWeapon( perkName, 0 );
		self giveStartAmmo( perkName );

		self _setPerk( perkName, useSlot );
		return;
	}

	if( IsSubStr( perkName, "specialty_weapon_" ) )
	{
		self _setPerk( perkName, useSlot );
		return;
	}

	self _setPerk( perkName, useSlot, setSlot );
}

// please call givePerk
_setPerk( perkName, useSlot, setSlot )
{
	AssertEx( IsDefined( perkName ), "_setPerk perkName not defined and should be" );
	AssertEx( IsDefined( useSlot ), "_setPerk useSlot not defined and should be" );

	self.perks[ perkName ] = true;
	self.perksPerkName[ perkName ] = perkName;
	self.perksUseSlot[ perkName ] = useSlot;
	
	if ( isDefined( level.perkSetFuncs[perkName] ) )
		self thread [[ level.perkSetFuncs[perkName] ]]();

	shortPerkName = maps\mp\_utility::strip_suffix( perkName, "_lefthand" );
	
	if (isdefined(setSlot))
		self setPerk( perkName, !IsDefined( level.scriptPerks[shortPerkName] ), useSlot, setSlot );
	else
		self setPerk( perkName, !IsDefined( level.scriptPerks[shortPerkName] ), useSlot );
}

_unsetPerk( perkName )
{
	self.perks[perkName] = undefined;
	self.perksPerkName[ perkName ] = undefined;
	self.perksUseSlot[ perkName ] = undefined;
	self.perksPerkPower[ perkName ] = undefined;

	if ( IsDefined( level.perkUnsetFuncs[perkName] ) )
		self thread [[level.perkUnsetFuncs[perkName]]]();

	shortPerkName = maps\mp\_utility::strip_suffix( perkName, "_lefthand" );
	
	self unsetPerk( perkName, !IsDefined( level.scriptPerks[shortPerkName] ) );
}

_clearPerks()
{
	foreach ( perkName, perkValue in self.perks )
	{
		if ( IsDefined( level.perkUnsetFuncs[perkName] ) )
			self [[level.perkUnsetFuncs[perkName]]]();
	}
	
	self.perks = [];
	self.perksPerkName = [];
	self.perksUseSlot = [];
	self.perksPerkPower = [];
	self clearPerks();
}

/*
============= 
///ScriptDocBegin
"Name: canGiveAbility( <abilityName> )"
"Summary: Checks to see if the ability can be given to the player."
"Module: Player"
"CallOn: A Player"
"MandatoryArg: <abilityName> The name of the ability from abilityTable.csv."
"Example: self canGiveAbility( "specialty_suppressweapon" );
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
canGiveAbility( abilityName )
{
	AssertEx( IsDefined( abilityName ), "canGiveAbility abilityName not defined and should be" );
	AssertEx( !IsSubStr( abilityName, "specialty_null" ), "canGiveAbility abilityName shouldn't be specialty_null, use _clearAbilities()" );
	
	return self _canGiveAbility( abilityName );
}

_canGiveAbility( abilityName )
{
	AssertEx( IsDefined( abilityName ), "_canGiveAbility abilityName not defined and should be" );
	
	// If a can give function is not defined for this ability assume true
	if ( !IsDefined( level.abilityCanSetFuncs ) || !IsDefined( level.abilityCanSetFuncs[ abilityName ] ) )
		return true;
	
	return self [[ level.abilityCanSetFuncs[ abilityName ] ]]();
}

/*
============= 
///ScriptDocBegin
"Name: giveAbility( <abilityName>, <useSlot> )"
"Summary: Gives the ability to the player."
"Module: Player"
"CallOn: A Player"
"MandatoryArg: <abilityName> The of the ability from abilityTable.csv."
"MandatoryArg: <useSlot> Boolean, should we put this ability in a perk slot."
"Example: self giveAbility( "specialty_scavenger", false );
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
giveAbility( abilityName, useSlot )
{
	AssertEx( IsDefined( abilityName ), "giveAbility abilityName not defined and should be" );
	AssertEx( IsDefined( useSlot ), "giveAbility useSlot not defined and should be" );
	AssertEx( !IsSubStr( abilityName, "specialty_null" ), "giveAbility abilityName shouldn't be specialty_null, use _clearAbilities()" );

	self _setAbility( abilityName, useSlot );
}

// please call giveAbility
_setAbility( abilityName, useSlot )
{
	AssertEx( IsDefined( abilityName ), "_setAbility abilityName not defined and should be" );
	AssertEx( IsDefined( useSlot ), "_setAbility useSlot not defined and should be" );

	self.abilities[ abilityName ] = true;

	if ( IsPlayer( self ) )
	{
		if( IsDefined( level.abilitySetFuncs[ abilityName ] ) )
			self thread [[ level.abilitySetFuncs[ abilityName ] ]]();
	}

	self SetPerk( abilityName, !IsDefined( level.scriptAbilities[ abilityName ] ), useSlot );
}

_unsetAbility( abilityName )
{
	self.abilities[ abilityName ] = undefined;

	if ( IsPlayer( self ) )
	{
		if( IsDefined( level.abilityUnsetFuncs[ abilityName ] ) )
			self thread [[ level.abilityUnsetFuncs[ abilityName ] ]]();
	}

	self UnSetPerk( abilityName, !IsDefined( level.scriptAbilities[ abilityName ] ) );
}

_clearAbilities()
{
	if ( IsPlayer( self ) )
	{
		if( IsDefined( level.abilityUnsetFuncs[ self.pers[ "ability" ] ] ) )
			self [[level.abilityUnsetFuncs[ self.pers[ "ability" ] ]]]();
	}

	self.abilities = [];
	self clearPerks();
}

/*
============= 
///ScriptDocBegin
"Name: _hasAbility( <abilityName>, <isPassiveAbility> )"
"Summary: Does the player have this ability and is it on?"
"Module: Player"
"CallOn: A Player"
"MandatoryArg: <abilityName> The of the ability from abilityTable.csv."
"OptionalArg: <isPassiveAbility> Is this a passive ability?"
"Example: if( self _hasAbility( "specialty_scavenger" ) )
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
_hasAbility( abilityName, isPassiveAbility )
{
	if( !IsDefined( isPassiveAbility ) )
		isPassiveAbility = false;

	if( isPassiveAbility )
	{
		if( IsDefined( self.abilities[ abilityName ] ) && self.abilities[ abilityName ] )
			return true;
	}
	else
	{
		if( IsDefined( self.pers[ "ability" ] ) && self.pers[ "ability" ] == abilityName && IsDefined( self.pers[ "abilityOn" ] ) && self.pers[ "abilityOn" ] )
			return true;
	}

	return false;
}

// Quick Sort - pass it an array it will come back sorted
quickSort(array, compare_func) 
{
    return quickSortMid(array, 0, array.size -1, compare_func);     
}

quickSortMid(array, start, end, compare_func)
{
	i = start;
	k = end;

    if(!IsDefined(compare_func))
        compare_func = ::quickSort_compare;
    
	if (end - start >= 1)
    {
        pivot = array[start];  

        while (k > i)         
        {
            while ( [[ compare_func ]](array[i], pivot) && i <= end && k > i)
	        	i++;                                 
            while ( ![[ compare_func ]](array[k], pivot) && k >= start && k >= i)
	            k--;                                      
	        if (k > i)                                 
	           array = swap(array, i, k);                    
        }
        array = swap(array, start, k);                                               
        array = quickSortMid(array, start, k - 1, compare_func); 
        array = quickSortMid(array, k + 1, end, compare_func);   
    }
	else
    	return array;
    
    return array;
}

quicksort_compare(left, right)
{
    return left<=right;
}

swap(array, index1, index2) 
{
	temp = array[index1];          
	array[index1] = array[index2];     
	array[index2] = temp;   
	return array;         
}

_suicide()
{
	if ( self isUsingRemote() && !IsDefined( self.fauxDead ) )
		self thread maps\mp\gametypes\_damage::PlayerKilled_internal( self, self, self, 10000, "MOD_SUICIDE", "frag_grenade_mp", (0,0,0), "none", 0, 1116, true );
	else if( !self isUsingRemote() && !IsDefined( self.fauxDead ) )
		self suicide();	
}

/*
=============
///ScriptDocBegin
"Name: isReallyAlive( <player> )"
"Summary: This makes sure the player is dead and also not fauxDead. This is better than just the isAlive() call."
"Module: Player"
"CallOn: player"
"MandatoryArg: <player> The player entity to check."
"Example: if( !isReallyAlive( player ) )"
"SPMP: multiplayer"
///ScriptDocEnd
=============
*/
isReallyAlive( player )
{
	if ( isAlive( player ) && !IsDefined( player.fauxDead ) )
		return true;
		
	return false;
}

/*
=============
///ScriptDocBegin
"Name: waittill_any_timeout_pause_on_death_and_prematch( <timeOut> , <string1> , <string2> , <string3> , <string4> , <string5> )"
"Summary: This will pause the timeout counter on death and prematch. Then start after alive or prematch is over."
"Module: Utility"
"CallOn: An entity"
"MandatoryArg: <param1> The time in seconds to wait for timeout."
"OptionalArg: <param2> The waittill strings to wait on."
"Example: self waittill_any_timeout_pause_on_death_and_prematch( 5.0, "finished" );"
"SPMP: multiplayer"
///ScriptDocEnd
=============
*/
waittill_any_timeout_pause_on_death_and_prematch( timeOut, string1, string2, string3, string4, string5 )
{
	ent = spawnstruct();

	if ( isdefined( string1 ) )
		self thread waittill_string_no_endon_death( string1, ent );

	if ( isdefined( string2 ) )
		self thread waittill_string_no_endon_death( string2, ent );

	if ( isdefined( string3 ) )
		self thread waittill_string_no_endon_death( string3, ent );

	if ( isdefined( string4 ) )
		self thread waittill_string_no_endon_death( string4, ent );

	if ( isdefined( string5 ) )
		self thread waittill_string_no_endon_death( string5, ent );

	ent thread _timeout_pause_on_death_and_prematch( timeOut, self );

	ent waittill( "returned", msg );
	ent notify( "die" );
	return msg;
}

_timeout_pause_on_death_and_prematch( delay, ent )
{
	self endon( "die" );

	inc = 0.05;
	while( delay > 0 )
	{
		if( IsPlayer( ent ) && !isReallyAlive( ent ) )
		{
			ent waittill( "spawned_player" );
		}
		if( GetDvarInt( "ui_inprematch" ) )
		{
			level waittill( "prematch_over" );
		}

		wait( inc );
		delay -= inc;
	}
	self notify( "returned", "timeout" );
}

playDeathSound()
{
	if ( IsDefined( level.customPlayDeathSound ) )
	{
		self thread [[ level.customPlayDeathSound ]]();
		return;
	}
		
	rand = RandomIntRange( 1,8 );
	
	if ( self maps\mp\killstreaks\_juggernaut::get_is_in_mech() )
		return;

	if ( self.team == "axis" )
	{
		if ( self HasFemaleCustomizationModel() )
		{
			self PlaySound( "generic_death_enemy_fm_"+ rand );	
		}
		else
		{
			self PlaySound( "generic_death_enemy_"+ rand );	
		}
	}
	else
	{
		if ( self HasFemaleCustomizationModel() )
		{
			self PlaySound( "generic_death_friendly_fm_"+ rand );
		}
		else
		{
			self PlaySound( "generic_death_friendly_"+ rand );
		}
	}
}


rankingEnabled()
{
	if ( !isPlayer( self ) )
		return false;
	
	return ( level.rankedMatch && !self.usingOnlineDataOffline );
}

// only true for private match
privateMatch()
{
	return ( !level.onlineGame || getDvarInt( "xblive_privatematch" ) );
}

// only true for playlist based LIVE and PSN games
matchMakingGame()
{
	return ( level.onlineGame && !getDvarInt( "xblive_privatematch" ) );
}

practiceRoundGame()
{
	return ( level.practiceRound );
}

setAltSceneObj( object, tagName, fov, forceLink )
{
	/*
	if ( !IsDefined( forceLink ) )
		forceLink = false;

	if ( !getDvarInt( "scr_pipmode" ) && !forceLink )
		return;
	
	self endon ( "disconnect" );

	if ( !isReallyAlive( self ) )
		return;

	if ( !forceLink && IsDefined( self.altSceneObject ) )
		return;

	self notify ( "altscene" );
	
	self.altSceneObject = object;

	self AlternateSceneCameraLinkTo( object, tagName, fov );
	
	self thread endSceneOnDeath( object );
	self thread endSceneOnDeath( self );
	
	self waittill ( "end_altScene" );
	
	self.altSceneObject = undefined;
	self AlternateSceneCameraUnlink();
	
	if ( !forceLink )
	{
		self endon ( "altscene" );
		wait ( 2.0 );
	}
	*/
}


endSceneOnDeath( object )
{
	self endon ( "altscene" );
	
	object waittill ( "death" );
	self notify ( "end_altScene" );
}


getMapName() // Returns the current map name
{
	return getDvar( "mapname" );
}


getGametypeNumLives()
{
	//commented out to allow diehardhard rules to support mulitiple life gametypes
	//if ( level.dieHardMode && !getWatchedDvar( "numlives" ) )
	//	return 1;
	//else
		return getWatchedDvar( "numlives" );
}


arrayInsertion( array, item, index )
{
	if ( array.size != 0 )
	{
		for ( i = array.size; i >= index; i-- )
		{
			array[i+1] = array[i];
		}
	}
	
	array[index] = item;
}


getProperty( dvar, defValue )
{
	value = defValue;
	/#
	setDevDvarIfUninitialized( dvar, defValue );
	#/

	value = getDvar( dvar, defValue );
	return value;
}


getIntProperty( dvar, defValue )
{
	value = defValue;

	/#
	setDevDvarIfUninitialized( dvar, defValue );
	#/

	value = getDvarInt( dvar, defValue );
	return value;
}


getFloatProperty( dvar, defValue )
{
	value = defValue;
	/#
	setDevDvarIfUninitialized( dvar, defValue );
	#/

	value = getDvarFloat( dvar, defValue );
	return value;
}

isChangingWeapon()
{
	return ( IsDefined( self.changingWeapon ) );
}

killShouldAddToKillstreak( weapon )
{	
	return true;	
}

/*
============= 
///ScriptDocBegin
"Name: isJuggernaut()"
"Summary: Returns if the player is a juggernaut or not."
"Module: Player"
"CallOn: player"
"Example: if( player isJuggernaut() )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isJuggernaut()
{
	if ( ( IsDefined( self.isJuggernaut ) && self.isJuggernaut == true ) )
		return true;

	if ( ( IsDefined( self.isJuggernautDef ) && self.isJuggernautDef == true ) )
		return true;
	
	if ( ( IsDefined( self.isJuggernautGL ) && self.isJuggernautGL == true ) )
		return true;
		
	if ( ( IsDefined( self.isJuggernautRecon ) && self.isJuggernautRecon == true ) )
		return true;	
	
	if ( ( IsDefined( self.isJuggernautManiac ) && self.isJuggernautManiac == true ) )
		return true;	

	return false;	
}


/*
============= 
///ScriptDocBegin
"Name: isKillstreakWeapon( <weapon> )"
"Summary: Returns if this is a killstreak weapon or not."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapon> The name of the weapon to check."
"Example: if( isKillstreakWeapon( sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isKillstreakWeapon( weapon )
{
	if( !IsDefined( weapon ) )
	{
		AssertMsg( "isKillstreakWeapon called without a weapon name passed in" );
		return false;
	}

	if ( weapon == "none" )
		return false;
	
	if( isDestructibleWeapon( weapon ) )
		return false;
	
	if( isBombSiteWeapon( weapon ) )
		return false;

	if ( isSubStr( weapon, "killstreak" ) )
		return true;
	
	// added this in TU since it was an available weapon in a csv, but doesn't have the word killstreak in it
	if ( weapon == "airdrop_sentry_marker_mp" )
		return true;
	
	//this is necessary because of weapons potentially named "_mp(somthign)" like the mp5
	tokens = getWeaponNameTokens( weapon  );
	foundSuffix = false;
	
	foreach(token in tokens)
	{
		if( token == "mp" )
		{
			foundSuffix = true;
			break;
		}
	}
		
	if ( !foundSuffix )
	{
		weapon += "_mp";
	}
	
	// Held killstreak weapons have _mp so check these after the suffix is added.
		
	if ( maps\mp\killstreaks\_airdrop::isAirdropMarker( weapon ) )
		return true;

	if ( IsDefined( level.killstreakWieldWeapons[weapon] ) )
		return true;
	
	// killstreak weapons are exclusive
	weaponInvType = WeaponInventoryType( weapon ); 
	if( IsDefined( weaponInvType ) && weaponInvType == "exclusive" )
		return true;
			
	return false;
}

/*
============= 
///ScriptDocBegin
"Name: isDestructibleWeapon( <weapon> )"
"Summary: Returns if this is a destructible weapon or not, like a barrel in the map."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapon> The name of the weapon to check."
"Example: if( isDestructibleWeapon( sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isDestructibleWeapon( weapon )
{
	if( !IsDefined( weapon ) )
	{
		AssertMsg( "isDestructibleWeapon called without a weapon name passed in" );
		return false;
	}

	switch( weapon )
	{
		case "destructible":
		case "destructible_car":
		case "destructible_toy":
		case "barrel_mp":
			return true;
	}
	
	return false;
}

isAugmentedGameMode()
{
	return GetDvarInt( "scr_game_high_jump", 0 );
}


isGrapplingHookGameMode()
{
	return GetDvarInt( "scr_game_grappling_hook", 0 );
}


isDivisionMode()
{
	return GetDvarInt( "scr_game_division", 0 );
}

/*
============= 
///ScriptDocBegin
"Name: isBombSiteWeapon( <weapon> )"
"Summary: Returns if this is a bomb site weapon or not, like a briefcase bomb in the map."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapon> The name of the weapon to check."
"Example: if( isBombSiteWeapon( sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isBombSiteWeapon( weapon )
{
	if( !IsDefined( weapon ) )
	{
		AssertMsg( "isBombSiteWeapon called without a weapon name passed in" );
		return false;
	}

	switch( weapon )
	{
		case "search_dstry_bomb_mp":
		case "search_dstry_bomb_defuse_mp":
		case "bomb_site_mp":
			return true;
	}
	
	return false;
}

/*
============= 
///ScriptDocBegin
"Name: isEnvironmentWeapon( <weapon> )"
"Summary: Returns if this is an environment weapon or not, like a turret in the map."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapon> The name of the weapon to check."
"Example: if( isEnvironmentWeapon( sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isEnvironmentWeapon( weapon )
{
	if( !IsDefined( weapon ) )
	{
		AssertMsg( "isEnvironmentWeapon called without a weapon name passed in" );
		return false;
	}

	if( weapon == "turret_minigun_mp" )
		return true;

	if( isSubStr( weapon, "_bipod_" ) )
		return true;

	return false;
}

/*
============= 
///ScriptDocBegin
"Name: isLootWeapon( <weaponName> )"
"Summary: Returns true if loot is in the weapon name."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weaponName> The name of the weapon to check."
"Example: if( isLootWeapon( sWeapon ) )
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isLootWeapon( weaponName )
{
	if( IsSubStr( weaponName, "loot" ) )
		return true;

	if( IsSubStr( weaponName, "atlas" ) )
		return true;

	if( IsSubStr( weaponName, "gold" ) )
		return true;

	if( IsSubStr( weaponName, "blops2" ) )
		return true;
	
	if( IsSubStr( weaponName, "ghosts" ) )
		return true;
	
	return false;
}

getWeaponNameTokens( weaponName )
{
	return strTok( weaponName, "_" );
}


/*
============= 
///ScriptDocBegin
"Name: getWeaponClass( <weapon> )"
"Summary: Returns the class of the weapon passed in."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapon> The name of the weapon to check."
"Example: weaponClass = getWeaponClass( sWeapon );"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getWeaponClass( weapon )
{
	baseName = getBaseWeaponName( weapon );
	
	weaponClass = tablelookup( "mp/statstable.csv", 4, baseName, 2 );
		
	// handle special case weapons like grenades, airdrop markers, etc...
	if ( weaponClass == "" )
	{
		weaponName = maps\mp\_utility::strip_suffix( weapon, "_lefthand" );
		weaponName = maps\mp\_utility::strip_suffix( weaponName, "_mp" );
		weaponClass = tablelookup( "mp/statstable.csv", 4, weaponName, 2 );
	}
	
	if ( isEnvironmentWeapon( weapon ) )
		weaponClass = "weapon_mg";
	else if ( isKillstreakWeapon( weapon ) )
		weaponClass = "killstreak"; 
	else if ( weapon == "none" ) //airdrop crates
		weaponClass = "other";
	else if ( weaponClass == "" )
		weaponClass = "other";
	
	assertEx( weaponClass != "", "ERROR: invalid weapon class for weapon " + weapon );
	
	return weaponClass;
}

/*
=============
///ScriptDocBegin
"Name: getWeaponAttachmentArrayFromStats( <weaponName> )"
"Summary: Collects all weapon attachment base names from the statstable.csv."
"Module: Utility"
"MandatoryArg: <weaponName>: Full or base name of the weapon. Base name in this case is iw5_name without the _mp."
"Example: getWeaponAttachmentArrayFromStats( "iw6_ak12_mp" )"
"SPMP: multiplayer"
///ScriptDocEnd
=============
*/
getWeaponAttachmentArrayFromStats( weaponName )
{
	weaponName = getBaseWeaponName( weaponName );
	
	if ( !IsDefined(level.weaponAttachments[weaponName]) )
	{
		attachments = [];
		for ( i = 0; i <= 29; i++ )
		{
			attachment = TableLookup( "mp/statsTable.csv", 4, weaponName, 11 + i );
			if ( attachment == "" )
				break;
			
			attachments[ attachments.size ] = attachment;
		}
		
		level.weaponAttachments[weaponName] = attachments;
	}
	
	return level.weaponAttachments[weaponName];
}

getWeaponAttachmentFromStats( weaponName, index )
{
	weaponName = getBaseWeaponName( weaponName );
	
	return TableLookup( "mp/statsTable.csv", 4, weaponName, 11 + index );
}

getBaseWeaponName( weaponName )
{
	tokens = getWeaponNameTokens( weaponName );
	
	retval = "";
	
	if ( tokens[0] == "iw5" || tokens[0] == "iw6" || tokens[0] == "s1" )
		retval = tokens[0] + "_" + tokens[1];
	else if( tokens[0] == "alt" )
		retval = tokens[1] + "_" + tokens[2];
	else if( (tokens.size > 1) && ((tokens[1] == "grenade") || (tokens[1] == "marker")) )
		retval = tokens[0] + "_" + tokens[1];
//	else if ( isLootWeapon(tokens) )
//		retval = tokens[0] + "_" + tokens[1];
	else
		retval = tokens[0];
	
	return retval;
}

fixAkimboString( weaponName, append )
{
	if( !IsDefined( append ) )
		append = true;
	
	startIndex = 0;
	for( i = 0; i < weaponName.size; i++ )
	{
		if ( weaponName[i] == "a" && weaponName[i+1] == "k" && weaponName[i+2] == "i" && weaponName[i+3] == "m" && weaponName[i+4] == "b" && weaponName[i+5] == "o" )
		{
			startIndex = i;
			break;
		}
	}
	
	weaponName = GetSubStr( weaponName, 0, startIndex ) + GetSubStr( weaponName, startIndex+6, weaponName.size );
	
	if ( append )
		weaponName += "_akimbo";
	
	return weaponName;
}

playSoundinSpace( alias, origin )
{
	playSoundAtPos( origin, alias );
}

limitDecimalPlaces( value, places )
{
	modifier = 1;
	for ( i = 0; i < places; i++ )
		modifier *= 10;
	
	newvalue = value * modifier;
	newvalue = Int( newvalue );
	newvalue = newvalue / modifier;
	
	return newvalue;
}

roundDecimalPlaces( value, places, style )
{
	if ( !isdefined( style ) )
		style = "nearest";
	
	modifier = 1;
	for ( i = 0; i < places; i++ )
		modifier *= 10;
	
	newValue = value * modifier;
	
	if ( style == "up" )
		roundedValue = ceil( newValue );
	else if ( style == "down" )
		roundedValue = floor( newValue ); 	
	else
		roundedValue = newvalue + 0.5;	
		
	newvalue = Int( roundedValue );
	newvalue = newvalue / modifier;
	
	return newvalue;
}

playerForClientId( clientId )
{
	foreach ( player in level.players )
	{
		if ( player.clientId == clientId )
			return player;
	}
	
	return undefined;
}

stringToFloat( stringVal )
{
	floatElements = strtok( stringVal, "." );
	
	floatVal = int( floatElements[0] );
	if ( IsDefined( floatElements[1] ) )
	{
		modifier = 1;
		for ( i = 0; i < floatElements[1].size; i++ )
			modifier *= 0.1;
		
		floatVal += int ( floatElements[1] ) * modifier;
	}
	
	return floatVal;	
}

setSelfUsable(caller)
{
	self makeUsable();
	
	foreach (player in level.players)
	{
		if (player != caller )
			self disablePlayerUse( player );
		else
			self enablePlayerUse( player );
	}
}

setSelfUnusuable()
{
	self makeUnusable();
	
	foreach ( player in level.players )
	{
		self disablePlayerUse( player );
	}
}

makeTeamUsable( team )
{
	self makeUsable();
	self thread _updateTeamUsable( team );
}

_updateTeamUsable( team )
{
	self endon ( "death" );
	
	for ( ;; )
	{
		foreach (player in level.players)
		{
			if ( player.team == team )
				self enablePlayerUse( player );	
			else
				self disablePlayerUse( player );	
		}	

		level waittill ( "joined_team" );		
	}
}

// More general version of makeTeamUsable() which handles FFA
makeEnemyUsable( owner )
{
	self makeUsable();
	self thread _updateEnemyUsable( owner );
}

// Only used for Tactical Insertion for now
// If used for other things, handle owner disappearing or changing team
_updateEnemyUsable( owner )
{
	// check what happens if the owner leaves

	self endon ( "death" );

	team = owner.team;

	for ( ;; )
	{
		if ( level.teambased )
		{
			foreach (player in level.players)
			{
				if ( player.team != team )
					self enablePlayerUse( player );	
				else
					self disablePlayerUse( player );	
			}
		}
		else
		{
			foreach (player in level.players)
			{
				if ( player != owner )
					self enablePlayerUse( player );	
				else
					self disablePlayerUse( player );	
			}
		}

		level waittill ( "joined_team" );		
	}
}

getNextLifeId()
{
	lifeId = getMatchData( "lifeCount" );
	if ( lifeId < level.MaxLives )
	{
		setMatchData( "lifeCount", lifeId+1 );
		return ( lifeId );
	}
	else
	{
		// all overflow calls clobbering last slot is better than 
		// returning an out of range index.
		return ( level.MaxLives - 1 );	
	}		
}

initGameFlags()
{
	if ( !IsDefined( game["flags"] ) )
		game["flags"] = [];
}

gameFlagInit( flagName, isEnabled )
{
	assert( IsDefined( game["flags"] ) );
	game["flags"][flagName] = isEnabled;
}

gameFlag( flagName )
{
	assertEx( IsDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( <flagName>, <isEnabled> )" );
	return ( game["flags"][flagName] );
}

gameFlagSet( flagName )
{
	assertEx( IsDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( <flagName>, <isEnabled> )" );
	game["flags"][flagName] = true;

	level notify ( flagName );
}

gameFlagClear( flagName )
{
	assertEx( IsDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( <flagName>, <isEnabled> )" );
	game["flags"][flagName] = false;
}

gameFlagWait( flagName )
{
	assertEx( IsDefined( game["flags"][flagName] ), "gameFlag " + flagName + " referenced without being initialized; usegameFlagInit( <flagName>, <isEnabled> )" );
	while ( !gameFlag( flagName ) )
		level waittill ( flagName );
}


isBulletDamage( meansofdeath )
{
	bulletDamage = "MOD_RIFLE_BULLET MOD_PISTOL_BULLET MOD_HEAD_SHOT";
	if( isSubstr( bulletDamage, meansofdeath ) )
		return true;
	return false;
}

isFMJDamage( sWeapon, sMeansOfDeath, attacker )
{
	// The Bullet Penetration perk comes from the fmj attachment in the attachmentTable.csv or from the weapon in statstable.csv
	return IsDefined( attacker ) && IsPlayer( attacker ) && attacker _hasPerk( "specialty_bulletpenetration" ) && IsDefined( sMeansOfDeath ) && isBulletDamage( sMeansOfDeath );
}

initLevelFlags()
{
	if ( !IsDefined( level.levelFlags ) )
		level.levelFlags = [];
}

levelFlagInit( flagName, isEnabled )
{
	assert( IsDefined( level.levelFlags ) );
	level.levelFlags[flagName] = isEnabled;
}

levelFlag( flagName )
{
	assertEx( IsDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( <flagName>, <isEnabled> )" );
	return ( level.levelFlags[flagName] );
}

levelFlagSet( flagName )
{
	assertEx( IsDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( <flagName>, <isEnabled> )" );
	level.levelFlags[flagName] = true;

	level notify ( flagName );
}

levelFlagClear( flagName )
{
	assertEx( IsDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( <flagName>, <isEnabled> )" );
	level.levelFlags[flagName] = false;

	level notify ( flagName );
}

levelFlagWait( flagName )
{
	assertEx( IsDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( <flagName>, <isEnabled> )" );
	while ( !levelFlag( flagName ) )
		level waittill ( flagName );
}

levelFlagWaitOpen( flagName )
{
	assertEx( IsDefined( level.levelFlags[flagName] ), "levelFlag " + flagName + " referenced without being initialized; use levelFlagInit( <flagName>, <isEnabled> )" );
	while ( levelFlag( flagName ) )
		level waittill ( flagName );
}

inVirtualLobby()
{
	if ( !isdefined( level.virtualLobbyActive ) || ( level.virtualLobbyActive == 0 ) )
		return false;
	return true;
}

initGlobals()
{
	if( !IsDefined( level.global_tables ))
	{
		level.global_tables[ "killstreakTable" ] = SpawnStruct();
		level.global_tables[ "killstreakTable" ].path =						"mp/killstreakTable.csv";
		level.global_tables[ "killstreakTable" ].index_col =				0;
		level.global_tables[ "killstreakTable" ].ref_col =					1;
		level.global_tables[ "killstreakTable" ].name_col =					2;
		level.global_tables[ "killstreakTable" ].desc_col =					3;
		level.global_tables[ "killstreakTable" ].adrenaline_col =			4;
		level.global_tables[ "killstreakTable" ].earned_hint_col =			5;
		level.global_tables[ "killstreakTable" ].sound_col =				6;
		level.global_tables[ "killstreakTable" ].earned_dialog_col =		7;
		level.global_tables[ "killstreakTable" ].allies_dialog_col =		8;
		level.global_tables[ "killstreakTable" ].opfor_dialog_col =			9;
		level.global_tables[ "killstreakTable" ].enemy_use_dialog_col =		10;
		level.global_tables[ "killstreakTable" ].weapon_col =				11;
		level.global_tables[ "killstreakTable" ].score_col =				12;
		level.global_tables[ "killstreakTable" ].icon_col =					13;
		level.global_tables[ "killstreakTable" ].overhead_icon_col =		14;
		level.global_tables[ "killstreakTable" ].overhead_icon_col_plus1 = 	15;
		level.global_tables[ "killstreakTable" ].overhead_icon_col_plus2 = 	16;
		level.global_tables[ "killstreakTable" ].overhead_icon_col_plus3 = 	17;
		level.global_tables[ "killstreakTable" ].dpad_icon_col =			18;
		level.global_tables[ "killstreakTable" ].unearned_icon_col =		19;
		
		// TODO: put all other tables in here
	}
}

isKillStreakDenied()
{
	if ( inVirtualLobby() )
		return false;
	return ( self isEMPed() || self isAirDenied() );
}

isEMPed()
{
	if ( self.team == "spectator" )
		return false;
		
	if ( inVirtualLobby() )
		return false;
	
	if ( level.teamBased )
    {
    	return ( level.teamEMPed[self.team] || ( IsDefined( self.empGrenaded ) && self.empGrenaded ) );
    }
    else
    {
    	return ( ( IsDefined( level.empPlayer ) && level.empPlayer != self ) || ( IsDefined( self.empGrenaded ) && self.empGrenaded ) );
	}
}

isEMPedByKillstreak()
{
	if ( self.team == "spectator" )
		return false;
		
	if ( inVirtualLobby() )
		return false;
	
	if ( level.teamBased )
    {
    	return ( level.teamEMPed[self.team] );
    }
    else
    {
    	return ( ( IsDefined( level.empPlayer ) && level.empPlayer != self ) );
	}
}

isAirDenied()
{
	return false;
}

isNuked()
{
	if ( self.team == "spectator" )
		return false;
		
    return ( IsDefined( self.nuked ) );
}

getPlayerForGuid( guid )
{
	foreach ( player in level.players )
	{
		if ( player.guid == guid )
			return player;
	}
	
	return undefined;
}

/*
============= 
///ScriptDocBegin
"Name: teamPlayerCardSplash( <splash>, <owner>, <team> )"
"Summary: Shows the player card splash to the team."
"Module: Utility"
"CallOn: Level"
"MandatoryArg: <splash> The splash to show from splashtable.csv."
"MandatoryArg: <owner> The owner of the splash, or who called the killstreak."
"OptionalArg: <team> The team to show the splash to, if undefined then it shows to everyone."
"OptionalArg: <optionalNumber> An optional number that gets rolled into the splash text."
"Example: thread teamPlayerCardSplash( "used_ac130", player, player.team );"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
teamPlayerCardSplash( splash, owner, team, optionalNumber )
{
	if ( level.hardCoreMode )
		return;
		
	foreach ( player in level.players )
	{
		if ( IsDefined( team ) && player.team != team )
			continue;
		
		if ( !IsPlayer(player) )
			continue;
		
		player thread maps\mp\gametypes\_hud_message::playerCardSplashNotify( splash, owner, optionalNumber );
	}
}
	

/*
============= 
///ScriptDocBegin
"Name: isCACPrimaryWeapon( <weapName> )"
"Summary: Returns true if the passed in weapon name is in one of the primary weapon classes."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapName> The name of the weapon to check."
"Example: if( isCACPrimaryWeapon( sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isCACPrimaryWeapon( weapName )
{
	switch ( getWeaponClass( weapName ) )
	{
		case "weapon_smg":
		case "weapon_assault":
		case "weapon_riot":
		case "weapon_sniper":
		case "weapon_lmg":
		case "weapon_shotgun":
		case "weapon_heavy":
		case "weapon_special":
			return true;
		default:
			return false;
	}
}


/*
============= 
///ScriptDocBegin
"Name: isCACSecondaryWeapon( <weapName> )"
"Summary: Returns true if the passed in weapon name is in one of the secondary weapon classes."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <weapName> The name of the weapon to check."
"Example: if( isCACSecondaryWeapon( sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isCACSecondaryWeapon( weapName )
{
	switch ( getWeaponClass( weapName ) )
	{
		case "weapon_projectile":
		case "weapon_pistol":
		case "weapon_machine_pistol":
		case "weapon_sec_special":
			return true;
		default:
			return false;
	}
}


getLastLivingPlayer( team )
{
	livePlayer = undefined;

	foreach ( player in level.players )
	{
		if ( IsDefined( team ) && player.team != team )
			continue;

		if ( !isReallyAlive( player ) && !player maps\mp\gametypes\_playerlogic::maySpawn() )
			continue;
		
		assertEx( !IsDefined( livePlayer ), "getLastLivingPlayer() found more than one live player on team." );
		
		livePlayer = player;				
	}

	return livePlayer;
}


getPotentialLivingPlayers()
{
	livePlayers = [];

	foreach ( player in level.players )
	{
		if ( !isReallyAlive( player ) && !player maps\mp\gametypes\_playerlogic::maySpawn() )
			continue;
		
		livePlayers[livePlayers.size] = player;
	}

	return livePlayers;
}


waitTillRecoveredHealth( time, interval )
{
	self endon("death");
	self endon("disconnect");

	fullHealthTime = 0;
	
	if( !IsDefined( interval ) )
		interval = .05;

	if( !IsDefined( time ) )
		time = 0;
	
	while(1)
	{
		if ( self.health != self.maxhealth )
			fullHealthTime = 0;
		else
			fullHealthTime += interval;
		
		wait interval;
		
		if ( self.health == self.maxhealth && fullHealthTime >= time )
			break;
	}

	return;
}

attachmentMap_toUnique( attachmentName, weaponName )
{
	nameUnique = attachmentName;
	weaponName = getBaseWeaponName( weaponName );

	if( isLootWeapon( weaponName ) )
		weaponName = maps\mp\gametypes\_class::getBaseFromLootVersion( weaponName );

	AssertEx( IsDefined( level.attachmentMap_baseToUnique ), "attachmentMap_toUnique() called without first calling buildAttachmentMaps()" );
	
	// Check first to see if there is a unique name for this attachment by weapon name then check by weapon class
	if ( IsDefined( level.attachmentMap_baseToUnique[ weaponName ] ) && IsDefined( level.attachmentMap_baseToUnique[ weaponName ][ attachmentName ] ) )
	{
		nameUnique = level.attachmentMap_baseToUnique[ weaponName ][ attachmentName ];
	}
	else
	{
		weapClass = TableLookup( "mp/statstable.csv", 4, weaponName, 2 );
		if ( IsDefined( level.attachmentMap_baseToUnique[ weapClass ] ) && IsDefined( level.attachmentMap_baseToUnique[ weapClass ][ attachmentName ] ) )
		{
			nameUnique = level.attachmentMap_baseToUnique[ weapClass ][ attachmentName ];
		}
	}
	return nameUnique;
}

attachmentPerkMap( attachmentName )
{
	AssertEx( IsDefined( level.attachmentMap_attachToPerk ), "attachmentPerkMap() called without first calling buildAttachmentMaps()" );
	
	perk = undefined;
	
	if ( IsDefined( level.attachmentMap_attachToPerk[ attachmentName ] ) )
	{
		perk = level.attachmentMap_attachToPerk[ attachmentName ];
	}
	return perk;
}


isAttachmentSniperScopeDefault( weaponName, attachName )
{
	tokens = StrTok( weaponName, "_" );
	
	return isAttachmentSniperScopeDefaultTokenized( tokens, attachName );
}

isAttachmentSniperScopeDefaultTokenized( weaponTokens, attachName )
{
	AssertEx( IsArray( weaponTokens ), "isAttachmentSniperScopeDefaultTokenized() called with non array weapon name." );
	
	result = false;
	if ( weaponTokens.size && IsDefined( attachName ) )
	{
		idx = 0;
		if ( weaponTokens[ 0 ] == "alt" )
		{
			idx = 1;
		}
		
		if ( weaponTokens.size >= 3 + idx && ( weaponTokens[ idx ] == "iw5" || weaponTokens[ idx ] == "iw6" ) )
		{
			if ( weaponClass( weaponTokens[ idx ] + "_" + weaponTokens[ idx + 1 ] + "_" + weaponTokens[ idx + 2 ] ) == "sniper" )
			{
				result = weaponTokens[ idx + 1 ] + "scope" == attachName;
			}
		}
	}
	return result;
}

getWeaponAttachmentsBaseNames( weaponName )
{
	attachmentsBase = GetWeaponAttachments( weaponName );
	foreach ( idx, attachment in attachmentsBase )
	{
		attachmentsBase[ idx ] = attachmentMap_toBase( attachment );
	}
	
	return attachmentsBase;
}

getAttachmentListBaseNames()
{
	attachmentList = [];
	
	index = 0;
	attachmentName = TableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_INDEX, index, ATTACHMENT_COL_REF_BASE );
	
	while ( attachmentName != "" )
	{
		if ( !array_contains( attachmentList, attachmentName ) )
		{
			attachmentList[ attachmentList.size ] = attachmentName;
		}
		
		index++;
		attachmentName = TableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_INDEX, index, ATTACHMENT_COL_REF_BASE );
	}
	
	return attachmentList;
}

getAttachmentListUniqeNames()
{
	attachmentList = [];
	
	index = 0;
	attachmentName = TableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_INDEX, index, ATTACHMENT_COL_REF_UNIQUE );
	
	while ( attachmentName != "" )
	{
		AssertEx( !IsDefined( attachmentList[ attachmentName ] ), "Duplicate unique attachment reference name found in attachmentTable.csv" );
		
		attachmentList[ attachmentList.size ] = attachmentName;
		
		index++;
		attachmentName = TableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_INDEX, index, ATTACHMENT_COL_REF_UNIQUE );
	}
	
	return attachmentList;
}

buildAttachmentMaps()
{
	// ------------- //
	// Build map of unique attachment names to base attachment names
	// ------------- //
	AssertEx( !IsDefined( level.attachmentMap_uniqueToBase ), "buildAttachmentMaps() called when map already existed." );
	
	attachmentNamesUnique = getAttachmentListUniqeNames();
	
	level.attachmentMap_uniqueToBase = [];
		
	foreach ( uniqueName in attachmentNamesUnique )
	{	
		baseName = TableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_REF_UNIQUE, uniqueName, ATTACHMENT_COL_REF_BASE );
		
		AssertEx( IsDefined( baseName ) && baseName != "", "No base attachment name found in attachmentTable.csv for: " + baseName );
		
		// Only add attachments with unique names
		if ( uniqueName == baseName )
			continue;
		
		level.attachmentMap_uniqueToBase[ uniqueName ] = baseName;
	}
	
	// ------------- //
	// Builds map of base attachment name (by class or weapon name) to unique name
	// ------------- //
	AssertEx( !IsDefined( level.attachmentMap_baseToUnique ), "buildAttachmentMaps() called when map already existed." );
	
	// Collect weapon classes and weapon names from the attachment map table
	weaponClassesAndNames = [];
	idxRow = 1;
	classOrName = TableLookupByRow( ATTACHMAP_TABLE, idxRow, ATTACHMAP_COL_CLASS_OR_WEAP_NAME );
	while ( classOrName != "" )
	{
		weaponClassesAndNames[ weaponClassesAndNames.size ] = classOrName;
		
		idxRow++;
		classOrName = TableLookupByRow( ATTACHMAP_TABLE, idxRow, ATTACHMAP_COL_CLASS_OR_WEAP_NAME );
	}

	attachmentNameColumns = [];
	
	// Collect weapon attachment base names from the attachment map table
	idxCol = 1;
	attachTitle = TableLookupByRow( ATTACHMAP_TABLE, ATTACHMAP_ROW_ATTACH_BASE_NAME, idxCol );
	while( attachTitle != "" )
	{
		attachmentNameColumns[ attachTitle ] = idxCol;
		
		idxCol++;
		attachTitle = TableLookupByRow( ATTACHMAP_TABLE, ATTACHMAP_ROW_ATTACH_BASE_NAME, idxCol );
	}
	
	level.attachmentMap_baseToUnique = [];
	
	foreach ( classOrName in weaponClassesAndNames )
	{
		foreach ( attachment, column in attachmentNameColumns )
		{
			attachNameUnique = TableLookup( ATTACHMAP_TABLE, ATTACHMAP_COL_CLASS_OR_WEAP_NAME, classOrName, column );
			if ( attachNameUnique == "" )
				continue;
			
			if ( !IsDefined( level.attachmentMap_baseToUnique[ classOrName ]  ) )
			{
				level.attachmentMap_baseToUnique[ classOrName ] = [];
			}
			
			AssertEx( !IsDefined( level.attachmentMap_baseToUnique[ classOrName ][ attachment ] ), "Multiple entries found for uniqe attachment of base name: " + attachment );
			
			level.attachmentMap_baseToUnique[ classOrName ][ attachment ] = attachNameUnique;
		}
	}
	
	// ------------- //
	// Builds map of attachment unique name to perk name
	// ------------- //
	AssertEx( !IsDefined( level.attachmentMap_attachToPerk ), "buildAttachmentMaps() called when map already existed." );
	
	level.attachmentMap_attachToPerk = [];
		
	foreach ( attachName in attachmentNamesUnique )
	{	
		perkName = TableLookup( ATTACHMENT_TABLE, ATTACHMENT_COL_REF_UNIQUE, attachName, ATTACHMENT_COL_PERK );
		
		if ( perkName == "" )
			continue;
	
		level.attachmentMap_attachToPerk[ attachName ] = perkName;
	}
}

attachmentMap_toBase( attachmentName )
{
	AssertEx( IsDefined( level.attachmentMap_uniqueToBase ), "validateAttachment() called without first calling buildAttachmentMaps()" );
	
	if ( IsDefined( level.attachmentMap_uniqueToBase[ attachmentName ] ) )
	{
		attachmentName = level.attachmentMap_uniqueToBase[ attachmentName ];
	}
	
	return attachmentName;
}


_objective_delete( objID )
{
	objective_delete( objID);
	
	if ( !IsDefined( level.reclaimedReservedObjectives ) ) 
	{
		level.reclaimedReservedObjectives = [];
		level.reclaimedReservedObjectives[0] = objID;
	}
	else
	{
		level.reclaimedReservedObjectives[ level.reclaimedReservedObjectives.size ] = objID;		
	}
	
	/#
	// Determine if we've accidently deleted an objID twice (very bad)
	tempIDs = [];
	for( i = 0; i < 32; i++)
	{
		tempIDs[ i ] = false;
	}
	
	foreach( objectID in level.reclaimedReservedObjectives )
	{
		if( tempIDs[ objectID ] == true )
			AssertMsg( "Deleting Minimap objID (" + objectID + ") Twice" );
		else
			tempIDs[ objectID ] = true;
	}
	#/
}


touchingBadTrigger()
{
	killTriggers = getEntArray( "trigger_hurt", "classname" );	
	foreach ( trigger in killTriggers )
	{
		if ( self isTouching( trigger ) )
			return true;
	}

	radTriggers = getEntArray( "radiation", "targetname" );	
	foreach ( trigger in radTriggers )
	{
		if ( self isTouching( trigger ) )
			return true;
	}
	
	if ( getdvar("g_gametype") == "hp" && IsDefined( level.zone ) && IsDefined( level.zone.trig ) && self isTouching( level.zone.trig ) )
		return true;
	
	return false;
}
	
setThirdPersonDOF( isEnabled )
{
	if ( isEnabled )
		self setDepthOfField( 0, 110, 512, 4096, 6, 1.8 );
	else
		self setDepthOfField( 0, 0, 512, 512, 4, 0 );
}



killTrigger( pos, radius, height )
{
	trig = spawn( "trigger_radius", pos, 0, radius, height );
	
	/#
	if ( getdvar( "scr_killtriggerdebug" ) == "1" )
		thread killTriggerDebug( pos, radius, height );
	#/
	
	for ( ;; )
	{
		/#
		if ( getdvar( "scr_killtriggerradius" ) != "" )
			radius = int(getdvar( "scr_killtriggerradius" ));
		#/
		
		trig waittill( "trigger", player );
		
		if ( !isPlayer( player ) )
			continue;
		
		player suicide();
	}
}

findIsFacing( ent1, ent2, tolerance )//finds if ent1 is facing ent2
{
	facingCosine = Cos( tolerance );
	//facingCosine = .90630778703664996324255265675432; //tolerance = 25;
		
	ent1ForwardVector = anglesToForward( ent1.angles );
	ent1ToTarget = ent2.origin - ent1.origin;
	ent1ForwardVector *= (1,1,0);
	ent1ToTarget *= (1,1,0 );
	
	ent1ToTarget = VectorNormalize( ent1ToTarget );
	ent1ForwardVector = VectorNormalize( ent1ForwardVector );
	
	targetCosine = VectorDot( ent1ToTarget, ent1ForwardVector );
	
	if ( targetCosine >= facingCosine )
		return true;
	else
		return false;
}

drawLine( start, end, timeSlice, color )
{
	drawTime = int(timeSlice * 20);
	for( time = 0; time < drawTime; time++ )
	{
		line( start, end, color,false, 1 );
		wait ( 0.05 );
	}
}

drawSphere( origin, radius, timeSlice, color )
{
	drawTime = int(timeSlice * 20);
	for ( time = 0; time < drawTime; time++ )
	{
		Sphere( origin, radius, color );
		wait( 0.05 );
	}
}

/*setRecoilScale sets a relative recoil scaler 
  if you pass 20 it will be a 20% reduction in recoil 
  passing it no values will reset scale override to whatever
  value already exists in self.recoilScale or no override.
*/
setRecoilScale( scaler, scaleOverride )
{	
	if ( !IsDefined( scaler ) )
		scaler = 0;
	
	if ( !IsDefined( self.recoilScale ) )
		self.recoilScale = scaler;
	else
		self.recoilScale += scaler;
	
	//for scale override
	if ( IsDefined( scaleOverride ) )
	{		
		//will not override a lower value.
		if ( IsDefined( self.recoilScale) && scaleOverride < self.recoilScale )
			scaleOverride = self.recoilScale;	
		
		scale = 100 - scaleOverride;
	}
	else
		scale = 100 - self.recoilScale;
	
	if ( scale < 0 )
		scale = 0;
	
	if ( scale > 100 )
		scale = 100;	
	
	if ( scale == 100 )
	{
		self player_recoilScaleOff();
		return;
	}
	
	self player_recoilScaleOn( scale );
}


//Remove undefined and reorders elements
//preserves index
cleanArray( array )
{
	newArray = [];
	
	foreach ( key, elem in array )
	{
		if ( !isdefined( elem ) )
			continue;
			
		newArray[ newArray.size ] = array[ key ];
	}

	return newArray;
}

/#
killTriggerDebug( pos, radius, height )
{
	for ( ;; )
	{
		for ( i = 0; i < 20; i++ )
		{
			angle = i / 20 * 360;
			nextangle = (i+1) / 20 * 360;
			
			linepos = pos + (cos(angle) * radius, sin(angle) * radius, 0);
			nextlinepos = pos + (cos(nextangle) * radius, sin(nextangle) * radius, 0);
			
			line( linepos, nextlinepos );
			line( linepos + (0,0,height), nextlinepos + (0,0,height) );
			line( linepos, linepos + (0,0,height) );
		}
		wait .05;
	}
}
#/

/*
============= 
///ScriptDocBegin
"Name: notUsableForJoiningPlayers( <player> )"
"Summary:  Makes sure newly joining players can't use already deployed equipment."
"Module: Entity"
"CallOn: An Entity"
"MandatoryArg: <player> The owner of the device."
"Example: self thread notUsableForJoiningPlayers( owner );"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
notUsableForJoiningPlayers( owner )
{
	self notify ( "notusablejoiningplayers" );

	self endon ( "death" );
	level endon ( "game_ended" );
	owner endon ( "disconnect" );
	owner endon ( "death" );
	self endon ( "notusablejoiningplayers" );

	// as players join they need to be set to not be able to use this
	while( true )
	{
		level waittill( "player_spawned", player );
		if( IsDefined( player ) && player != owner )
		{
			self disablePlayerUse( player );
		}
	}
}

/*
============= 
///ScriptDocBegin
"Name: isStrStart( <string>, <subStr> )"
"Summary:  Returns true or false if the strings starts with the sub string passed in."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <string> The base string to check against."
"MandatoryArg: <subStr> The sub string to check for."
"Example: if( isStrStart( sWeapon, "gl_" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isStrStart( string, subStr )
{
	return ( GetSubStr( string, 0, subStr.size ) == subStr );
}

disableAllStreaks()
{
	level.killstreaksDisabled = true;
}

enableAllStreaks()
{
	level.killstreaksDisabled = undefined;
}

/*
============= 
///ScriptDocBegin
"Name: validateUseStreak( <optional_streakname>, <disable_print_output> )"
"Summary:  Returns true or false if the killstreak is usable right now."
"Module: Utility"
"CallOn: Player"
"OptionalArg: <optional_streakname> A specific streakname to check"
"OptionalArg: <disable_print_output> If true, won't print out any warnings"
"Example: if( self validateUseStreak() )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
validateUseStreak( optional_streakname, disable_print_output ) // self == player
{
	if ( IsDefined(optional_streakname) )
	{
		streakName = optional_streakname;
	}
	else
	{
		self_pers_killstreaks = self.pers["killstreaks"];
		streakName = self_pers_killstreaks[ self.killstreakIndexWeapon ].streakName;
	}

	if ( IsDefined( level.killstreaksDisabled ) && level.killstreaksDisabled )
		return false;
	
	if( !self IsOnGround() && isRideKillstreak( streakName ) )
		return false;

	if ( self isUsingRemote() || self isInRemoteTransition() )
		return false;

	if( IsDefined( self.selectingLocation ) )
		return false;

	if( shouldPreventEarlyUse( streakName ) && level.killstreakRoundDelay )
	{
		timePassed = 0;
		if(IsDefined(level.prematch_done_time))
			timePassed = (GetTime() - level.prematch_done_time)/1000;
		
		if( timePassed < level.killstreakRoundDelay )
		{
			timeUntilUse = int(level.killstreakRoundDelay - timePassed + 0.5);
			if(!timeUntilUse)
				timeUntilUse = 1;
			if ( !(IsDefined(disable_print_output) && disable_print_output) )
				self IPrintLnBold( &"MP_UNAVAILABLE_FOR_N", timeUntilUse );
			return false;
		}
	}

//	if( IsDefined( self.nuked ) && self.nuked )
//	{
//		// we shouldn't stop you from using non electronic killstreaks
//		if( isKillstreakAffectedByEMP( streakName ) )
//		{
//			if ( !(IsDefined(disable_print_output) && disable_print_output) )
//				self IPrintLnBold( &"MP_UNAVAILABLE_FOR_N_WHEN_NUKE", level.nukeEmpTimeRemaining );
//			return false;
//		}
//	}

	if ( self isEMPed() )
	{
		if ( !( IsDefined( disable_print_output ) && disable_print_output ) )
		{
			if ( IsDefined( level.empTimeRemaining ) && level.empTimeRemaining > 0 )
				self IPrintLnBold( &"MP_UNAVAILABLE_FOR_N_WHEN_EMP", level.empTimeRemaining );
			else if ( IsDefined( self.empEndTime ) && Int( ( self.empEndTime - GetTime()  ) / 1000 ) > 0 )
				self IPrintLnBold( &"MP_UNAVAILABLE_FOR_N", Int( ( self.empEndTime - GetTime() ) / 1000 ) );
		}
		return false;
	}
	
//	if ( self isAirDenied() )
//	{
//		// allow air_superiority to be called against itself
//		if ( isFlyingKillstreak( streakName ) )
//		{
//			//NOTE: EMP's in MT games depend on the fact that one team cannot cast an EMP while emp'd.  
//			//See EMP_JamTeams( ownerTeam ) if this ever changes...
//			if ( !(IsDefined(disable_print_output) && disable_print_output) )
//				self IPrintLnBold( &"KILLSTREAKS_UNAVAILABLE_WHEN_AA"  );
//			return false;
//		}
//	}

	if( self IsUsingTurret() && ( isRideKillstreak( streakName ) || isCarryKillstreak( streakName ) ) )
	{
		if ( !(IsDefined(disable_print_output) && disable_print_output) )
		self IPrintLnBold( &"MP_UNAVAILABLE_USING_TURRET" );
		return false;
	}

	if( IsDefined( self.lastStand ) && !self _hasPerk( "specialty_finalstand" ) )
	{
		if ( !(IsDefined(disable_print_output) && disable_print_output) )
		self IPrintLnBold( &"MP_UNAVILABLE_IN_LASTSTAND" );
		return false;
	}

	if( !self isWeaponEnabled() )
		return false;

//	if( IsDefined( level.civilianJetFlyBy ) && isFlyingKillstreak( streakName ) )
//	{
//		if ( !(IsDefined(disable_print_output) && disable_print_output) )
//		self IPrintLnBold( &"KILLSTREAKS_CIVILIAN_AIR_TRAFFIC" );
//		return false;
//	}

	return true;
}


/*
============= 
///ScriptDocBegin
"Name: isRideKillstreak( <streakName> )"
"Summary:  Returns true or false if the killstreak is one that you ride in."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( isRideKillstreak( "ac130" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isRideKillstreak( streakName )
{
	switch( streakName )
	{
		case "missile_strike":
		case "orbital_strike":
		case "orbital_strike_laser":
		case "orbital_strike_chem":
		case "orbital_strike_laser_chem":
		case "orbital_strike_cluster":
		case "orbital_strike_drone":
		case "mp_solar":
		case "mp_dam":
		case "mp_levity":
		case "mp_terrace":
		case "mp_detroit":
		case "orbitalsupport":
		case "mp_recreation":
		case "recon_ugv":
		case "warbird":
		case "assault_ugv":
			return true;
	
		default:
			return false;
	}
}

/*
============= 
///ScriptDocBegin
"Name: isCarryKillstreak( <streakName> )"
"Summary:  Returns true or false if the killstreak is one that you carry."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( isCarryKillstreak( "sentry" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isCarryKillstreak( streakName )
{
	switch( streakName )
	{
	case "sentry":		
	case "deployable_ammo":
	case "deployable_grenades":
	case "deployable_exp_ammo":
	case "remote_mg_sentry_turret":
		return true;

	default:
		return false;
	}
}


/*
============= 
///ScriptDocBegin
"Name: shouldPreventEarlyUse( <streakName> )"
"Summary:  Returns true if the killstreak should be prvent from being used at the start of a round."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( shouldPreventEarlyUse( "predator_missile" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
shouldPreventEarlyUse( streakName )
{
	switch ( streakName )
	{
		case "warbird":
		case "missile_strike":
		case "orbitalsupport":
		case "strafing_run_airstrike":
		case "orbital_strike_laser":
			return true;
	}

	return false;
}

/*
============= 
///ScriptDocBegin
"Name: isKillstreakAffectedByEMP( <streakName> )"
"Summary:  Returns true or false if the killstreak is able to be jammed by EMP."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( isKillstreakAffectedByEMP( "guard_dog" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isKillstreakAffectedByEMP( streakName )
{
	switch( streakName )
	{
	case "deployable_ammo":
	case "deployable_grenades":
	case "deployable_juicebox":
	case "placeable_barrier":
	case "agent":
	case "recon_agent":
	case "high_value_target":
	case "eyes_on":
	case "speed_boost":
	case "refill_grenades":
		return false;

	default:
		return true;
	}
}

isKillstreakAffectedByJammer( streakName )
{
	return (isKillstreakAffectedByEMP( streakName ) && !isFlyingKillstreak( streakName ));
}

/*
============= 
///ScriptDocBegin
"Name: isFlyingKillstreak( <streakName> )"
"Summary:  Returns true or false if the killstreak is a flying killstreak."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( isFlyingKillstreak( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isFlyingKillstreak( streakName )
{
	switch( streakName )
	{
		case "airdrop_sentry_minigun":
		case "airdrop_assault":
		case "airdrop_support":
		case "orbitalsupport":
		case "orbital_carepackag":
		case "missile_strike":
		case "orbital_strike":
		case "orbital_strike_laser":
		case "orbital_strike_chem":
		case "orbital_strike_laser_chem":
		case "orbital_strike_cluster":
		case "orbital_strike_drone":
			return false;
	
		default:
			return true;
	}
}

/*
============= 
///ScriptDocBegin
"Name: isAllTeamStreak( <streakName> )"
"Summary:  Returns true or false if the killstreak is an all team killstreak."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( isAllTeamStreak( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isAllTeamStreak( streakName )
{
	isTeamStreak = getKillstreakAllTeamStreak( streakName );
	
	if ( !IsDefined( isTeamStreak ) )
		return false;
	
	if ( Int(isTeamStreak) == 1 )
		return true;
	
	return false;
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakRowNum( <streakName> )"
"Summary:  Returns the row number of a killstreak from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakRowNum( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakRowNum( streakName )
{
	return TableLookupRowNum( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakIndex( <streakName> )"
"Summary:  Returns the index column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakIndex( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakIndex( streakName )
{
	indexString = TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].index_col );
	if( indexString == "" )
		index = -1;
	else
		index = int( indexString );
	return index;
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakReference( <streakName> )"
"Summary:  Returns the reference column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakReference( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakReference( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].ref_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakName( <streakName> )"
"Summary:  Returns the name column (as IString) from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakName( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakName( streakName )
{
	return TableLookupIString( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].name_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakDescription( <streakName> )"
"Summary:  Returns the description column (as IString) from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakDescription( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakDescription( streakName )
{
	return TableLookupIString( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].desc_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakKills( <streakName> )"
"Summary:  Returns the kills column (as IString) from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakKills( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakKills( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].adrenaline_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakEarnedHint( <streakName> )"
"Summary:  Returns the earned hint column (as IString) from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakEarnedHint( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakEarnedHint( streakName )
{
	return TableLookupIString( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].earned_hint_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakSound( <streakName> )"
"Summary:  Returns the sound column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakSound( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakSound( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].sound_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakEarnedDialog( <streakName> )"
"Summary:  Returns the earned dialog column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakEarnedDialog( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakEarnedDialog( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].earned_dialog_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakAlliesDialog( <streakName> )"
"Summary:  Returns the allies dialog column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakAlliesDialog( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakAlliesDialog( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].allies_dialog_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakEnemyDialog( <streakName> )"
"Summary:  Returns the enemy dialog column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakEnemyDialog( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakEnemyDialog( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].enemy_dialog_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakEnemyUseDialog( <streakName> )"
"Summary:  Returns the enemy use dialog column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakEnemyUseDialog( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakEnemyUseDialog( streakName )
{
	return int( TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].enemy_use_dialog_col ) );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakWeapon( <streakName> )"
"Summary:  Returns the weapon column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakWeapon( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakWeapon( streakName, modules )
{
	if ( IsDefined( modules ) && modules.size > 0 )
	{
		moduleWeapon = _getModuleKillstreakWeapon( streakName, modules );
		if ( IsDefined( moduleWeapon ) )
	    	return moduleWeapon;
	}
	
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].weapon_col );
}

_getModuleKillstreakWeapon( streakName, modules )
{
	foreach ( module in modules )
	{
		switch ( module )
		{
			case "warbird_ai_attack":
			case "warbird_ai_follow":
				if ( issubstr ( streakName, "warbird" ) )
					return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, "uav", level.global_tables[ "killstreakTable" ].weapon_col );
				break;
			case "assault_ugv_ai":
				if ( issubstr ( streakName, "ugv" ) )
					return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, "uav", level.global_tables[ "killstreakTable" ].weapon_col );
				break;
			case "turretheadenergy_mp":
			case "turretheadrocket_mp":
			case "turretheadmg_mp":
				if ( issubstr ( streakName, "ripped_turret" ) )
					return module;
				break;
			default:
				break;
		}
	}
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakScore( <streakName> )"
"Summary:  Returns the score column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakScore( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakScore( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].score_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakIcon( <streakName> )"
"Summary:  Returns the icon column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakIcon( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakIcon( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].icon_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakOverheadIcon( <streakName> )"
"Summary:  Returns the overhead icon column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakOverheadIcon( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakOverheadIcon( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].overhead_icon_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakDpadIcon( <streakName> )"
"Summary:  Returns the dpad icon column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakDpadIcon( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakDpadIcon( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].dpad_icon_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakUnearnedIcon( <streakName> )"
"Summary:  Returns the unearned icon column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakUnearnedIcon( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakUnearnedIcon( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].unearned_icon_col );
}

/*
============= 
///ScriptDocBegin
"Name: getKillstreakAllTeamStreak( <streakName> )"
"Summary:  Returns the all team streak column from the killstreakTable."
"Module: Utility"
"CallOn: None"
"MandatoryArg: <streakName> The killstreak name to check against."
"Example: if( getKillstreakAllTeamStreak( "helicopter" ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getKillstreakAllTeamStreak( streakName )
{
	return TableLookup( level.global_tables[ "killstreakTable" ].path, level.global_tables[ "killstreakTable" ].ref_col, streakName, level.global_tables[ "killstreakTable" ].all_team_streak_col );
}

/*
============= 
///ScriptDocBegin
"Name: currentActiveVehicleCount( <extra> )"
"Summary:  Returns the count of active vehicles currently in the match."
"OptionalArg: <extra> The number of extra vehicles to add on to the count."
"Module: Utility"
"CallOn: None"
"Example: if( currentActiveVehicleCount( level.fauxVehicleCount ) > 8 )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
currentActiveVehicleCount( extra )
{
	if( !IsDefined( extra ) )
		extra = 0;

	count = extra;
	if( IsDefined( level.helis ) )
		count += level.helis.size;
	if( IsDefined( level.littleBirds ) )
		count += level.littleBirds.size;
	if( IsDefined( level.ugvs ) )
		count += level.ugvs.size;

	if( IsDefined( level.isHorde ) && level.isHorde )
	{
		if( IsDefined( level.flying_attack_drones ) )
			count += level.flying_attack_drones.size;
		if( IsDefined( level.trackingDrones ) )
			count += level.trackingDrones.size;
	}

	return count;
}

/*
============= 
///ScriptDocBegin
"Name: maxVehiclesAllowed()"
"Summary:  Returns the number of max vehicles allowed at once in a match."
"Module: Utility"
"CallOn: None"
"Example: if( currentActiveVehicleCount() > maxVehiclesAllowed() )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
maxVehiclesAllowed()
{
	return MAX_VEHICLES;
}

/*
============= 
///ScriptDocBegin
"Name: incrementFauxVehicleCount()"
"Summary:  Increments the level.fauxVehicleCount variable."
"Module: Utility"
"CallOn: None"
"Example: incrementFauxVehicleCount();"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
incrementFauxVehicleCount()
{
	level.fauxVehicleCount++;
}

/*
============= 
///ScriptDocBegin
"Name: decrementFauxVehicleCount()"
"Summary:  Decrements the level.fauxVehicleCount variable."
"Module: Utility"
"CallOn: None"
"Example: decrementFauxVehicleCount();"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
decrementFauxVehicleCount()
{
	level.fauxVehicleCount--;
	if( level.fauxVehicleCount < 0 )
		level.fauxVehicleCount = 0;
}

/*
============= 
///ScriptDocBegin
"Name: lightWeightScalar()"
"Summary:  Returns the movement speed scalar value for Lightweight."
"Module: Utility"
"CallOn: None"
"Example: self.moveSpeedScaler = lightWeightScalar();"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
lightWeightScalar( power )
{
	return LIGHTWEIGHT_SCALAR;
}

allowTeamChoice()
{
	/#	// this needs to be a non-dev block for the compass reports
	if ( GetDvarInt( "scr_skipclasschoice", 0 ) > 0 )
		return 0;
	#/

	allowed = int( tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 4 ) );
	assert( IsDefined( allowed ) );
	
	return allowed;
}

allowClassChoice()
{
	/#	// this needs to be a non-dev block for the compass reports
	if ( GetDvarInt( "scr_skipclasschoice", 0 ) > 0 )
		return 0;
	#/

	allowed = int( tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 5 ) );
	assert( IsDefined( allowed ) );
	
	return allowed;	
}

showGenericMenuOnMatchStart()
{
	if ( allowTeamChoice() || allowClassChoice() )
		return 0;
	
	needGenericMenu = int( tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 7 ) );
	assert( IsDefined( needGenericMenu ) );
	
	return needGenericMenu;
}

/*
============= 
///ScriptDocBegin
"Name: isBuffEquippedOnWeapon( <buffRef>, <weaponRef> )"
"Summary: Returns true if the weapon buff is equipped on this weapon."
"Module: Utility"
"CallOn: player"
"MandatoryArg: <buffRef> The name of the weapon buff to check."
"MandatoryArg: <weaponRef> The name of the weapon to check."
"Example: if( isBuffEquippedOnWeapon( "specialty_bulletpenetration", sWeapon ) )"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
isBuffEquippedOnWeapon( buffRef, weaponRef )
{
	return false;
}


setCommonRulesFromMatchRulesData( skipFriendlyFire )
{
	//	game options
	timeLimit = GetMatchRulesData( "commonOption", "timeLimit" );
	SetDynamicDvar( "scr_" + level.gameType + "_timeLimit", timeLimit );
	registerTimeLimitDvar( level.gameType, timeLimit );
	
	scoreLimit = GetMatchRulesData( "commonOption", "scoreLimit" );
	SetDynamicDvar( "scr_" + level.gameType + "_scoreLimit", scoreLimit );
	registerScoreLimitDvar( level.gameType, scoreLimit );
	
	SetDynamicDvar( "scr_game_matchstarttime", GetMatchRulesData( "commonOption", "preMatchTimer" ) );
	SetDynamicDvar( "scr_game_roundstarttime", GetMatchRulesData( "commonOption", "preRoundTimer" ) );
	SetDynamicDvar( "scr_game_suicidespawndelay", GetMatchRulesData( "commonOption", "suicidePenalty" ) );
	SetDynamicDvar( "scr_team_teamkillspawndelay", GetMatchRulesData( "commonOption", "teamKillPenalty" ) );
	SetDynamicDvar( "scr_team_teamkillkicklimit", GetMatchRulesData( "commonOption", "teamKillKickLimit" ) );
	
	//	player options
	numLives = GetMatchRulesData( "commonOption", "numLives" );
	SetDynamicDvar( "scr_" + level.gameType + "_numLives", numLives );
	registerNumLivesDvar( level.gameType, numLives );
	
	SetDynamicDvar( "scr_player_maxhealth", GetMatchRulesData( "commonOption", "maxHealth" ) );
	SetDynamicDvar( "scr_player_healthregentime", GetMatchRulesData( "commonOption", "healthRegen" ) );
	
	//	cut for MW3
	//level.matchRules_damageMultiplier = GetMatchRulesData( "commonOption", "damageMultiplier" );
	//level.matchRules_vampirism = GetMatchRulesData( "commonOption", "vampirism" );
	level.matchRules_damageMultiplier = 0;
	level.matchRules_vampirism = 0;
	
	//	team options
	SetDynamicDvar( "scr_game_spectatetype", GetMatchRulesData( "commonOption", "spectateModeAllowed" ) );
	SetDynamicDvar( "scr_game_lockspectatorpov", GetMatchRulesData( "commonOption", "spectateModePOV" ) );
	SetDynamicDvar( "scr_game_allowkillcam", GetMatchRulesData( "commonOption", "showKillcam" ) );
	SetDynamicDvar( "scr_game_forceuav", GetMatchRulesData( "commonOption", "radarAlwaysOn" ) );
	SetDynamicDvar( "scr_" + level.gameType + "_playerrespawndelay", GetMatchRulesData( "commonOption", "respawnDelay" ) );
	SetDynamicDvar( "scr_" + level.gameType + "_waverespawndelay", GetMatchRulesData( "commonOption", "waveRespawnDelay" ) );
	SetDynamicDvar( "scr_player_forcerespawn", GetMatchRulesData( "commonOption", "forceRespawn" ) );
	
	//	gameplay options
	level.matchRules_allowCustomClasses = GetMatchRulesData( "commonOption", "allowCustomClasses" );
	level.customClassPickCount = GetMatchRulesData( "commonOption", "classPickCount" );
	
	SetDynamicDvar( "scr_game_high_jump", GetMatchRulesData( "commonOption", "highJump" ) );
	SetDynamicDvar( "jump_slowdownEnable", GetDvar( "scr_game_high_jump" ) == "0" );
	//SetDynamicDvar( "scr_game_hardpoints", GetMatchRulesData( "commonOption", "allowKillstreaks" ) ); // allowKillstreaks no longer in UI, can use custom class restrictions instead
	SetDynamicDvar( "scr_game_hardpoints", 1 );
	//SetDynamicDvar( "scr_game_perks", GetMatchRulesData( "commonOption", "allowPerks" ) ); // allowPerks no longer in UI, can use custom class restrictions instead
	SetDynamicDvar( "scr_game_perks", 1 );
	SetDynamicDvar( "g_hardcore", GetMatchRulesData( "commonOption", "hardcoreModeOn" ) );
	SetDynamicDvar( "scr_thirdPerson", GetMatchRulesData( "commonOption", "forceThirdPersonView" ) );
	SetDynamicDvar( "camera_thirdPerson", GetMatchRulesData( "commonOption", "forceThirdPersonView" ) );	// This is what the game uses, set both anyway so they're in sync.
	SetDynamicDvar( "scr_game_onlyheadshots", GetMatchRulesData( "commonOption", "headshotsOnly" ) );
	if ( !IsDefined( skipFriendlyFire ) )
		SetDynamicDvar( "scr_team_fftype", GetMatchRulesData( "commonOption", "ffType" ) );		
	SetDynamicDvar( "scr_game_killstreakdelay", GetMatchRulesData( "commonOption", "streakGracePeriod" ) );

	level.dynamicEventsType = GetMatchRulesData( "commonOption", "dynamicEventsType" );
	level.mapStreaksDisabled = GetMatchRulesData( "commonOption", "mapStreaksDisabled" );
	level.chatterDisabled = GetMatchRulesData( "commonOption", "chatterDisabled" );
	level.announcerDisabled = GetMatchRulesData( "commonOption", "announcerDisabled" );
	level.matchRules_switchTeamDisabled = GetMatchRulesData( "commonOption", "switchTeamDisabled" );
	level.grenadeGracePeriod = GetMatchRulesData( "commonOption", "grenadeGracePeriod" );

	//	hardcore overrides these options
	if ( GetMatchRulesData( "commonOption", "hardcoreModeOn" ) )
	{
		SetDynamicDvar( "scr_team_fftype", 1 );
		SetDynamicDvar( "scr_player_maxhealth", 30 );
		SetDynamicDvar( "scr_player_healthregentime", 0 );
		SetDynamicDvar( "scr_player_respawndelay", 10 ); // hardcore_settings.cfg only sets this
		//SetDynamicDvar( "scr_" + level.gameType + "_playerrespawndelay", 10 );
		SetDynamicDvar( "scr_game_allowkillcam", 0 );
		SetDynamicDvar( "scr_game_forceuav", 0 );
	}

	SetDvar( "bg_compassShowEnemies", getDvar( "scr_game_forceuav" ) );
}


reInitializeMatchRulesOnMigration()
{
	assert( isUsingMatchRulesData() );
	assert( IsDefined( level.initializeMatchRules ) );
	
	while(1)
	{
		level waittill( "host_migration_begin" );
		[[level.initializeMatchRules]]();
	}
}

// this should only be called on killstreaks when the player gets in, it'll then reset to the last thermal that was set
//	if the thermal that was set is the game thermal then this is not necessary unless they can switch thermals while in
reInitializeThermal( ent ) // self == player
{
	self endon( "disconnect" );
	
	if( IsDefined( ent ) )
		ent endon( "death" );

	while( true )
	{
		level waittill( "host_migration_begin" );
		if( IsDefined( self.lastVisionSetThermal ) )
			self VisionSetThermalForPlayer( self.lastVisionSetThermal, 0 );
	}
}

/#
reInitializeDevDvarsOnMigration()
{
	level notify( "reInitializeDevDvarsOnMigration" );
	level endon( "reInitializeDevDvarsOnMigration" );

	while(1)
	{
		level waittill( "host_migration_begin" );

		// put all dev dvars that need to be reset after a host migration here
		SetDevDvarIfUninitialized( "scr_emp_timeout", 15.0 );
		SetDevDvarIfUninitialized( "scr_nuke_empTimeout", 60.0 );
		SetDevDvarIfUninitialized( "scr_remote_turret_timeout", 60.0 );
		SetDevDvarIfUninitialized( "scr_devchangetimelimit", -1 );
	}
}
#/

GetMatchRulesSpecialClass( team, index )
{
	class = [];

	// Primary	
	class["loadoutPrimary"] 				= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "weapon" );
	class["loadoutPrimaryAttachment"] 		= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "attachment", 0 );
	class["loadoutPrimaryAttachment2"] 		= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "attachment", 1 );
	class["loadoutPrimaryAttachment3"] 		= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "attachment", 2 );
	class["loadoutPrimaryCamo"] 			= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "camo" );
	class["loadoutPrimaryReticle"] 			= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "reticle" );
	
	// Secondary
	class["loadoutSecondary"] 				= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "weapon" );
	class["loadoutSecondaryAttachment"] 	= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "attachment", 0 );
	class["loadoutSecondaryAttachment2"] 	= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "attachment", 1 );
	class["loadoutSecondaryAttachment3"] 	= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "attachment", 2 );
	class["loadoutSecondaryCamo"] 			= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "camo" );
	class["loadoutSecondaryReticle"] 		= getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "reticle" );

	// Equipment
	class["loadoutEquipment"] 				= getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 0, "equipment" );
	class["loadoutEquipmentExtra"] 			= getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 0, "extra" );
	class["loadoutOffhand"] 				= getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 1, "equipment" );
	class["loadoutOffhandExtra"] 			= getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 1, "extra" );

	// Perks
	for ( i = 0; i < 6; i++ )
	{
		class["loadoutPerks"][i] 			= getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", i );
	}
	
	// Wildcards
	for ( i = 0; i < 3; i++ )
	{
		class["loadoutWildcards"][i] 		= getMatchRulesData( "defaultClasses", team, index, "class", "wildcardSlots", i );
	}
	
	// Streaks
	for ( i = 0; i < 4; i++ )
	{
		class["loadoutKillstreaks"][i] 		= getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", i, "streak" );
		for ( j = 0; j < 3; j++ )
		{
			class["loadoutKillstreakModules"][i][j] = getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", i, "modules", j );
		}
	}

	class["loadoutJuggernaut"] 				= getMatchRulesData( "defaultClasses", team, index, "juggernaut" );
		
	return class;
}


recipeClassApplyJuggernaut( removeJuggernaut )
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	if ( !isdefined ( level.isHorde ) )
	{
		//	wait until giveloadout() and spawnPlayer() are complete
		if ( level.inGracePeriod && !self.hasDoneCombat )
			self waittill( "applyLoadout" );
		else
			self waittill( "spawned_player" );
	}
	else
		self waittill ( "applyLoadout" );
	
	if ( removeJuggernaut )
	{
		self notify( "lost_juggernaut" );
		wait( 0.5 );
	}		
	
	if ( !IsDefined( self.isJuiced ) )
	{
		self.moveSpeedScaler = 0.7;
		self maps\mp\gametypes\_weapons::updateMoveSpeedScale();
	}
	self.juggMoveSpeedScaler = 0.7;	// for unset juiced
	self disableWeaponPickup();
	
	if ( !getDvarInt( "camera_thirdPerson" ) && !isdefined ( level.isHorde ) )
	{
		self.juggernautOverlay = newClientHudElem( self );
		self.juggernautOverlay.x = 0;
		self.juggernautOverlay.y = 0;
		self.juggernautOverlay.alignX = "left";
		self.juggernautOverlay.alignY = "top";
		self.juggernautOverlay.horzAlign = "fullscreen";
		self.juggernautOverlay.vertAlign = "fullscreen";
		self.juggernautOverlay SetShader( level.juggSettings[ "juggernaut" ].overlay, 640, 480 );
		self.juggernautOverlay.sort = -10;
		self.juggernautOverlay.archived = true;
		self.juggernautOverlay.hidein3rdperson = true;
	}

	self thread maps\mp\killstreaks\_juggernaut::juggernautSounds();
	
	if ( level.gameType != "jugg" || ( IsDefined( level.matchRules_showJuggRadarIcon ) && level.matchRules_showJuggRadarIcon ) )
		self setPerk( "specialty_radarjuggernaut", true, false );	
		
	//	portable radar for Team Juggernaut 
	if ( IsDefined( self.isJuggModeJuggernaut ) && self.isJuggModeJuggernaut )
	{
		portable_radar = spawn( "script_model", self.origin );
		portable_radar.team = self.team;

		portable_radar makePortableRadar( self );
		self.personalRadar = portable_radar;

		self thread maps\mp\killstreaks\_juggernaut::radarMover( portable_radar );
	}		

	level notify( "juggernaut_equipped", self );

	self thread maps\mp\killstreaks\_juggernaut::juggRemover();	
}

/*
============= 
///ScriptDocBegin
"Name: updateSessionState( <state>, <icon> )"
"Summary: Updates the player to the state passed in."
"Module: Utility"
"CallOn: player"
"MandatoryArg: <state> The state to update to, either playing, dead, spectator, or intermission."
"MandatoryArg: <icon> The status icon to show while in this state."
"Example: self updateSessionState( "dead" );"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
updateSessionState( sessionState )
{
	assert( sessionState == "playing" || sessionState == "dead" || sessionState == "spectator" || sessionState == "intermission" );
	self.sessionstate = sessionState;
	self SetClientOmnvar( "ui_session_state", sessionState );
}

/*
============= 
///ScriptDocBegin
"Name: cac_getCustomClassLoc()"
"Summary: Returns which custom class is being used (customClasses or privateMatchCustomClasses)."
"Module: Utility"
"Example: self GetPlayerData( cac_getCustomClassLoc(), classIndex, "perks", 0 );"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
cac_getCustomClassLoc()
{
	if (isdefined(level.forceCustomClassLoc))
		return level.forceCustomClassLoc;
	
	if( GetDvarInt( "xblive_privatematch" ) || ( IsSystemLink() ) )
	{
		return "privateMatchCustomClasses";
	}
	else
	{
		return "customClasses";
	}
}

/*
============= 
///ScriptDocBegin
"Name: getClassIndex( <className> )"
"Summary: Returns the class index number from the level.classMap array."
"Module: Utility"
"Example: index = getClassIndex( "custom1" );"
"SPMP: multiplayer"
///ScriptDocEnd
============= 
*/
getClassIndex( className )
{
	assert( IsDefined( level.classMap[className] ) );

	return level.classMap[className];
}

/* 
 * LAST STAND STUFF
 * added by Carlos... should be moved into its own file if we decide to move forward with it
 */
 
isTeamInLastStand()
{
	myteam = getLivingPlayers( self.team );
	foreach ( guy in myteam )
	{
		if ( guy != self && ( !isdefined( guy.lastStand ) || !guy.lastStand ) )
		{
			return false;
		}
	}
	return true;
}

killTeamInLastStand( team )
{
	myteam = getLivingPlayers( team );
	foreach ( guy in myteam )
	{
		if ( isdefined( guy.lastStand ) && guy.lastStand )
		{
			guy thread maps\mp\gametypes\_damage::dieAfterTime( RandomIntRange( 1, 3 ) ); // delay so we don't get a bunch of guys dying at once
		}
	}
}

switch_to_last_weapon( lastWeapon )
{
	if ( !IsAI( self ) )
	{
		self SwitchToWeapon( lastWeapon );
	}
	else
	{
		// Bots/Agents handle weapon switching internally, so they just need to set their scripted weapon back to "none"
		self SwitchToWeapon( "none" );
	}
}

/* 
=============
///ScriptDocBegin
"Name: IsAITeamParticipant( <ent> )"
"Summary: Checks if the entity is an AI (bot or agent) that can participate in team player game activities"
"Summary: This would be something like checking to make sure a certain number of teammates are guarding a flag in domination, etc."
"Summary: It does not include "squadmember" type agents, who can participate in the game but shouldn't be counted as an individual member of a team"
"CallOn: Any entity"
"MandatoryArg: <ent> The entity in question."
"Example: if ( IsAITeamParticipant( ent ) )"
///ScriptDocEnd
============
 */
IsAITeamParticipant( ent )
{
	// Do not add IsDefined(ent.agent_teamParticipant here, it just masks the real issue.  Find out why ent.agent_teamParticipant is undefined (since it never should be)
	if ( IsAgent( ent ) && ent.agent_teamParticipant == true )
		return true;
	
	if ( IsBot( ent ) )
		return true;
	
	return false;
}

/* 
=============
///ScriptDocBegin
"Name: IsTeamParticipant( <ent> )"
"Summary: Checks if the entity is a player, bot, or agent that can participate in team player game activities"
"Summary: This would be something like checking to make sure a certain number of teammates are guarding a flag in domination, etc."
"Summary: It does not include "squadmember" type agents, who can participate in the game but shouldn't be counted as an individual member of a team"
"CallOn: Any entity"
"MandatoryArg: <ent> The entity in question."
"Example: if ( IsTeamParticipant( ent ) )"
///ScriptDocEnd
============
 */
IsTeamParticipant( ent )
{
	if ( IsAITeamParticipant( ent ) )
		return true;
	
	if ( IsPlayer( ent ) )
		return true;
	
	return false;
}

/* 
=============
///ScriptDocBegin
"Name: IsAIGameParticipant( <ent> )"
"Summary: Checks if the entity is a bot player or a human-type agent (any AI-controlled player that can participate in player game activities)"
"CallOn: Any entity"
"MandatoryArg: <ent> The entity in question."
"Example: if ( IsAIGameParticipant( player ) )"
///ScriptDocEnd
============
 */
IsAIGameParticipant( ent )
{
	// Do not add IsDefined(ent.agent_gameParticipant here, it just masks the real issue.  Find out why ent.agent_gameParticipant is undefined (since it never should be)
	if ( IsAgent( ent ) && ent.agent_gameParticipant == true )
		return true;
	
	if ( IsBot( ent ) )
		return true;
	
	return false;
}

/* 
=============
///ScriptDocBegin
"Name: IsGameParticipant( <ent> )"
"Summary: Checks if the entity is a human, a bot player, or a human-type agent (any player that can participate in player game activities)"
"CallOn: Any entity"
"MandatoryArg: <ent> The entity in question."
"Example: if ( IsGameParticipant( player ) )"
///ScriptDocEnd
============
 */
IsGameParticipant( ent )
{
	if ( IsAIGameParticipant( ent ) )
	    return true;
	
	if ( IsPlayer( ent ) )
	    return true;
	
	return false;
}

/* 
=============
///ScriptDocBegin
"Name: getTeamIndex( <team name> )"
"Summary: Returns the code references team index for the team passed in."
"CallOn: none"
"MandatoryArg: <team name> The team name that you want the index for."
"Example: index = getTeamIndex( "allies" );"
///ScriptDocEnd
============
*/
getTeamIndex( team )
{
	/* in code 
		TEAM_FREE == 0
		TEAM_AXIS == 1
		TEAM_ALLIES == 2
	*/
	
	AssertEx( IsDefined( team ), "getTeamIndex: team is undefined!" );

	teamIndex = 0;
	if( level.teambased )
	{
		switch( team )
		{
		case "axis":
			teamIndex = 1;
			break;
		case "allies":
			teamIndex = 2;
			break;
		}
	}

	return teamIndex;
}

/* 
=============
///ScriptDocBegin
"Name: isMeleeMOD( <means of death> )"
"Summary: Returns true/false if the means of death was due to melee."
"CallOn: none"
"MandatoryArg: <means of death> The way the damage was taken."
"Example: if( isMeleeMOD( sMeansOfDeath ) )"
///ScriptDocEnd
============
*/
isMeleeMOD( sMeansOfDeath )
{
	return sMeansOfDeath == "MOD_MELEE" || sMeansOfDeath == "MOD_MELEE_ALT";
}

/* 
=============
///ScriptDocBegin
"Name: isHeadShot( <weapon name>, <hit location>, <means of death>, <attacker> )"
"Summary: Returns true/false if the damage taken was a head shot."
"CallOn: none"
"MandatoryArg: <weapon name> The weapon that caused the damage."
"MandatoryArg: <hit location> The hit location of the damage."
"MandatoryArg: <means of death> The way the damage was taken."
"OptionalArg: <attacker> The entity that caused the damage."
"Example: if( isHeadShot( sWeapon, sHitLoc, sMeansOfDeath, attacker ) )"
///ScriptDocEnd
============
*/
isHeadShot( sWeapon, sHitLoc, sMeansOfDeath, attacker )
{	
	if ( IsDefined( attacker ) )
	{
		if ( IsDefined( attacker.owner ) )
		{
			if ( attacker.code_classname == "script_vehicle" )
				return false;
			if ( attacker.code_classname == "misc_turret" )
				return false;
			if ( attacker.code_classname == "script_model" )
				return false;
		}
		if ( IsDefined( attacker.agent_type ) )
		{
			if ( attacker.agent_type == "dog" || attacker.agent_type == "alien" )
				return false;
		}
	}

	return( sHitLoc == "head" || sHitLoc == "helmet" ) && !isMeleeMOD( sMeansOfDeath ) && sMeansOfDeath != "MOD_IMPACT" && !isEnvironmentWeapon( sWeapon );
}

attackerIsHittingTeam( victim, attacker )
{
	if ( !level.teamBased )
		return false;
	else if ( !isDefined( attacker ) || !isDefined( victim ) )
		return false;
	else if ( !isDefined( victim.team ) || !isDefined( attacker.team ) )
		return false;
	else if ( victim == attacker )
		return false;
	else if ( victim.pers[ "team" ] == attacker.team && IsDefined( attacker.teamChangedThisFrame ) )
		return false;
	else if ( isDefined(attacker.scrambled) && attacker.scrambled )
		return false;
	else if ( victim.team == attacker.team  )
		return true;
	else
		return false;
}

/* 
=============
///ScriptDocBegin
"Name: set_high_priority_target_for_bot( <bot> )"
"Summary: Sets this trigger to be a high priority target for the passed-in bot"
"MandatoryArg: <bot> : A bot trigger"
"Example: trigger set_high_priority_target_for_bot( bot );"
///ScriptDocEnd
============
 */
set_high_priority_target_for_bot( bot )
{
	Assert(IsDefined(self.bot_interaction_type));
	if( !( isdefined( self.high_priority_for ) && array_contains( self.high_priority_for, bot ) ) )
	{
		self.high_priority_for = array_add( self.high_priority_for, bot );
		bot notify("calculate_new_level_targets");	
	}
}

/* 
=============
///ScriptDocBegin
"Name: add_to_bot_use_targets( <new_use_target>, <use_time> )"
"Summary: Adds a trigger to the level-specific array that bots look at to determine level-specific use actions"
"Summary: This trigger needs to have a target which consists of exactly one pathnode, which is what bots will use as a destination"
"Summary: Will do nothing if bots are not in use"
"MandatoryArg: <new_use_target> : A use trigger"
"MandatoryArg: <use_time> : The amount of time (in seconds) needed to use the trigger"
"Example: add_to_bot_use_targets( target, 0.5 );"
///ScriptDocEnd
============
 */
add_to_bot_use_targets( new_use_target, use_time )
{
	if ( IsDefined(level.bot_funcs["bots_add_to_level_targets"]) )
	{
		new_use_target.use_time = use_time;
		new_use_target.bot_interaction_type = "use";
		[[ level.bot_funcs["bots_add_to_level_targets"] ]](new_use_target);
	}
}

/* 
=============
///ScriptDocBegin
"Name: remove_from_bot_use_targets( <use_target_to_remove> )"
"Summary: Removes a trigger from the level-specific array that bots look at to determine level-specific use actions"
"Summary: Will do nothing if bots are not in use"
"MandatoryArg: <use_target_to_remove> : A use trigger"
"Example: remove_from_bot_use_targets( trigger );"
///ScriptDocEnd
============
 */
remove_from_bot_use_targets( use_target_to_remove )
{
	if ( IsDefined(level.bot_funcs["bots_remove_from_level_targets"]) )
	{
		[[ level.bot_funcs["bots_remove_from_level_targets"] ]](use_target_to_remove);
	}
}

/* 
=============
///ScriptDocBegin
"Name: add_to_bot_damage_targets( <new_damage_target> )"
"Summary: Adds a trigger to the level-specific array that bots look at to determine level-specific damage actions"
"Summary: Will do nothing if bots are not in use"
"MandatoryArg: <new_damage_target> : A trigger_damage"
"Example: add_to_bot_damage_targets( target );"
///ScriptDocEnd
============
 */
add_to_bot_damage_targets( new_damage_target )
{
	if ( IsDefined(level.bot_funcs["bots_add_to_level_targets"]) )
	{
		new_damage_target.bot_interaction_type = "damage";
		[[ level.bot_funcs["bots_add_to_level_targets"] ]](new_damage_target);
	}
}

/* 
=============
///ScriptDocBegin
"Name: remove_from_bot_damage_targets( <damage_target_to_remove> )"
"Summary: Removes a trigger from the level-specific array that bots look at to determine level-specific damage actions"
"Summary: Will do nothing if bots are not in use"
"MandatoryArg: <damage_target_to_remove> : A trigger_damage"
"Example: remove_from_bot_damage_targets( trigger );"
///ScriptDocEnd
============
 */
remove_from_bot_damage_targets( damage_target_to_remove )
{
	if ( IsDefined(level.bot_funcs["bots_remove_from_level_targets"]) )
	{
		[[ level.bot_funcs["bots_remove_from_level_targets"] ]](damage_target_to_remove);
	}
}

/* 
=============
///ScriptDocBegin
"Name: notify_enemy_bots_bomb_used( <type> )"
"Summary: Notifies all necessary bots that a bomb is being used"
"Summary: Will do nothing if bots are not in use"
"MandatoryArg: <type> : "plant" or "defuse" "
"Example: self notify_enemy_bots_bomb_used( "plant" );"
///ScriptDocEnd
============
 */
notify_enemy_bots_bomb_used( type )
{
	if ( IsDefined(level.bot_funcs["notify_enemy_bots_bomb_used"]) )
	{
		self [[ level.bot_funcs["notify_enemy_bots_bomb_used"] ]](type);
	}
}

/* 
=============
///ScriptDocBegin
"Name: get_rank_xp_and_prestige_for_bot()"
"Summary: Gets the rankXP and prestige values for this bot"
"Summary: Will do nothing if bots are not in use"
"Example: rank_values = self get_rank_xp_and_prestige_for_bot();"
///ScriptDocEnd
============
 */
get_rank_xp_and_prestige_for_bot()
{
	if ( IsDefined(level.bot_funcs["bot_get_rank_xp_and_prestige"]) )
	{
		return self [[ level.bot_funcs["bot_get_rank_xp_and_prestige"] ]]();
	}
}

/* 
=============
///ScriptDocBegin
"Name: set_rank_xp_and_prestige_for_bot()"
"Summary: Sets the rankXP and prestige values for this bot"
"Summary: Will do nothing if bots are not in use"
"Example: self set_rank_xp_and_prestige_for_bot();"
///ScriptDocEnd
============
 */
set_rank_xp_and_prestige_for_bot()
{
	rank_values = self get_rank_xp_and_prestige_for_bot();
	if ( IsDefined(rank_values) )
	{
		self.pers["rankxp"] = rank_values.rankxp;
		self.pers["prestige"] = rank_values.prestige;
		self.pers["prestige_fake"] = rank_values.prestige;
	}
}

/*
=============
///ScriptDocBegin
"Name: set_console_status( <set_console_status> )"
"Summary: "
"Module: Entity"
"CallOn: An entity"
"MandatoryArg: <param1>: "
"OptionalArg: <param2>: "
"Example: "
///ScriptDocEnd
=============
*/
set_console_status()
{
	if ( !isdefined( level.Console ) )
		level.Console = GetDvar( "consoleGame" ) == "true";
	else
		AssertEx( level.Console == ( GetDvar( "consoleGame" ) == "true" ), "Level.console got set incorrectly." );

	if ( !isdefined( level.xenon ) )
		level.xenon = GetDvar( "xenonGame" ) == "true";
	else
		AssertEx( level.xenon == ( GetDvar( "xenonGame" ) == "true" ), "Level.xenon got set incorrectly." );

	if ( !isdefined( level.ps3 ) )
		level.ps3 = GetDvar( "ps3Game" ) == "true";
	else
		AssertEx( level.ps3 == ( GetDvar( "ps3Game" ) == "true" ), "Level.ps3 got set incorrectly." );

	if ( !isdefined( level.xb3 ) )
		level.xb3 = GetDvar( "xb3Game" ) == "true";
	else
		AssertEx( level.xb3 == ( GetDvar( "xb3Game" ) == "true" ), "Level.xb3 got set incorrectly." );

	if ( !isdefined( level.ps4 ) )
		level.ps4 = GetDvar( "ps4Game" ) == "true";
	else
		AssertEx( level.ps4 == ( GetDvar( "ps4Game" ) == "true" ), "Level.ps4 got set incorrectly." );
}

/*
=============
///ScriptDocBegin
"Name: IsGen4Renderer( )"
"Summary: Used to determine if the game is a Gen4 renderer. Gen1 = ps1. These are games that can support more things than a Xbox360"
"Module: Utility"
"CallOn: An entity"
"Example: if ( IsGen4Renderer() )"
///ScriptDocEnd
=============
*/
is_gen4()
{
	AssertEx( isdefined( level.Console ) && isdefined( level.xb3 ) && isdefined( level.ps4 ), "is_gen4() called before set_console_status() has been run." );

	if ( level.xb3 || level.ps4 || !level.console )
		return true;
	else
		return false;
}

setdvar_cg_ng( dvar_name, current_gen_val, next_gen_val )
{
	if ( !isdefined( level.console ) || !isdefined( level.xb3 ) || !isdefined( level.ps4 ) )
		set_console_status();
	AssertEx( IsDefined( level.console ) && IsDefined( level.xb3 ) && IsDefined( level.ps4 ), "Expected platform defines to be complete." );

	if ( is_gen4() )
		setdvar( dvar_name, next_gen_val );
	else
		setdvar( dvar_name, current_gen_val );
}

isValidTeamTarget( attacker, target )
{
	return ( IsDefined( target.team ) && target.team != attacker.team );
}

isValidFFATarget( attacker, target )
{
	return ( IsDefined( target.owner ) && target.owner != attacker );
}

/* 
=============
///ScriptDocBegin
"Name: getHeliPilotMeshOffset()"
"Summary: Returns the vector value of the helicopter pilot mesh offset."
"CallOn: none"
"Example: mesh_offset = getHeliPilotMeshOffset();"
///ScriptDocEnd
============
*/
getHeliPilotMeshOffset()
{
	return ( 0, 0, 5000 );
}

/* 
=============
///ScriptDocBegin
"Name: getHeliPilotTraceOffset()"
"Summary: Returns the vector value of how far above / below the heli to trace to find the heli mesh"
"CallOn: none"
"Example: trace_offset = getHeliPilotTraceOffset();"
///ScriptDocEnd
============
*/
getHeliPilotTraceOffset()
{
	return ( 0, 0, 2500 );
}


/* 
=============
///ScriptDocBegin
"Name: revertVisionSetForPlayer( <time> )"
"Summary: Reverts the player's visionset back to his default visionset."
"CallOn: player"
"OptionalArg: <time>: sets the transition time in seconds. Default is 1."
"Example: player revertVisionSetForPlayer( 1 );"
///ScriptDocEnd
============
*/
revertVisionSetForPlayer( time )
{
	AssertEx( isPlayer( self ), "revertVisionSetForPlayer() called on a non player entity" );
	
	if( !isDefined( time ) )
		time = 1;
	if ( IsDefined( level.nukeDetonated ) && isDefined( level.nukeVisionSet )  )
	{
		self SetClientTriggerVisionSet( level.nukeVisionSet, time );
		self VisionSetNakedForPlayer( level.nukeVisionSet, time ); // TODO: take this out since we're not going to have the nuke
		self set_visionset_for_watching_players( level.nukeVisionSet, time );
	}
	else if( isDefined( self.usingRemote ) && isDefined( self.rideVisionSet ) )
	{
		self SetClientTriggerVisionSet( self.rideVisionSet, time );
		self VisionSetNakedForPlayer( self.rideVisionSet, time ); // go to ride visionset
		self set_visionset_for_watching_players( self.rideVisionSet, time );
	}
	else
	{
		self SetClientTriggerVisionSet( "", time );
		self VisionSetNakedForPlayer( "", time ); // go to default visionset
		self set_visionset_for_watching_players( "", time );
	}
}

set_light_set_for_player( lightset )
{
	if ( !IsPlayer( self ) )
		return;
	
	if( isDefined( level.lightset_current ) )
		level.lightset_previous = level.lightset_current;
	
	level.lightset_current = lightset;
	self LightSetForPlayer( lightset );
}

clear_light_set_for_player()
{
	if ( !IsPlayer( self ) )
		return;
		
	lightset = GetMapCustom( "map" );
	
	if( isDefined( level.lightset_previous ) )
	{
		lightset = level.lightset_previous;
		level.lightset_previous = undefined;
	}
	
	level.lightset_current = lightset;
	self LightSetForPlayer( lightset );	
}

light_set_override_for_player( lightset_name, enable_duration, lightset_duration, disable_duration )
{
	if ( !IsPlayer( self ) )
		return;
		
	self LightSetOverrideEnableForPlayer( lightset_name, enable_duration );
	self waitForTimeOrNotifies( lightset_duration, [ "death", "disconnect" ] );
	if ( IsDefined( self ) )
	{
		self LightSetOverrideDisableForPlayer( disable_duration );
	}
}

getUniqueId() // self == player
{
	if ( IsDefined( self.pers["guid"] ) )
		return self.pers["guid"];
	
	playerGuid = self getGuid();
	if ( playerGuid == "0000000000000000" )
	{
		if ( IsDefined( level.guidGen ) )
			level.guidGen++;
		else
			level.guidGen = 1;
		
		playerGuid = "script" + level.guidGen;
	}
	
	self.pers["guid"] = playerGuid;
	
	return self.pers["guid"];
}

/*
=============
///ScriptDocBegin
"Name: get_players_watching( <just_spectators>, <just_killcam> )"
"Summary: Returns a list of the players "watching" this player - either through a killcam or by spectating"
"CallOn: A player"
"OptionalArg: <just_spectators>: If true, we will only return spectators watching."
"OptionalArg: <just_killcam>: If true, we will only return players watching the killcam."
"Example: players_watching = self get_players_watching();"
"SPMP: multiplayer"
///ScriptDocEnd
=============
*/
get_players_watching( just_spectators, just_killcam )
{
	if( !IsDefined( just_spectators ) )
		just_spectators = false;
	if( !IsDefined( just_killcam ) )
		just_killcam = false;
	
	entity_num_self = self GetEntityNumber();
	players_watching = [];
	foreach( player in level.players )
	{
		if ( !IsDefined( player ) || player == self )
			continue;
		
		player_is_watching = false;
		
		if( !just_killcam )
		{
			if ( ( IsDefined( player.team ) && player.team == "spectator" ) || player.sessionstate == "spectator" )
			{
				spectatingPlayer = player GetSpectatingPlayer();
				if ( IsDefined(spectatingPlayer) && spectatingPlayer == self )
					player_is_watching = true;
			}
			
			if ( player.forcespectatorclient == entity_num_self )
				player_is_watching = true;
		}
		
		if( !just_spectators )
		{
			if ( player.killcamentity == entity_num_self )
				player_is_watching = true;
		}
		
		if ( player_is_watching )
			players_watching[players_watching.size] = player;
	}
	
	return players_watching;
}

/*
=============
///ScriptDocBegin
"Name: set_visionset_for_watching_players( <new_visionset>, <new_visionset_transition_time>, <time_in_new_visionset>, <is_missile_visionset> )"
"Summary: Sets a visionset for any players watching the current player"
"CallOn: A player"
"MandatoryArg: <new_visionset>: The visionset to apply"
"MandatoryArg: <new_visionset_transition_time>: How long to transition to the new visionset"
"MandatoryArg: <time_in_new_visionset>: Once transitioned, how long the player will stay in this vision set until he transitions back to the default"
"OptionalArg: <is_missile_visionset>: If true, will use VisionSetMissilecamForPlayer"
"OptionalArg: <just_spectators>: If true, we will only affect spectators watching."
"OptionalArg: <just_killcam>: If true, we will only affect players watching the killcam."
"Example: self set_visionset_for_watching_players( "black_bw", 0.50, 1.0 );"
"SPMP: multiplayer"
"NoteLine: <time_in_new_visionset> This doesn't automatically transition the player after this time.  It handles the case where he would be a spectator"
"NoteLine: <time_in_new_visionset> and then join a team during the new visionset.  In that case we need to make sure to reset the visionset"
///ScriptDocEnd
=============
*/
set_visionset_for_watching_players( new_visionset, new_visionset_transition_time, time_in_new_visionset, is_missile_visionset, just_spectators, just_killcam, is_apply_visionset )
{
	players_watching = self get_players_watching( just_spectators, just_killcam );
	foreach( player in players_watching )
	{
		player notify("changing_watching_visionset");
		if ( IsDefined(is_missile_visionset) && is_missile_visionset )
			player VisionSetMissilecamForPlayer( new_visionset, new_visionset_transition_time );
		else if ( IsDefined( is_apply_visionset ) && is_apply_visionset )
			player VisionSetPostApplyForPlayer( new_visionset, new_visionset_transition_time );
		else
			player VisionSetNakedForPlayer( new_visionset, new_visionset_transition_time );
		if ( new_visionset != "" && IsDefined(time_in_new_visionset) )
		{
			player thread reset_visionset_on_team_change( self, new_visionset_transition_time + time_in_new_visionset, is_apply_visionset );
			player thread reset_visionset_on_disconnect( self, is_apply_visionset );
			
			if ( player isInKillcam() )
				player thread reset_visionset_on_spawn();
		}
	}
}

reset_visionset_on_spawn()
{
	self endon( "disconnect" );
	
	self waittill("spawned");
	self VisionSetNakedForPlayer( "", 0.0 );
	self VisionSetPostApplyForPlayer( "", 0.0 );
}

reset_visionset_on_team_change( current_player_watching, time_till_default_visionset, is_apply_visionset )
{
	self endon("changing_watching_visionset");
	current_player_watching endon( "disconnect" );
	
	time_started = GetTime();
	team_started = self.team;
	while( GetTime() - time_started < time_till_default_visionset * 1000 )
	{
		if ( self.team != team_started || !array_contains( current_player_watching get_players_watching(), self ) )
		{
			// Changed team before the visionset was done, so make sure to end it early otherwise it could get stuck on
			if ( IsDefined( is_apply_visionset ) && is_apply_visionset )
				self VisionSetPostApplyForPlayer( "", 0.0 );
			else
				self VisionSetNakedForPlayer( "", 0.0 );
			self notify("changing_visionset");
			break;
		}
		
		wait(0.05);
	}
}

reset_visionset_on_disconnect( entity_watching, is_apply_visionset )
{
	self endon("changing_watching_visionset");
	entity_watching waittill("disconnect");
	if ( IsDefined( is_apply_visionset ) && is_apply_visionset )
		self VisionSetPostApplyForPlayer( "", 0.0 );
	else
		self VisionSetNakedForPlayer( "", 0.0 );
}

/*
=============
///ScriptDocBegin
"Name: _validateAttacker()"
"Summary: Returns undefined if attacker no longer exists or is invalid"
"Example: if ( isDefined( _validateAttacker( eAttacker ) )"
"SPMP: mp"
///ScriptDocEnd
=============
*/
_validateAttacker( eAttacker )
{
	if ( IsAgent( eAttacker ) && (!IsDefined( eAttacker.isActive ) || !eAttacker.isActive) )
		return undefined;
	
	return eAttacker;
}

/*
=============
///ScriptDocBegin
"Name: _setNameplateMaterial()"
"Summary: wrapper for built-in setnameplateMaterial so we can properly restore nameplate
"Module: Utility"
"CallOn: Player"
"Example: agent SetNameplateMaterial( "player_name_bg_green_agent", "player_name_bg_red_agent" ); 
"SPMP: multiplayer"
///ScriptDocEnd
=============
*/
_setNameplateMaterial( friendlyMat, enemyMat )
{
	if ( !IsDefined( self.nameplateMaterial ) )
	{
		self.nameplateMaterial = [];
		self.prevNameplateMaterial = [];
	}
	else
	{
		self.prevNameplateMaterial[0] = self.nameplateMaterial[0];
		self.prevNameplateMaterial[1] = self.nameplateMaterial[1];
	}
	self.nameplateMaterial[0] = friendlyMat;
	self.nameplateMaterial[1] = enemyMat;
	
	self SetNameplateMaterial( friendlyMat, enemyMat );
}

_restorePreviousNameplateMaterial()
{
	if ( IsDefined( self.prevNameplateMaterial ) )
	{
		self SetNameplateMaterial( self.prevNameplateMaterial[0], self.prevNameplateMaterial[1] );
	}
	else
	{
		self SetNameplateMaterial( "", "" );
	}
	self.nameplateMaterial = undefined;
	self.prevNameplateMaterial = undefined;
}

#using_animtree( "animated_props" );
FindAndPlayAnims(name, bOffset)
{
	AnimatedPropArray = GetEntArray(name,"targetname");
	if(AnimatedPropArray.size > 0)
	{
		foreach(prop in AnimatedPropArray)
		{
			bDelta = false;
			if(isdefined(prop.script_animation))
		   	{
				if(isdefined(prop.script_parameters) && prop.script_parameters == "delta_anim")
		   		{
					bDelta = true;
		   		}
				prop thread PlayAnim(bOffset, bDelta);
		   	}
		}
	}
}
PlayAnim(bOffset, bDelta)
{
	if(bOffset == true)
	{
		wait(RandomFloatRange(0.0, 1));
	}
	
	if(bDelta == false)
	{
		self ScriptModelPlayAnim(self.script_animation); 
	}
	else
	{	
		self ScriptModelPlayAnimDeltaMotion(self.script_animation); 
	}
}

playerAllowHighJump(enable, type)
{
	self _playerAllow("highjump", enable, type, ::AllowHighJump);
}

playerAllowHighJumpDrop(enable, type)
{
	self _playerAllow("highjumpdrop", enable, type, ::AllowHighJumpDrop);
}

playerAllowBoostJump(enable, type)
{
	self _playerAllow("boostjump", enable, type, ::AllowBoostJump);
}

playerAllowPowerSlide(enable, type)
{
	self _playerAllow("powerslide", enable, type, ::AllowPowerSlide);
}

playerAllowDodge(enable, type)
{
	self _playerAllow("dodge", enable, type, ::AllowDodge);
}

_playerAllow(ability, enable, type, enableFunc, isBuiltinFunc)
{
	if ( !IsDefined(self.playerDisableAbilityTypes) )
		self.playerDisableAbilityTypes = [];
	
	if ( !IsDefined(self.playerDisableAbilityTypes[ability]) )
	{
		self.playerDisableAbilityTypes[ability] = [];
	}
	
	if ( !IsDefined( type ) )
		type = "default";
	
	if( !IsDefined(isBuiltinFunc) )
		isBuiltinFunc = true;
	
	if ( enable )
	{
		self.playerDisableAbilityTypes[ability] = array_remove(self.playerDisableAbilityTypes[ability], type);
		if ( !self.playerDisableAbilityTypes[ability].size )
		{
			if ( isBuiltinFunc )
				self call [[enableFunc]] ( true );
			else
				self [[enableFunc]] ( true );
		}
	}
	else
	{
		if ( !IsDefined( array_find( self.playerDisableAbilityTypes[ability], type ) ) )
			self.playerDisableAbilityTypes[ability] = array_add(self.playerDisableAbilityTypes[ability], type);
		
		if ( isBuiltinFunc )
			self call [[enableFunc]] ( false );
		else
			self [[enableFunc]] ( false );
	}
}

/*
=============
///ScriptDocBegin
"Name: makeGloballyUsableByType( <type>, <hintString>, <player>, <team> )"
"Summary: Registers an entity as globally usable, so its hint string is visible regardless of how far away from the player it is. Priority is automatically assigned by type and time.  Priority is automatically managed as more global use ents are registered."
"CallOn: An entity"
"MandatoryArg: <type>: The type of hint string to automatically assign priority: coopStreakPrompt, killstreakRemote"
"MandatoryArg: <hintString>: Localized string to display as hint string."
"OptionalArg: <player>: If specified, only this player can see the hint string, may be undefined."
"OptionalArg: <team>: If specified, only this team can see the hint string, may be undefined, not used if player is specified."
"Example: warbird.UsableEnt makeGloballyUsableByType( "killstreakRemote", &"MP_WARBIRD_PLAYER_PROMPT", self );"
"SPMP: mp"
///ScriptDocEnd
=============
*/
makeGloballyUsableByType( type, hintString, player, team )
{
	Assert( !IsDefined( player ) || !IsDefined( team ) );
	
	priority = 500;
	// lower number equals higher priority, -255 to 255 is the priority for ground ents, this system will stay outside of that range
	// and it will make use of decimals to sub prioritize multiple global use ents of the same type
	switch( type )
	{
		case "killstreakRemote": // a per player prompt
			priority = 300;
			break;
		case "coopStreakPrompt": // a team prompt for coop scorestreaks
			priority = 301;
			break;
		default:
			AssertMsg( "makeGloballyUsableByType(): type <" + type + "> is not defined; valid options are coopStreakPrompt & killstreakRemote (or add it if needed)" );
			break;
	}
	
	_insertIntoGlobalUsableList( priority, type, player, team );
	
	self MakeGlobalUsable( priority, player, team );
	self SetHintString( hintString );
	self SetCursorHint( "HINT_NOICON" );
}

_insertIntoGlobalUsableList( priority, type, player, team )
{
	if ( !IsDefined( level.globalUsableEnts ) )
		level.globalUsableEnts = [];
	
	firstIndex = -1;
	for ( i = 0; i < level.globalUsableEnts.size; i++ )
	{
		nextEntStruct = level.globalUsableEnts[i];
		if ( nextEntStruct.priority > priority )
		{
			if ( firstIndex == -1 )
				firstIndex = i;
			break;
		}
		
		if ( nextEntStruct.priority == priority )
		{
			nextEntStruct.priority += 0.01;
			if ( nextEntStruct.enabled )
				nextEntStruct.ent MakeGlobalUsable( nextEntStruct.priority, nextEntStruct.player, nextEntStruct.team );
			
			if ( firstIndex == -1 )
				firstIndex = i;
		}
	}
	
	if ( firstIndex == -1 )
		firstIndex = 0;
	
	entStruct = SpawnStruct();
	entStruct.ent = self;
	entStruct.priority = priority;
	entStruct.type = type;
	entStruct.player = player;
	entStruct.team = team;
	entStruct.enabled = true;

	level.globalUsableEnts = array_insert( level.globalUsableEnts, entStruct, firstIndex );
}

/*
=============
///ScriptDocBegin
"Name: makeGloballyUnusableByType()"
"Summary: Unregisters an entity as globally usable."
"CallOn: An entity"
"Example: warbird.UsableEnt makeGloballyUnusableByType();"
"SPMP: mp"
///ScriptDocEnd
=============
*/
makeGloballyUnusableByType()
{
	foundEnt = undefined;
	foreach ( entStruct in level.globalUsableEnts )
	{
		if ( entStruct.ent == self )
		{
			foundEnt = entStruct;
			break;
		}
	}
	
	if ( IsDefined( foundEnt ) )
	{
		foundEntPriority = foundEnt.priority;
		level.globalUsableEnts = array_remove( level.globalUsableEnts, foundEnt );
		self MakeGlobalUnusable();
		
		foreach ( entStruct in level.globalUsableEnts )
		{
			if ( foundEntPriority > entStruct.priority && ( Int( foundEntPriority ) == Int( entStruct.priority ) ) )
			{
				entStruct.priority -= 0.01;
				if ( entStruct.enabled )
					entStruct.ent MakeGlobalUsable( entStruct.priority, entStruct.player, entStruct.team );
			}
		}
	}
}

/*
=============
///ScriptDocBegin
"Name: disableGloballyUsableByType()"
"Summary: Disables an entity as globally usable but remembers priority and hint string data."
"CallOn: An entity"
"Example: warbird.UsableEnt disableGloballyUsableByType();"
"SPMP: mp"
///ScriptDocEnd
=============
*/
disableGloballyUsableByType()
{
	foreach ( entStruct in level.globalUsableEnts )
	{
		if ( entStruct.ent == self )
		{
			if ( entStruct.enabled )
			{
				entStruct.ent MakeGlobalUnusable();
				entStruct.enabled = false;
			}
			break;
		}
	}
}

/*
=============
///ScriptDocBegin
"Name: enableGloballyUsableByType()"
"Summary: Enables an entity as globally usable that was previously disabled."
"CallOn: An entity"
"Example: warbird.UsableEnt enableGloballyUsableByType();"
"SPMP: mp"
///ScriptDocEnd
=============
*/
enableGloballyUsableByType()
{
	foreach ( entStruct in level.globalUsableEnts )
	{
		if ( entStruct.ent == self )
		{
			if ( !entStruct.enabled )
			{
				entStruct.ent MakeGlobalUsable( entStruct.priority, entStruct.player, entStruct.team );
				entStruct.enabled = true;
			}
			break;
		}
	}
}

/*
=============
///ScriptDocBegin
"Name: setDOF()"
"Summary: set Depth of Field for a player"
"MandatoryArg: <DOFset>: Array of dof values. "nearStart", "nearEnd", "farStart", "farEnd", "nearBlur", and "farBlur"
"CallOn: A player"
"Example: player setDOF(level.dofDefault);"
"SPMP: mp"
///ScriptDocEnd
=============
*/
setDOF(DOFset)
{
	self setdepthoffield( DOFset[ "nearStart" ], DOFset[ "nearEnd" ], DOFset[ "farStart" ], DOFset[ "farEnd" ], DOFset[ "nearBlur" ], DOFset[ "farBlur" ] );
}


/*
=============
///ScriptDocBegin
"Name: is_exo_ability_weapon( <weapon_name> )"
"Summary: returns true if the weapon_name is that of an Exo Ability."
"Module: Entity"
"CallOn: N/A"
"MandatoryArg: <weapon_name>: a string"
"Example: if ( is_exo_ability_weapon( "exoshield_equipment_mp" ) )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
is_exo_ability_weapon( weapon_name )		// weapon_name = string
{
	switch( weapon_name )
	{
		case "adrenaline_mp":
		case "extra_health_mp":
		case "exocloak_equipment_mp":
		case "exohover_equipment_mp":
		case "exoping_equipment_mp":
		case "exorepulsor_equipment_mp":
		case "exoshield_equipment_mp":
		case "exoshieldhorde_equipment_mp":
		case "exohoverhorde_equipment_mp":
		case "exocloakhorde_equipment_mp":
		case "exomute_equipment_mp":
			return true;
		default:
			return false;
	}
}


/*
============= 
///ScriptDocBegin
"Name: isEnemy( <potentialEnemy> )
"Summary: returns if the <potentialEnemy> is an enemy. Works for team based and FFA modes.
"Module: Utility"
"Example: foreach ( char in level.characters ) { if ( owner isEnemy( char ) ) { doStuff; } }
"SPMP: both"
///ScriptDocEnd
============= 
*/ 
isEnemy( potentialEnemy )
{
	if ( level.teamBased )
	{
		return self isPlayerOnEnemyTeam( potentialEnemy );
	}
	else
	{
		return self isPlayerFFAEnemy( potentialEnemy );
	}
}

isPlayerOnEnemyTeam( potentialEnemy )
{
	return ( potentialEnemy.team != self.team );
}

isPlayerFFAEnemy( potentialEnemy )
{
	if ( IsDefined( potentialEnemy.owner ) )	
	{
		return ( potentialEnemy.owner != self );
	}
	else
	{
		return ( potentialEnemy != self );
	}
}


/*
=============
///ScriptDocBegin
"Name: isMLGSystemLink()"
"Summary: Check to see if we are currently in system link, with MLG rules enabled."
"Module: Utility"
"Example: if ( isMLGSystemLink() )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
isMLGSystemLink()
{
	if ( ( IsSystemLink() && GetDvarInt( "xblive_competitionmatch" ) ) )
		return true;
	
	return false;
}

/*
=============
///ScriptDocBegin
"Name: isMLGSplitScreen()"
"Summary: Check to see if we are currently in split screen, with MLG rules enabled."
"Module: Utility"
"Example: if ( isMLGSplitScreen() )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
isMLGSplitScreen()
{
	if ( ( IsSplitScreen() && GetDvarInt( "xblive_competitionmatch" ) ) )
		return true;
	
	return false;
}

/*
=============
///ScriptDocBegin
"Name: isMLGPrivateMatch()"
"Summary: Check to see if we are currently in private match, with MLG rules enabled."
"Module: Utility"
"Example: if ( isMLGPrivateMatch() )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
isMLGPrivateMatch()
{
	if ( ( privateMatch() && GetDvarInt( "xblive_competitionmatch" ) ) )
		return true;
	
	return false;
}

/*
=============
///ScriptDocBegin
"Name: isMLGMatch()"
"Summary: Check to see if we are currently in private match or system link, with MLG rules enabled."
"Module: Utility"
"Example: if ( isMLGMatch() )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
isMLGMatch()
{
	if ( isMLGSystemLink() || isMLGSplitScreen() || isMLGPrivateMatch() )
		return true;
	
	return false;
}

spawnFXShowToTeam( fxId, team, origin, fwd )
{
	fx = SpawnFX( fxId, origin, fwd );
	fx fxShowToTeam( team );
	
	return fx;
}

fxShowToTeam( team )
{
	self thread showFXToTeam( team );
	SetFXKillOnDelete( self, true );
	TriggerFX(self);
}

showFXToTeam( team )
{
	self endon("death");
	level endon( "game_ended" );
	
	while (true)
	{
		self Hide();
		foreach(player in level.players)
		{
			playerTeam = player.team;
			if( playerTeam != "axis" || player IsMLGSpectator() )
				playerTeam = "allies";
			
			if( (team == playerTeam) || (team == "neutral") )
				self ShowToPlayer( player );
		}
		
		level waittill("joined_team");
	}
}

get_spawn_weapon_name( loadout )			// self = player
{
	weapon_name = "iw5_combatknife_mp";
	
	if ( IsDefined( loadout.primaryName ) && (loadout.primaryName != "none") && (loadout.primaryName != "iw5_combatknife_mp") )
	{
		weapon_name = loadout.primaryName;
	}
	else if ( IsDefined( loadout.secondaryName ) && (loadout.secondaryName != "none") )
	{
		weapon_name = loadout.secondaryName;
	}
	
	return weapon_name;
}

playerSaveAngles()
{
	self.restoreAngles = self GetPlayerAngles();
}

playerRestoreAngles()
{
	if ( IsDefined( self.restoreAngles ) )
	{
		if ( self.team != "spectator" )
			self SetPlayerAngles( self.restoreAngles );
		self.restoreAngles = undefined;
	}
}

setMLGIcons( object, icon )
{
	object maps\mp\gametypes\_gameobjects::set2DIcon( "mlg", icon );
	object maps\mp\gametypes\_gameobjects::set3DIcon( "mlg", icon );
}

spawnPatchClip( name, clip_origin, clip_angles )
{
    copy_ent = GetEnt(name, "targetname");
    if(!IsDefined(copy_ent))
        return undefined;
    
    clip = spawn("script_model", clip_origin);
    clip CloneBrushmodelToScriptmodel(copy_ent);
    clip.angles = clip_angles;
    
    return clip;
}

/*
=============
///ScriptDocBegin
"Name: isCOOP()"
"Summary: Check to see if we are currently in a cooperative game type."
"Module: Utility"
"Example: if ( isCOOP() )"
"SPMP: MP"
///ScriptDocEnd
=============
*/
isCOOP()
{
	if ( IsDefined( level.isHorde ) && level.isHorde )
		return true;

	if ( IsDefined( level.isZombieGame ) && level.isZombieGame )
		return true;
	
	return false;
}
