#include common_scripts\utility;
#include maps\_utility;
#include maps\_hud_util;
/* 
	Functions called only from _utility
*/ 

linetime_proc( start, end, color, timer )
{
	for ( i = 0; i < timer * 20; i++ )
	{
		Line( start, end, color );
		wait( 0.05 );
	}
}

structarray_swap( object1, object2 )
{
	index1 = object1.struct_array_index;
	index2 = object2.struct_array_index;
	self.array[ index2 ] = object1;
	self.array[ index1 ] = object2;
	self.array[ index1 ].struct_array_index = index1;
	self.array[ index2 ].struct_array_index = index2;
}

waitSpread_code( start, end )
{
	waittillframeend;// give every other waitspreader in this frame a chance to increment wait_spreaders

	Assert( level._wait_spreaders >= 1 );

	allotment = [];

	if ( level._wait_spreaders == 1 )
	{
		allotment[ 0 ] = RandomFloatRange( start, end );
		level._wait_spreader_allotment = allotment;
		level._active_wait_spread = undefined;
		return;
	}

	allotment[ 0 ] = start;
	allotment[ allotment.size ] = end;

	for ( i = 1; i < level._wait_spreaders - 1; i++ )
	{
		allotment = waitSpread_insert( allotment );
	}

	level._wait_spreader_allotment = array_randomize( allotment );
	level._active_wait_spread = undefined;
}

waitSpread_insert( allotment )
{
	gapIndex = -1;
	gap = 0;
	// get the largest waitspread gap
	for ( p = 0; p < allotment.size - 1; p++ )
	{
		newgap = allotment[ p + 1 ] - allotment[ p ];
		if ( newgap <= gap )
			continue;

		gap = newgap;
		gapIndex = p;
	}

	Assert( gap > 0 );
	newAllotment = [];

	for ( i = 0; i < allotment.size; i++ )
	{
		if ( gapIndex == i - 1 )
		{
			newAllotment[ newAllotment.size ] = RandomFloatRange( allotment[ gapIndex ], allotment[ gapIndex + 1 ] );
		}
		newAllotment[ newAllotment.size ] = allotment[ i ];
	}

	return newAllotment;
}


waittill_objective_event_proc( requireTrigger )
{
	while ( level._deathSpawner[ self.script_deathchain ] > 0 )
		level waittill( "spawner_expired" + self.script_deathchain );

	if ( requireTrigger )
		self waittill( "trigger" );

	flag = self get_trigger_flag();
	flag_set( flag );
}

wait_until_done_speaking()
{
	self endon( "death" );
	while ( self.isSpeaking )
	{
		wait( 0.05 );
	}
}

wait_for_trigger_think( ent )
{
	self endon( "death" );
	ent endon( "trigger" );
	self waittill( "trigger" );

	ent notify( "trigger" );
}

wait_for_trigger( msg, type )
{
	triggers = GetEntArray( msg, type );
	ent = SpawnStruct();

	array_thread( triggers, ::wait_for_trigger_think, ent );
	ent waittill( "trigger" );
}


ent_waits_for_level_notify( msg )
{
	level waittill( msg );
	self notify( "done" );
}

ent_waits_for_notify( ent, msg )
{
	ent waittill( msg );
	self notify( "done" );
}

ent_waits_for_trigger( trigger )
{
	trigger waittill( "trigger" );
	self notify( "done" );
}

ent_times_out( timer )
{
	wait( timer );
	self notify( "done" );
}

update_debug_friendlycolor_on_death()
{
	self notify( "debug_color_update" );
	self endon( "debug_color_update" );
	num = self.unique_id;
	self waittill( "death" );
	level._debug_color_friendlies[ num ] = undefined;

	// updates the debug color friendlies info
	level notify( "updated_color_friendlies" );
}


update_debug_friendlycolor( num )
{
	thread update_debug_friendlycolor_on_death();
	if ( IsDefined( self.script_forcecolor ) )
	{
		level._debug_color_friendlies[ num ] = self.script_forceColor;
	}
	else
	{
		level._debug_color_friendlies[ num ] = undefined;
	}
	// updates the debug color friendlies info
	level notify( "updated_color_friendlies" );
}

insure_player_does_not_set_forcecolor_twice_in_one_frame()
{
	/#
	AssertEx( !isdefined( self.setforcecolor ), "Tried to set forceColor on an ai twice in one frame. Don't spam set_force_color." );
	self.setforcecolor = true;
	waittillframeend;
	if ( !isalive( self ) )
		return;
	self.setforcecolor = undefined;
	#/
}


new_color_being_set( color )
{
	self notify( "new_color_being_set" );
	self.new_force_color_being_set = true;
	maps\_colors::left_color_node();

	self endon( "new_color_being_set" );
	self endon( "death" );

	// insure we're only getting one color change, multiple in one frame will get overwritten.
	waittillframeend;
	waittillframeend;

	if ( IsDefined( self.script_forcecolor ) )
	{
		// grab the current colorCode that AI of this color are forced to, if there is one
		self.currentColorCode = level._currentColorForced[ self maps\_colors::get_team() ][ self.script_forcecolor ];
		if ( IsDefined( self.dontColorMove ) )
			self.dontColorMove = undefined;
		else
			self thread maps\_colors::goto_current_ColorIndex();
	}

	self.new_force_color_being_set = undefined;
	self notify( "done_setting_new_color" );
	/#
	update_debug_friendlycolor( self.unique_id );
	#/
}

radio_queue_thread( msg )
{
	queueTime = GetTime();
	for ( ;; )
	{
		if ( !isdefined( self._radio_queue ) )
			break;

		self waittill( "finished_radio" );
		if ( GetTime() > queueTime + 7500 )
			return;
	}

	self._radio_queue = true;

	wait_for_buffer_time_to_pass( level._last_mission_sound_time, 0.5 );

	level._player play_sound_on_entity( level._scr_radio[ msg ] );
	self._radio_queue = undefined;
	level._last_mission_sound_time = GetTime();

	self notify( "finished_radio" );
}

