// RAVEN BEGIN
// bdube: note that this file is no longer merged with legacy engine updates
//
// MERGE_DATE 09/30/2004




#include "Game_local.h"
#include "ai/AI_Manager.h"
#include "Projectile.h"
#include "spawner.h"

/*
===============================================================================

	idProjectile

===============================================================================
*/

static const float BOUNCE_SOUND_MIN_VELOCITY	= 200.0f;
static const float BOUNCE_SOUND_MAX_VELOCITY	= 400.0f;

const idEventDef EV_Explode( "<explode>", NULL );
const idEventDef EV_Fizzle( "<fizzle>", NULL );
const idEventDef EV_RadiusDamage( "<radiusdmg>", "E" );
const idEventDef EV_ResidualDamage ( "<residualdmg>", "E" );
const idEventDef EV_EmitDamage ( "<emitdmg>", "E" );

CLASS_DECLARATION( idEntity, idProjectile )
	EVENT( EV_Explode,			idProjectile::Event_Explode )
	EVENT( EV_Fizzle,			idProjectile::Event_Fizzle )
	EVENT( EV_Touch,			idProjectile::Event_Touch )
	EVENT( EV_RadiusDamage,		idProjectile::Event_RadiusDamage )
	EVENT( EV_ResidualDamage,	idProjectile::Event_ResidualDamage )
	EVENT( EV_EmitDamage,		idProjectile::Event_EmitDamage )
END_CLASS

/*
================
idProjectile::idProjectile
================
*/
idProjectile::idProjectile( void ) {
	methodOfDeath		= -1;
	owner				= NULL;
	memset( &projectileFlags, 0, sizeof( projectileFlags ) );
	damagePower			= 1.0f;

	memset( &renderLight, 0, sizeof( renderLight ) );

	lightDefHandle		= -1;
	lightOffset.Zero();
	lightStartTime		= 0;
	lightEndTime		= 0;
	lightColor.Zero();

	visualAngles.Zero();
	angularVelocity.Zero();
	speed.Init( 0.0f, 0.0f, 0.0f, 0.0f );
	updateVelocity		= false;

	rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() );

	flyEffect			= NULL;
	flyEffectAttenuateSpeed = 0.0f;
	bounceCount			= 0;
	hitCount			= 0;
	state				= SPAWNED;
	
	fl.networkSync		= true;

	prePredictTime		= 0;

	syncPhysics			= false;

	launchTime			= 0;
	launchOrig			= vec3_origin;
	launchDir			= vec3_origin;
	launchSpeed			= 0.0f;
}

/*
================
idProjectile::Spawn
================
*/
void idProjectile::Spawn( void ) {
 	physicsObj.SetSelf( this );
// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_PUSH_HEAP_MEM(this);
// RAVEN END
 	physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
// RAVEN BEGIN
// mwhitlock: Dynamic memory consolidation
	RV_POP_HEAP();
// RAVEN END
 	physicsObj.SetContents( 0 );
 	physicsObj.SetClipMask( 0 );
 	physicsObj.PutToRest();
 	SetPhysics( &physicsObj );
	prePredictTime = spawnArgs.GetInt( "predictTime", "0" );
	syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" );

	if ( gameLocal.isClient ) {
		Hide();
	}
}

/*
================
idProjectile::Save
================
*/
void idProjectile::Save( idSaveGame *savefile ) const {
	
	savefile->WriteInt( methodOfDeath );				// cnicholson: Added unsaved var
	owner.Save( savefile );
	savefile->Write( &projectileFlags, sizeof( projectileFlags ) );
	savefile->WriteFloat( damagePower );

   	savefile->WriteRenderLight( renderLight );

   	savefile->WriteInt( ( int )lightDefHandle );
	savefile->WriteVec3( lightOffset );
 	savefile->WriteInt( lightStartTime );
 	savefile->WriteInt( lightEndTime );
 	savefile->WriteVec3( lightColor );

	savefile->WriteStaticObject( physicsObj );

	savefile->WriteAngles( visualAngles );				// cnicholson: added unsaved var
	savefile->WriteAngles( angularVelocity );			// cnicholson: moved var

	// Save speed
	savefile->WriteBool ( updateVelocity );
	savefile->WriteFloat( speed.GetStartTime() );
	savefile->WriteFloat( speed.GetDuration() );
	savefile->WriteFloat( speed.GetStartValue() );
	savefile->WriteFloat( speed.GetEndValue() );	

	// rotation; this is a class, so it doesnt get saved here

	flyEffect.Save( savefile );							// cnicholson: added unsaved var
	savefile->WriteFloat( flyEffectAttenuateSpeed );	// cnicholson: added unsaved var
	savefile->WriteInt ( bounceCount );
	savefile->WriteInt ( hitCount );

 	savefile->WriteInt( (int)state );
}

/*
================
idProjectile::Restore
================
*/
void idProjectile::Restore( idRestoreGame *savefile ) {
	float	fset;
	idVec3	temp;

	savefile->ReadInt( methodOfDeath );					// cnicholson: Added unrestored var
	owner.Restore( savefile );
	savefile->Read( &projectileFlags, sizeof( projectileFlags ) );
	savefile->ReadFloat( damagePower );

	savefile->ReadRenderLight( renderLight );

	savefile->ReadInt( (int &)lightDefHandle );
	if ( lightDefHandle != -1 ) {
		//get the handle again as it's out of date after a restore!
		lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
	}
	savefile->ReadVec3( lightOffset );
 	savefile->ReadInt( lightStartTime );
 	savefile->ReadInt( lightEndTime );
 	savefile->ReadVec3( lightColor );

	// Restore the physics
	savefile->ReadStaticObject( physicsObj );
	RestorePhysics ( &physicsObj );

	savefile->ReadAngles( visualAngles );				// cnicholson: added unrestored var
	savefile->ReadAngles( angularVelocity );			// cnicholson: moved var

	// Restore speed
	savefile->ReadBool ( updateVelocity );
	savefile->ReadFloat( fset );
	speed.SetStartTime( fset );
	savefile->ReadFloat( fset );
	speed.SetDuration( fset );
	savefile->ReadFloat( fset );
	speed.SetStartValue( fset );
	savefile->ReadFloat( fset );
	speed.SetEndValue( fset );
	
	// rotation?

	flyEffect.Restore( savefile );						// cnicholson: added unrestored var
	savefile->ReadFloat( flyEffectAttenuateSpeed );		// cnicholson: added unsaved var
	savefile->ReadInt ( bounceCount );
	savefile->ReadInt ( hitCount );

	savefile->ReadInt( (int &)state );
}

/*
================
idProjectile::GetOwner
================
*/
idEntity *idProjectile::GetOwner( void ) const {
	return owner.GetEntity();
}

/*
================
idProjectile::SetSpeed
================
*/
void idProjectile::SetSpeed( float s, int accelTime ) {
	idVec3 vel;
	vel = physicsObj.GetLinearVelocity();
	vel.Normalize();
	speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s );

	if ( accelTime > 0 ) {
		updateVelocity = true;
	} else {
		updateVelocity = false;
	}

	// Update the velocity to match the direction we are facing and include any accelerations
	physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel );			
}

