#include common_scripts\utility;

/*

Handles Old-School exploders.  This is coupled with _createfx and _fx since a Radiant Created "exploder" can trigger a createfx created effect.

This Script is legacy, tried and true, It eats up entites when using script_brushmodel swapping and a lot of script_models and the damage triggers.

We should in a future game consider some entiity optimization in code.  A way to Join models and brushes as one entity would be key.

 */

GetExploders( id )
{
	id += "";
	if (isdefined(level.createFXexploders))
		return level.createFXexploders[ id ];
	
	// old way
	array = [];
	foreach (ent in level.createFxent)
	{
		if ( !isdefined( ent ) )
			continue;

		if ( ent.v[ "type" ] != "exploder" )
			continue;

		if ( !isdefined( ent.v[ "exploder" ] ) )
			continue;

		if ( ent.v[ "exploder" ] == id )
			array[array.size] = ent;
	}
	return array;
}

setup_individual_exploder( ent )
{
	exploder_num = ent.script_exploder;
	if ( !IsDefined( level.exploders[ exploder_num ] ) )
	{
		level.exploders[ exploder_num ] = [];
	}

	targetname = ent.targetname;
	if ( !IsDefined( targetname ) )
		targetname = "";

	level.exploders[ exploder_num ][ level.exploders[ exploder_num ].size ] = ent;
	if ( exploder_model_starts_hidden( ent ) )
	{
		ent Hide();
		return;
	}

	if ( exploder_model_is_damaged_model( ent ) )
	{
		ent Hide();
		ent NotSolid();
		if ( IsDefined( ent.spawnflags ) && ( ent.spawnflags & 1 ) )
		{
			if ( IsDefined( ent.script_disconnectpaths ) )
			{
				ent ConnectPaths();
			}
		}
		return;
	}

	if ( exploder_model_is_chunk( ent ) )
	{
		ent Hide();
		ent NotSolid();
		if ( IsDefined( ent.spawnflags ) && ( ent.spawnflags & 1 ) )
			ent ConnectPaths();
		return;
	}
}