ent_wait_for_flag_or_time_elapses( flagname, timer )
{
	self endon( flagname );
	wait( timer );
}

waittill_either_function_internal( ent, func, parm )
{
	ent endon( "done" );
	[[ func ]]( parm );
	ent notify( "done" );
}


HintPrintWait( length, breakfunc )
{
	if ( !isdefined( breakfunc ) )
	{
		wait( length );
		return;
	}

	timer = length * 20;
	for ( i = 0; i < timer; i++ )
	{
		if ( [[ breakfunc ]]() )
			break;
		wait( 0.05 );
	}
}

hint_timeout( timeout )
{
	wait( timeout );
	self.timed_out = true;
}

HintPrint( string, breakfunc, parm1, parm2, parm3, timeout )
{
	Assert( IsPlayer( self ) );

	if ( !isalive( self ) )
		return;

	MYFADEINTIME = 1.0;
	MYFLASHTIME = 0.75;
	MYALPHAHIGH = 0.95;
	MYALPHALOW = 0.4;

	if ( isdefined( self.current_global_hint ) )
	{
		if ( self.current_global_hint == string )
			return;
	}

	ent_flag_waitopen( "global_hint_in_use" );
	if ( isdefined( self.current_global_hint ) )
	{
		if ( self.current_global_hint == string )
			return;
	}
		
	ent_flag_set( "global_hint_in_use" );

	self.current_global_hint = string;
	
	Hint = createClientFontString( "default", 2 );
	
	thread destroy_hint_on_friendlyfire( hint );
	level endon( "friendlyfire_mission_fail" );
	
	//Hint.color = ( 1, 1, .5 ); //remove color so that color highlighting on PC can show up.

	Hint.color = ( getdvarfloat( "color_neutral_r" ), getdvarfloat( "color_neutral_g" ), getdvarfloat( "color_neutral_b" ) ); 
	Hint.alpha = 0.9;
	Hint.x = 0;
	Hint.y = -68;
	Hint.alignx = "center";
	Hint.aligny = "middle";
	Hint.horzAlign = "center";
	Hint.vertAlign = "middle";
	Hint.foreground = false;
	Hint.hidewhendead = true;
	Hint.hidewheninmenu = true;
	Hint.hintstring = true;

	Hint SetText( string );

	Hint.alpha = 0;
	Hint FadeOverTime( MYFADEINTIME );
	Hint.alpha = MYALPHAHIGH;
	HintPrintWait( MYFADEINTIME, breakfunc );

	parms = 0;
	if ( IsDefined( parm3 ) )
		parms = 3;
	else
	if ( IsDefined( parm2 ) )
		parms = 2;
	else
	if ( IsDefined( parm1 ) )
		parms = 1;

	timeout_ent = SpawnStruct();
	timeout_ent.timed_out = false;
	if ( IsDefined( timeout ) )
	{
		timeout_ent thread hint_timeout( timeout );
	}

	if ( IsDefined( breakfunc ) )
	{
		for ( ;; )
		{

			Hint FadeOverTime( MYFLASHTIME );
			Hint.alpha = MYALPHALOW;
			HintPrintWait( MYFLASHTIME, breakfunc );

			if ( parms == 3 )
			{
				if ( [[ breakfunc ]]( parm1, parm2, parm3 ) )
					break;
			}
			else
			if ( parms == 2 )
			{
				if ( [[ breakfunc ]]( parm1, parm2 ) )
					break;
			}
			else
			if ( parms == 1 )
			{
				if ( [[ breakfunc ]]( parm1 ) )
					break;
			}
			else
			{
				if ( [[ breakfunc ]]() )
					break;
			}

			Hint FadeOverTime( MYFLASHTIME );
			Hint.alpha = MYALPHAHIGH;
			HintPrintWait( MYFLASHTIME, breakfunc );

			if ( timeout_ent.timed_out )
				break;

			if ( parms == 3 )
			{
				if ( [[ breakfunc ]]( parm1, parm2, parm3 ) )
					break;
			}
			else
			if ( parms == 2 )
			{
				if ( [[ breakfunc ]]( parm1, parm2 ) )
					break;
			}
			else
			if ( parms == 1 )
			{
				if ( [[ breakfunc ]]( parm1 ) )
					break;
			}
			else
			{
				if ( [[ breakfunc ]]() )
					break;
			}
		}
	}
	else
	{
		for ( i = 0; i < 1; i++ )
		{
			Hint FadeOverTime( MYFLASHTIME );
			Hint.alpha = MYALPHALOW;
			HintPrintWait( MYFLASHTIME, breakfunc );

			Hint FadeOverTime( MYFLASHTIME );
			Hint.alpha = MYALPHAHIGH;
			HintPrintWait( MYFLASHTIME, breakfunc );
		}
	}
	
	hint notify( "destroying" );
	self.current_global_hint = undefined;
	Hint Destroy();
	ent_flag_clear( "global_hint_in_use" );
}

destroy_hint_on_friendlyfire( hint )
{
	hint endon( "destroying" );
	
	level waittill( "friendlyfire_mission_fail" );
	
	if ( !isdefined( hint ) )
		return;
	
	self.current_global_hint = undefined;
	hint Destroy();
	ent_flag_clear( "global_hint_in_use" );
}
	