/*
================
idProjectile::Create
================
*/
void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) {
	idDict		args;
	idStr		shaderName;
	idVec3		light_color;
	idVec3		light_offset;
	idVec3		tmp;
	idMat3		axis;
	
	Unbind();

	axis = dir.ToMat3();

 	physicsObj.SetOrigin( start );
 	physicsObj.SetAxis( axis );

 	physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner );
	physicsObj.extraPassEntity = extraPassEntity;

	owner = _owner;

	memset( &renderLight, 0, sizeof( renderLight ) );
	shaderName = spawnArgs.GetString( "mtr_light_shader" );
	if ( *shaderName ) {
		renderLight.shader = declManager->FindMaterial( shaderName, false );
		renderLight.pointLight = true;
		renderLight.lightRadius[0] =
		renderLight.lightRadius[1] =
		renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" );
		spawnArgs.GetVector( "light_color", "1 1 1", light_color );
		renderLight.shaderParms[0] = light_color[0];
		renderLight.shaderParms[1] = light_color[1];
		renderLight.shaderParms[2] = light_color[2];
		renderLight.shaderParms[3] = 1.0f;
// RAVEN BEGIN
// dluetscher: added detail levels to render lights
		renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
// dluetscher: set the projectile lights to be no shadows
		renderLight.noShadows = !G_DynamicLightShadowsEnabled();
// RAVEN END
	}
	else if ( gameLocal.isMultiplayer ) {
		const char *className = spawnArgs.GetString( "classname", "" );
		if ( !idStr::Icmpn( className, "projectile_rocket", 17 ) ) {
			// MP rocket trail effects don't carry a light segment in stock assets.
			// Add an explicit projectile light so rockets still illuminate nearby geometry.
			renderLight.shader = declManager->FindMaterial( "lights/fire2", false );
			if ( renderLight.shader ) {
				renderLight.pointLight = true;
				renderLight.lightRadius[0] = 200.0f;
				renderLight.lightRadius[1] = 200.0f;
				renderLight.lightRadius[2] = 200.0f;
				renderLight.shaderParms[0] = 0.905882f;
				renderLight.shaderParms[1] = 0.517647f;
				renderLight.shaderParms[2] = 0.160784f;
				renderLight.shaderParms[3] = 1.0f;
				renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL;
				renderLight.noShadows = true;
			}
		}
	}

	spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset );

	lightStartTime = 0;
	lightEndTime = 0;

	damagePower = 1.0f;

 	UpdateVisuals();

	state = CREATED;
}

/*
=================
idProjectile::~idProjectile
=================
*/
idProjectile::~idProjectile() {
	StopSound( SND_CHANNEL_ANY, false );
	FreeLightDef();
	SetPhysics( NULL );
}

/*
=================
idProjectile::FreeLightDef
=================
*/
void idProjectile::FreeLightDef( void ) {
	if ( lightDefHandle != -1 ) {
		gameRenderWorld->FreeLightDef( lightDefHandle );
		lightDefHandle = -1;
	}
}

/*
=================
idProjectile::Launch
=================
*/
void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) {
	float			fuse;
	idVec3			velocity;
	float			linear_friction;
	float			angular_friction;
	float			contact_friction;
	float			bounce;
	float			mass;
	float			gravity;
	float			temp, temp2;
	idVec3			gravVec;
	idVec3			tmp;
	int				contents;
 	int				clipMask;

 	// allow characters to throw projectiles during cinematics, but not the player
 	if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) {
 		cinematic = owner.GetEntity()->cinematic;
 	} else {
 		cinematic = false;
 	} 

	// Set the damage
	damagePower = dmgPower;

	if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) {
		spawnArgs.GetVector( "velocity", "0 0 0", tmp );
		temp = tmp[0];
	} else {
		float speedRandom;
		if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) {
			temp += gameLocal.random.CRandomFloat()*speedRandom;
		}
	}
	if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) {
		temp2 = temp;
	}
	float speedDuration;
	speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) );
	speed.Init( gameLocal.time, speedDuration, temp, temp2 );
	if ( speedDuration > 0 && temp != temp2 ) {
		// only support constant velocity projectiles in MP
		// ( we also assume that no MP projectiles use speedRandom )
		assert( !gameLocal.isServer );
		updateVelocity = true;
	}
	launchSpeed = temp;

	spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity );

	linear_friction		= spawnArgs.GetFloat( "linear_friction" );
	angular_friction	= spawnArgs.GetFloat( "angular_friction" );
	contact_friction	= spawnArgs.GetFloat( "contact_friction" );
	bounce				= spawnArgs.GetFloat( "bounce" );
	mass				= spawnArgs.GetFloat( "mass" );
	gravity				= spawnArgs.GetFloat( "gravity" );
	fuse				= spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() );
	bounceCount			= spawnArgs.GetInt( "bounce_count", "-1" );
	
	//spawn impact entity information
	impactEntity				= spawnArgs.GetString("def_impactEntity","");
	numImpactEntities			= spawnArgs.GetInt("numImpactEntities","0");
	ieMinPitch					= spawnArgs.GetInt("ieMinPitch","0");
	ieMaxPitch					= spawnArgs.GetInt("ieMaxPitch","0");
	ieSlicePercentage			= spawnArgs.GetFloat("ieSlicePercentage","0.0");
	
	projectileFlags.detonate_on_world	= spawnArgs.GetBool( "detonate_on_world" );
	projectileFlags.detonate_on_actor	= spawnArgs.GetBool( "detonate_on_actor" );
	projectileFlags.randomShaderSpin	= spawnArgs.GetBool( "random_shader_spin" );
	projectileFlags.detonate_on_bounce  = spawnArgs.GetBool( "detonate_on_bounce" );

	lightStartTime = 0;
	lightEndTime = 0;

	impactedEntity = 0;

	if ( health ) {
		fl.takedamage = true;
	}

// RAVEN BEGIN
// abahr:
	gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero;