setupExploders()
{
	level.exploders = [];

	// Hide exploder models.
	ents = GetEntArray( "script_brushmodel", "classname" );
	smodels = GetEntArray( "script_model", "classname" );
	for ( i = 0; i < smodels.size; i++ )
		ents[ ents.size ] = smodels[ i ];

	foreach ( ent in ents )
	{
		if ( IsDefined( ent.script_prefab_exploder ) )
			ent.script_exploder = ent.script_prefab_exploder;
			
		if ( IsDefined( ent.masked_exploder ) )
			continue;
			
		if ( IsDefined( ent.script_exploder ) )
		{
			setup_individual_exploder( ent );
		}
	}

	script_exploders = [];

	potentialExploders = GetEntArray( "script_brushmodel", "classname" );
	for ( i = 0; i < potentialExploders.size; i++ )
	{
		if ( IsDefined( potentialExploders[ i ].script_prefab_exploder ) )
			potentialExploders[ i ].script_exploder = potentialExploders[ i ].script_prefab_exploder;

		if ( IsDefined( potentialExploders[ i ].script_exploder ) )
			script_exploders[ script_exploders.size ] = potentialExploders[ i ];
	}

	potentialExploders = GetEntArray( "script_model", "classname" );
	for ( i = 0; i < potentialExploders.size; i++ )
	{
		if ( IsDefined( potentialExploders[ i ].script_prefab_exploder ) )
			potentialExploders[ i ].script_exploder = potentialExploders[ i ].script_prefab_exploder;

		if ( IsDefined( potentialExploders[ i ].script_exploder ) )
			script_exploders[ script_exploders.size ] = potentialExploders[ i ];
	}

	potentialExploders = GetEntArray( "script_origin", "classname" );
	for ( i = 0; i < potentialExploders.size; i++ )
	{
		if ( IsDefined( potentialExploders[ i ].script_prefab_exploder ) )
			potentialExploders[ i ].script_exploder = potentialExploders[ i ].script_prefab_exploder;

		if ( IsDefined( potentialExploders[ i ].script_exploder ) )
			script_exploders[ script_exploders.size ] = potentialExploders[ i ];
	}

	potentialExploders = GetEntArray( "item_health", "classname" );
	for ( i = 0; i < potentialExploders.size; i++ )
	{
		if ( IsDefined( potentialExploders[ i ].script_prefab_exploder ) )
			potentialExploders[ i ].script_exploder = potentialExploders[ i ].script_prefab_exploder;

		if ( IsDefined( potentialExploders[ i ].script_exploder ) )
			script_exploders[ script_exploders.size ] = potentialExploders[ i ];
	}
	
	
	potentialExploders = level.struct;
	for ( i = 0; i < potentialExploders.size; i++ )
	{
		if ( !IsDefined( potentialExploders[ i ] ) )
			continue; // these must be getting deleted somewhere else?
		if ( IsDefined( potentialExploders[ i ].script_prefab_exploder ) )
			potentialExploders[ i ].script_exploder = potentialExploders[ i ].script_prefab_exploder;

		if ( IsDefined( potentialExploders[ i ].script_exploder ) )
		{
			if ( !IsDefined( potentialExploders[ i ].angles ) )
				potentialExploders[ i ].angles = ( 0, 0, 0 );
			script_exploders[ script_exploders.size ] = potentialExploders[ i ];

		}
	}	
	

	if ( !IsDefined( level.createFXent ) )
		level.createFXent = [];

	acceptableTargetnames							 = [];
	acceptableTargetnames[ "exploderchunk visible" ] = true;
	acceptableTargetnames[ "exploderchunk"		   ] = true;
	acceptableTargetnames[ "exploder"			   ] = true;

	thread setup_flag_exploders();

	for ( i = 0; i < script_exploders.size; i++ )
	{
		exploder = script_exploders[ i ];
		
		
		ent						 = createExploder( exploder.script_fxid );
		ent.v					 = [];
		ent.v[ "origin"		   ] = exploder.origin;
		ent.v[ "angles"		   ] = exploder.angles;
		ent.v[ "delay"		   ] = exploder.script_delay;
		ent.v[ "delay_post"	   ] = exploder.script_delay_post;
		ent.v[ "firefx"		   ] = exploder.script_firefx;
		ent.v[ "firefxdelay"   ] = exploder.script_firefxdelay;
		ent.v[ "firefxsound"   ] = exploder.script_firefxsound;
		ent.v[ "firefxtimeout" ] = exploder.script_firefxtimeout;
		ent.v[ "earthquake"	   ] = exploder.script_earthquake;
		ent.v[ "rumble"		   ] = exploder.script_rumble;
		ent.v[ "damage"		   ] = exploder.script_damage;
		ent.v[ "damage_radius" ] = exploder.script_radius;
		ent.v[ "soundalias"	   ] = exploder.script_soundalias;
		ent.v[ "repeat"		   ] = exploder.script_repeat;
		ent.v[ "delay_min"	   ] = exploder.script_delay_min;
		ent.v[ "delay_max"	   ] = exploder.script_delay_max;
		ent.v[ "target"		   ] = exploder.target;
		ent.v[ "ender"		   ] = exploder.script_ender;
		ent.v[ "physics"	   ] = exploder.script_physics;
		ent.v[ "type"		   ]  = "exploder";
// 		ent.v[ "worldfx" ] = true;
		if ( !IsDefined( exploder.script_fxid ) )
			ent.v[ "fxid" ] = "No FX";
		else
			ent.v[ "fxid"	  ]	 = exploder.script_fxid;
		ent.v	 [ "exploder" ]	 = exploder.script_exploder;
		AssertEx( IsDefined( exploder.script_exploder ), "Exploder at origin " + exploder.origin + " has no script_exploder" );
		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;
		}

		if ( !IsDefined( ent.v[ "delay" ] ) )
			ent.v[ "delay" ] = 0;

		if ( IsDefined( exploder.target ) )
		{
			get_ent = GetEntArray( ent.v[ "target" ], "targetname" )[ 0 ];
			if ( IsDefined( get_ent ) )
			{
				org				  = get_ent.origin;
				ent.v[ "angles" ] = VectorToAngles( org - ent.v[ "origin" ] );
			}
			else
			{
				get_ent = get_target_ent( ent.v[ "target" ] );
				if ( IsDefined( get_ent ) )
				{
					org				  = get_ent.origin;
					ent.v[ "angles" ] = VectorToAngles( org - ent.v[ "origin" ] );
				}
			}
		}
		
		// this basically determines if its a brush / model exploder or not
		if ( !IsDefined( exploder.code_classname ) )
		{
			//I assume everything that doesn't have a code_classname is a struct that needs a script_modelname to make its way into the game
			ent.model = exploder;
			if ( IsDefined( ent.model.script_modelname ) )
			{
				PreCacheModel( ent.model.script_modelname );
			}
		}
		else if ( exploder.code_classname == "script_brushmodel" || IsDefined( exploder.model ) )
		{
			ent.model				   = exploder;
			ent.model.disconnect_paths = exploder.script_disconnectpaths;
		}

		if ( IsDefined( exploder.targetname ) && IsDefined( acceptableTargetnames[ exploder.targetname ] ) )
			ent.v[ "exploder_type" ] = exploder.targetname;
		else
			ent.v[ "exploder_type" ] = "normal";
		
		if ( IsDefined( exploder.masked_exploder ) )
		{
			ent.v[ "masked_exploder"						] = exploder.model;
			ent.v[ "masked_exploder_spawnflags"				] = exploder.spawnflags;
			ent.v[ "masked_exploder_script_disconnectpaths" ] = exploder.script_disconnectpaths;
			exploder Delete();
		}
		ent common_scripts\_createfx::post_entity_creation_function();
	}
}