ShowHintPrint_struct( struct )
{
	struct.timed_out = false;
	Assert( IsPlayer( self ) );

	if ( !isalive( self ) )
		return;

	MYFADEINTIME = 1.0;
	MYFLASHTIME = 0.75;
	MYALPHAHIGH = 0.95;
	MYALPHALOW = 0.4;

	ent_flag_waitopen( "global_hint_in_use" );
	ent_flag_set( "global_hint_in_use" );

	// could have been stopped while waiting for the hint area to open
	if ( struct.timed_out )
		return;

	if ( IsDefined( struct.timeout ) )
	{
		struct thread hint_timeout( struct.timeout );
	}

	Hint = createClientFontString( "objective", 2 );

	//Hint.color = ( 1, 1, .5 ); //remove color so that color highlighting on PC can show up.
	Hint.alpha = 0.9;
	Hint.x = 0;
	Hint.y = -38;
	Hint.alignx = "center";
	Hint.aligny = "middle";
	Hint.horzAlign = "center";
	Hint.vertAlign = "middle";
	Hint.foreground = false;
	Hint.hidewhendead = true;

	Hint SetText( struct.string );

	Hint.alpha = 0;
	Hint FadeOverTime( MYFADEINTIME );
	Hint.alpha = MYALPHAHIGH;
	HintPrintWait( MYFADEINTIME );

	for ( ;; )
	{
		Hint FadeOverTime( MYFLASHTIME );
		Hint.alpha = MYALPHALOW;
		HintPrintWait( MYFLASHTIME );

		if ( struct.timed_out )
			break;

		Hint FadeOverTime( MYFLASHTIME );
		Hint.alpha = MYALPHAHIGH;
		HintPrintWait( MYFLASHTIME );

		if ( struct.timed_out )
			break;
	}

	Hint Destroy();
	ent_flag_clear( "global_hint_in_use" );
}

lerp_player_view_to_tag_internal( player, tag, lerptime, fraction, right_arc, left_arc, top_arc, bottom_arc, hit_geo )
{
	if ( IsDefined( self.first_frame_time ) && self.first_frame_time == GetTime() )
	{
		// the base ai / vehicle / model just was put into the first frame and it takes a server frame for the
		// tags to get into position
		wait( 0.10 );
	}

	origin = self GetTagOrigin( tag );
	angles = self GetTagAngles( tag );
	player lerp_player_view_to_position( origin, angles, lerptime, fraction, right_arc, left_arc, top_arc, bottom_arc, hit_geo );

	if ( hit_geo )
		return;

	player PlayerLinkTo( self, tag, fraction, right_arc, left_arc, top_arc, bottom_arc, false );
}

lerp_player_view_to_tag_oldstyle_internal( player, tag, lerptime, fraction, right_arc, left_arc, top_arc, bottom_arc, hit_geo )
{
	if ( IsDefined( self.first_frame_time ) && self.first_frame_time == GetTime() )
	{
		// the base ai / vehicle / model just was put into the first frame and it takes a server frame for the
		// tags to get into position
		wait( 0.10 );
	}

	origin = self GetTagOrigin( tag );
	angles = self GetTagAngles( tag );
	player lerp_player_view_to_position_oldstyle( origin, angles, lerptime, fraction, right_arc, left_arc, top_arc, bottom_arc, true );

	if ( hit_geo )
		return;

	player PlayerLinkToDelta( self, tag, fraction, right_arc, left_arc, top_arc, bottom_arc, false );
}

function_stack_wait( localentity )
{
	self endon( "death" );
	localentity waittill_either( "function_done", "death" );
}

function_stack_wait_finish( localentity )
{
	function_stack_wait( localentity );

	if ( !isdefined( self ) )
		return false;

	//if isdefined but not sentient -> good enough
	if ( !issentient( self ) )
		return true;

	//if is defined and is sentient and is alive -> good
	if ( IsAlive( self ) )
		return true;

	//if is defined and is sentient but dead -> false
	return false;
}

function_stack_proc( caller, func, param1, param2, param3, param4, param5 )
{
	self endon( "death" );

	if ( !isdefined( caller.function_stack ) )
		caller.function_stack = [];

	caller.function_stack[ caller.function_stack.size ] = self;

	self thread function_stack_self_death( caller );
	function_stack_caller_waits_for_turn( caller );

	if ( IsDefined( caller ) && IsDefined( caller.function_stack ) )
	{
		self.function_stack_func_begun = true;
		self notify( "function_stack_func_begun" );

		if ( IsDefined( param5 ) )
			caller [[ func ]]( param1, param2, param3, param4, param5 );
		else
		if ( IsDefined( param4 ) )
			caller [[ func ]]( param1, param2, param3, param4 );
		else
		if ( IsDefined( param3 ) )
			caller [[ func ]]( param1, param2, param3 );
		else
		if ( IsDefined( param2 ) )
			caller [[ func ]]( param1, param2 );
		else
		if ( IsDefined( param1 ) )
			caller [[ func ]]( param1 );
		else
			caller [[ func ]]();
		
		if ( IsDefined( caller ) && IsDefined( caller.function_stack ) )
		{
			caller.function_stack = array_remove( caller.function_stack, self );
			caller notify( "level_function_stack_ready" );
		}
	}

	if ( IsDefined( self ) )
	{
		self.function_stack_func_begun = false;
		self notify( "function_done" );
	}
}

function_stack_self_death( caller )
{
	self endon( "function_done" );

	self waittill( "death" );

	if ( IsDefined( caller ) )
	{
		caller.function_stack = array_remove( caller.function_stack, self );
		caller notify( "level_function_stack_ready" );
	}
}


function_stack_caller_waits_for_turn( caller )
{
	caller endon( "death" );
	self endon( "death" );
	caller endon( "clear_function_stack" );

	while ( caller.function_stack[ 0 ] != self )
		caller waittill( "level_function_stack_ready" );
}

unflash_flag( seconds )
{
	level endon( "player_flashed" );
	wait( seconds );
	flag_clear( "player_flashed" );
}

wait_for_sounddone_or_death( org )
{
	self endon( "death" );
	org waittill( "sounddone" );
	return true;
}