// RAVEN END

	Unbind();

	contents = 0;
	clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX;
	
	// all projectiles are projectileclip
	clipMask |= CONTENTS_PROJECTILECLIP;

	if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) {
		clipMask |= CONTENTS_LARGESHOTCLIP;
	}

	if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) {
		clipMask |= CONTENTS_MOVEABLECLIP;
	}
	if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) {
		clipMask |= CONTENTS_MONSTERCLIP;
	}
	
	if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) {
		contents |= CONTENTS_TRIGGER;
	}

 	if ( !spawnArgs.GetBool( "no_contents", "1" ) ) {
 		contents |= CONTENTS_PROJECTILE;
 	}

	clipMask |= CONTENTS_PROJECTILE;

	// don't do tracers on client, we don't know origin and direction
	if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) {
		SetModel( spawnArgs.GetString( "model_tracer" ) );
		projectileFlags.isTracer = true;
	}

	physicsObj.SetMass( mass );
	physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
	physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce );
	physicsObj.SetGravity( gravVec );
	physicsObj.SetContents( contents );
 	physicsObj.SetClipMask( clipMask | CONTENTS_WATER );
	physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity );
	physicsObj.SetOrigin( start );
	physicsObj.SetAxis( dir.ToMat3() );

	if ( !gameLocal.isClient ) {
		if ( fuse <= 0 ) {
			// run physics for 1 second
			RunPhysics();
			PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
		} else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
			fuse -= timeSinceFire;
			if ( fuse < 0.0f ) {
				fuse = 0.0f;
			}
			PostEventSec( &EV_Explode, fuse );
		} else {
			fuse -= timeSinceFire;
			if ( fuse < 0.0f ) {
				fuse = 0.0f;
			}
			PostEventSec( &EV_Fizzle, fuse );
		}
	}

	idQuat q( dir.ToMat3().ToQuat() );
	rotation.Init( gameLocal.GetTime(), 0.0f, q, q );

	if ( projectileFlags.isTracer ) {
		StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL );
	} else {
		StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
	}

	// used for the plasma bolts but may have other uses as well
	if ( projectileFlags.randomShaderSpin ) {
		float f = gameLocal.random.RandomFloat();
		f *= 0.5f;
		renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f;
	}

 	UpdateVisuals();

	// Make sure these come after update visuals so the origin and axis are correct
	PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis );
	
	flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true );
	if ( !flyEffect ) {
		const char* classname = spawnArgs.GetString( "classname" );
		if ( g_grenadeTrail.GetBool() && !idStr::Icmpn( classname, "projectile_grenade", 18 ) ) {
			const idDecl* grenadeTrail = ( const idDecl * )declManager->FindEffect( "effects/weapons/grenadelauncher/trail_mp", false );
			if ( !grenadeTrail ) {
				grenadeTrail = ( const idDecl * )declManager->FindEffect( "effects/weapons/grenadelauncher/trail", false );
			}
			if ( grenadeTrail ) {
				flyEffect = PlayEffect( grenadeTrail, renderEntity.origin, renderEntity.axis, true );
			}
		}
	}
	flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" );

	state = LAUNCHED;

	hitCount = 0;

	predictTime = prePredictTime;

	if ( spawnArgs.GetFloat( "delay_emit_damage" ) > 0.0f ) {
		PostEventSec( &EV_EmitDamage, spawnArgs.GetFloat( "wait_emit_damage", "0" ), this );
	}

	if ( gameLocal.isServer ) {
		// store launch information for networking
		launchTime = gameLocal.time;
		launchOrig = physicsObj.GetOrigin();
		launchDir = dir;
	}

	if ( g_perfTest_noProjectiles.GetBool() ) {
		PostEventMS( &EV_Remove, 0 );
	}
}

/*
================
idProjectile::Think
================
*/
void idProjectile::Think( void ) {
	// run physics
	if ( thinkFlags & TH_PHYSICS ) {

		// Update the velocity to match the changing speed
		if ( updateVelocity ) {
			idVec3 vel;
			vel = physicsObj.GetLinearVelocity ( );
			vel.Normalize ( );
			physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel );			
			if ( speed.IsDone ( gameLocal.time ) ) {
				updateVelocity = false;
			}
		}
		
		RunPhysics();
		
		// If we werent at rest and are now then start the atrest fuse
		if ( physicsObj.IsAtRest( ) ) {
			float fuse = spawnArgs.GetFloat( "fuse_atrest" );
			if ( fuse > 0.0f ) {
				if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
					CancelEvents( &EV_Explode );
					PostEventSec( &EV_Explode, fuse );
				} else {
					CancelEvents( &EV_Fizzle );
					PostEventSec( &EV_Fizzle, fuse );
				}
			}
		}

		// Stop the trail effect if the physics flag was removed
		if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) {
			if ( physicsObj.IsAtRest( ) ) {
				flyEffect->Stop( );
				flyEffect = NULL;				
			} else {
				float speed;
				speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) );
				flyEffect->Attenuate( speed / flyEffectAttenuateSpeed );
			}
		}

		UpdateVisualAngles();
	}
		
	Present();

	// add the light
 	if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) {
		renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset;
		renderLight.axis = GetPhysics()->GetAxis();
		if ( ( lightDefHandle != -1 ) ) {
			if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) {
				idVec3 color( 0, 0, 0 );
				if ( gameLocal.time < lightEndTime ) {
					float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime );
					color.Lerp( lightColor, color, frac );
				} 
				renderLight.shaderParms[SHADERPARM_RED] = color.x;
				renderLight.shaderParms[SHADERPARM_GREEN] = color.y;
				renderLight.shaderParms[SHADERPARM_BLUE] = color.z;
			} 
			gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight );
		} else {
			lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
		}
	}
}

/*
=================
idProjectile::UpdateVisualAngles
=================
*/
void idProjectile::UpdateVisualAngles() {
	idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() );

	if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) {
		rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat()  );
		return;
	}

	if( physicsObj.GetNumContacts() ) {
		return;
	}

	if( !rotation.IsDone(gameLocal.GetTime()) ) {
		return;
	}

	if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) {
		return;
	}

	visualAngles += angularVelocity;
	idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat();
	rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q  );
}

/*
=================
idProjectile::Collide
=================
*/
bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) {
	bool dummy = false;
	return Collide( collision, velocity, dummy );
}

bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) {
 	idEntity*	ent;
	idEntity*	actualHitEnt = NULL;
 	idEntity*	ignore;
 	const char*	damageDefName;
 	idVec3		dir;
 	bool		canDamage;
 	
 	hitTeleporter = false;

	if ( state == EXPLODED || state == FIZZLED ) {
		return true;
	}

	// allow projectiles to hit triggers (teleports)
	// predict this on a client
	if( collision.c.contents & CONTENTS_TRIGGER ) {
		idEntity* trigger = gameLocal.entities[ collision.c.entityNum ];
		
		if( trigger ) {
			if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) {
				
				hitTeleporter = true;
				
				trace_t trace;
				
				trace.endpos = physicsObj.GetOrigin();
				trace.endAxis = physicsObj.GetAxis();

				trace.c.contents = collision.c.contents;
				trace.c.entityNum = collision.c.entityNum;
				if( trigger->GetPhysics()->GetClipModel() ) {
					trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId();
				} else {
					trace.c.id = 0;
				}
				

				trigger->Signal( SIG_TOUCH );
				trigger->ProcessEvent( &EV_Touch, this, &trace );
			}
		}

		// when we hit a trigger, align our velocity to the trigger's coordinate plane
		if( gameLocal.isServer ) {
			idVec3 up( 0.0f, 0.0f, 1.0f );
			idVec3 right = collision.c.normal.Cross( up );
			idMat3 mat( collision.c.normal, right, up );

			physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) );
			physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) );

			// update the projectile's launchdir and launch origin
			// this will propagate the change to the clients for prediction
			// re-launch the projectile

			idVec3 newDir = physicsObj.GetLinearVelocity();
			newDir.Normalize();
			launchTime = gameLocal.time;
			launchDir = newDir;
			physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) );
			physicsObj.SetAxis( newDir.ToMat3() );
			launchOrig = physicsObj.GetOrigin();
		}

		if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) {
			return false;
		}
	}

	// don't predict collisions on projectiles that need their physics synced (e.g. grenades)
	if ( gameLocal.isClient && (!g_clientProjectileCollision.GetBool() || syncPhysics) ) {
		// optionally do not try to predict detonates, that causes projectiles disappearing
		// but most of the time works well to stop projectiles clipping through geometry onc lients
		return false;
	}

 	// remove projectile when a 'noimpact' surface is hit
 	if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
 		PostEventMS( &EV_Remove, 0 );
		StopEffect( "fx_fly" );
		if( flyEffect)	{
			//flyEffect->Event_Remove();
		}
 		return true;
 	}
 
	// get the entity the projectile collided with
	ent = gameLocal.entities[ collision.c.entityNum ];
	if ( ent == owner.GetEntity() ) {
		// assert( 0 );		// twhitaker: this isn't necessary
		return true;
	}

 	// just get rid of the projectile when it hits a player in noclip
 	if ( ent->IsType( idPlayer::GetClassType() ) && static_cast<idPlayer *>( ent )->noclip ) {
   		PostEventMS( &EV_Remove, 0 );
  		common->DPrintf( "Projectile collision no impact\n" );
   		return true;
   	}

	// If the hit entity is bound to an actor use the actor instead
	if ( ent->GetTeamMaster( ) && ent->GetTeamMaster( )->IsType ( idActor::GetClassType() ) ) {
		actualHitEnt = ent;
		ent = ent->GetTeamMaster( );
	}

	// Can the projectile damage?  
	canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE ));
  
 	// direction of projectile
 	dir = velocity;
 	dir.Normalize();
 
 	// projectiles can apply an additional impulse next to the rigid body physics impulse
