#include common_scripts\utility;
#include common_scripts\_createfx;
//#include soundscripts\_audio;
//#include soundscripts\_audio_dynamic_ambi;


CONST_MAX_SP_CREATEFX 		= 1500;
CONST_MAX_SP_CREATESOUND 	= 384;
initFX()
{
	if ( !isdefined( level.func ) )
		level.func = [];
	
	if ( !isdefined( level.func[ "create_triggerfx" ] ) )
		 level.func[ "create_triggerfx" ] = ::create_triggerfx;

	if ( !IsDefined( level._fx ) )
		level._fx = SpawnStruct();
		 
	create_lock( "createfx_looper", 20 );
	level.fxfireloopmod = 1;

	// wrapper for the exploder function so we dont have to use flags and do ifs/waittills on every exploder call
	level._fx.exploderFunction = common_scripts\_exploder::exploder_before_load;
	waittillframeend;// Wait one frame so the effects get setup by the maps fx thread
	waittillframeend;// Wait another frame so effects can be loaded based on start functions. Without this FX are initialiazed before they are defined by start functions.
	level._fx.exploderFunction = common_scripts\_exploder::exploder_after_load;
	
	level._fx.server_culled_sounds = false;
	if ( GetDvarInt( "serverCulledSounds" ) == 1 )
		level._fx.server_culled_sounds = true;
	
	if ( level.createFX_enabled )
		level._fx.server_culled_sounds = false;
	
/#
	SetDevDvarIfUninitialized( "scr_map_exploder_dump", 0 );
	SetDevDvarIfUninitialized( "createfx_removedupes", 0 );
		
	if ( GetDvarInt( "r_reflectionProbeGenerate" ) == 1 )
		level._fx.server_culled_sounds = true;
#/

	// Give createfx_common time to delete triggers to free up entity slots.
	if ( level.createFX_enabled )
	{
		level waittill( "createfx_common_done" );
	}
	
/#
	remove_dupes();
#/
	
	for ( i = 0; i < level.createFXent.size; i++ )
	{
		ent = level.createFXent[ i ];
		ent set_forward_and_up_vectors();
		
		switch ( ent.v[ "type" ] )
		{
			case "loopfx":
				ent thread loopfxthread();
				break;
			case "oneshotfx":
				ent thread oneshotfxthread();
				break;
			case "soundfx":
				ent thread create_loopsound();
				break;
			case "soundfx_interval":
				ent thread create_interval_sound();
				break;
			case "reactive_fx":
				ent add_reactive_fx();
				break;
			case "soundfx_dynamic":
				ent thread create_dynamicambience();
				break;
		}
	}
		
	check_createfx_limit();
}

remove_dupes()
{
/#
	if ( GetDvarInt( "createfx_removedupes" ) == 0 )
		return;

	new_ents = [];
	for ( i = 0; i < level.createFXent.size; i++ )
	{
		add_ent = true;
		i_ent = level.createFXent[ i ];
		for ( j = i + 1; j < level.createFXent.size; j++ )
		{
			j_ent = level.createFXent[ j ];
			
			if ( j_ent.v[ "type" ] == i_ent.v[ "type" ] )
			{
				if ( j_ent.v[ "origin" ] == i_ent.v[ "origin" ] )
				{
					println( "^3--REMOVING DUPE'D " + j_ent.v[ "type" ] + " AT " + j_ent.v[ "origin" ] );
					add_ent = false;
				}
			}
		}
		
		if ( add_ent )
			new_ents[ new_ents.size ] = i_ent;
	}
	
	level.createFXent = new_ents;
#/
}

check_createfx_limit()
{
/#
	if ( !isSP() )
	{
		return;
	}

	fx_count = 0;
	sound_count = 0;
	foreach ( ent in level.createFXent )
	{
		if ( is_createfx_type( ent, "fx" ) )
			fx_count++;
		else if ( is_createfx_type( ent, "sound" ) )
			sound_count++;
	}
	
	println( "^5Total CreateFX FX Ents: " + fx_count );
	println( "^5Total CreateFX SOUND Ents: " + sound_count );
	
	check_limit_type( "fx", fx_count );
	check_limit_type( "sound", sound_count );
#/
}