setup_flag_exploders()
{
	// createfx has to do 2 waittillframeends so we have to do 3 to make sure this comes after
	// createfx is all done setting up. Who will raise the gambit to 4? 
	waittillframeend;
	waittillframeend;
	waittillframeend;
	exploder_flags = [];

	foreach ( ent in level.createFXent )
	{
		if ( ent.v[ "type" ] != "exploder" )
			continue;
		theFlag = ent.v[ "flag" ];

		if ( !IsDefined( theFlag ) )
		{
			continue;
		}

		if ( theFlag == "nil" )
		{
			ent.v[ "flag" ] = undefined;
		}

		exploder_flags[ theFlag ] = true;
	}

	foreach ( msg, _ in exploder_flags )
	{
		thread exploder_flag_wait( msg );
	}
}

exploder_flag_wait( msg )
{
	if ( !flag_exist( msg ) )
		flag_init( msg );
	flag_wait( msg );

	foreach ( ent in level.createFXent )
	{
		if ( ent.v[ "type" ] != "exploder" )
			continue;
		theFlag = ent.v[ "flag" ];

		if ( !IsDefined( theFlag ) )
		{
			continue;
		}

		if ( theFlag != msg )
			continue;
		ent activate_individual_exploder();
	}
}

exploder_model_is_damaged_model( ent )
{
	return( IsDefined( ent.targetname ) ) && ( ent.targetname == "exploder" );
}

exploder_model_starts_hidden( ent )
{
	return( ent.model == "fx" ) && ( ( !IsDefined( ent.targetname ) ) || ( ent.targetname != "exploderchunk" ) );
}

exploder_model_is_chunk( ent )
{
	return( IsDefined( ent.targetname ) ) && ( ent.targetname == "exploderchunk" );
}