// RAVEN BEGIN
// abahr: added call to SkipDamageImpulse changed where push comes from
	damageDefName = NULL;
	if ( collision.c.materialType ) {
		damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) );
	}
	if ( !damageDefName || !*damageDefName ) {
		damageDefName = spawnArgs.GetString ( "def_damage" );
	}

	if( damageDefName && damageDefName[0] ) {
		const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false );
		if ( dict ) {
 			ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict );
 		}
	}
// RAVEN END
 
	//Spawn any impact entities if necessary.
	SpawnImpactEntities(collision, velocity);

	//Apply any impact force if the necessary
	//ApplyImpactForce(ent, collision, dir);
 
	// MP: projectiles open doors
	if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) {
		ent->ProcessEvent( &EV_Activate , this );
	}

	// If the projectile hits water then we need to let the projectile keep going
	if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) {
		if ( !physicsObj.IsInWater( ) ) {
			StopEffect( "fx_fly" );
			if( flyEffect)	{
				//flyEffect->Event_Remove();
			}

			// Match stock behavior by preferring an explicit water impact effect.
			const rvDeclMatType* waterMaterialType = collision.c.materialType;
			if ( waterMaterialType == NULL ) {
				waterMaterialType = declManager->FindMaterialType( "water", false );
			}
			gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", waterMaterialType ), collision.c.point, collision.c.normal.ToMat3(), false, vec3_origin, true );
		}
		// Pass through water
		return false;
	} else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) {
		if ( !projectileFlags.detonate_on_actor ) {
			return false;
		}
	} else {
		bool bounce = false;
		
		// Determine if the projectile should bounce
		bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage;
		bounce = bounce && (bounceCount == -1 || bounceCount > 0);
		//assert(collision.c.material);
		if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) {
			bounce = !projectileFlags.detonate_on_bounce;
		}
		
		if ( bounce ) {
			if ( bounceCount != -1 ) {
				bounceCount--;
			}
			
			StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL );

			float len = velocity.Length();
			if ( len > BOUNCE_SOUND_MIN_VELOCITY ) {
				if ( ent->IsType ( idMover::GetClassType ( ) ) ) {			
					ent->PlayEffect( 
						gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), 
						collision.c.point, collision.c.normal.ToMat3(), 
						false, vec3_origin, true );
				} else {
					gameLocal.PlayEffect( 
						gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), 
						collision.c.point, collision.c.normal.ToMat3(), 
						false, vec3_origin, true );
				}			
			} else {
				// FIXME: clean up
				idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() );
				axis[0].ProjectOntoPlane( collision.c.normal );
				axis[0].Normalize();
				axis[2] = collision.c.normal;
				axis[1] = axis[2].Cross( axis[0] ).ToNormal();

				rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() );
			}
			if ( actualHitEnt
				&& actualHitEnt != ent
				&& actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) )
			{//bleh...
 				if ( damageDefName[0] != '\0' ) {
					idVec3 dir = velocity;
					dir.Normalize();
					actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
				}
			}
			return false;		
		}
	}

	SetOrigin( collision.endpos );
//	SetAxis( collision.endAxis );

	// unlink the clip model because we no longer need it
	GetPhysics()->UnlinkClip();

	ignore = NULL;

// RAVEN BEGIN
// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects.
	bool willPlayDamageEffect = true;

// jnewquist: Use accessor for static class type 
		if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) {
			// if the projectile hit an ai
			if ( ent->IsType( idAI::GetClassType() ) ) {
				idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
				player->AddProjectileHits( 1 );

// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects.
				idAI * ai_ent = static_cast<idAI* >(ent);
				if( ai_ent->team == player->team)	{
					willPlayDamageEffect = false;
				}
			}
		}

// RAVEN END

	// if the hit entity takes damage
	if ( canDamage ) {

 		if ( damageDefName[0] != '\0' ) {
			idVec3 dir = velocity;
			dir.Normalize();
// RAVEN BEGIN
// jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint
//	otherwise damage zones for head attachments no-worky
			int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id);
			if ( ent->IsType(idActor::GetClassType()) )
			{
				idActor* entActor = static_cast<idActor*>(ent);
				if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) )
				{
					idAFAttachment* headEnt = static_cast<idAFAttachment*>(entActor->GetHead());
					if ( headEnt && headEnt->entityNumber == collision.c.entityNum )
					{//hit ent's head, get the proper joint for the head
						hitJoint = entActor->GetAnimator()->GetJointHandle("head");
					}
				}
			}	
// RAVEN END
 			ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint );
			
			if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) {
				statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 );			
				hitCount++;
			}
		}
	}
	
	ignore = ent;

	// TODO: fixme
	ent->AddDamageEffect ( collision, velocity, damageDefName, owner );

// RAVEN BEGIN
// jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any effects.
// this should make sure that only explosion effects play when the player shoots his comrades. 
	if( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") )	{
		DefaultDamageEffect( collision, velocity, damageDefName );
	}
// RAVEN END

/*
	// if the projectile causes a damage effect
	bool explodeFX = true;
 	if ( spawnArgs.GetBool( "impact_damage_effect" ) ) {
		// if the hit entity has a special damage effect
		if ( ent->spawnArgs.GetBool( "bleed" ) ) {
 			ent->AddDamageEffect( collision, velocity, damageDefName );
		} else {
 			DefaultDamageEffect( collision, velocity, damageDefName );
			explodeFX = false;
		}
	}
*/

	// don't predict explosions on clients
	if( gameLocal.isClient ) {
		return true;
	}

	Explode( &collision, false, ignore );

	return true;
}