init_vision_set( visionset )
{
	level._lvl_visionset = visionset;

	if ( !isdefined( level._vision_cheat_enabled ) )
		level._vision_cheat_enabled = false;

	return level._vision_cheat_enabled;
}

array_waitlogic1( ent, msg, timeout )
{
	self array_waitlogic2( ent, msg, timeout );

	self._array_wait = false;
	self notify( "_array_wait" );
}

array_waitlogic2( ent, msg, timeout )
{
	ent endon( msg );
	ent endon( "death" );

	if ( IsDefined( timeout ) )
		wait timeout;
	else
		ent waittill( msg );
}

exec_call( func )
{
	if ( func.parms.size == 0 )
	{
		func.caller call [[ func.func ]]();
	}
	else
	if ( func.parms.size == 1 )
	{
		func.caller call [[ func.func ]]( func.parms[ 0 ] );
	}
	else
	if ( func.parms.size == 2 )
	{
		func.caller call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ] );
	}
	else
	if ( func.parms.size == 3 )
	{
		func.caller call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ] );
	}
	if ( func.parms.size == 4 )
	{
		func.caller call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ], func.parms[ 3 ] );
	}
	if ( func.parms.size == 5 )
	{
		func.caller call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ], func.parms[ 3 ], func.parms[ 4 ] );
	}
}

exec_call_noself( func )
{
	if ( func.parms.size == 0 )
	{
		call [[ func.func ]]();
	}
	else
	if ( func.parms.size == 1 )
	{
		call [[ func.func ]]( func.parms[ 0 ] );
	}
	else
	if ( func.parms.size == 2 )
	{
		call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ] );
	}
	else
	if ( func.parms.size == 3 )
	{
		call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ] );
	}
	if ( func.parms.size == 4 )
	{
		call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ], func.parms[ 3 ] );
	}
	if ( func.parms.size == 5 )
	{
		call [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ], func.parms[ 3 ], func.parms[ 4 ] );
	}
}

exec_func( func, endons )
{
	if ( !isdefined( func.caller ) )
		return;

	for ( i = 0; i < endons.size; i++ )
	{
		endons[ i ].caller endon( endons[ i ].ender );
	}

	if ( func.parms.size == 0 )
	{
		func.caller [[ func.func ]]();
	}
	else
	if ( func.parms.size == 1 )
	{
		func.caller [[ func.func ]]( func.parms[ 0 ] );
	}
	else
	if ( func.parms.size == 2 )
	{
		func.caller [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ] );
	}
	else
	if ( func.parms.size == 3 )
	{
		func.caller [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ] );
	}
	if ( func.parms.size == 4 )
	{
		func.caller [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ], func.parms[ 3 ] );
	}
	if ( func.parms.size == 5 )
	{
		func.caller [[ func.func ]]( func.parms[ 0 ], func.parms[ 1 ], func.parms[ 2 ], func.parms[ 3 ], func.parms[ 4 ] );
	}
}

waittill_func_ends( func, endons )
{
	self endon( "all_funcs_ended" );
	self endon( "any_funcs_aborted" );
	exec_func( func, endons );
	self.count--;
	self notify( "func_ended" );
}

waittill_abort_func_ends( func, endons )
{
	self endon( "all_funcs_ended" );
	self endon( "any_funcs_aborted" );
	exec_func( func, endons );
	self.abort_count--;
	self notify( "abort_func_ended" );
}

do_abort( array )
{
	self endon( "all_funcs_ended" );

	if ( !array.size )
		return;

	count_to_reach = 0;
	self.abort_count = array.size;
	endons = [];

	self array_levelthread( array, ::waittill_abort_func_ends, endons );

	for ( ;; )
	{
		if ( self.abort_count <= count_to_reach )
			break;
		self waittill( "abort_func_ended" );
	}
	self notify( "any_funcs_aborted" );
}


translate_local_on_ent( entity )
{
	if ( IsDefined( self.forward ) )
	{
		forward = AnglesToForward( entity.angles );
		entity.origin += forward * self.forward;
	}
	if ( IsDefined( self.right ) )
	{
		right = AnglesToRight( entity.angles );
		entity.origin += right * self.right;
	}
	if ( IsDefined( self.up ) )
	{
		up = AnglesToUp( entity.angles );
		entity.origin += up * self.up;
	}

	if ( IsDefined( self.yaw ) )
	{
		entity AddYaw( self.yaw );
	}
	if ( IsDefined( self.pitch ) )
	{
		entity AddPitch( self.pitch );
	}
	if ( IsDefined( self.roll ) )
	{
		entity AddRoll( self.roll );
	}
}