show_exploder_models_proc( num )
{
	num += "";

	//prof_begin( "hide_exploder" );
	if ( IsDefined( level.createFXexploders ) )
	{	// do optimized flavor if available
		exploders = level.createFXexploders[ num ];
		if ( IsDefined( exploders ) )
		{
			foreach ( ent in exploders )
			{
				//pre exploded geo.  don't worry about deleted exploder geo..
				if ( ! exploder_model_starts_hidden( ent.model )
						 && ! exploder_model_is_damaged_model( ent.model )
						 && !exploder_model_is_chunk( ent.model ) )
				{
						ent.model Show();
				}
	
				//exploded geo and should be shown
				if ( IsDefined( ent.brush_shown ) )
					ent.model Show();
			}
		}
	}
	else
	{
		for ( i = 0; i < level.createFXent.size; i++ )
		{
			ent = level.createFXent[ i ];
			if ( !IsDefined( ent ) )
				continue;
	
			if ( ent.v[ "type" ] != "exploder" )
				continue;
	
			// make the exploder actually removed the array instead?
			if ( !IsDefined( ent.v[ "exploder" ] ) )
				continue;
	
			if ( ent.v[ "exploder" ] + "" != num )
				continue;
	
			if ( IsDefined( ent.model ) )
			{
	
				//pre exploded geo.  don't worry about deleted exploder geo..
				if ( ! exploder_model_starts_hidden( ent.model ) && ! exploder_model_is_damaged_model( ent.model ) && !exploder_model_is_chunk( ent.model ) )
				{
						ent.model Show();
				}
	
				//exploded geo and should be shown
				if ( IsDefined( ent.brush_shown ) )
					ent.model Show();
	
			}
		}
	}
	//prof_end( "hide_exploder" );
}

stop_exploder_proc( num )
{
	num += "";

	if ( IsDefined( level.createFXexploders ) )
	{	// do optimized flavor if available
		exploders = level.createFXexploders[ num ];
		if ( IsDefined( exploders ) )
		{
			foreach ( ent in exploders )
			{
				if ( !IsDefined( ent.looper ) )
					continue;
		
				ent.looper Delete();
			}
		}
	}
	else
	{
		for ( i = 0; i < level.createFXent.size; i++ )
		{
			ent = level.createFXent[ i ];
			if ( !IsDefined( ent ) )
				continue;
	
			if ( ent.v[ "type" ] != "exploder" )
				continue;
	
			// make the exploder actually removed the array instead?
			if ( !IsDefined( ent.v[ "exploder" ] ) )
				continue;
	
			if ( ent.v[ "exploder" ] + "" != num )
				continue;
	
			if ( !IsDefined( ent.looper ) )
				continue;
	
			ent.looper Delete();
		}
	}
}

get_exploder_array_proc( msg )
{
	msg += "";
	array = [];
	if ( IsDefined( level.createFXexploders ) )
	{	// do optimized flavor if available
		exploders = level.createFXexploders[ msg ];
		if ( IsDefined( exploders ) )
		{
			array = exploders;
		}
	}
	else
	{
		foreach ( ent in level.createFXent )
		{
			if ( ent.v[ "type" ] != "exploder" )
				continue;
	
			// make the exploder actually removed the array instead?
			if ( !IsDefined( ent.v[ "exploder" ] ) )
				continue;
	
			if ( ent.v[ "exploder" ] + "" != msg )
				continue;
	
			array[ array.size ] = ent;
		}
	}
	return array;
}

hide_exploder_models_proc( num )
{
	num += "";

	//prof_begin( "hide_exploder" );

	if ( IsDefined( level.createFXexploders ) )
	{	// do optimized flavor if available
		exploders = level.createFXexploders[ num ];
		if ( IsDefined( exploders ) )
		{
			foreach ( ent in exploders )
			{
				if ( IsDefined( ent.model ) )
					ent.model Hide();
			}
		}
	}
	else
	{
		for ( i = 0; i < level.createFXent.size; i++ )
		{
			ent = level.createFXent[ i ];
			if ( !IsDefined( ent ) )
				continue;
	
			if ( ent.v[ "type" ] != "exploder" )
				continue;
	
			// make the exploder actually removed the array instead?
			if ( !IsDefined( ent.v[ "exploder" ] ) )
				continue;
	
			if ( ent.v[ "exploder" ] + "" != num )
				continue;
	
	
			if ( IsDefined( ent.model ) )
					ent.model Hide();
	
		}
	}
	//prof_end( "hide_exploder" );
}