void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity)
{
	if( impactEntity.Length() == 0 || numImpactEntities == 0 )
		return;

	const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity);
	if(impactEntityDict == NULL)
		return;

	idVec3 tempDirection;
	idVec3 direction;
	direction.Zero();

	idVec3 up = collision.c.normal;

	//Calculate the axes for that are oriented to the impact point. 
	idMat3 impactAxes;

	idVec3 right = velocity.Cross(up);
	idVec3 forward = up.Cross(right);

	right.Normalize();
	forward.Normalize();
	impactAxes[0] = forward;
	impactAxes[1] = right;
	impactAxes[2] = up;

	//Calculate the reflection vector by calculating the forward component and up component of the projectile direction
	//idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up));
	idVec3 reflectionVelocity = up * 0.01f;

	//The algorithm below will launch entities at a random pitch and somewhat random yaw.
	//The yaw is calculated by dividing 360 by the number of entities to spawn. This creates
	//a distribution slice. Then using the slice percentage, this will determine how much of the
	//slice to use.
	//This creates a random,but somewhat even coverage of the circle.

	//Calculate the slice size and pick a random start position.
    int sliceSize = 360 / numImpactEntities;
	int startPosition = rvRandom::irand(0, 360);

	//Move the origin away from the collision point. This prevents the projectiles
	//from colliding with the surface.
	idVec3 origin = collision.endpos;
	origin += 10.0f * collision.c.normal;
	for(int i = 0;i < numImpactEntities; i++)
	{
		idProjectile* spawnProjectile = NULL;
		gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile);
		if(spawnProjectile != NULL)
		{
			int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch);
			int sliceMiddle = (i * sliceSize) + startPosition;
			int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2;

			int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness);
			yaw = yaw % 360;

			float cosPitch = idMath::Cos(DEG2RAD(pitch));
			tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw));
			tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw));
			tempDirection.z = idMath::Sin(DEG2RAD(pitch));

			spawnProjectile->SetOwner(owner);
			spawnProjectile->methodOfDeath = methodOfDeath;

			//Now orient the direction to the surface world orientation.
			direction = impactAxes * tempDirection;
			spawnProjectile->Launch(origin, direction, reflectionVelocity);
		}
	}
}

/*
=================
idProjectile::DefaultDamageEffect
=================
*/
void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) {
	idEntity* ent;
	idMat3	  axis;
	ent = gameLocal.entities[ tr.c.entityNum ];	

	// Make sure we want to play effects
	if  ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" ))
		&& owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) {
		return;
	}

	// Effect axis when hitting actors is along the direction of impact because actor models are 
	// very detailed.
	if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) {
		idVec3 dir;
		dir = velocity;
		dir.Normalize ( );
		axis = ((-dir + tr.c.normal) * 0.5f).ToMat3();
		
		// Play an actor specific impact effect?
		const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType );
		if ( actorImpactEffect ) {
			gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true );
			return;
		}
	} else {
		axis = tr.c.normal.ToMat3();
	}
	
	// Play an impact effect on the entity that got hit
	if ( ent->IsType( idMover::GetClassType ( ) ) ) {
		ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, true );
	} else { 
		gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, true );
	}
}

/*
================
idProjectile::Killed
================
*/
void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
	if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
		Explode( NULL, true );
		physicsObj.ClearContacts();
		physicsObj.PutToRest();
	} else {
		Fizzle();
	}
}

/*
================
idProjectile::Fizzle
================
*/
void idProjectile::Fizzle( void ) {

	if ( gameLocal.isClient || state == EXPLODED || state == FIZZLED ) {
		return;
	}

	StopSound( SND_CHANNEL_BODY, false );
	StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
	
	gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );

	fl.takedamage = false;
	physicsObj.SetContents( 0 );
	physicsObj.GetClipModel()->Unlink();
	physicsObj.PutToRest();

	// No more fly effects
	StopEffect( "fx_fly" );
	if( flyEffect)	{
		//flyEffect->Event_Remove();
	}

	Hide();
	FreeLightDef();

	state = FIZZLED;

	CancelEvents( &EV_EmitDamage );
 	CancelEvents( &EV_Fizzle );
	PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
}

/*
================
idProjectile::Event_RadiusDamage
================
*/
void idProjectile::Event_RadiusDamage( idEntity *ignore ) {
	const char *splash_damage = spawnArgs.GetString( "def_splash_damage" );
	if ( splash_damage[0] != '\0' ) {
		gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount );
	}
}

/*
================
idProjectile::Event_ResidualDamage
================
*/
void idProjectile::Event_ResidualDamage ( idEntity* ignore ) {
	const char *residual_damage = spawnArgs.GetString( "def_residual_damage" );
	if ( residual_damage[0] != '\0' ) {
		gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount );
	}

	// Keep the loop going
	PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore );
}

void idProjectile::Event_EmitDamage ( idEntity* ignore ) {
	const char *emit_damage = spawnArgs.GetString( "def_emit_damage" );
	if ( emit_damage[0] != '\0' ) {
		gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, emit_damage, damagePower, &hitCount );
	}

	// Keep the loop going
	PostEventSec ( &EV_EmitDamage, spawnArgs.GetFloat ( "delay_emit_damage" ), ignore );
}

/*
================
idProjectile::Explode
================
*/
void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) {
	idVec3		normal, endpos;
	int			removeTime;

	if ( state == EXPLODED || state == FIZZLED ) {
		return;
	}

	CancelEvents( &EV_EmitDamage );

	if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) {
		GetPhysics()->SetAxis( normal.ToMat3() );
	} else {
		normal = collision ? collision->c.normal : idVec3( 0, 0, 1 );
	}
	endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin();

	removeTime = spawnArgs.GetInt( "remove_time", "1500" );

	// play sound
	StopSound( SND_CHANNEL_BODY, false );
	StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL );

	if ( showExplodeFX ) {
		idVec3 fxDir;
		if ( physicsObj.GetGravityNormal( ) != vec3_zero ) {
			fxDir = -physicsObj.GetGravityNormal( );
		} else { 
			fxDir = -physicsObj.GetLinearVelocity( );
			fxDir.Normalize( );
		}
		// FIXME: This should be done in a better way
		PlayDetonateEffect( endpos, fxDir.ToMat3() );
	}

	// Stop the fly effect without destroying particles to ensure the trail within can persist.
	StopEffect( "fx_fly" );	
	if( flyEffect)	{
		//flyEffect->Event_Remove();
	}
	
	// Stop the remaining particles
	StopAllEffects( );

	Hide();
	FreeLightDef();

	GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal );

	fl.takedamage = false;
	physicsObj.SetContents( 0 );
	physicsObj.PutToRest();

	state = EXPLODED;

	if ( gameLocal.isClient ) {
		return;
	}

 	// alert the ai
 	gameLocal.AlertAI( owner.GetEntity() );

	// bind the projectile to the impact entity if necesary
	if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) {
		Bind( gameLocal.entities[collision->c.entityNum], true );
	}

	// splash damage
	removeTime = 0;
	float delay = spawnArgs.GetFloat( "delay_splash" );
	if ( delay ) {
		if ( removeTime < delay * 1000 ) {
			removeTime = ( delay + 0.10 ) * 1000;
		}
		PostEventSec( &EV_RadiusDamage, delay, ignore );
	} else {
		Event_RadiusDamage( ignore );
	}

	// Residual damage (damage over time)
	delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) );
	if ( delay > 0.0f ) {
		PostEventMS ( &EV_ResidualDamage, delay, ignore );

		// Keep the projectile around until the residual damage is done		
		delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) );
		if ( removeTime < delay ) {
			removeTime = delay;
		}
	}
			
 	CancelEvents( &EV_Explode );
	PostEventMS( &EV_Remove, removeTime );
}

/*
================
idProjectile::GetVelocity
================
*/
idVec3 idProjectile::GetVelocity( const idDict *projectile ) {
	idVec3 velocity;	

	velocity.Zero ( );
	if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) {
		projectile->GetVector( "velocity", "0 0 0", velocity );
	}
	return velocity;
}

/*
================
idProjectile::GetGravity
================
*/
idVec3 idProjectile::GetGravity( const idDict *projectile ) {
	if ( projectile ) {
		return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" );
	}
	return vec3_origin;
}