check_limit_type( type, count )
{
/#
	limit = undefined;
	if ( type == "fx" )
	{
		limit = CONST_MAX_SP_CREATEFX;
	}
	else if ( type == "sound" )
	{
		limit = CONST_MAX_SP_CREATESOUND;
	}
	
	if ( count > limit )
		AssertMsg( "CREATEFX: You have too many " + type + " createFX ents. You need to reduce the amount.\nYou have " + count + " and the limit is " + limit );
#/
}


print_org( fxcommand, fxId, fxPos, waittime )
{
	if ( GetDvar( "debug" ) == "1" )
	{
		println( "{" );
		println( "\"origin\" \"" + fxPos[ 0 ] + " " + fxPos[ 1 ] + " " + fxPos[ 2 ] + "\"" );
		println( "\"classname\" \"script_model\"" );
		println( "\"model\" \"fx\"" );
		println( "\"script_fxcommand\" \"" + fxcommand + "\"" );
		println( "\"script_fxid\" \"" + fxId + "\"" );
		println( "\"script_delay\" \"" + waittime + "\"" );
		println( "}" );
	}
}

PlatformMatches()
{
	if ( IsDefined( self.v[ "platform" ] ) && IsDefined( level.currentgen ) )
	{
		platform = self.v[ "platform" ];

		if (( platform == "cg" && !level.currentgen ) ||
		    ( platform == "ng" && !level.nextgen ) ||
			( platform == "xenon" && !level.xenon ) ||
		    ( platform == "ps3" && !level.ps3 ) ||
		    ( platform == "pc" && !level.pc ) ||
			( platform == "xb3" && !level.xb3 ) ||
			( platform == "ps4" && !level.ps4 ) ||
			( platform == "pccg" && !level.pccg ) ||
			( platform == "!cg" && level.currentgen ) ||
			( platform == "!ng" && level.nextgen ) ||
		    ( platform == "!xenon" && level.xenon ) ||
		    ( platform == "!ps3" && level.ps3 ) ||
		    ( platform == "!pc" && level.pc ) ||
			( platform == "!xb3" && level.xb3 ) ||
			( platform == "!ps4" && level.ps4 ) ||
			( platform == "!pccg" && level.pccg ) )
		{
			return false;
		}
	}

	return true;
}

OneShotfx( fxId, fxPos, waittime, fxPos2 )
{
//	level thread print_org ("OneShotfx", fxId, fxPos, waittime);
//    level thread OneShotfxthread (fxId, fxPos, waittime, fxPos2);
}

exploderfx( num, fxId, fxPos, waittime, fxPos2, fireFx, fireFxDelay, fireFxSound, fxSound, fxQuake, fxDamage, soundalias, repeat, delay_min, delay_max, damage_radius, fireFxTimeout, exploder_group )
{
	if ( 1 )
	{
		ent = createExploder( fxId );
		ent.v[ "origin" ] = fxPos;
		ent.v[ "angles" ] = ( 0, 0, 0 );
		if ( isdefined( fxPos2 ) )
			ent.v[ "angles" ] = vectortoangles( fxPos2 - fxPos );
		ent.v[ "delay" ] = waittime;
		ent.v[ "exploder" ] = num;
		if (isdefined(level.createFXexploders))
		{	// if we're using the optimized lookup, add it in the proper place
			ary = level.createFXexploders[ ent.v[ "exploder" ] ];
			if (!isdefined(ary))
				ary = [];
			ary[ary.size] = ent;
			level.createFXexploders[ ent.v[ "exploder" ] ] = ary;
		}
		// deprecated
		return;
	}
	fx = spawn( "script_origin", ( 0, 0, 0 ) );
//	println ("total ", getentarray ("script_origin","classname").size);
	fx.origin = fxPos;
	fx.angles = vectortoangles( fxPos2 - fxPos );
//	fx.targetname = "exploder";
	fx.script_exploder = num;
	fx.script_fxid = fxId;
	fx.script_delay = waittime;

	fx.script_firefx = fireFx;
	fx.script_firefxdelay = ( fireFxDelay );// for awhile the script exported strings for this value so we cast it to float
	fx.script_firefxsound = fireFxSound;

	fx.script_sound = fxSound;
	fx.script_earthquake = fxQuake;
	fx.script_damage = ( fxDamage );
	fx.script_radius = ( damage_radius );
	fx.script_soundalias = soundalias;
	fx.script_firefxtimeout = ( fireFxTimeout );
	fx.script_repeat = ( repeat );
	fx.script_delay_min = ( delay_min );
	fx.script_delay_max = ( delay_max );
	fx.script_exploder_group = exploder_group;

	forward = anglestoforward( fx.angles );
	forward  *= ( 150 );
	fx.targetPos = fxPos + forward;

	if ( !isdefined( level._script_exploders ) )
		level._script_exploders = [];
	level._script_exploders[ level._script_exploders.size ] = fx;
}