delete_exploder_proc( num )
{
	num += "";

	//prof_begin( "delete_exploder" );
	if ( IsDefined( level.createFXexploders ) )
	{	// do optimized flavor if available
		exploders = level.createFXexploders[ num ];
		if ( IsDefined( exploders ) )
		{
			foreach ( ent in exploders )
			{
				if ( IsDefined( ent.model ) )
					ent.model Delete();
			}
		}
	}
	else
	{
		for ( i = 0; i < level.createFXent.size; i++ )
		{
			ent = level.createFXent[ i ];
			if ( !IsDefined( ent ) )
				continue;
	
			if ( ent.v[ "type" ] != "exploder" )
				continue;
	
			// make the exploder actually removed the array instead?
			if ( !IsDefined( ent.v[ "exploder" ] ) )
				continue;
	
			if ( ent.v[ "exploder" ] + "" != num )
				continue;
	
			if ( IsDefined( ent.model ) )
				ent.model Delete();
		}
	}
	//ends trigger threads.
	level notify( "killexplodertridgers" + num );

	//prof_end( "delete_exploder" );
}

exploder_damage()
{
	if ( IsDefined( self.v[ "delay" ] ) )
		delay = self.v[ "delay" ];
	else
		delay = 0;

	if ( IsDefined( self.v[ "damage_radius" ] ) )
		radius = self.v[ "damage_radius" ];
	else
		radius = 128;

	damage = self.v[ "damage" ];
	origin = self.v[ "origin" ];

	wait( delay );
	
	if ( IsDefined( level.custom_radius_damage_for_exploders ) )
		[[ level.custom_radius_damage_for_exploders ]]( origin, radius, damage );
	else
		// Range, max damage, min damage
		RadiusDamage( origin, radius, damage, damage );
}

activate_individual_exploder_proc()
{
	if ( IsDefined( self.v[ "firefx" ] ) )
		self thread fire_effect();

	if ( IsDefined( self.v[ "fxid" ] ) && self.v[ "fxid" ] != "No FX" )
		self thread cannon_effect();
	else
	if ( IsDefined( self.v[ "soundalias" ] ) && self.v[ "soundalias" ] != "nil" )
		self thread sound_effect();

	if ( IsDefined( self.v[ "loopsound" ] ) && self.v[ "loopsound" ] != "nil" )
		self thread effect_loopsound();

	if ( IsDefined( self.v[ "damage" ] ) )
		self thread exploder_damage();

	if ( IsDefined( self.v[ "earthquake" ] ) )
		self thread exploder_earthquake();

	if ( IsDefined( self.v[ "rumble" ] ) )
		self thread exploder_rumble();

	if ( self.v[ "exploder_type" ] == "exploder" )
		self thread brush_show();
	else
	if ( ( self.v[ "exploder_type" ] == "exploderchunk" ) || ( self.v[ "exploder_type" ] == "exploderchunk visible" ) )
		self thread brush_throw();
	else
		self thread brush_delete();
}

brush_delete()
{
// 		if( ent.v[ "exploder_type" ] != "normal" && !isdefined( ent.v[ "fxid" ] ) && !isdefined( ent.v[ "soundalias" ] ) )
// 		if( !isdefined( ent.script_fxid ) )

	num = self.v[ "exploder" ];
	if ( IsDefined( self.v[ "delay" ]) && (self.v[ "delay" ] >= 0) )
		wait( self.v[ "delay" ] );
	else
		wait( 0.05 ); // so it disappears after the replacement appears

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


	Assert( IsDefined( self.model ) );

	if ( IsDefined( self.model.classname ) ) // script_struct support - Nate -- self is always a struct so it will never have a classname, using self.model instead -Carlos
		if ( isSP() && ( self.model.spawnflags & 1 ) )
			self.model call [[ level.connectPathsFunction ]]();

	if ( level.createFX_enabled )
	{
		if ( IsDefined( self.exploded ) )
			return;

		self.exploded = true;
		self.model Hide();
		self.model NotSolid();

		wait( 3 );
		self.exploded = undefined;
		self.model Show();
		self.model Solid();
		return;
	}

	if ( !IsDefined( self.v[ "fxid" ] ) || self.v[ "fxid" ] == "No FX" )
		self.v[ "exploder" ] = undefined;

	waittillframeend;// so it hides stuff after it shows the new stuff
	
//	if ( IsDefined( self.classname ) ) // script_struct support nate --- self is always a struct so it will never have a classname, using self.model instead -Carlos
		if ( IsDefined( self.model ) && IsDefined( self.model.classname ) )
		{
			self.model Delete();
		}
}