dynamic_run_speed_proc( pushdist, sprintdist, stopdist, jogdist, group )
{
	self notify( "start_dynamic_run_speed" );

	self endon( "death" );
	self endon( "stop_dynamic_run_speed" );
	self endon( "start_dynamic_run_speed" );
	level endon( "_stealth_spotted" );

	if ( self ent_flag_exist( "_stealth_custom_anim" ) )
		self ent_flag_waitopen( "_stealth_custom_anim" );

	if ( !self ent_flag_exist( "dynamic_run_speed_stopped" ) )
	{
		self ent_flag_init( "dynamic_run_speed_stopped" );
		self ent_flag_init( "dynamic_run_speed_stopping" );
	}
	else
	{
		self ent_flag_clear( "dynamic_run_speed_stopping" );
		self ent_flag_clear( "dynamic_run_speed_stopped" );
	}

	self.run_speed_state = "";
	self.old_moveplaybackrate = self.moveplaybackrate;

	self thread stop_dynamic_run_speed();

	//MUCH faster to do distancesquared checks than distance
	pushdist2rd 	 = pushdist * pushdist;
	sprintdist2rd 	 = sprintdist * sprintdist;
	stopdist2rd 	 = stopdist * stopdist;
	jogdist2rd		 = jogdist * jogdist;

	while ( 1 )
	{
		wait .05;

		//the player that is the closest to us should have the most effect on us, therefore
		//the player closest to us will determine if we run, jog, or stop. sprinting to catch up
		//is actually done through both this test and a second test to see if the player further 
		//away is actually ahead of us (that's done in dynamic_run_ahead test ) which uses the 
		//wait_for_player function in go_to_node in _spawner

		player = level._players[ 0 ];
		foreach ( value in level._players )
		{
			if ( DistanceSquared( player.origin, self.origin ) > DistanceSquared( value.origin, self.origin ) )
				player = value;
		}

		vec = AnglesToForward( self.angles );
		vec2 = VectorNormalize( ( player.origin - self.origin ) );
		vecdot = VectorDot( vec, vec2 );

		//how far is the player
		dist2rd 		 = DistanceSquared( self.origin, player.origin );
		dist2rd_group 	 = dist2rd;

		if ( IsDefined( group ) )
		{
			//not worrying about supporting multiple players since coop is not a priority
			key = getClosest( player.origin, group );
			dist2rd_group = DistanceSquared( key.origin, player.origin );
		}

		//is the player actually ahead of us, even though we're not facing him?
		ahead = false;
		if ( IsDefined( self.last_set_goalent ) )
			ahead = [[ level._drs_ahead_test ]]( self.last_set_goalent, sprintdist );
		else if ( IsDefined( self.last_set_goalnode ) )
			ahead = [[ level._drs_ahead_test ]]( self.last_set_goalnode, sprintdist );

		if ( IsDefined( self.cqbwalking ) && self.cqbwalking && !self.dontChangeMovePlaybackRate )
				self.moveplaybackrate = 1;

		if ( dist2rd < sprintdist2rd || vecdot > - .25 || ahead )
		{
			dynamic_run_set( "sprint" );
			wait .5;
			continue;
		}

		else if ( dist2rd < pushdist2rd || vecdot > - .25 )
		{
			dynamic_run_set( "run" );
			wait .5;
			continue;
		}

		//noticed when didn't check the current movement - guys would jump out of cover to a kneal (which is default for stopping on the run )			
		else if ( dist2rd_group > stopdist2rd )
		{
			if ( self.a.movement != "stop" )
			{
				dynamic_run_set( "stop" );
				wait .5;
			}
			continue;
		}

		else if ( dist2rd > jogdist2rd )
		{
			dynamic_run_set( "jog" );
			wait .5;
			continue;
		}
	}
}

stop_dynamic_run_speed()
{
	self endon( "start_dynamic_run_speed" );
	self endon( "death" );

	self stop_dynamic_run_speed_wait();

	if ( !self.dontChangeMovePlaybackRate )
		self.moveplaybackrate = self.old_moveplaybackrate;

	if ( IsDefined( level._scr_anim[ "generic" ][ "DRS_run" ] ) )
	{
		if ( IsArray( level._scr_anim[ "generic" ][ "DRS_run" ] ) )
			self set_generic_run_anim_array( "DRS_run" );
		else
			self set_generic_run_anim( "DRS_run" );
	}
	else
		self clear_run_anim();

	self notify( "stop_loop" );
	self ent_flag_clear( "dynamic_run_speed_stopping" );
	self ent_flag_clear( "dynamic_run_speed_stopped" );
}

stop_dynamic_run_speed_wait()
{
	level endon( "_stealth_spotted" );
	self waittill( "stop_dynamic_run_speed" );
}

dynamic_run_ahead_test( node, dist )
{
	//only nodes and structs dont have classnames - ents do
	if ( !isdefined( node.classname ) )
	{
		//only structs don't have types, nodes do
		if ( !isdefined( node.type ) )
			getfunc = maps\_spawner::get_target_structs;
		else
			getfunc = maps\_spawner::get_target_nodes;
	}
	else
		getfunc = maps\_spawner::get_target_ents;

	return maps\_spawner::go_to_node_wait_for_player( node, getfunc, dist );
}

dynamic_run_set( speed )
{
	if ( self.run_speed_state == speed )
		return;

	self.run_speed_state = speed;

	switch( speed )
	{
		case "sprint":
			if ( IsDefined( self.cqbwalking ) && self.cqbwalking && !self.dontChangeMovePlaybackRate )
				self.moveplaybackrate = 1;
			else if ( !self.dontChangeMovePlaybackRate )
				self.moveplaybackrate = 1.15;

			if ( IsArray( level._scr_anim[ "generic" ][ "DRS_sprint" ] ) )
				self set_generic_run_anim_array( "DRS_sprint" );
			else
				self set_generic_run_anim( "DRS_sprint" );
			//self AllowedStances( "stand", "crouch", "prone" );

			self notify( "stop_loop" );
			self anim_stopanimscripted();
			self ent_flag_clear( "dynamic_run_speed_stopped" );
			break;
		case "run":
			if ( !self.dontChangeMovePlaybackRate )
				self.moveplaybackrate = self.old_moveplaybackrate;

			if ( IsDefined( level._scr_anim[ "generic" ][ "DRS_run" ] ) )
			{
				if ( IsArray( level._scr_anim[ "generic" ][ "DRS_run" ] ) )
					self set_generic_run_anim_array( "DRS_run" );
				else
					self set_generic_run_anim( "DRS_run" );
			}
			else
				self clear_run_anim();
			//self AllowedStances( "stand", "crouch", "prone" );

			self notify( "stop_loop" );
			self anim_stopanimscripted();
			self ent_flag_clear( "dynamic_run_speed_stopped" );
			break;
		case "stop":
			self thread dynamic_run_speed_stopped();
			break;
		case "jog":
			if ( !self.dontChangeMovePlaybackRate )
				self.moveplaybackrate = self.old_moveplaybackrate;

			if ( IsDefined( level._scr_anim[ "generic" ][ "DRS_combat_jog" ] ) )
			{
				if ( IsArray( level._scr_anim[ "generic" ][ "DRS_combat_jog" ] ) )
					self set_generic_run_anim_array( "DRS_combat_jog" );
				else
					self set_generic_run_anim( "DRS_combat_jog" );
			}
			else
				self clear_run_anim();
			//self AllowedStances( "stand", "crouch", "prone" );

			self notify( "stop_loop" );
			self anim_stopanimscripted();
			self ent_flag_clear( "dynamic_run_speed_stopped" );
			break;
		case "crouch":
			assertmsg( "thought this was depricated...come grab me - mo" );
			/*
			if ( !self.dontChangeMovePlaybackRate )
				self.moveplaybackrate = self.old_moveplaybackrate;

			self clear_run_anim();
			//self AllowedStances( "crouch" );

			self notify( "stop_loop" );
			self anim_stopanimscripted();
			self ent_flag_clear( "dynamic_run_speed_stopped" );
			*/
			break;
	}
}