/*
loopfxRotate(fxId, fxPos, waittime, angle, fxStart, fxStop, timeout)
{
	level thread print_org ("loopfx", fxId, fxPos, waittime);
    level thread loopfxthread (fxId, fxPos, waittime, fxPos2, fxStart, fxStop, timeout);
}
*/


loopfx( fxId, fxPos, waittime, fxPos2, fxStart, fxStop, timeout )
{
	println( "Loopfx is deprecated!" );
	ent = createLoopEffect( fxId );
	ent.v[ "origin" ] = fxPos;
	ent.v[ "angles" ] = ( 0, 0, 0 );
	if ( isdefined( fxPos2 ) )
		ent.v[ "angles" ] = vectortoangles( fxPos2 - fxPos );
	ent.v[ "delay" ] = waittime;
}

/*
loopfx(fxId, fxPos, waittime, fxPos2, fxStart, fxStop, timeout)
{
	level thread print_org ("loopfx", fxId, fxPos, waittime);
    level thread loopfxthread (fxId, fxPos, waittime, fxPos2, fxStart, fxStop, timeout);
}
*/

create_looper()
{
	//assert (isdefined(self.looper));
	self.looper = playLoopedFx( level._effect[ self.v[ "fxid" ] ], self.v[ "delay" ], self.v[ "origin" ], 0, self.v[ "forward" ], self.v[ "up" ] );
	create_loopsound();
}

create_loopsound()
{
	if ( !PlatformMatches() )
	{
		return;
	}

	self notify( "stop_loop" );
	
	if ( !IsDefined( self.v[ "soundalias" ] ) )
		return;
	
	if ( self.v[ "soundalias" ] == "nil" )
		return;
	
	/#
	if ( GetDvar( "r_reflectionProbeGenerate" ) == "1" )
		return;
	#/

	culled = false;
	end_on = undefined;
	if ( isdefined( self.v[ "stopable" ] ) && self.v[ "stopable" ] )
	{
		if ( IsDefined( self.looper ) )
			end_on = "death";
		else
			end_on = "stop_loop";
	}
	else
	{
		if ( level._fx.server_culled_sounds && IsDefined( self.v[ "server_culled" ] ) )
			culled = self.v[ "server_culled" ];
	}
	
	ent = self;
	if ( IsDefined( self.looper ) )
		ent = self.looper;
	
	createfx_ent = undefined;
	if ( level.createFX_enabled )
		createfx_ent = self;
	
	ent loop_fx_sound_with_angles( self.v[ "soundalias" ], self.v[ "origin" ], self.v[ "angles" ], culled, end_on, createfx_ent );
}

create_interval_sound()
{
	if ( !PlatformMatches() )
	{
		return;
	}

	self notify( "stop_loop" );
	
	if ( !IsDefined( self.v[ "soundalias" ] ) )
		return;
	if ( self.v[ "soundalias" ] == "nil" )
		return;

	ender = undefined;
	runner = self;
	
/#
	if ( GetDvar( "r_reflectionProbeGenerate" ) == "1" )
		return;
#/

	
	if( ( IsDefined( self.v[ "stopable" ] ) && self.v[ "stopable" ] ) || level.createFX_enabled )
	{
		if ( IsDefined( self.looper ) )
		{
			runner = self.looper;
			ender = "death";
		}
		else
			ender = "stop_loop";
		
	}
	
	runner thread loop_fx_sound_interval_with_angles( self.v[ "soundalias" ], self.v[ "origin" ], self.v[ "angles" ], ender, undefined, self.v[ "delay_min" ], self.v[ "delay_max" ] );
}