brush_throw()
{
	if ( IsDefined( self.v[ "delay" ] ) )
		wait( self.v[ "delay" ] );

	ent = undefined;
	if ( IsDefined( self.v[ "target" ] ) )
		ent = get_target_ent( self.v[ "target" ] );

	if ( !IsDefined( ent ) )
	{
		self.model Delete();
		return;
	}

	self.model Show();

	if ( IsDefined( self.v[ "delay_post" ] ) )
		wait( self.v[ "delay_post" ] );

	startorg = self.v[ "origin" ];
	startang = self.v[ "angles" ];
	org		 = ent.origin;

	temp_vec = ( org - self.v[ "origin" ] );
	x		 = temp_vec[ 0 ];
	y		 = temp_vec[ 1 ];
	z		 = temp_vec[ 2 ];

	physics = IsDefined( self.v[ "physics" ] );
	if ( physics )
	{
		target = undefined;
		if ( IsDefined( ent.target ) )
			target = ent get_target_ent();

		if ( !IsDefined( target ) )
		{
			contact_point = startorg; // no spin just push it.
			throw_vec	  = ent.origin;
		}
		else
		{
			contact_point = ent.origin;
			throw_vec	  = ( ( target.origin - ent.origin ) * self.v[ "physics" ] );

		}
		self.model PhysicsLaunchClient( contact_point, throw_vec );
		return;
	}
	else
	{
		self.model RotateVelocity( ( x, y, z ), 12 );
		self.model MoveGravity( ( x, y, z ), 12 );
	}

	if ( level.createFX_enabled )
	{
		if ( IsDefined( self.exploded ) )
			return;

		self.exploded = true;
		wait( 3 );
		self.exploded	   = undefined;
		self.v[ "origin" ] = startorg;
		self.v[ "angles" ] = startang;
		self.model Hide();
		return;
	}

	self.v[ "exploder" ] = undefined;
	wait( 6 );
	self.model Delete();
}

brush_show()
{
	if ( IsDefined( self.v[ "delay" ] ) )
		wait( self.v[ "delay" ] );

	Assert( IsDefined( self.model ) );

	if ( !IsDefined( self.model.script_modelname ) )
	{
		self.model Show();
		self.model Solid();
	}
	else // script_structs don't pass in their .model value SO use script_modelname on them saves an entity.
	{
		model = self.model spawn_tag_origin();
		if ( IsDefined( self.model.script_linkname ) )
			model.script_linkname = self.model.script_linkname;
		model SetModel( self.model.script_modelname );
		model Show();
	}

	self.brush_shown = true; // used for hiding an exploder.

	if ( isSP() && !IsDefined( self.model.script_modelname ) && ( self.model.spawnflags & 1 ) )
	{
		if ( !IsDefined( self.model.disconnect_paths ) )
			self.model call [[ level.connectPathsFunction ]]();
		else
			self.model call [[ level.disconnectPathsFunction ]]();
	}

	if ( level.createFX_enabled )
	{
		if ( IsDefined( self.exploded ) )
			return;

		self.exploded = true;
		wait( 3 );
		self.exploded = undefined;
		
		if ( !IsDefined( self.model.script_modelname ) )
		{
			self.model Hide();
			self.model NotSolid();
		}
	}
}

exploder_rumble()
{
	if ( !isSP() )
		return;
	
	self exploder_delay();
	level.player PlayRumbleOnEntity( self.v[ "rumble" ] );
}

exploder_delay()
{
	if ( !IsDefined( self.v[ "delay" ] ) )
		self.v[ "delay" ] = 0;

	min_delay = self.v[ "delay" ];
	max_delay = self.v[ "delay" ] + 0.001; // cant randomfloatrange on the same #
	if ( IsDefined( self.v[ "delay_min" ] ) )
		min_delay = self.v[ "delay_min" ];

	if ( IsDefined( self.v[ "delay_max" ] ) )
		max_delay = self.v[ "delay_max" ];

	if ( min_delay > 0 )
		wait( RandomFloatRange( min_delay, max_delay ) );
}