dynamic_run_speed_stopped()
{
	self endon( "death" );

	if ( self ent_flag( "dynamic_run_speed_stopped" ) )
		return;
	if ( self ent_flag( "dynamic_run_speed_stopping" ) )
		return;

	self endon( "stop_dynamic_run_speed" );

	self ent_flag_set( "dynamic_run_speed_stopping" );
	self ent_flag_set( "dynamic_run_speed_stopped" );

	self endon( "dynamic_run_speed_stopped" );

	//->turned this off because I think checking the current movement would fix whatever this was trying to
	//if( self ent_flag_exist( "_stealth_stance_handler" ) )
	//	self ent_flag_waitopen( "_stealth_stance_handler" );

	stop = "DRS_run_2_stop";
	self maps\_anim::anim_generic_custom_animmode( self, "gravity", stop );
	self ent_flag_clear( "dynamic_run_speed_stopping" );// this flag gets cleared if we endon

	//if he's already playing a looping animation - we can assume its part of level logic and 
	//it makes sense not to play a stopping animation or play dialogue to tell you to catch up
	if ( IsDefined( self.loops ) && self.loops > 0 )
		return;

	while ( self ent_flag( "dynamic_run_speed_stopped" ) )
	{
		//->turned this off because I think checking the current movement would fix whatever this was trying to
		//if( self ent_flag_exist( "_stealth_stance_handler" ) )
		//	self ent_flag_waitopen( "_stealth_stance_handler" ); 

		idle = "DRS_stop_idle";
		self thread maps\_anim::anim_generic_loop( self, idle );

		if ( IsDefined( level._scr_anim[ "generic" ][ "signal_go" ] ) )
			self handsignal( "go" );

		wait RandomFloatRange( 12, 20 );

		if ( self ent_flag_exist( "_stealth_stance_handler" ) )
			self ent_flag_waitopen( "_stealth_stance_handler" );

		self notify( "stop_loop" );

		if ( !self ent_flag( "dynamic_run_speed_stopped" ) )
			return;

		if ( IsDefined( level._dynamic_run_speed_dialogue ) )
		{
			string = random( level._dynamic_run_speed_dialogue );
			level thread radio_dialogue_queue( string );
		}

		if ( IsDefined( level._scr_anim[ "generic" ][ "signal_go" ] ) )
			self handsignal( "go" );
	}
}

g_speed_get_func()
{
	return Int( GetDvar( "g_speed" ) );
}

g_speed_set_func( goalspeed )
{
	SetSavedDvar( "g_speed", Int( goalspeed ) );
}

movespeed_get_func()
{
	return self.movespeedscale;
}

movespeed_set_func( scale )
{
	self.movespeedscale = scale;
	self SetMoveSpeedScale( scale );
}

autosave_tactical_setup()
{
	if ( flag_exist( "autosave_tactical_player_nade" ) )
		return;

	flag_init( "autosave_tactical_player_nade" );
	level._autosave_tactical_player_nades = 0;
	NotifyOnCommand( "autosave_player_nade", "+frag" );
	NotifyOnCommand( "autosave_player_nade", "-smoke" );
	NotifyOnCommand( "autosave_player_nade", "+smoke" );

	array_thread( level._players, ::autosave_tactical_grenade_check );
}

autosave_tactical_grenade_check()
{
	while ( 1 )
	{
		//this one hit's as soon as the button is pressed - that's why we want
		//to set the flag here and not after the grenade has left the hand
		//with "grenade fire" 
		self waittill( "autosave_player_nade" );
		flag_set( "autosave_tactical_player_nade" );

		self waittill( "grenade_fire", grenade );
		thread autosave_tactical_grenade_check_dieout( grenade );
	}
}

autosave_tactical_grenade_check_dieout( grenade )
{
	level._autosave_tactical_player_nades++;
	grenade waittill_notify_or_timeout( "death", 10 );
	level._autosave_tactical_player_nades--;

	//give stealth a chance to notify of any issues because of the grenade before we clear the flag
	//so that the system checking for saving the game can verify those notifies first
	waittillframeend;

	if ( !level._autosave_tactical_player_nades )
		flag_clear( "autosave_tactical_player_nade" );
}

autosave_tactical_proc()
{
	level notify( "autosave_tactical_proc" );
	level endon( "autosave_tactical_proc" );

	level thread notify_delay( "kill_save", 5 );
	level endon( "kill_save" );

	level endon( "autosave_tactical_player_nade" );
	if ( flag( "autosave_tactical_player_nade" ) )
	{
		flag_waitopen_or_timeout( "autosave_tactical_player_nade", 4 );

		//we still have nades?	
		if ( flag( "autosave_tactical_player_nade" ) )
			return;
	}

	enemies = GetAIArray( "axis" );
	foreach ( ai in enemies )
	{
		if ( IsDefined( ai.enemy ) && IsPlayer( ai.enemy ) )
			return;
	}

	waittillframeend;// give endon's a chance to register
	autosave_by_name();
}