create_dynamicambience()
{
	if ( !PlatformMatches() )
	{
		return;
	}

	if ( !IsDefined( self.v[ "ambiencename" ] ) )
		return;
	if ( self.v[ "ambiencename" ] == "nil" )
		return;

	if ( isSP() )
	{
		// TODO - Make this work in SP
		/*
		if ( IsDefined( self.dambInfoStruct ) )
		{
			DAMB_stop_preset_at_point( self.v[ "ambiencename" ], self.dambInfoStruct.unique_id );
		}

		DAMB_start_preset_at_point( self.v[ "ambiencename" ], self.v[ "origin" ], undefined, self.v[ "dynamic_distance" ], self.dambInfoStruct.unique_id );
		*/
	}
	else
	{
		if ( GetDvar( "createfx" ) == "on" )
		{
			// wait till the player spawns and createfxlogic starts before continuing.
			flag_wait( "createfx_started" );
		}

		if ( IsDefined( self.dambInfoStruct ) )
		{
			level.player StopDynamicAmbience( self.dambInfoStruct.unique_id );
		}

		self.dambInfoStruct = SpawnStruct();
		self.dambInfoStruct assign_unique_id();		
		level.player PlayDynamicAmbience( self.v[ "ambiencename" ], self.v[ "origin" ], self.v[ "dynamic_distance" ], self.dambInfoStruct.unique_id );
	}
}

loopfxthread()
{
	waitframe();
//	println ( "fx testing running Id: ", fxId );
//    if ((isdefined (level.scr_sound)) && (isdefined (level.scr_sound[fxId])))
//	   loopSound(level.scr_sound[fxId], fxPos);

	if ( isdefined( self.fxStart ) )
		level waittill( "start fx" + self.fxStart );

	while ( 1 )
	{
		/*
		if (isdefined (ent.org2))
		{
			fxAngle = vectorNormalize (ent.org2 - ent.org);
			looper = playLoopedFx( level._effect[fxId], ent.delay, ent.org, 0, fxAngle );
		}
		else
			looper = playLoopedFx( level._effect[fxId], ent.delay, ent.org, 0 );
		*/
		create_looper();

		if ( isdefined( self.timeout ) )
			thread loopfxStop( self.timeout );

		if ( isdefined( self.fxStop ) )
			level waittill( "stop fx" + self.fxStop );
		else
			return;

		if ( isdefined( self.looper ) )
			self.looper delete();

		if ( isdefined( self.fxStart ) )
			level waittill( "start fx" + self.fxStart );
		else
			return;
	}
}

loopfxChangeID( ent )
{
	self endon( "death" );
	ent waittill( "effect id changed", change );
}

loopfxChangeOrg( ent )
{
	self endon( "death" );
	for ( ;; )
	{
		ent waittill( "effect org changed", change );
		self.origin = change;
	}
}

loopfxChangeDelay( ent )
{
	self endon( "death" );
	ent waittill( "effect delay changed", change );
}

loopfxDeletion( ent )
{
	self endon( "death" );
	ent waittill( "effect deleted" );
	self delete();
}

loopfxStop( timeout )
{
	self endon( "death" );
	wait( timeout );
	self.looper delete();
}

loopSound( sound, Pos, waittime )
{
//	level thread print_org ("loopSound", sound, Pos, waittime);
	level thread loopSoundthread( sound, Pos, waittime );
}

loopSoundthread( sound, pos, waittime )
{
	org = spawn( "script_origin", ( pos ) );

	org.origin = pos;
//	println ("hello1 ", org.origin, sound);
	org playLoopSound( sound );
}

gunfireloopfx( fxId, fxPos, shotsMin, shotsMax, shotdelayMin, shotdelayMax, betweenSetsMin, betweenSetsMax )
{
    thread gunfireloopfxthread( fxId, fxPos, shotsMin, shotsMax, shotdelayMin, shotdelayMax, betweenSetsMin, betweenSetsMax );
}