effect_loopsound()
{
	if ( IsDefined( self.loopsound_ent ) )
	{
		self.loopsound_ent Delete();
	}
	// save off this info in case we delete the effect
	origin = self.v[ "origin" ];
	alias  = self.v[ "loopsound" ];
	self exploder_delay();

	self.loopsound_ent = play_loopsound_in_space( alias, origin );
}

sound_effect()
{
	self effect_soundalias();
}

effect_soundalias()
{
	// save off this info in case we delete the effect
	origin = self.v[ "origin" ];
	alias  = self.v[ "soundalias" ];
	self exploder_delay();
	play_sound_in_space( alias, origin );
}

exploder_earthquake()
{
	self exploder_delay();
	do_earthquake( self.v[ "earthquake" ], self.v[ "origin" ] );
}

exploder_playSound()
{
	if ( !IsDefined( self.v[ "soundalias" ] ) || self.v[ "soundalias" ] == "nil" )
		return;

	play_sound_in_space( self.v[ "soundalias" ], self.v[ "origin" ] );
}

fire_effect()
{
	forward = self.v[ "forward" ];
	up		= self.v[ "up" ];

	org = undefined;

	firefxSound = self.v[ "firefxsound" ];
	origin		= self.v[ "origin" ];
	firefx		= self.v[ "firefx" ];
	ender		= self.v[ "ender" ];
	if ( !IsDefined( ender ) )
		ender = "createfx_effectStopper";

	fireFxDelay = 0.5;
	if ( IsDefined( self.v[ "firefxdelay" ] ) )
		fireFxDelay = self.v[ "firefxdelay" ];

	self exploder_delay();

	if ( IsDefined( firefxSound ) )
		loop_fx_sound_with_angles( firefxSound, origin, (0, 0, 0), 1, ender );

	PlayFX( level._effect[ firefx ], self.v[ "origin" ], forward, up );
}

cannon_effect()
{
	if ( IsDefined( self.v[ "repeat" ] ) )
	{
		thread exploder_playSound();
		for ( i = 0; i < self.v[ "repeat" ]; i++ )
		{
			PlayFX( level._effect[ self.v[ "fxid" ] ], self.v[ "origin" ], self.v[ "forward" ], self.v[ "up" ] );
			self exploder_delay();
		}
		return;
	}
	if ( !isdefined( self.v[ "delay" ] ) )
		self.v[ "delay" ] = 0;
	if(self.v[ "delay" ] >= 0)
	{
		self exploder_delay();
		preroll = 0;
	}
	else
		preroll = self.v[ "delay" ];

	if ( IsDefined( self.looper ) )
		self.looper Delete();

	self.looper = SpawnFx( getfx( self.v[ "fxid" ] ), self.v[ "origin" ], self.v[ "forward" ], self.v[ "up" ] );
	if ( level.createFX_enabled )
		SetFXKillOnDelete(self.looper,true);
	if(self.v[ "delay" ] >= 0)
	TriggerFX( self.looper );
	else
		TriggerFX( self.looper, preroll );
	exploder_playSound();
}

activate_exploder( num, players, startTime )
{
	num += "";
	//prof_begin( "activate_exploder" );

	//here's a hook so you can know when a certain number of an exploder is going off
	level notify( "exploding_" + num );
	
	found_server_exploder = false;
	
	if ( IsDefined( level.createFXexploders ) && !level.createFX_enabled )
	{	// do optimized form if available
		exploders = level.createFXexploders[ num ];
		if ( IsDefined( exploders ) )
		{
			foreach ( ent in exploders )
			{
				if ( !ent check_exploder_platform() )
					continue;
				ent activate_individual_exploder();
				found_server_exploder = true;
			}
		}
	}
	else
	{
		for ( i = 0;i < level.createFXent.size;i++ )
		{
			ent = level.createFXent[ i ];
			if ( !IsDefined( ent ) )
				continue;
	
			if ( ent.v[ "type" ] != "exploder" )
				continue;
	
			// make the exploder actually removed the array instead?
			if ( !IsDefined( ent.v[ "exploder" ] ) )
				continue;
	
			if ( ent.v[ "exploder" ] + "" != num )
				continue;

			if ( !ent check_exploder_platform() )
				continue;
		
			ent activate_individual_exploder();
			found_server_exploder = true;
		}
	}
	
	if ( !shouldRunServerSideEffects() && !found_server_exploder )
		activate_clientside_exploder( num, players, startTime );

	//prof_end( "activate_exploder" );
}