/*
================
idProjectile::PlayPainEffect
================
*/
void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir )  {
	static int  damageTable[] = { 100, 50, 25, 10, 0 };
	int			index;

	// Normalize the damage value to the damage table	
	for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ );
	
	// loop until we find a pain effect, trying lower damage numbers if needed
	for ( ; damageTable[index]; index ++ ) {
		// Try the pain effect for the  current damage value and if it plays then
		// we are done
		if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3()  ) ) {
			return;
		}
	}

	// Play the default pain effect		
	ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() );
}

/*
================
idProjectile::PlayDetonateEffect
================
*/
void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis ) {
	if( physicsObj.HasGroundContacts() ) {
		if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) {
			trace_t tr;
			idVec3 down;
			down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f;
			gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this );
			gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true );
		} else {
			gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true );
		}
		return;
	}

	gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true );
}

/*
================
idProjectile::Event_Explode
================
*/
void idProjectile::Event_Explode( void ) {
	// events are processed outside of the think loop, so set the current thinking ent appropriately
	idEntity* think = gameLocal.currentThinkingEntity;
	gameLocal.currentThinkingEntity = this;
	Explode( NULL, true );
	gameLocal.currentThinkingEntity = think;
}

/*
================
idProjectile::Event_Fizzle
================
*/
void idProjectile::Event_Fizzle( void ) {
	idEntity* think = gameLocal.currentThinkingEntity;
	gameLocal.currentThinkingEntity = this;
	Fizzle();
	gameLocal.currentThinkingEntity = think;
}

/*
================
idProjectile::Event_Touch
================
*/
void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) {
	if ( IsHidden() ) {
		return;
	}

	if ( other != owner.GetEntity() ) {
		idEntity* think = gameLocal.currentThinkingEntity;
		gameLocal.currentThinkingEntity = this;

		trace_t collision;

		memset( &collision, 0, sizeof( collision ) );
		collision.c.point = GetPhysics()->GetOrigin();
		collision.c.normal.Set( 0, 0, 1 );
		DefaultDamageEffect( collision, collision.c.normal, NULL );
		Explode( NULL, true );

		gameLocal.currentThinkingEntity = think;
	}
}

/*
================
idProjectile::ClientPredictionThink
================
*/
void idProjectile::ClientPredictionThink( void ) {
	if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) {
		return;
	}
	if ( !syncPhysics && state == LAUNCHED ) {
		idMat3 axis = launchDir.ToMat3();
		idVec3 origin( launchOrig );
		origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir;
		physicsObj.SetAxis( axis );
		physicsObj.SetOrigin( origin );
		physicsObj.SetLinearVelocity( launchSpeed * launchDir );
	}
	Think();
}

/*
================
idProjectile::WriteToSnapshot
================
*/
void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const {
	if ( syncPhysics ) {
		physicsObj.WriteToSnapshot( msg );
	}

	msg.WriteBits( state, 3 );
	if ( state >= LAUNCHED ) {
		// feed the client with start position, direction and time. let the client do everything else
		// this won't change during projectile life and be completely deltified away
		msg.WriteLong( launchTime );
		msg.WriteFloat( launchOrig[ 0 ] );
		msg.WriteFloat( launchOrig[ 1 ] );
		msg.WriteFloat( launchOrig[ 2 ] );
		msg.WriteDir( launchDir, 24 );
	}
}

/*
================
idProjectile::ReadFromSnapshot
================
*/
void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) {
	projectileState_t newState;

	if ( syncPhysics ) {
		physicsObj.ReadFromSnapshot( msg );
	}

	newState = (projectileState_t) msg.ReadBits( 3 );
	if ( newState >= LAUNCHED ) {
		launchTime = msg.ReadLong();
		launchOrig[ 0 ] = msg.ReadFloat();
		launchOrig[ 1 ] = msg.ReadFloat();
		launchOrig[ 2 ] = msg.ReadFloat();
		launchDir = msg.ReadDir( 24 );
	}
	// we always create and launch at the same time
	// state on a client can be SPAWNED, LAUNCHED, EXPLODED
	// expect never to get a CREATED projectile, they should launch right away
	assert( state != CREATED && state != FIZZLED && newState != CREATED );
	if ( newState != state ) {
		switch ( newState ) {
		case LAUNCHED:
			Create( NULL, launchOrig, launchDir );
			Launch( launchOrig, launchDir, vec3_origin );
			Show();
			break;
		case FIZZLED:
		case EXPLODED:
			if ( state != EXPLODED ) {
				StopSound( SND_CHANNEL_BODY, false );
				StopAllEffects();
				Hide();
				FreeLightDef();
				state = EXPLODED;
			}
			break;
		}
	}

	if ( msg.HasChanged() ) {
		if( !syncPhysics && state == LAUNCHED ) {
			idMat3 axis = launchDir.ToMat3();
			idVec3 origin( launchOrig );
			origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir;
			physicsObj.SetAxis( axis );
			physicsObj.SetOrigin( origin );
			physicsObj.SetLinearVelocity( launchSpeed * launchDir );
		}
	
		UpdateVisuals();
	}
}

/*
===============
idProjectile::ClientStale
===============
*/
bool idProjectile::ClientStale( void ) {
	// delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway )
	StopAllEffects();
	return true;
}

/*
================
idProjectile::GetPhysicsToVisualTransform
================
*/
bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
	axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose();

	origin.Zero();
	return true;
}

/*
===============================================================================

	idGuidedProjectile

===============================================================================
*/

CLASS_DECLARATION( idProjectile, idGuidedProjectile )
END_CLASS

/*
================
idGuidedProjectile::idGuidedProjectile( void )
================
*/
idGuidedProjectile::idGuidedProjectile( void ) {
	guideType = GUIDE_NONE;
	turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f );
}

/*
=================
idGuidedProjectile::~idGuidedProjectile
=================
*/
idGuidedProjectile::~idGuidedProjectile() {
}

/*
================
idGuidedProjectile::Save
================
*/
void idGuidedProjectile::Save( idSaveGame *savefile ) const {
	savefile->WriteInt ( guideType );
	guideEnt.Save( savefile );
	savefile->WriteVec3 ( guideDir );
	savefile->WriteVec3 ( guidePos );
	savefile->WriteJoint ( guideJoint );
	savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var

	savefile->WriteInt ( driftTime );
	savefile->WriteInt ( driftRate );
	savefile->WriteFloat ( driftRange );
	savefile->WriteFloat ( driftRadius );
	savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var
	savefile->WriteFloat ( driftAngle );
	savefile->WriteFloat ( driftAngleStep );
	savefile->WriteFloat ( driftProjectRange );

	savefile->WriteFloat( turn_max.GetStartTime() );
	savefile->WriteFloat( turn_max.GetDuration() );
	savefile->WriteFloat( turn_max.GetStartValue() );
	savefile->WriteFloat( turn_max.GetEndValue() );

	savefile->WriteInt ( launchTime );
	savefile->WriteInt ( guideDelay );
	savefile->WriteInt ( driftDelay );
}