gunfireloopfxthread( fxId, fxPos, shotsMin, shotsMax, shotdelayMin, shotdelayMax, betweenSetsMin, betweenSetsMax )
{
	level endon( "stop all gunfireloopfx" );
	waitframe();

	if ( betweenSetsMax < betweenSetsMin )
	{
		temp = betweenSetsMax;
		betweenSetsMax = betweenSetsMin;
		betweenSetsMin = temp;
	}

	betweenSetsBase = betweenSetsMin;
	betweenSetsRange = betweenSetsMax - betweenSetsMin;

	if ( shotdelayMax < shotdelayMin )
	{
		temp = shotdelayMax;
		shotdelayMax = shotdelayMin;
		shotdelayMin = temp;
	}

	shotdelayBase = shotdelayMin;
	shotdelayRange = shotdelayMax - shotdelayMin;

	if ( shotsMax < shotsMin )
	{
		temp = shotsMax;
		shotsMax = shotsMin;
		shotsMin = temp;
	}

	shotsBase = shotsMin;
	shotsRange = shotsMax - shotsMin;

	fxEnt = spawnFx( level._effect[ fxId ], fxPos );
	
	if ( !level.createFX_enabled )
		fxEnt willNeverChange();
		
    for ( ;; )
    {
		shotnum = shotsBase + randomint( shotsRange );
		for ( i = 0;i < shotnum;i++ )
		{
			triggerFx( fxEnt );

			wait( shotdelayBase + randomfloat( shotdelayRange ) );
		}
        wait( betweenSetsBase + randomfloat( betweenSetsRange ) );
    }
}

gunfireloopfxVec( fxId, fxPos, fxPos2, shotsMin, shotsMax, shotdelayMin, shotdelayMax, betweenSetsMin, betweenSetsMax )
{
    thread gunfireloopfxVecthread( fxId, fxPos, fxPos2, shotsMin, shotsMax, shotdelayMin, shotdelayMax, betweenSetsMin, betweenSetsMax );
}

gunfireloopfxVecthread( fxId, fxPos, fxPos2, shotsMin, shotsMax, shotdelayMin, shotdelayMax, betweenSetsMin, betweenSetsMax )
{
	level endon( "stop all gunfireloopfx" );
	waitframe();

	if ( betweenSetsMax < betweenSetsMin )
	{
		temp = betweenSetsMax;
		betweenSetsMax = betweenSetsMin;
		betweenSetsMin = temp;
	}

	betweenSetsBase = betweenSetsMin;
	betweenSetsRange = betweenSetsMax - betweenSetsMin;

	if ( shotdelayMax < shotdelayMin )
	{
		temp = shotdelayMax;
		shotdelayMax = shotdelayMin;
		shotdelayMin = temp;
	}

	shotdelayBase = shotdelayMin;
	shotdelayRange = shotdelayMax - shotdelayMin;

	if ( shotsMax < shotsMin )
	{
		temp = shotsMax;
		shotsMax = shotsMin;
		shotsMin = temp;
	}

	shotsBase = shotsMin;
	shotsRange = shotsMax - shotsMin;

	fxPos2 = vectornormalize( fxPos2 - fxPos );

	fxEnt = spawnFx( level._effect[ fxId ], fxPos, fxPos2 );

	if ( !level.createFX_enabled )
		fxEnt willNeverChange();

	for ( ;; )
	{
		shotnum = shotsBase + randomint( shotsRange );
		for ( i = 0;i < int( shotnum / level.fxfireloopmod );i++ )
		{
			triggerFx( fxEnt );
			delay = ( ( shotdelayBase + randomfloat( shotdelayRange ) ) * level.fxfireloopmod );
			if ( delay < .05 )
				delay = .05;
			wait delay;
		}
		wait( shotdelayBase + randomfloat( shotdelayRange ) );
		wait( betweenSetsBase + randomfloat( betweenSetsRange ) );
	}
}

setfireloopmod( value )
{
	level.fxfireloopmod = 1 / value;
}