/*
=============
///ScriptDocBegin
"Name: exploder( <num>, <players>, <startTime> )"
"Summary: Sets off the desired exploder"
"Module: Utility"
"MandatoryArg: <num>: The exploder number"
"OptionalArg: <player(s)>: (MP_ONLY) Players to activate exploder for. Defaults to all players."
"OptionalArg: <startTime>: (MP_ONLY) The time at which the exploder should be triggered.  This can be in the past."
"Example: exploder( 5 );"
"SPMP: both"
///ScriptDocEnd
=============
*/
exploder( num, players, startTime )
{
	[[ level._fx.exploderFunction ]]( num, players, startTime );
}


/*
=============
///ScriptDocBegin
"Name: kill_exploder( <num> )"
"Summary: Hard kills the desired exploder"
"Module: FX"
"MandatoryArg: <num>: The exploder number"
"Example: kill_exploder( 5 );"
"SPMP: both"
///ScriptDocEnd
=============
*/
kill_exploder( num )
{
	exploders = getexploders( num );
	
	if (isdefined(exploders))
	{
		foreach (ent in exploders)
		{
			if ( IsDefined( ent.looper ) )
			{
				SetFXKillOnDelete(ent.looper, true);
			}
		}
		waitframe();
		foreach (ent in exploders)
		{
			ent pauseEffect();
		}
	}
}

check_exploder_platform()
{
	ent = self;
	if ( IsDefined( ent.v[ "platform" ] ) && IsDefined( level.currentgen ) )
	{
		platform = ent.v[ "platform" ];
		if ( ( platform == "cg" && !level.currentgen ) ||
			( platform == "ng" && !level.nextgen ) )
		{
			return false;
		}
	}
	return true;
}

activate_clientside_exploder( exploderName, players, startTime  )
{
	if ( !is_valid_clientside_exploder_name( exploderName ) )
	{
		PrintLn( "^1ERROR: Exploder Index '" + exploderName + "' is not a valid exploder index >= 0" );
		return;
	}
	
	exploder_num = Int( exploderName );
	ActivateClientExploder( exploder_num, players, startTime );
}

deactivate_clientside_exploder( exploderName, players, shouldKill )
{
	if ( !is_valid_clientside_exploder_name( exploderName ) )
	{
		PrintLn( "^1ERROR: Exploder Index '" + exploderName + "' is not a valid exploder index >= 0" );
		return;
	}
	
	exploder_num = Int( exploderName );
	StopClientExploder( exploder_num, players, shouldKill );
}

is_valid_clientside_exploder_name( exploderName )
{
	if ( !IsDefined( exploderName ) )
		return false;
	
	exploder_num = exploderName;
	if ( IsString( exploderName ) )
	{
		exploder_num = Int( exploderName );
		if ( exploder_num == 0 && exploderName != "0" )
			return false;
	}
	
	return exploder_num >= 0;
}

shouldRunServerSideEffects()
{
	if ( isSP() )
		return true;
		
	if ( !IsDefined( level.createFX_enabled ) )
		level.createFX_enabled = ( GetDvar( "createfx" ) != "" );
	
	if ( level.createFX_enabled )
		return true;
	else
		return GetDvar( "clientSideEffects" ) != "1";
}

exploder_before_load( num, players, startTime  )
{
	// gotta wait twice because the createfx_init function waits once then inits all exploders. This guarentees
	// that if an exploder is run on the first frame, it happens after the fx are init.
	waittillframeend;
	waittillframeend;
	activate_exploder( num, players, startTime  );
}

exploder_after_load( num, players, startTime  )
{
	activate_exploder( num, players, startTime  );
}