music_play_internal_stop_with_fade_then_call( name, fade_time, numtimestoplay, timescale, overrideCheat )
{
	music_stop( fade_time );

	level endon( "stop_music" );

	wait fade_time;
	thread music_play( name, undefined, numtimestoplay, timescale, overrideCheat );
}

music_loop_internal_stop_with_fade_then_call( name, length, fade_time, numtimestoplay, timescale, overrideCheat, stop_for_stealth )
{
	music_stop( fade_time );

	level endon( "stop_music" );

	wait fade_time;
	thread music_loop_internal( name, length, undefined, numtimestoplay, timescale, overrideCheat, stop_for_stealth );
}

music_loop_internal( name, post_music_delay, fade_time, numtimestoplay, timescale, overrideCheat, stop_for_stealth )
{
	if ( IsDefined( fade_time ) && fade_time > 0 )
	{
		thread music_loop_internal_stop_with_fade_then_call( name, post_music_delay, fade_time, numtimestoplay, timescale, overrideCheat, stop_for_stealth );
		return;
	}
	music_stop();

	level endon( "stop_music" );

	MusicPlayWrapper( name, timescale, overrideCheat );

	if ( IsDefined( stop_for_stealth ) && stop_for_stealth == true && flag_exist( "_stealth_spotted" ) )
	{
		level endon( "_stealth_spotted" );
		thread music_loop_stealth_pause( name, post_music_delay, fade_time, numtimestoplay );
	}

	delay = musicLength( name );
	
	if ( post_music_delay <= 10 )
	{
		delay += post_music_delay;
	}
	
	wait delay;
	music_loop( name, post_music_delay, fade_time, timescale, overrideCheat );
}

music_loop_stealth_pause( name, length, fade_time, numtimestoplay )
{
	level endon( "stop_music" );

	flag_wait( "_stealth_spotted" );
	MusicStop( .5 );

	while ( flag( "_stealth_spotted" ) )
	{
		flag_waitopen( "_stealth_spotted" );
		wait 1;
	}

	thread music_loop( name, length, fade_time, numtimestoplay );
}

DoSlide( slideModel, allowedAcceleration, dampening )
{
	self endon( "death" );
	self endon( "stop_sliding" );

	player = self;

	entNum = undefined;
	/#
	entNum = slidemodel getEntNum();
	#/

	
	while ( 1 )
	{
		movement = player GetNormalizedMovement();
		forward = AnglesToForward( player.angles );
		right = AnglesToRight( player.angles );

		movement = ( movement[ 1 ] * right[ 0 ] + movement[ 0 ] * forward[ 0 ], movement[ 1 ] * right[ 1 ] + movement[ 0 ] * forward[ 1 ], 0 );

		// maybe we should plant movement to be perpendicular to ground normal?

		slideModel.slideVelocity += movement * allowedAcceleration;

		wait .05;

		slideModel.slideVelocity *= 1 - dampening;
	}
}

kill_deathflag_proc( time )
{
	self endon( "death" );
	if ( IsDefined( time ) )
		wait( RandomFloat( time ) );

	PlayFXOnTag( getfx( "flesh_hit" ), self, "tag_eye" );
	self Kill( level._player.origin );
}

update_rumble_intensity( player )
{
	self endon( "death" );
	for ( ;; )
	{
		intensity = 1 - self.intensity;
		intensity *= 1000;
		self.origin = player.origin + ( 0, 0, intensity );
		/#
		if( is_coop() )
			PrintLn( "^3Warning! Using get_rumble_ent will cause the same rumbles to apply to all of the coop players!" );
		#/
		wait( 0.05 );
	}
}

start_glow( model )
{
	ent = Spawn( "script_model", self.origin );
	self.glow_model = ent;
	ent.angles = self.angles;
	ent SetModel( model );
	ent endon( "death" );
	self waittill( "death" );
	ent Delete();
}

blend_default_dof( progress, start, end )
{
	foreach ( key, value in level._dofDefault )
	{
		level._dofDefault[ key ] = start[ key ] * ( 1 - progress ) + end[ key ] * progress;
	}
}

process_blend( func, caller, var1, var2, var3 )
{
	waittillframeend;// wait for the blend settings to get set;
	AssertEx( IsDefined( self.time ), "Blend needs a .time set to know how long to blend over." );
	if ( !isdefined( self.start ) )
		self.start = 0;
	if ( !isdefined( self.end ) )
		self.end = 1;
	if ( !isdefined( self.base ) )
		self.base = 0;

	frames = self.time * 20;
	range = self.end - self.start;

	if ( IsDefined( var3 ) )
	{
		for ( i = 0; i <= frames; i++ )
		{
			value = self.base + ( i * range / frames );
			caller thread [[ func ]]( value, var1, var2, var3 );
			wait( 0.05 );
		}
	}
	else
	if ( IsDefined( var2 ) )
	{
		for ( i = 0; i <= frames; i++ )
		{
			value = self.base + ( i * range / frames );
			caller thread [[ func ]]( value, var1, var2 );
			wait( 0.05 );
		}
	}
	else
	if ( IsDefined( var1 ) )
	{
		for ( i = 0; i <= frames; i++ )
		{
			value = self.base + ( i * range / frames );
			caller thread [[ func ]]( value, var1 );
			wait( 0.05 );
		}
	}
	else
	{
		for ( i = 0; i <= frames; i++ )
		{
			value = self.base + ( i * range / frames );
			caller thread [[ func ]]( value );
			wait( 0.05 );
		}
	}
}