/*
================
idGuidedProjectile::Restore
================
*/
void idGuidedProjectile::Restore( idRestoreGame *savefile ) {
	float set;
	savefile->ReadInt ( guideType );
	guideEnt.Restore( savefile );
	savefile->ReadVec3 ( guideDir );
	savefile->ReadVec3 ( guidePos );
	savefile->ReadJoint ( guideJoint );
	savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var
	
	savefile->ReadInt ( driftTime );
	savefile->ReadInt ( driftRate );
	savefile->ReadFloat ( driftRange );
	savefile->ReadFloat ( driftRadius );
	savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var
	savefile->ReadFloat ( driftAngle );
	savefile->ReadFloat ( driftAngleStep );
	savefile->ReadFloat ( driftProjectRange );
	
	savefile->ReadFloat( set );
	turn_max.SetStartTime( set );
	savefile->ReadFloat( set );
	turn_max.SetDuration( set );
	savefile->ReadFloat( set );
	turn_max.SetStartValue( set );
	savefile->ReadFloat( set );
	turn_max.SetEndValue( set );

	savefile->ReadInt ( launchTime );
	savefile->ReadInt ( guideDelay );
	savefile->ReadInt ( driftDelay );
}

/*
================
idGuidedProjectile::GetGuideDir
================
*/
bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist  ) {
	// Dont start guiding immeidately?
	if ( gameLocal.GetTime() - launchTime < guideDelay ) {
		return false;
	}
	
	switch ( guideType ) {
		case GUIDE_ENTITY:
			// If the guide entity is gone or dead then cancel the guide
			if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) {
				CancelGuide ( );
				return false;
			}
			// Use eye position for actors and center of bounds for everything else
			if ( guideJoint != INVALID_JOINT ) {
				idMat3 jointAxis;
				guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis );
				outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis);
				if ( !guidePos.Compare( vec3_origin ) ) {
					jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis;
					outDir += jointAxis[0]*guidePos[0];
					outDir += jointAxis[1]*guidePos[1];
					outDir += jointAxis[2]*guidePos[2];
				}
			} else {
				outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter();
				if ( guideEnt->IsType( idActor::GetClassType() ) ) {
					outDir += static_cast<idActor *>(guideEnt.GetEntity())->GetEyePosition();
					outDir *= 0.5f;
				}
			}
			outDir -= physicsObj.GetOrigin();
			break;

		case GUIDE_POS:
			outDir = guidePos - physicsObj.GetOrigin();
			break;
			
		case GUIDE_DIR:
			// Project our current position on to the desired direction
			outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir);
			
			// Seek towards a point forward along our desired direction
			outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin();
			break;
			
		default:
			return false;
	}

	// Add drifting
	if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) {		
		idMat3 axis;
		
		outDist = outDir.NormalizeFast();
		axis    = outDir.ToMat3();			
			
		if ( gameLocal.time > driftTime ) {				
			driftRadius	= driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity;
			driftTime	= gameLocal.time + driftRate;
		} else {
			driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec );
			idMath::AngleNormalize360 ( driftAngle );
		}

		float angle;
		angle = DEG2RAD ( driftAngle );
		outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange );
		outDir += axis[2] * (driftRadius * idMath::Sin ( angle ));
		outDir += axis[1] * (driftRadius * idMath::Cos ( angle ));

		outDir -= physicsObj.GetOrigin();
	}

	outDist = outDir.Normalize ( );
			
	return true;
}

/*
================
idGuidedProjectile::Think
================
*/
void idGuidedProjectile::Think( void ) {

	if ( state == LAUNCHED ) {
		idVec3	dir;
		idVec3	vel;
		float	angle;
		float	maxangle;
		idMat3	axis;
		float	dist;

		// crank up to normal speed ?
		if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) {
			float newSpeed = spawnArgs.GetFloat( "speed" );
			float newSpeed2;
			if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) {
				newSpeed2 = newSpeed;
			}
			float newSpeedDuration;
			newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) );
			speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 );
			guideDelay = 0;
		}

		if ( !GetGuideDir( dir, dist ) ) {
			idProjectile::Think();
			return;
		}
						
		// Direction of travel
		vel = physicsObj.GetLinearVelocity();
		vel.Normalize();

		// Calculate the angle between the current projectile direction and where we want to go					
		angle = RAD2DEG( idMath::ACos( dir * vel ) );
								
		// Make sure the angle doesnt cross our max turn radius								
		maxangle = turn_max.GetCurrentValue( gameLocal.time );
		if ( angle < -maxangle ) {
			angle = -maxangle;
		} else if ( angle > maxangle ) {
			angle = maxangle;
		}

		// Debug information
		if ( g_debugWeapon.GetBool ( ) ) {
			gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f );
			gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f );
		}
					
		// Calculate the new axis by rotating the current forward vector around the cross of the forward
		// vector and the direction vector.
		vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle );
		physicsObj.SetLinearVelocity( vel * GetSpeed ( ) );

		// If within the minium distance to the target anything over a 45 degree change will cancel the guide
		if ( guideMinDist != 0.0f && dist < guideMinDist ) {
			// Stop guiding if we have passed our target
			vel = physicsObj.GetLinearVelocity ( );
			vel.Normalize( );		
			if ( vel * dir < 0.7f ) {
				guideType = GUIDE_NONE;
			}
		}
		
		idProjectile::Think();						
	} else { 
		idProjectile::Think();
	}
}

/*
=================
idGuidedProjectile::Launch
=================
*/
void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) {
	idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower );
	
	launchTime = gameLocal.GetTime();

	if ( owner.GetEntity() ) {
		if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) {
			GuideTo ( static_cast<idAI *>( owner.GetEntity() )->GetEnemy() );
		}
	}

	guideMinDist	= spawnArgs.GetFloat ( "min_dist", "128" );
	guideDelay		= SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) );

	if ( guideDelay ) {
		float delaySpeed;
		if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) )	{
			float delaySpeed2;
			if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) {
				delaySpeed2 = delaySpeed;
			}
			float delaySpeedDuration;
			delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) );
			speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 );
			physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity );
		}
	}

	driftTime		= 0;
	driftRate		= SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) );
	driftRange		= spawnArgs.GetFloat ( "driftRange" );
	driftDiversity	= spawnArgs.GetFloat ( "driftDiversity", ".5" );
	driftAngle		= gameLocal.random.RandomFloat ( ) * 360.0f;
	driftAngleStep	= spawnArgs.GetFloat ( "driftRotate" );
	driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f);
	driftDelay		= SEC2MS(spawnArgs.GetFloat ( "driftDelay" ));
	
	driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" );

	// Turn rate can be ramped up over time
	turn_max.Init ( gameLocal.time, 
					SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )), 
					0, 
					spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() );

 	UpdateVisuals();
}

/*
=================
idGuidedProjectile::Launch
=================
*/
void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
	if ( guideEnt.IsValid() ) {
		guideEnt->GuidedProjectileIncoming( NULL );
	}
	idProjectile::Killed( inflictor, attacker, damage, dir, location );
}

/*
===============================================================================

	rvDriftingProjectile

===============================================================================
*/

CLASS_DECLARATION( idProjectile, rvDriftingProjectile )
END_CLASS

/*
================
rvDriftingProjectile::rvDriftingProjectile( void )
================
*/
rvDriftingProjectile::rvDriftingProjectile( void ) {
}

/*
=================
rvDriftingProjectile::~rvDriftingProjectile
=================
*/
rvDriftingProjectile::~rvDriftingProjectile ( void ) {
}