setup_fx()
{
	if ( ( !isdefined( self.script_fxid ) ) || ( !isdefined( self.script_fxcommand ) ) || ( !isdefined( self.script_delay ) ) )
	{
//		println (self.script_fxid);
//		println (self.script_fxcommand);
//		println (self.script_delay);
//		println ("Effect at origin ", self.origin," doesn't have script_fxid/script_fxcommand/script_delay");
//		self delete();
		return;
	}

//	println ("^a Command:", self.script_fxcommand, " Effect:", self.script_fxID, " Delay:", self.script_delay, " ", self.origin);
	if ( isdefined( self.model ) )
		if ( self.model == "toilet" )
		{
			self thread burnville_paratrooper_hack();
			return;
		}

	org = undefined;
	if ( isdefined( self.target ) )
	{
		ent = getent( self.target, "targetname" );
		if ( isdefined( ent ) )
			org = ent.origin;
	}

	fxStart = undefined;
	if ( isdefined( self.script_fxstart ) )
		fxStart = self.script_fxstart;

	fxStop = undefined;
	if ( isdefined( self.script_fxstop ) )
		fxStop = self.script_fxstop;

	if ( self.script_fxcommand == "OneShotfx" )
		OneShotfx( self.script_fxId, self.origin, self.script_delay, org );
	if ( self.script_fxcommand == "loopfx" )
		loopfx( self.script_fxId, self.origin, self.script_delay, org, fxStart, fxStop );
	if ( self.script_fxcommand == "loopsound" )
		loopsound( self.script_fxId, self.origin, self.script_delay );

	self delete();
}

burnville_paratrooper_hack()
{
	normal = ( 0, 0, self.angles[ 1 ] );
//	println ("z:       paratrooper fx hack: ", normal);
	id = level._effect[ self.script_fxId ];
	origin = self.origin;

//	if (isdefined (self.script_delay))
//		wait (self.script_delay);

	wait 1;
	level thread burnville_paratrooper_hack_loop( normal, origin, id );
	self delete();
}

burnville_paratrooper_hack_loop( normal, origin, id )
{
	while ( 1 )
	{
	//	iprintln ("z:        playing paratrooper fx", origin);

		playfx( id, origin );
		wait( 30 + randomfloat( 40 ) );
	}
}

create_triggerfx()
{
	//assert (isdefined(self.looper));
	if( ! verify_effects_assignment( self.v[ "fxid" ] ) )
		return;
		
	self.looper = spawnFx( level._effect[ self.v[ "fxid" ] ], self.v[ "origin" ], self.v[ "forward" ], self.v[ "up" ] );
	triggerFx( self.looper, self.v[ "delay" ] );

	if ( !level.createFX_enabled )
		self.looper willNeverChange();

	create_loopsound();
}

verify_effects_assignment( effectID )
{
	if( isdefined ( level._effect[ effectID ] ) )
		return true;
	if( ! isdefined( level._missing_FX ) )
		level._missing_FX = [];
	level._missing_FX[ self.v[ "fxid" ] ] = effectID;
	verify_effects_assignment_print( effectID );
	return false;
}

verify_effects_assignment_print( effectID )
{
	
	level notify ( "verify_effects_assignment_print" );
	level endon ( "verify_effects_assignment_print" );
	wait .05; //allow errors on the same frame to que up before printing
	
	println("Error:");
	println("Error:**********MISSING EFFECTS IDS**********");
	keys = getarraykeys( level._missing_FX );
	foreach( key in keys )
	{
		println( "Error: Missing Effects ID assignment for: "+ key  );
	}
	println("Error:");
	
	assertmsg( "Missing Effects ID assignments ( see console )" );
}

OneShotfxthread()
{
	wait(0.05);

	if ( !PlatformMatches() )
	{
		return;
	}

	if ( self.v[ "delay" ] > 0 )
	    wait self.v[ "delay" ];

	[[ level.func[ "create_triggerfx" ] ]]();
}

//---------------------------------------------------------
// Reactive Section
//---------------------------------------------------------
CONST_MAX_REACTIVE_SOUND_ENTS = 4;
CONST_NEXT_PLAY_TIME = 3000;
add_reactive_fx()
{
	if ( !PlatformMatches() )
	{
		return;
	}

	// MP should not start this thread in release
	if ( !isSP() && GetDVar( "createfx" ) == "" )
	{
		return;
	}

	if ( !IsDefined( level._fx.reactive_thread ) )
	{
		level._fx.reactive_thread = true;
		level thread reactive_fx_thread();
	}
	
	if ( !IsDefined( level._fx.reactive_fx_ents ) )
	{
		level._fx.reactive_fx_ents = [];
	}
	
	level._fx.reactive_fx_ents[ level._fx.reactive_fx_ents.size ] = self;
	self.next_reactive_time = 3000;
}