add_trace_fx_proc( name )
{
	waittillframeend;// give the scripts that set the params a chance to do so
	if ( !isdefined( level._trace_fx ) )
	{
		level._trace_fx = [];
	}

	AssertEx( IsDefined( self.surface ), "Tracefx for " + name + " has no .surface" );
	if ( !isdefined( level._trace_fx ) )
	{
		level._trace_fx[ name ] = [];
	}

	if ( IsDefined( self.fx ) )
		level._trace_fx[ name ][ self.surface ][ "fx" ] = self.fx;

	if ( IsDefined( self.fx_array ) )
		level._trace_fx[ name ][ self.surface ][ "fx_array" ] = self.fx_array;

	if ( IsDefined( self.sound ) )
		level._trace_fx[ name ][ self.surface ][ "sound" ] = self.sound;

	if ( IsDefined( self.rumble ) )
		level._trace_fx[ name ][ self.surface ][ "rumble" ] = self.rumble;

	if ( !isdefined( level._trace_fx[ name ][ "default" ] ) )
	{
		level._trace_fx[ name ][ "default" ] = level._trace_fx[ name ][ self.surface ];
	}
}

put_interactive_in_volume( toy )
{
	ent = SpawnStruct();
	PreCacheModel( toy.model );
	ent.toy_model = toy.model;
	ent.origin = toy.origin;
	ent.angles = toy.angles;
	ent.script_noteworthy = toy.script_noteworthy;
	ent.target = toy.target;
	ent.targetname = toy.targetname;

	self.interactives[ self.interactives.size ] = ent;

	toy Delete();
}

put_toy_in_volume( toy )
{
	ent = SpawnStruct();
	PreCacheModel( toy.model );
	ent.toy_model = toy.model;
	ent.origin = toy.origin;
	ent.angles = toy.angles;
	ent.script_noteworthy = toy.script_noteworthy;
	ent.target = toy.target;
	ent.destructible_type = toy.destructible_type;
	ent.script_noflip = toy.script_noflip;

	self.destructibles[ self.destructibles.size ] = ent;

	toy Delete();
}

get_color_info_from_trigger()
{
	/#
	if ( IsDefined( self.script_color_allies ) )
	{
		AssertEx( !isdefined( self.script_color_axis ), "trigger had axis and allies" );
	}
	else
	if ( IsDefined( self.script_color_axis ) )
	{
		AssertEx( !isdefined( self.script_color_allies ), "trigger had axis and allies" );
	}
	else
		AssertMsg( "Trigger didnt have allies or axis" );
	#/

	team = "allies";
	if ( IsDefined( self.script_color_axis ) )
		team = "axis";

	team = maps\_colors::get_team( team );// to cast team3

	colorCodes = [];
	if ( team == "allies" )
	{
		array = maps\_colors::get_colorcodes_from_trigger( self.script_color_allies, "allies" );
		colorCodes = array[ "colorCodes" ];
	}
	else
	{
		array = maps\_colors::get_colorcodes_from_trigger( self.script_color_axis, "axis" );
		colorCodes = array[ "colorCodes" ];
	}

	AssertEx( colorCodes.size, "Tried to get codes from trigger but there were none" );

	info = [];
	info[ "team" ] = team;
	info[ "codes" ] = colorCodes;

	return info;
}


delayThread_proc( func, timer, param1, param2, param3, param4, param5 )
{
	self endon( "death" );

	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 );
	}
}

add_wait_asserter()
{
	level notify( "kill_add_wait_asserter" );
	level endon( "kill_add_wait_asserter" );

	// wait until the end of the frame
	for ( i = 0; i < 20; i++ )
	{
		waittillframeend;
	}
	AssertMsg( "Added a wait or func and then didn't do_wait immediately." );
}

update_battlechatter_hud()
{
	/#
	if ( getdvar( "loc_warnings", 0 ) == "1" )
		return;

	if ( getdvarint( "debug_hud" ) != 1 )
		return;

	if ( !isdefined( level._bcs_hud ) )
	{
		x = -50;
		y = 460;
		x_offset = 22;

		hud = newHudElem();
		hud.x = x;
		hud.y = y;
		hud.color = ( 0.4, 0.55, 0.9 );
		level._bcs_hud = hud;
	}

	msg = "BCS enabled:           ";
	count = 0;
	
	// just gonna ignore team3 and neutral since it doesnt apply often
	teams = [];
	teams[ "allies" ] = level._battlechatter[ "allies" ];
	teams[ "axis" ] = level._battlechatter[ "axis" ];
	
	foreach ( team, val in teams )
	{
		if ( val )
		{
			msg = msg + team + " ";
		}
		count++;
	}
	
	if ( count )
	{
		level._bcs_hud settext( msg );
	}
	else
	{
		level._bcs_hud settext( "BCS disabled" );
	}
	#/
}

compareSizesFx( org, array, dist, compareFunc )
{
	if ( !array.size )
		return undefined;
	if ( IsDefined( dist ) )
	{
		struct = undefined;
		keys = GetArrayKeys( array );
		for ( i = 0; i < keys.size; i++ )
		{
			newdist = Distance( array[ keys[ i ] ].v[ "origin" ], org );
			if ( [[ compareFunc ]]( newDist, dist ) )
				continue;
			dist = newdist;
			struct = array[ keys[ i ] ];
		}
		return struct;
	}

	keys = GetArrayKeys( array );
	struct = array[ keys[ 0 ] ];
	dist = Distance( struct.v[ "origin" ], org );
	for ( i = 1; i < keys.size; i++ )
	{
		newdist = Distance( array[ keys[ i ] ].v[ "origin" ], org );
		if ( [[ compareFunc ]]( newDist, dist ) )
			continue;
		dist = newdist;
		struct = array[ keys[ i ] ];
	}
	return struct;
}