/*
================
rvDriftingProjectile::Save
================
*/
void rvDriftingProjectile::Save( idSaveGame *savefile ) const {
	savefile->WriteVec3 ( startDir );
	savefile->WriteVec3 ( startOrigin );
	savefile->WriteMat3 ( startAxis );
	savefile->WriteFloat ( startSpeed );
	savefile->WriteFloat ( driftOffsetMax );
	savefile->WriteFloat ( driftSpeedMax );
	savefile->WriteFloat ( driftTime );

	savefile->WriteInterpolate( driftSpeed );
	for( int ix = 0; ix < 2; ++ix ) {
		savefile->WriteInterpolate( driftOffset[ix] );
	}
}

/*
================
rvDriftingProjectile::Restore
================
*/
void rvDriftingProjectile::Restore( idRestoreGame *savefile ) {
	savefile->ReadVec3 ( startDir );
	savefile->ReadVec3 ( startOrigin );
	savefile->ReadMat3 ( startAxis );
	savefile->ReadFloat ( startSpeed );
	savefile->ReadFloat ( driftOffsetMax );
	savefile->ReadFloat ( driftSpeedMax );
	savefile->ReadFloat ( driftTime );

	savefile->ReadInterpolate( driftSpeed );
	for( int ix = 0; ix < 2; ++ix ) {
		savefile->ReadInterpolate( driftOffset[ix] );
	}
}

/*
================
rvDriftingProjectile::Think
================
*/
void rvDriftingProjectile::Think( void ) {
	idVec3 diff;
	idVec3 origin;
	idVec3 oldOrigin;
	idVec3 dir;
	float  dist;
	
	oldOrigin = GetPhysics()->GetOrigin ( );
	
	diff   = oldOrigin - startOrigin;
	dist   = diff.Length ( );
	origin = startOrigin + startDir * dist;
	
	if ( driftSpeed.IsDone ( gameLocal.time ) ) {
		driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime,
						  driftSpeed.GetCurrentValue ( gameLocal.time ),
						  gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) );
	}
	
	if ( driftOffset[0].IsDone ( gameLocal.time ) ) {
		driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, 
						driftOffset[0].GetCurrentValue ( gameLocal.time ),
						gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) );
	}

	if ( driftOffset[1].IsDone ( gameLocal.time ) ) {
		driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime, 
						driftOffset[1].GetCurrentValue ( gameLocal.time ),
						gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) );
	}

	origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time );
	origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time );
	
	GetPhysics ( )->SetOrigin ( origin );
	GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) );
	
	idProjectile::Think();

	// Now orient the projectile using the old origin
	dir = GetPhysics()->GetOrigin() - oldOrigin;
	dir.Normalize ( );	
	GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) );
}

/*
=================
rvDriftingProjectile::Launch
=================
*/
void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) {
	startDir	= dir;	
	startOrigin = start;
	startAxis   = dir.ToMat3();

	driftOffsetMax	= spawnArgs.GetFloat ( "driftOffset", "50" );
	driftSpeedMax	= spawnArgs.GetFloat ( "driftSpeed", "50" );
	driftTime	= SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) );

	idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower );

	startSpeed = GetPhysics()->GetLinearVelocity().Length ( );
}

/*
=================
rvDriftingProjectile::Launch
=================
*/
void rvDriftingProjectile::UpdateVisualAngles ( void ) {
	rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat()  );
}

/*
===============================================================================

	rvSpawnerProjectile

===============================================================================
*/

CLASS_DECLARATION( idProjectile, rvSpawnerProjectile )
	EVENT( EV_PostSpawn,		rvSpawnerProjectile::Event_PostSpawn )
END_CLASS

/*
================
rvSpawnerProjectile::rvSpawnerProjectile( void )
================
*/
rvSpawnerProjectile::rvSpawnerProjectile( void ) {
	spawnState = STATE_NONE;
}

/*
=================
rvSpawnerProjectile::~rvSpawnerProjectile
=================
*/
rvSpawnerProjectile::~rvSpawnerProjectile ( void ) {
	if ( spawnState == STATE_ADDED && spawner ) {
		spawner->RemoveSpawnPoint ( this );
	}
}

/*
=================
rvSpawnerProjectile::SetSpawner
=================
*/
void rvSpawnerProjectile::Spawn ( void ) {
	if ( *spawnArgs.GetString ( "spawner" ) ) {
		PostEventMS ( &EV_PostSpawn, 0 );
	}
}

/*
=================
rvSpawnerProjectile::SetSpawner
=================
*/
void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) {
	spawner = _spawner;
}

/*
=================
rvSpawnerProjectile::Think
=================
*/
void rvSpawnerProjectile::Think ( void ) {
	idProjectile::Think ( );
	
	if ( physicsObj.IsAtRest ( ) ) {
		if ( spawnState == STATE_NONE && spawner ) {
			spawner->AddSpawnPoint ( this );
			spawnState = STATE_ADDED;
		}
	}
}

/*
=================
rvSpawnerProjectile::Event_PostSpawn
=================
*/
void rvSpawnerProjectile::Event_PostSpawn ( void ) {
	const char* temp;
	temp = spawnArgs.GetString ( "spawner" );
	if ( temp && *temp ) {
		idEntity* ent;
		ent = gameLocal.FindEntity ( temp );
		if ( !ent ) {
			gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) ); 
		} else if ( !ent->IsType ( rvSpawner::GetClassType() ) )  {
			gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) ); 
		} else {
			SetSpawner ( static_cast<rvSpawner*>(ent) );
		}
	}
}


/*
===============================================================================

	rvMIRVProjectile

===============================================================================
*/
idEventDef		EV_LaunchWarheads( "launchWarheads" );

CLASS_DECLARATION( idProjectile, rvMIRVProjectile )
	EVENT( EV_LaunchWarheads,		rvMIRVProjectile::Event_LaunchWarheads )
END_CLASS

/*
================
rvMIRVProjectile::rvMIRVProjectile( void )
================
*/
rvMIRVProjectile::rvMIRVProjectile( void ) {

}

/*
=================
rvMIRVProjectile::~rvMIRVProjectile
=================
*/
rvMIRVProjectile::~rvMIRVProjectile ( void ) {
	
}

/*
================
void rvMIRVProjectile::Spawn( void ) 
================
*/
void rvMIRVProjectile::Spawn( void ) {
	
	float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0");
	//post event for warhead launch
	PostEventSec( &EV_LaunchWarheads, launchDelay );
}

/*
================
void rvMIRVProjectile::Event_LaunchWarheads( void )
================
*/
void rvMIRVProjectile::Event_LaunchWarheads( void ) {

	const char* warhead;
	int count;
	
	warhead = spawnArgs.GetString("def_warhead","");
	count = spawnArgs.GetFloat("warhead_count","0");

	if( warhead && warhead[0] )	{
		
		//start launching!
		float angle = (360.0f / count);
		idMat3 normalMat = this->GetPhysics()->GetAxis( );
		idVec3 normal = normalMat[0];
		idVec3 axis = normalMat[1];

		float t;
		idMat3 axisMat;
		idProjectile * warheadEntity;
		
		//hey how 'bout that.
		normal.Normalize();

		for( t = 0; t< count; t++)	{
			
			//rotate axis around normal by angle degrees.
			axisMat = axis.ToMat3();
			axisMat.RotateArbitrary( normal, angle * t);
			warheadEntity =  static_cast<idProjectile*>(gameLocal.SpawnEntityDef( warhead));
			warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this );
			warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] );

		}
		//foom!
		gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
		PostEventSec( &EV_Explode, 0 );
	}



}
// RAVEN END