reactive_fx_thread()
{
	if ( !isSp() )
	{
		if ( GetDvar( "createfx" ) == "on" )
		{
			// wait till the player spawns and createfxlogic starts before continuing.
			flag_wait( "createfx_started" );
		}
	}

	// TODO: Switch to using level notify instead
	// HOWEVER!!! we will need the trigger_radius for when we are in CREATEFX mode for MP only!!!
	// use level.mp_createfx condition
//	trigger = Spawn( "trigger_radius", level.player.origin, 0, 5000, 5000 );
//	trigger SetCanDamage( true );
	
//	trigger EnableLinkTo();
//	trigger LinkTo( level.player );
	
	level._fx.reactive_sound_ents = [];
	
	explosion_radius = 256;

	while ( 1 )
	{
//		trigger waittill( "damage", dmg, attacker, dir_vec, point, type );
		level waittill( "code_damageradius", attacker, explosion_radius, point, weapon_name );

		ents = sort_reactive_ents( point, explosion_radius );

		foreach ( i, ent in ents )
			ent thread play_reactive_fx( i );
	}
	}

vector2d( vec )
{
	return ( vec[ 0 ], vec[ 1 ], 0 );
}

sort_reactive_ents( point, explosion_radius )
{
	closest = [];
	time = GetTime();
	foreach ( ent in level._fx.reactive_fx_ents )
	{
		if ( ent.next_reactive_time > time )
			continue;
		
		radius_squared = ent.v[ "reactive_radius" ] + explosion_radius;
		radius_squared *= radius_squared;
		if ( DistanceSquared( point, ent.v[ "origin" ] ) < radius_squared )
		{
			closest[ closest.size ] = ent;
		}
	}
			
	foreach ( ent in closest )
	{
		playerToEnt = vector2d( ent.v[ "origin" ] - level.player.origin );
		playerToPoint = vector2d( point - level.player.origin );
		vec1 = VectorNormalize( playerToEnt );
		vec2 = VectorNormalize( playerToPoint );
		ent.dot = VectorDot( vec1, vec2 );
	}

	// Sort from lowest dot to greatest
	for ( i = 0; i < closest.size - 1; i++ )
	{
		for ( j = i + 1; j < closest.size; j++ )
		{
			if ( closest[ i ].dot > closest[ j ].dot )
			{
				temp = closest[ i ];
				closest[ i ] = closest[ j ];
				closest[ j ] = temp;
			}
		}
	}
	
	// remove the .origin and .dot since we're done with sortbydistance()
	foreach ( ent in closest )
	{
		ent.origin = undefined;
		ent.dot = undefined;
	}
	
	// Make sure we only have 4
	for ( i = CONST_MAX_REACTIVE_SOUND_ENTS; i < closest.size; i++ )
	{
		closest[ i ] = undefined;
	}
	
	return closest;
}

play_reactive_fx( num )
{
	sound_ent = get_reactive_sound_ent();
	
	if ( !IsDefined( sound_ent ) )
		return;

/#
	self.is_playing = true;
#/

	self.next_reactive_time = GeTTime() + CONST_NEXT_PLAY_TIME;
	sound_ent.origin = self.v[ "origin" ];
	sound_ent.is_playing = true;
	
	wait( num * RandomFloatRange( 0.05, 0.1 ) );
	
	if ( isSP() )
	{
		sound_ent PlaySound( self.v[ "soundalias" ], "sounddone" );
		sound_ent waittill( "sounddone" );
	}
	else
	{
		sound_ent PlaySound( self.v[ "soundalias" ] );
		wait( 2 );
	}
	
	// wait for sounddone to be removed compeletely
	// bug in prague port to iw6 where in the same frame this got called again after the sounddone notify
	// odd thing, even 0.05 doesn't work. Code should fix this!
	wait( 0.1 );
	sound_ent.is_playing = false;

/#
	self.is_playing = undefined;
#/
}

get_reactive_sound_ent()
{
	foreach ( ent in level._fx.reactive_sound_ents )
	{
		if ( !ent.is_playing )
			return ent;
	}

	if ( level._fx.reactive_sound_ents.size < CONST_MAX_REACTIVE_SOUND_ENTS )
	{
		ent = Spawn( "script_origin", ( 0, 0, 0 ) );
		ent.is_playing = false;

		level._fx.reactive_sound_ents[ level._fx.reactive_sound_ents.size ] = ent;

		return ent;
	}

	return undefined;
}