DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Projectile.cpp (84164B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28  
     29 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Game_local.h"
     33 
     34 /*
     35 ===============================================================================
     36 
     37 	idProjectile
     38 
     39 ===============================================================================
     40 */
     41 
     42 
     43 idCVar g_projectileDebug( "g_projectileDebug", "0", CVAR_BOOL, "Debug projectiles" );
     44 
     45 
     46 // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight.
     47 
     48 // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight.
     49 idArray< idProjectile::simulatedProjectile_t, idProjectile::MAX_SIMULATED_PROJECTILES >	idProjectile::projectilesToSimulate;
     50 
     51 static const int BFG_DAMAGE_FREQUENCY			= 333;
     52 static const float BOUNCE_SOUND_MIN_VELOCITY	= 200.0f;
     53 static const float BOUNCE_SOUND_MAX_VELOCITY	= 400.0f;
     54 
     55 const idEventDef EV_Explode( "<explode>", NULL );
     56 const idEventDef EV_Fizzle( "<fizzle>", NULL );
     57 const idEventDef EV_RadiusDamage( "<radiusdmg>", "e" );
     58 const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' );
     59 
     60 const idEventDef EV_CreateProjectile( "projectileCreateProjectile", "evv" );
     61 const idEventDef EV_LaunchProjectile( "projectileLaunchProjectile", "vvv" );
     62 const idEventDef EV_SetGravity( "setGravity", "f" );
     63 
     64 CLASS_DECLARATION( idEntity, idProjectile )
     65 	EVENT( EV_Explode,				idProjectile::Event_Explode )
     66 	EVENT( EV_Fizzle,				idProjectile::Event_Fizzle )
     67 	EVENT( EV_Touch,				idProjectile::Event_Touch )
     68 	EVENT( EV_RadiusDamage,			idProjectile::Event_RadiusDamage )
     69 	EVENT( EV_GetProjectileState,	idProjectile::Event_GetProjectileState )
     70 	EVENT( EV_CreateProjectile,		idProjectile::Event_CreateProjectile )
     71 	EVENT( EV_LaunchProjectile,		idProjectile::Event_LaunchProjectile )
     72 	EVENT( EV_SetGravity,			idProjectile::Event_SetGravity )
     73 END_CLASS
     74 
     75 /*
     76 ================
     77 idProjectile::idProjectile
     78 ================
     79 */
     80 idProjectile::idProjectile() :
     81 	launchOrigin( 0.0f ), 
     82 	launchAxis( mat3_identity ) {
     83 	owner				= NULL;
     84 	lightDefHandle		= -1;
     85 	thrust				= 0.0f;
     86 	thrust_end			= 0;
     87 	smokeFly			= NULL;
     88 	smokeFlyTime		= 0;
     89 	state				= SPAWNED;
     90 	lightOffset			= vec3_zero;
     91 	lightStartTime		= 0;
     92 	lightEndTime		= 0;
     93 	lightColor			= vec3_zero;
     94 	damagePower			= 1.0f;
     95 	launchedFromGrabber = false;
     96 	mTouchTriggers		= false;
     97 	mNoExplodeDisappear = false;
     98 	memset( &projectileFlags, 0, sizeof( projectileFlags ) );
     99 	memset( &renderLight, 0, sizeof( renderLight ) );
    100 
    101 	// note: for net_instanthit projectiles, we will force this back to false at spawn time
    102 	fl.networkSync		= true;
    103 }
    104 
    105 /*
    106 ================
    107 idProjectile::Spawn
    108 ================
    109 */
    110 void idProjectile::Spawn() {
    111 	physicsObj.SetSelf( this );
    112 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
    113 	physicsObj.SetContents( 0 );
    114 	physicsObj.SetClipMask( 0 );
    115 	physicsObj.PutToRest();
    116 	SetPhysics( &physicsObj );
    117 	mNoExplodeDisappear = spawnArgs.GetBool( "no_explode_disappear", mNoExplodeDisappear );
    118 	mTouchTriggers = spawnArgs.GetBool( "touch_triggers", mTouchTriggers );
    119 }
    120 
    121 /*
    122 ================
    123 idProjectile::Save
    124 ================
    125 */
    126 void idProjectile::Save( idSaveGame *savefile ) const {
    127 
    128 	owner.Save( savefile );
    129 
    130 	projectileFlags_s flags = projectileFlags;
    131 	LittleBitField( &flags, sizeof( flags ) );
    132 	savefile->Write( &flags, sizeof( flags ) );
    133 
    134 	savefile->WriteFloat( thrust );
    135 	savefile->WriteInt( thrust_end );
    136 
    137 	savefile->WriteRenderLight( renderLight );
    138 	savefile->WriteInt( (int)lightDefHandle );
    139 	savefile->WriteVec3( lightOffset );
    140 	savefile->WriteInt( lightStartTime );
    141 	savefile->WriteInt( lightEndTime );
    142 	savefile->WriteVec3( lightColor );
    143 
    144 	savefile->WriteParticle( smokeFly );
    145 	savefile->WriteInt( smokeFlyTime );
    146 
    147 	savefile->WriteInt( originalTimeGroup );
    148 
    149 	savefile->WriteInt( (int)state );
    150 
    151 	savefile->WriteFloat( damagePower );
    152 
    153 	savefile->WriteStaticObject( physicsObj );
    154 	savefile->WriteStaticObject( thruster );
    155 }
    156 
    157 /*
    158 ================
    159 idProjectile::Restore
    160 ================
    161 */
    162 void idProjectile::Restore( idRestoreGame *savefile ) {
    163 
    164 	owner.Restore( savefile );
    165 
    166 	savefile->Read( &projectileFlags, sizeof( projectileFlags ) );
    167 	LittleBitField( &projectileFlags, sizeof( projectileFlags ) );
    168 
    169 	savefile->ReadFloat( thrust );
    170 	savefile->ReadInt( thrust_end );
    171 
    172 	savefile->ReadRenderLight( renderLight );
    173 	savefile->ReadInt( (int &)lightDefHandle );
    174 	savefile->ReadVec3( lightOffset );
    175 	savefile->ReadInt( lightStartTime );
    176 	savefile->ReadInt( lightEndTime );
    177 	savefile->ReadVec3( lightColor );
    178 
    179 	savefile->ReadParticle( smokeFly );
    180 	savefile->ReadInt( smokeFlyTime );
    181 
    182 	savefile->ReadInt( originalTimeGroup );
    183 
    184 	savefile->ReadInt( (int &)state );
    185 
    186 	savefile->ReadFloat( damagePower );
    187 
    188 	savefile->ReadStaticObject( physicsObj );
    189 	RestorePhysics( &physicsObj );
    190 
    191 	savefile->ReadStaticObject( thruster );
    192 	thruster.SetPhysics( &physicsObj );
    193 
    194 	if ( smokeFly != NULL ) {
    195 		idVec3 dir;
    196 		dir = physicsObj.GetLinearVelocity();
    197 		dir.NormalizeFast();
    198 		gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
    199 	}
    200 
    201 	if ( lightDefHandle >= 0 ) {
    202 		lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
    203 	}
    204 }
    205 
    206 /*
    207 ================
    208 idProjectile::GetOwner
    209 ================
    210 */
    211 idEntity *idProjectile::GetOwner() const {
    212 	return owner.GetEntity();
    213 }
    214 
    215 /*
    216 ================
    217 idProjectile::Create
    218 ================
    219 */
    220 void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) {
    221 	idDict		args;
    222 	idStr		shaderName;
    223 	idVec3		light_color;
    224 	idVec3		light_offset;
    225 	idVec3		tmp;
    226 	idMat3		axis;
    227 
    228 	Unbind();
    229 
    230 	// align z-axis of model with the direction
    231 	axis = dir.ToMat3();
    232 	tmp = axis[2];
    233 	axis[2] = axis[0];
    234 	axis[0] = -tmp;
    235 
    236 	physicsObj.SetOrigin( start );
    237 	physicsObj.SetAxis( axis );
    238 
    239 	physicsObj.GetClipModel()->SetOwner( owner );
    240 
    241 	this->owner = owner;
    242 
    243 	memset( &renderLight, 0, sizeof( renderLight ) );
    244 	shaderName = spawnArgs.GetString( "mtr_light_shader" );
    245 	if ( *(const char *)shaderName ) {
    246 		renderLight.shader = declManager->FindMaterial( shaderName, false );
    247 		renderLight.pointLight = true;
    248 		renderLight.lightRadius[0] =
    249 		renderLight.lightRadius[1] =
    250 		renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" );
    251 #ifdef ID_PC
    252 		renderLight.lightRadius *= 1.5f;
    253 		renderLight.forceShadows = true;
    254 #endif
    255 		spawnArgs.GetVector( "light_color", "1 1 1", light_color );
    256 		renderLight.shaderParms[0] = light_color[0];
    257 		renderLight.shaderParms[1] = light_color[1];
    258 		renderLight.shaderParms[2] = light_color[2];
    259 		renderLight.shaderParms[3] = 1.0f;
    260 	}
    261 
    262 	spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset );
    263 
    264 	lightStartTime = 0;
    265 	lightEndTime = 0;
    266 	smokeFlyTime = 0;
    267 
    268 	damagePower = 1.0f;
    269 
    270 	if(spawnArgs.GetBool("reset_time_offset", "0")) {
    271 		renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
    272 	}
    273 
    274 	UpdateVisuals();
    275 
    276 	state = CREATED;
    277 }
    278 
    279 /*
    280 =================
    281 idProjectile::~idProjectile
    282 =================
    283 */
    284 idProjectile::~idProjectile() {
    285 	StopSound( SND_CHANNEL_ANY, false );
    286 	FreeLightDef();
    287 }
    288 
    289 /*
    290 =================
    291 idProjectile::FreeLightDef
    292 =================
    293 */
    294 void idProjectile::FreeLightDef() {
    295 	if ( lightDefHandle != -1 ) {
    296 		gameRenderWorld->FreeLightDef( lightDefHandle );
    297 		lightDefHandle = -1;
    298 	}
    299 }
    300 
    301 /*
    302 =================
    303 idProjectile::Launch
    304 =================
    305 */
    306 void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) {
    307 	float			fuse;
    308 	float			startthrust;
    309 	float			endthrust;
    310 	idVec3			velocity;
    311 	idAngles		angular_velocity;
    312 	float			linear_friction;
    313 	float			angular_friction;
    314 	float			contact_friction;
    315 	float			bounce;
    316 	float			mass;
    317 	float			speed;
    318 	float			gravity;
    319 	idVec3			gravVec;
    320 	idVec3			tmp;
    321 	idMat3			axis;
    322 	int				thrust_start;
    323 	int				contents;
    324 	int				clipMask;
    325 
    326 	// allow characters to throw projectiles during cinematics, but not the player
    327 	if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) {
    328 		cinematic = owner.GetEntity()->cinematic;
    329 	} else {
    330 		cinematic = false;
    331 	}
    332 
    333 	thrust				= spawnArgs.GetFloat( "thrust" );
    334 	startthrust			= spawnArgs.GetFloat( "thrust_start" );
    335 	endthrust			= spawnArgs.GetFloat( "thrust_end" );
    336 
    337 	spawnArgs.GetVector( "velocity", "0 0 0", velocity );
    338 	
    339 	speed = velocity.Length() * launchPower;
    340 
    341 	damagePower = dmgPower;
    342 
    343 	spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
    344 
    345 	linear_friction		= spawnArgs.GetFloat( "linear_friction" );
    346 	angular_friction	= spawnArgs.GetFloat( "angular_friction" );
    347 	contact_friction	= spawnArgs.GetFloat( "contact_friction" );
    348 	bounce				= spawnArgs.GetFloat( "bounce" );
    349 	mass				= spawnArgs.GetFloat( "mass" );
    350 	gravity				= spawnArgs.GetFloat( "gravity" );
    351 	fuse				= spawnArgs.GetFloat( "fuse" );
    352 
    353 	projectileFlags.detonate_on_world	= spawnArgs.GetBool( "detonate_on_world" );
    354 	projectileFlags.detonate_on_actor	= spawnArgs.GetBool( "detonate_on_actor" );
    355 	projectileFlags.randomShaderSpin	= spawnArgs.GetBool( "random_shader_spin" );
    356 
    357 	if ( mass <= 0 ) {
    358 		gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
    359 	}
    360 
    361 	thrust *= mass;
    362 	thrust_start = SEC2MS( startthrust ) + gameLocal.time;
    363 	thrust_end = SEC2MS( endthrust ) + gameLocal.time;
    364 
    365 	lightStartTime = 0;
    366 	lightEndTime = 0;
    367 
    368 	if ( health ) {
    369 		fl.takedamage = true;
    370 	}
    371 
    372 	gravVec = gameLocal.GetGravity();
    373 	gravVec.NormalizeFast();
    374 
    375 	Unbind();
    376 
    377 	// align z-axis of model with the direction
    378 	axis = dir.ToMat3();
    379 	tmp = axis[2];
    380 	axis[2] = axis[0];
    381 	axis[0] = -tmp;
    382 
    383 	contents = 0;
    384 	clipMask = MASK_SHOT_RENDERMODEL;
    385 	if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) {
    386 		contents |= CONTENTS_TRIGGER;
    387 	}
    388 	if ( !spawnArgs.GetBool( "no_contents" ) ) {
    389 		contents |= CONTENTS_PROJECTILE;
    390 		clipMask |= CONTENTS_PROJECTILE;
    391 	}
    392 
    393 	if ( !idStr::Cmp( this->GetEntityDefName(), "projectile_helltime_killer" ) ) {
    394 		contents = CONTENTS_MOVEABLECLIP;
    395 		clipMask = CONTENTS_MOVEABLECLIP;
    396 		fuse = 10.0f;
    397 	}
    398 
    399 	// don't do tracers on client, we don't know origin and direction
    400 	if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) {
    401 		SetModel( spawnArgs.GetString( "model_tracer" ) );
    402 		projectileFlags.isTracer = true;
    403 	}
    404 
    405 	physicsObj.SetMass( mass );
    406 	physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
    407 	if ( contact_friction == 0.0f ) {
    408 		physicsObj.NoContact();
    409 	}
    410 	physicsObj.SetBouncyness( bounce );
    411 	physicsObj.SetGravity( gravVec * gravity );
    412 	physicsObj.SetContents( contents );
    413 	physicsObj.SetClipMask( clipMask );
    414 	physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity );
    415 	physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
    416 	physicsObj.SetOrigin( start );
    417 	physicsObj.SetAxis( axis );
    418 
    419 	launchOrigin = start;
    420 	launchAxis = axis;
    421 
    422 	thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) );
    423 
    424 	if ( !common->IsClient() || fl.skipReplication ) {
    425 		if ( fuse <= 0 ) {
    426 			// run physics for 1 second
    427 			RunPhysics();
    428 			PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
    429 		} else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
    430 			fuse -= timeSinceFire;
    431 			if ( fuse < 0.0f ) {
    432 				fuse = 0.0f;
    433 			}
    434 			PostEventSec( &EV_Explode, fuse );
    435 		} else {
    436 			fuse -= timeSinceFire;
    437 			if ( fuse < 0.0f ) {
    438 				fuse = 0.0f;
    439 			}
    440 			PostEventSec( &EV_Fizzle, fuse );
    441 		}
    442 	}
    443 
    444 	if ( projectileFlags.isTracer ) {
    445 		StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL );
    446 	} else {
    447 		StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
    448 	}
    449 
    450 	smokeFlyTime = 0;
    451 	const char *smokeName = spawnArgs.GetString( "smoke_fly" );
    452 	if ( *smokeName != '\0' ) {
    453 		smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
    454 		smokeFlyTime = gameLocal.time;
    455 	}
    456 
    457 	originalTimeGroup = timeGroup;
    458 
    459 	// used for the plasma bolts but may have other uses as well
    460 	if ( projectileFlags.randomShaderSpin ) {
    461 		float f = gameLocal.random.RandomFloat();
    462 		f *= 0.5f;
    463 		renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f;
    464 	}
    465 
    466 	UpdateVisuals();
    467 
    468 	state = LAUNCHED;
    469 }
    470 
    471 /*
    472 ================
    473 idProjectile::Think
    474 ================
    475 */
    476 void idProjectile::Think() {
    477 
    478 	if ( thinkFlags & TH_THINK ) {
    479 		if ( thrust && ( gameLocal.time < thrust_end ) ) {
    480 			// evaluate force
    481 			thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust );
    482 			thruster.Evaluate( gameLocal.time );
    483 		}
    484 	}
    485 
    486 	if ( mTouchTriggers ) {
    487 		TouchTriggers();
    488 	}
    489 	
    490 	// if the projectile owner is a player
    491 	if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
    492 		idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
    493 			
    494 		// Remove any projectiles spectators threw.
    495 		if( player != NULL && player->spectating ) {
    496 			PostEventMS( &EV_Remove, 0 );
    497 		}
    498 	}
    499 
    500 	// run physics
    501 	RunPhysics();
    502 
    503 	DecayOriginAndAxisDelta();
    504 
    505 	Present();
    506 
    507 	AddParticlesAndLight();
    508 }
    509 
    510 /*
    511 =================
    512 idProjectile::AddParticlesAndLight
    513 =================
    514 */
    515 void idProjectile::AddParticlesAndLight() {
    516 	// add the particles
    517 	if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) {
    518 		idVec3 dir = -GetPhysics()->GetLinearVelocity();
    519 		dir.Normalize();
    520 		SetTimeState ts(originalTimeGroup);
    521 
    522 		if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3(), timeGroup /*_D3XP*/ ) ) {
    523 			smokeFlyTime = gameLocal.time;
    524 		}
    525 	}
    526 
    527 	// add the light
    528 	if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) {
    529 		renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset;
    530 		renderLight.axis = GetPhysics()->GetAxis();
    531 		if ( ( lightDefHandle != -1 ) ) {
    532 			if ( lightEndTime > 0 && gameLocal.time <= lightEndTime ) {
    533 				idVec3 color( 0, 0, 0 );
    534 				if ( gameLocal.time < lightEndTime ) {
    535 					float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime );
    536 					color.Lerp( lightColor, color, frac );
    537 				} 
    538 				renderLight.shaderParms[SHADERPARM_RED] = color.x;
    539 				renderLight.shaderParms[SHADERPARM_GREEN] = color.y;
    540 				renderLight.shaderParms[SHADERPARM_BLUE] = color.z;
    541 			} 
    542 			gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight );
    543 		} else {
    544 			lightDefHandle = gameRenderWorld->AddLightDef( &renderLight );
    545 		}
    546 	}
    547 }
    548 
    549 /*
    550 =================
    551 idProjectile::Collide
    552 =================
    553 */
    554 bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) {
    555 	idEntity	*ent;
    556 	idEntity	*ignore;
    557 	const char	*damageDefName;
    558 	idVec3		dir;
    559 	float		push;
    560 	float		damageScale;
    561 
    562 	if ( state == EXPLODED || state == FIZZLED ) {
    563 		return true;
    564 	}
    565 	
    566 	const bool isHitscan = spawnArgs.GetBool( "net_instanthit" );
    567 
    568 	// hanlde slow projectiles here.
    569 	if ( common->IsClient() && !isHitscan ) {
    570 
    571 		// This is a replicated slow projectile, predict the explosion.
    572 		if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !isHitscan ) ) {
    573 			Explode( collision, NULL );
    574 			return true;
    575 		}
    576 
    577 	}
    578 
    579 	// remove projectile when a 'noimpact' surface is hit
    580 	if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
    581 		PostEventMS( &EV_Remove, 0 );
    582 		common->DPrintf( "Projectile collision no impact\n" );
    583 		return true;
    584 	}
    585 
    586 	// get the entity the projectile collided with
    587 	ent = gameLocal.entities[ collision.c.entityNum ];
    588 	if ( ent == owner.GetEntity() ) {
    589 		assert( 0 );
    590 		return true;
    591 	}
    592 
    593 	// just get rid of the projectile when it hits a player in noclip
    594 	if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
    595 		PostEventMS( &EV_Remove, 0 );
    596 		return true;
    597 	}
    598 
    599 	// direction of projectile
    600 	dir = velocity;
    601 	dir.Normalize();
    602 
    603 	// projectiles can apply an additional impulse next to the rigid body physics impulse
    604 	if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) {
    605 		if ( !common->IsClient() ) {
    606 			ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir );
    607 		}
    608 	}
    609 
    610 	// MP: projectiles open doors
    611 	if ( common->IsMultiplayer() && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) {
    612 		if ( !common->IsClient() ) {
    613 			ent->ProcessEvent( &EV_Activate , this );
    614 		}
    615 	}
    616 
    617 	if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
    618 		if ( !projectileFlags.detonate_on_actor ) {
    619 			return false;
    620 		}
    621 	} else {
    622 		if ( !projectileFlags.detonate_on_world ) {
    623 			if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) {
    624 				float len = velocity.Length();
    625 				if ( len > BOUNCE_SOUND_MIN_VELOCITY ) {
    626 					SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) );
    627 					StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL );
    628 				}
    629 			}
    630 			return false;
    631 		}
    632 	}
    633 
    634 	SetOrigin( collision.endpos );
    635 	SetAxis( collision.endAxis );
    636 
    637 	// To see the explosion on the collision surface instead of
    638 	// at the muzzle, clear the deltas.
    639 	CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
    640 
    641 	// unlink the clip model because we no longer need it
    642 	GetPhysics()->UnlinkClip();
    643 
    644 	damageDefName = spawnArgs.GetString( "def_damage" );
    645 
    646 	ignore = NULL;
    647 
    648 	// if the projectile causes a damage effect
    649 	if ( spawnArgs.GetBool( "impact_damage_effect" ) ) {
    650 		// if the hit entity has a special damage effect
    651 		if ( ent->spawnArgs.GetBool( "bleed" ) ) {
    652 			ent->AddDamageEffect( collision, velocity, damageDefName );
    653 		} else {
    654 			AddDefaultDamageEffect( collision, velocity );
    655 		}
    656 	}
    657 
    658 	// if the hit entity takes damage
    659 	if ( ent->fl.takedamage ) {
    660 		if ( damagePower ) {
    661 			damageScale = damagePower;
    662 		} else {
    663 			damageScale = 1.0f;
    664 		}
    665 
    666 		// if the projectile owner is a player
    667 		if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
    668 			// if the projectile hit an actor
    669 			if ( ent->IsType( idActor::Type ) ) {
    670 				idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
    671 				player->AddProjectileHits( 1 );
    672 				damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
    673 			}
    674 		}
    675 
    676 		if ( damageDefName[0] != '\0' ) {
    677 
    678 			bool killedByImpact = true;
    679 
    680 			if( ent->health <= 0 ) {
    681 				killedByImpact = false;
    682 			}
    683 
    684 			// Only handle the server's own attacks here. Attacks by other players on the server occur through
    685 			// reliable messages.
    686 			if ( !common->IsMultiplayer() || common->IsClient() || ( common->IsServer() && owner.GetEntityNum() == gameLocal.GetLocalClientNum() ) || ( common->IsServer() && !isHitscan ) ) {
    687 				ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
    688 			} else {
    689 				if( ent->IsType( idPlayer::Type ) ==  false && common->IsServer() ) {
    690 					ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) );
    691 				}
    692 			}
    693 
    694 			// Check if we are hitting an actor. and see if we killed him.
    695 			if( !common->IsClient() && ent->health <= 0 && killedByImpact ) {
    696 				if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) {
    697 					if ( ent->IsType( idActor::Type ) && ent != owner.GetEntity() ) {
    698 						idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
    699 						player->AddProjectileKills();
    700 					}
    701 				}
    702 
    703 			}
    704 
    705 			ignore = ent;
    706 		}
    707 	}
    708 
    709 	Explode( collision, ignore );
    710 
    711 	if ( !common->IsClient() && owner.GetEntity() != NULL && owner.GetEntity()->IsType( idPlayer::Type ) ) {
    712 		idPlayer *player = static_cast<idPlayer *>( owner.GetEntity() );
    713 		int kills = player->GetProjectileKills();
    714 
    715 		if( kills >= 2 && common->IsMultiplayer() && strstr( GetName(), "projectile_rocket" ) != 0 ) {
    716 			player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG );
    717 		}
    718 
    719 		// projectile is done dealing damage. 
    720 		player->ResetProjectileKills();
    721 	}
    722 
    723 	return true;
    724 }
    725 
    726 /*
    727 =================
    728 idProjectile::DefaultDamageEffect
    729 =================
    730 */
    731 void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) {
    732 	const char *decal, *sound, *typeName;
    733 	surfTypes_t materialType;
    734 
    735 	if ( collision.c.material != NULL ) {
    736 		materialType = collision.c.material->GetSurfaceType();
    737 	} else {
    738 		materialType = SURFTYPE_METAL;
    739 	}
    740 
    741 	// get material type name
    742 	typeName = gameLocal.sufaceTypeNames[ materialType ];
    743 
    744 	// play impact sound
    745 	sound = projectileDef.GetString( va( "snd_%s", typeName ) );
    746 	if ( *sound == '\0' ) {
    747 		sound = projectileDef.GetString( "snd_metal" );
    748 	}
    749 	if ( *sound == '\0' ) {
    750 		sound = projectileDef.GetString( "snd_impact" );
    751 	}
    752 	if ( *sound != '\0' ) {
    753 		soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL );
    754 	}
    755 
    756 	// project decal
    757 	decal = projectileDef.GetString( va( "mtr_detonate_%s", typeName ) );
    758 	if ( *decal == '\0' ) {
    759 		decal = projectileDef.GetString( "mtr_detonate" );
    760 	}
    761 	if ( *decal != '\0' ) {
    762 		gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, projectileDef.GetFloat( "decal_size", "6.0" ), decal );
    763 	}
    764 }
    765 
    766 /*
    767 =================
    768 idProjectile::AddDefaultDamageEffect
    769 =================
    770 */
    771 void idProjectile::AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ) {
    772 
    773 	DefaultDamageEffect( this, spawnArgs, collision, velocity );
    774 
    775 	if ( common->IsServer() && fl.networkSync ) {
    776 		idBitMsg	msg;
    777 		byte		msgBuf[MAX_EVENT_PARAM_SIZE];
    778 		lobbyUserID_t excluding;
    779 
    780 		if ( spawnArgs.GetBool( "net_instanthit" ) && owner.GetEntityNum() < MAX_PLAYERS ) {
    781 			excluding = gameLocal.lobbyUserIDs[owner.GetEntityNum()];
    782 		}
    783 
    784 		msg.InitWrite( msgBuf, sizeof( msgBuf ) );
    785 		msg.BeginWriting();
    786 		msg.WriteFloat( collision.c.point[0] );
    787 		msg.WriteFloat( collision.c.point[1] );
    788 		msg.WriteFloat( collision.c.point[2] );
    789 		msg.WriteDir( collision.c.normal, 24 );
    790 		msg.WriteLong( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 );
    791 		msg.WriteFloat( velocity[0], 5, 10 );
    792 		msg.WriteFloat( velocity[1], 5, 10 );
    793 		msg.WriteFloat( velocity[2], 5, 10 );
    794 		ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excluding );
    795 	}
    796 }
    797 
    798 /*
    799 ================
    800 idProjectile::Killed
    801 ================
    802 */
    803 void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
    804 	if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
    805 		trace_t collision;
    806 
    807 		memset( &collision, 0, sizeof( collision ) );
    808 		collision.endAxis = GetPhysics()->GetAxis();
    809 		collision.endpos = GetPhysics()->GetOrigin();
    810 		collision.c.point = GetPhysics()->GetOrigin();
    811 		collision.c.normal.Set( 0, 0, 1 );
    812 		Explode( collision, NULL );
    813 		physicsObj.ClearContacts();
    814 		physicsObj.PutToRest();
    815 	} else {
    816 		Fizzle();
    817 	}
    818 }
    819 
    820 /*
    821 ================
    822 idProjectile::Fizzle
    823 ================
    824 */
    825 void idProjectile::Fizzle() {
    826 
    827 	if ( state == EXPLODED || state == FIZZLED ) {
    828 		return;
    829 	}
    830 
    831 	StopSound( SND_CHANNEL_BODY, false );
    832 	StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
    833 
    834 	// fizzle FX
    835 	const char *psystem = spawnArgs.GetString( "smoke_fuse" );
    836 	if ( psystem != NULL && *psystem != NULL ) {
    837 //FIXME:SMOKE		gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem );
    838 	}
    839 
    840 	// we need to work out how long the effects last and then remove them at that time
    841 	// for example, bullets have no real effects
    842 	if ( smokeFly && smokeFlyTime ) {
    843 		smokeFlyTime = 0;
    844 	}
    845 
    846 	fl.takedamage = false;
    847 	physicsObj.SetContents( 0 );
    848 	physicsObj.GetClipModel()->Unlink();
    849 	physicsObj.PutToRest();
    850 
    851 	Hide();
    852 	FreeLightDef();
    853 
    854 	state = FIZZLED;
    855 
    856 	if ( common->IsClient() && !fl.skipReplication ) {
    857 		return;
    858 	}
    859 
    860 	CancelEvents( &EV_Fizzle );
    861 	PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) );
    862 }
    863 
    864 /*
    865 ================
    866 idProjectile::Event_RadiusDamage
    867 ================
    868 */
    869 void idProjectile::Event_RadiusDamage( idEntity *ignore ) {
    870 	const char *splash_damage = spawnArgs.GetString( "def_splash_damage" );
    871 	if ( splash_damage[0] != '\0' ) {
    872 		gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner.GetEntity(), ignore, this, splash_damage, damagePower );
    873 	}
    874 }
    875 
    876 /*
    877 ================
    878 idProjectile::Event_RadiusDamage
    879 ================
    880 */
    881 void idProjectile::Event_GetProjectileState() {
    882 	idThread::ReturnInt( state );
    883 }
    884 
    885 /*
    886 ================
    887 idProjectile::Explode
    888 ================
    889 */
    890 void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
    891 	const char *fxname, *light_shader, *sndExplode;
    892 	float		light_fadetime;
    893 	idVec3		normal;
    894 	int			removeTime;
    895 
    896 	if ( mNoExplodeDisappear ) {
    897 		PostEventMS( &EV_Remove, 0 );
    898 		return;
    899 	}
    900 
    901 	if ( state == EXPLODED || state == FIZZLED ) {
    902 		return;
    903 	}
    904 
    905 	// activate rumble for player
    906 	idPlayer *player = gameLocal.GetLocalPlayer();
    907 	const bool isHitscan = spawnArgs.GetBool( "net_instanthit" );
    908 	if ( player != NULL && isHitscan == false ) {
    909 
    910 		// damage
    911 		const char *damageDefName = spawnArgs.GetString( "def_damage" );
    912 		const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
    913 		int damage;
    914 		if ( damageDef != NULL ) {
    915 			damage = damageDef->GetInt( "damage" );
    916 		} else {
    917 			damage = 200;
    918 		}
    919 		float damageScale = idMath::ClampFloat( 0.25f, 1.0f, (float)damage * ( 1.0f / 200.0f ) );	// 50...200 -> min...max rumble
    920 
    921 		// distance
    922 		float dist = ( GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthFast();
    923 		float distScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist * ( 1.0f / 4000.0f ) ) + 0.25f );		// 0...4000 -> max...min rumble
    924 
    925 		distScale *= damageScale;	// apply damage scale here, weaker damage produces less rumble
    926 
    927 		// determine rumble
    928 		float highMag = distScale;
    929 		int highDuration = idMath::Ftoi( 300.0f * distScale );
    930 		float lowMag = distScale * 0.75f;
    931 		int lowDuration = idMath::Ftoi( 500.0f * distScale );
    932 
    933 		player->SetControllerShake( highMag, highDuration, lowMag, lowDuration );
    934 	}
    935 
    936 	// stop sound
    937 	StopSound( SND_CHANNEL_BODY2, false );
    938 
    939 	// play explode sound
    940 	switch ( ( int ) damagePower ) {
    941 		case 2: sndExplode = "snd_explode2"; break;
    942 		case 3: sndExplode = "snd_explode3"; break;
    943 		case 4: sndExplode = "snd_explode4"; break;
    944 		default: sndExplode = "snd_explode"; break;
    945 	}
    946 	StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL );
    947 
    948 	// we need to work out how long the effects last and then remove them at that time
    949 	// for example, bullets have no real effects
    950 	if ( smokeFly && smokeFlyTime ) {
    951 		smokeFlyTime = 0;
    952 	}
    953 
    954 	Hide();
    955 	FreeLightDef();
    956 
    957 	if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) {
    958 		GetPhysics()->SetAxis( normal.ToMat3() );
    959 	}
    960 	GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal );
    961 
    962 	// default remove time
    963 	if( fl.skipReplication && !spawnArgs.GetBool( "net_instanthit" ) ) {
    964 		removeTime = spawnArgs.GetInt( "remove_time", "6000" );
    965 	} else {
    966 		removeTime = spawnArgs.GetInt( "remove_time", "1500" );
    967 	}
    968 
    969 	// change the model, usually to a PRT
    970 	fxname = NULL;
    971 	if ( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) {
    972 		fxname = g_testParticleName.GetString();
    973 	} else {
    974 		fxname = spawnArgs.GetString( "model_detonate" );
    975 	}
    976 
    977 	int surfaceType = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL;
    978 	if ( !( fxname != NULL && *fxname != NULL ) ) {
    979 		if ( ( surfaceType == SURFTYPE_NONE ) || ( surfaceType == SURFTYPE_METAL ) || ( surfaceType == SURFTYPE_STONE ) ) {
    980 			fxname = spawnArgs.GetString( "model_smokespark" );
    981 		} else if ( surfaceType == SURFTYPE_RICOCHET ) {
    982 			fxname = spawnArgs.GetString( "model_ricochet" );
    983 		} else {
    984 			fxname = spawnArgs.GetString( "model_smoke" );
    985 		}
    986 	}
    987 
    988 	// If the explosion is in liquid, spawn a particle splash
    989 	idVec3 testOrg = GetPhysics()->GetOrigin();
    990 	int testC = gameLocal.clip.Contents( testOrg, NULL, mat3_identity, CONTENTS_WATER, this );
    991 	if ( testC & CONTENTS_WATER ) {
    992 		idFuncEmitter *splashEnt;
    993 		idDict splashArgs;
    994 
    995 		splashArgs.Set( "model", "sludgebulletimpact.prt" );
    996 		splashArgs.Set( "start_off", "1" );
    997 		splashEnt = static_cast<idFuncEmitter *>( gameLocal.SpawnEntityType( idFuncEmitter::Type, &splashArgs ) );
    998 
    999 		splashEnt->GetPhysics()->SetOrigin( testOrg );
   1000 		splashEnt->PostEventMS( &EV_Activate, 0, this );
   1001 		splashEnt->PostEventMS( &EV_Remove, 1500 );
   1002 
   1003 		// HACK - if this is a chaingun bullet, don't do the normal effect
   1004 		if ( !idStr::Cmp( spawnArgs.GetString( "def_damage" ), "damage_bullet_chaingun" ) ) {
   1005 			fxname = NULL;
   1006 		}
   1007 	}
   1008 
   1009 	if ( fxname && *fxname ) {
   1010 		SetModel( fxname );
   1011 		renderEntity.shaderParms[SHADERPARM_RED] = 
   1012 		renderEntity.shaderParms[SHADERPARM_GREEN] = 
   1013 		renderEntity.shaderParms[SHADERPARM_BLUE] = 
   1014 		renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f;
   1015 		renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
   1016 		renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat();
   1017 		Show();
   1018 		removeTime = ( removeTime > 3000 ) ? removeTime : 3000;
   1019 	}
   1020 
   1021 	// explosion light
   1022 	light_shader = spawnArgs.GetString( "mtr_explode_light_shader" );
   1023 
   1024 	if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") )
   1025 	{
   1026 		light_shader = "lights/midnight_grenade";
   1027 	}
   1028 	
   1029 	if ( *light_shader ) {
   1030 		renderLight.shader = declManager->FindMaterial( light_shader, false );
   1031 		renderLight.pointLight = true;
   1032 		renderLight.lightRadius[0] =
   1033 		renderLight.lightRadius[1] =
   1034 		renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" );
   1035 #ifdef ID_PC
   1036 		renderLight.lightRadius *= 2.0f;
   1037 		renderLight.forceShadows = true;
   1038 #endif
   1039 
   1040 		// Midnight ctf
   1041 		if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) {
   1042 			renderLight.lightRadius[0] =
   1043 			renderLight.lightRadius[1] =
   1044 			renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ) * 2;
   1045 		}
   1046 		
   1047 		spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor );
   1048 		renderLight.shaderParms[SHADERPARM_RED] = lightColor.x;
   1049 		renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y;
   1050 		renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z;
   1051 		renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f;
   1052 		renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
   1053 
   1054 		// Midnight ctf
   1055 		if ( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool("si_midnight") ) {
   1056 			light_fadetime = 3.0f;
   1057 		} else {
   1058 			light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" );
   1059 		}
   1060 
   1061 		lightStartTime = gameLocal.time;
   1062 		lightEndTime = MSEC_ALIGN_TO_FRAME( gameLocal.time + SEC2MS( light_fadetime ) );
   1063 		BecomeActive( TH_THINK );
   1064 	}
   1065 
   1066 	fl.takedamage = false;
   1067 	physicsObj.SetContents( 0 );
   1068 	physicsObj.PutToRest();
   1069 
   1070 	state = EXPLODED;
   1071 
   1072 	if ( common->IsClient() && !fl.skipReplication ) {
   1073 		return;
   1074 	}
   1075 
   1076 	// alert the ai
   1077 	gameLocal.AlertAI( owner.GetEntity() );
   1078 
   1079 	// bind the projectile to the impact entity if necesary
   1080 	if ( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) {
   1081 		Bind( gameLocal.entities[collision.c.entityNum], true );
   1082 	}
   1083 
   1084 	// splash damage
   1085 	if ( !projectileFlags.noSplashDamage ) {
   1086 		float delay = spawnArgs.GetFloat( "delay_splash" );
   1087 		if ( delay ) {
   1088 			if ( removeTime < delay * 1000 ) {
   1089 				removeTime = ( delay + 0.10 ) * 1000;
   1090 			}
   1091 			PostEventSec( &EV_RadiusDamage, delay, ignore );
   1092 		} else {
   1093 			Event_RadiusDamage( ignore );
   1094 		}
   1095 	}
   1096 
   1097 	// spawn debris entities
   1098 	int fxdebris = spawnArgs.GetInt( "debris_count" );
   1099 	if ( fxdebris ) {
   1100 		const idDict *debris = gameLocal.FindEntityDefDict( "projectile_debris", false );
   1101 		if ( debris ) {
   1102 			int amount = gameLocal.random.RandomInt( fxdebris );
   1103 			for ( int i = 0; i < amount; i++ ) {
   1104 				idEntity *ent;
   1105 				idVec3 dir;
   1106 				dir.x = gameLocal.random.CRandomFloat() * 4.0f;
   1107 				dir.y = gameLocal.random.CRandomFloat() * 4.0f;
   1108 				dir.z = gameLocal.random.RandomFloat() * 8.0f;
   1109 				dir.Normalize();
   1110 
   1111 				gameLocal.SpawnEntityDef( *debris, &ent, false );
   1112 				if ( ent == NULL || !ent->IsType( idDebris::Type ) ) {
   1113 					gameLocal.Error( "'projectile_debris' is not an idDebris" );
   1114 					return;
   1115 				}
   1116 
   1117 				idDebris *debris = static_cast<idDebris *>(ent);
   1118 				debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
   1119 				debris->Launch();
   1120 			}
   1121 		}
   1122 		debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false );
   1123 		if ( debris ) {
   1124 			int amount = gameLocal.random.RandomInt( fxdebris );
   1125 			for ( int i = 0; i < amount; i++ ) {
   1126 				idEntity *ent;
   1127 				idVec3 dir;
   1128 				dir.x = gameLocal.random.CRandomFloat() * 8.0f;
   1129 				dir.y = gameLocal.random.CRandomFloat() * 8.0f;
   1130 				dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f;
   1131 				dir.Normalize();
   1132 
   1133 				gameLocal.SpawnEntityDef( *debris, &ent, false );
   1134 				if ( ent == NULL || !ent->IsType( idDebris::Type ) ) {
   1135 					gameLocal.Error( "'projectile_shrapnel' is not an idDebris" );
   1136 					break;
   1137 				}
   1138 
   1139 				idDebris *debris = static_cast<idDebris *>(ent);
   1140 				debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() );
   1141 				debris->Launch();
   1142 			}
   1143 		}
   1144 	}
   1145 
   1146 	CancelEvents( &EV_Explode );
   1147 	PostEventMS( &EV_Remove, removeTime );
   1148 }
   1149 
   1150 /*
   1151 ================
   1152 idProjectile::GetVelocity
   1153 ================
   1154 */
   1155 idVec3 idProjectile::GetVelocity( const idDict *projectile ) {
   1156 	idVec3 velocity;
   1157 
   1158 	projectile->GetVector( "velocity", "0 0 0", velocity );
   1159 	return velocity;
   1160 }
   1161 
   1162 /*
   1163 ================
   1164 idProjectile::GetGravity
   1165 ================
   1166 */
   1167 idVec3 idProjectile::GetGravity( const idDict *projectile ) {
   1168 	float gravity;
   1169 
   1170 	gravity = projectile->GetFloat( "gravity" );
   1171 	return idVec3( 0, 0, -gravity );
   1172 }
   1173 
   1174 /*
   1175 ================
   1176 idProjectile::Event_Explode
   1177 ================
   1178 */
   1179 void idProjectile::Event_Explode() {
   1180 	trace_t collision;
   1181 
   1182 	memset( &collision, 0, sizeof( collision ) );
   1183 	collision.endAxis = GetPhysics()->GetAxis();
   1184 	collision.endpos = GetPhysics()->GetOrigin();
   1185 	collision.c.point = GetPhysics()->GetOrigin();
   1186 	collision.c.normal.Set( 0, 0, 1 );
   1187 	AddDefaultDamageEffect( collision, collision.c.normal );
   1188 	Explode( collision, NULL );
   1189 }
   1190 
   1191 /*
   1192 ================
   1193 idProjectile::Event_Fizzle
   1194 ================
   1195 */
   1196 void idProjectile::Event_Fizzle() {
   1197 	Fizzle();
   1198 }
   1199 
   1200 /*
   1201 ================
   1202 idProjectile::Event_Touch
   1203 ================
   1204 */
   1205 void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) {
   1206 	if ( common->IsClient() ) {
   1207 		return;
   1208 	}
   1209 
   1210 	if ( IsHidden() ) {
   1211 		return;
   1212 	}
   1213 
   1214 	// Projectiles do not collide with flags
   1215 	if ( other->IsType( idItemTeam::Type ) ) {
   1216 		return;
   1217 	}
   1218 
   1219 	if ( other != owner.GetEntity() ) {
   1220 		trace_t collision;
   1221 
   1222 		memset( &collision, 0, sizeof( collision ) );
   1223 		collision.endAxis = GetPhysics()->GetAxis();
   1224 		collision.endpos = GetPhysics()->GetOrigin();
   1225 		collision.c.point = GetPhysics()->GetOrigin();
   1226 		collision.c.normal.Set( 0, 0, 1 );
   1227 		AddDefaultDamageEffect( collision, collision.c.normal );
   1228 		Explode( collision, NULL );
   1229 	}
   1230 }
   1231 
   1232 /*
   1233 ================
   1234 idProjectile::CatchProjectile
   1235 ================
   1236 */
   1237 void idProjectile::CatchProjectile( idEntity* o, const char* reflectName ) {
   1238 	idEntity *prevowner = owner.GetEntity();
   1239 
   1240 	owner = o;
   1241 	physicsObj.GetClipModel()->SetOwner( o );
   1242 
   1243 	if ( this->IsType( idGuidedProjectile::Type ) ) {
   1244 		idGuidedProjectile *proj = static_cast<idGuidedProjectile*>(this);
   1245 
   1246 		proj->SetEnemy( prevowner );
   1247 	}
   1248 
   1249 	idStr s = spawnArgs.GetString( "def_damage" );
   1250 	s += reflectName;
   1251 
   1252 	const idDict *damageDef = gameLocal.FindEntityDefDict( s, false );
   1253 	if ( damageDef ) {
   1254 		spawnArgs.Set( "def_damage", s );	
   1255 	}
   1256 }
   1257 
   1258 /*
   1259 ================
   1260 idProjectile::GetProjectileState
   1261 ================
   1262 */
   1263 int idProjectile::GetProjectileState() {
   1264 
   1265 	return (int)state;
   1266 }
   1267 
   1268 /*
   1269 ================
   1270 idProjectile::Event_CreateProjectile
   1271 ================
   1272 */
   1273 void idProjectile::Event_CreateProjectile( idEntity *owner, const idVec3 &start, const idVec3 &dir ) {
   1274 	Create(owner, start, dir);
   1275 }
   1276 
   1277 /*
   1278 ================
   1279 idProjectile::Event_LaunchProjectile
   1280 ================
   1281 */
   1282 void idProjectile::Event_LaunchProjectile( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity ) {
   1283 	Launch(start, dir, pushVelocity);
   1284 }
   1285 
   1286 /*
   1287 ================
   1288 idProjectile::Event_SetGravity
   1289 ================
   1290 */
   1291 void idProjectile::Event_SetGravity( float gravity ) {
   1292 	idVec3 gravVec;
   1293 
   1294 	gravVec = gameLocal.GetGravity();
   1295 	gravVec.NormalizeFast();
   1296 	physicsObj.SetGravity(gravVec * gravity);
   1297 }
   1298 
   1299 /*
   1300 =================
   1301 idProjectile::ClientPredictionCollide
   1302 =================
   1303 */
   1304 bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) {
   1305 	idEntity *ent;
   1306 
   1307 	// remove projectile when a 'noimpact' surface is hit
   1308 	if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) {
   1309 		return false;
   1310 	}
   1311 
   1312 	// get the entity the projectile collided with
   1313 	ent = gameLocal.entities[ collision.c.entityNum ];
   1314 	if ( ent == NULL ) {
   1315 		return false;
   1316 	}
   1317 
   1318 	// don't do anything if hitting a noclip player
   1319 	if ( ent->IsType( idPlayer::Type ) && static_cast<idPlayer *>( ent )->noclip ) {
   1320 		return false;
   1321 	}
   1322 
   1323 	if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast<const idAFAttachment*>(ent)->GetBody()->IsType( idActor::Type ) ) ) {
   1324 		if ( !projectileDef.GetBool( "detonate_on_actor" ) ) {
   1325 			return false;
   1326 		}
   1327 	} else {
   1328 		if ( !projectileDef.GetBool( "detonate_on_world" ) ) {
   1329 			return false;
   1330 		}
   1331 	}
   1332 
   1333 	// if the projectile causes a damage effect
   1334 	if ( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) {
   1335 		// if the hit entity does not have a special damage effect
   1336 		if ( !ent->spawnArgs.GetBool( "bleed" ) ) {
   1337 			// predict damage effect
   1338 			DefaultDamageEffect( soundEnt, projectileDef, collision, velocity );
   1339 		}
   1340 	}
   1341 	return true;
   1342 }
   1343 
   1344 /*
   1345 ================
   1346 idProjectile::ClientThink
   1347 ================
   1348 */
   1349 void idProjectile::ClientThink( const int curTime, const float fraction, const bool predict ) {
   1350 	if ( fl.skipReplication ) {
   1351 		Think();
   1352 	} else {
   1353 		if ( !renderEntity.hModel ) {
   1354 			return;
   1355 		}
   1356 		InterpolatePhysicsOnly( fraction );
   1357 		Present();
   1358 		AddParticlesAndLight();
   1359 	}
   1360 }
   1361 
   1362 /*
   1363 ================
   1364 idProjectile::ClientPredictionThink
   1365 ================
   1366 */
   1367 void idProjectile::ClientPredictionThink() {
   1368 	if ( !renderEntity.hModel ) {
   1369 		return;
   1370 	}
   1371 	Think();
   1372 }
   1373 
   1374 /*
   1375 ================
   1376 idProjectile::WriteToSnapshot
   1377 ================
   1378 */
   1379 void idProjectile::WriteToSnapshot( idBitMsg &msg ) const {
   1380 	msg.WriteBits( owner.GetSpawnId(), 32 );
   1381 	msg.WriteBits( state, 3 );
   1382 	msg.WriteBits( fl.hidden, 1 );
   1383 	
   1384 	physicsObj.WriteToSnapshot( msg );
   1385 }
   1386 
   1387 /*
   1388 ================
   1389 idProjectile::ReadFromSnapshot
   1390 ================
   1391 */
   1392 void idProjectile::ReadFromSnapshot( const idBitMsg &msg ) {
   1393 	projectileState_t newState;
   1394 
   1395 	owner.SetSpawnId( msg.ReadBits( 32 ) );
   1396 	newState = (projectileState_t) msg.ReadBits( 3 );
   1397 
   1398 	if ( msg.ReadBits( 1 ) ) {
   1399 		Hide();
   1400 	} else {
   1401 		Show();
   1402 	}
   1403 
   1404 	while( state != newState ) {
   1405 		switch( state ) {
   1406 			case SPAWNED: {
   1407 				Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) );
   1408 				break;
   1409 			}
   1410 			case CREATED: {
   1411 				// the right origin and direction are required if you want bullet traces
   1412 				Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin );
   1413 				break;
   1414 			}
   1415 			case LAUNCHED: {
   1416 				if ( newState == FIZZLED ) {
   1417 					Fizzle();
   1418 				} else {
   1419 					trace_t collision;
   1420 					memset( &collision, 0, sizeof( collision ) );
   1421 					collision.endAxis = GetPhysics()->GetAxis();
   1422 					collision.endpos = GetPhysics()->GetOrigin();
   1423 					collision.c.point = GetPhysics()->GetOrigin();
   1424 					collision.c.normal.Set( 0, 0, 1 );
   1425 					Explode( collision, NULL );
   1426 				}
   1427 				break;
   1428 			}
   1429 			case FIZZLED:
   1430 			case EXPLODED: {
   1431 				StopSound( SND_CHANNEL_BODY2, false );
   1432 				gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );
   1433 				state = SPAWNED;
   1434 				break;
   1435 			}
   1436 		}
   1437 	}
   1438 
   1439 	physicsObj.ReadFromSnapshot( msg );
   1440 
   1441 	if ( msg.HasChanged() ) {
   1442 		UpdateVisuals();
   1443 	}
   1444 }
   1445 
   1446 /*
   1447 ================
   1448 idProjectile::ClientReceiveEvent
   1449 ================
   1450 */
   1451 bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
   1452 	trace_t collision;
   1453 	idVec3 velocity;
   1454 
   1455 	switch( event ) {
   1456 		case EVENT_DAMAGE_EFFECT: {
   1457 			memset( &collision, 0, sizeof( collision ) );
   1458 			collision.c.point[0] = msg.ReadFloat();
   1459 			collision.c.point[1] = msg.ReadFloat();
   1460 			collision.c.point[2] = msg.ReadFloat();
   1461 			collision.c.normal = msg.ReadDir( 24 );
   1462 			int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() );
   1463 			collision.c.material = ( index != -1 ) ? static_cast<const idMaterial *>( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL;
   1464 			velocity[0] = msg.ReadFloat( 5, 10 );
   1465 			velocity[1] = msg.ReadFloat( 5, 10 );
   1466 			velocity[2] = msg.ReadFloat( 5, 10 );
   1467 			DefaultDamageEffect( this, spawnArgs, collision, velocity );
   1468 			return true;
   1469 		}
   1470 		default: {
   1471 			return idEntity::ClientReceiveEvent( event, time, msg );
   1472 		}
   1473 	}
   1474 }
   1475 
   1476 
   1477 /*
   1478 ========================
   1479 idProjectile::QueueToSimulate
   1480 ========================
   1481 */
   1482 void idProjectile::QueueToSimulate( int startTime ) {
   1483 	assert( common->IsMultiplayer() && common->IsServer() );
   1484 	
   1485 	for ( int i = 0; i < MAX_SIMULATED_PROJECTILES; i++ ) {
   1486 		if ( projectilesToSimulate[i].projectile == NULL ) {
   1487 			projectilesToSimulate[i].projectile = this;
   1488 			projectilesToSimulate[i].startTime= startTime;
   1489 			if ( g_projectileDebug.GetBool() ) {
   1490 				int delta = gameLocal.GetServerGameTimeMs() - startTime;
   1491 				idLib::Printf( "Simulating projectile %d. Approx %d delay.\n", GetEntityNumber(), delta);
   1492 			}
   1493 			return;
   1494 		}
   1495 	}
   1496 	
   1497 	idLib::Warning("Unable to simulate more projectiles this frame");
   1498 }
   1499 
   1500 /*
   1501 ========================
   1502 idProjectile::SimulateProjectileFrame
   1503 ========================
   1504 */
   1505 void idProjectile::SimulateProjectileFrame( int msec, int endTime ) {
   1506 	idVec3 oldOrigin = GetPhysics()->GetOrigin();	
   1507 
   1508 	GetPhysics()->Evaluate( msec, endTime );
   1509 	SetOrigin( GetPhysics()->GetOrigin() );
   1510 	SetAxis( GetPhysics()->GetAxis() );
   1511 
   1512 	if ( g_projectileDebug.GetBool() ) {
   1513 		float delta = ( GetPhysics()->GetOrigin() - oldOrigin ).Length();
   1514 		idLib::Printf( "Simulated projectile %d. Delta: %.2f \n", GetEntityNumber(), delta );
   1515 		//clientGame->renderWorld->DebugLine( idColor::colorYellow, oldOrigin, GetPhysics()->GetOrigin(), 5000 );
   1516 	}
   1517 }
   1518 
   1519 /*
   1520 ========================
   1521 idProjectile::PostSimulate
   1522 ========================
   1523 */
   1524 void idProjectile::PostSimulate( int endTime ) {
   1525 	if ( state == EXPLODED || state == FIZZLED ) {
   1526 		// Already exploded. To see the explosion on the collision surface instead of
   1527 		// at the muzzle, don't set the deltas to the launch origin and axis.
   1528 		CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
   1529 	} else {
   1530 		CreateDeltasFromOldOriginAndAxis( launchOrigin, launchAxis );
   1531 	}
   1532 }
   1533 
   1534 
   1535 
   1536 /*
   1537 ===============================================================================
   1538 
   1539 	idGuidedProjectile
   1540 
   1541 ===============================================================================
   1542 */
   1543 
   1544 const idEventDef EV_SetEnemy( "setEnemy", "E" );
   1545 
   1546 CLASS_DECLARATION( idProjectile, idGuidedProjectile )
   1547 	EVENT( EV_SetEnemy,		idGuidedProjectile::Event_SetEnemy )
   1548 END_CLASS
   1549 
   1550 /*
   1551 ================
   1552 idGuidedProjectile::idGuidedProjectile
   1553 ================
   1554 */
   1555 idGuidedProjectile::idGuidedProjectile() {
   1556 	enemy			= NULL;
   1557 	speed			= 0.0f;
   1558 	turn_max		= 0.0f;
   1559 	clamp_dist		= 0.0f;
   1560 	rndScale		= ang_zero;
   1561 	rndAng			= ang_zero;
   1562 	rndUpdateTime	= 0;
   1563 	angles			= ang_zero;
   1564 	burstMode		= false;
   1565 	burstDist		= 0;
   1566 	burstVelocity	= 0.0f;
   1567 	unGuided		= false;
   1568 }
   1569 
   1570 /*
   1571 =================
   1572 idGuidedProjectile::~idGuidedProjectile
   1573 =================
   1574 */
   1575 idGuidedProjectile::~idGuidedProjectile() {
   1576 }
   1577 
   1578 /*
   1579 ================
   1580 idGuidedProjectile::Spawn
   1581 ================
   1582 */
   1583 void idGuidedProjectile::Spawn() {
   1584 }
   1585 
   1586 /*
   1587 ================
   1588 idGuidedProjectile::Save
   1589 ================
   1590 */
   1591 void idGuidedProjectile::Save( idSaveGame *savefile ) const {
   1592 	enemy.Save( savefile );
   1593 	savefile->WriteFloat( speed );
   1594 	savefile->WriteAngles( rndScale );
   1595 	savefile->WriteAngles( rndAng );
   1596 	savefile->WriteInt( rndUpdateTime );
   1597 	savefile->WriteFloat( turn_max );
   1598 	savefile->WriteFloat( clamp_dist );
   1599 	savefile->WriteAngles( angles );
   1600 	savefile->WriteBool( burstMode );
   1601 	savefile->WriteBool( unGuided );
   1602 	savefile->WriteFloat( burstDist );
   1603 	savefile->WriteFloat( burstVelocity );
   1604 }
   1605 
   1606 /*
   1607 ================
   1608 idGuidedProjectile::Restore
   1609 ================
   1610 */
   1611 void idGuidedProjectile::Restore( idRestoreGame *savefile ) {
   1612 	enemy.Restore( savefile );
   1613 	savefile->ReadFloat( speed );
   1614 	savefile->ReadAngles( rndScale );
   1615 	savefile->ReadAngles( rndAng );
   1616 	savefile->ReadInt( rndUpdateTime );
   1617 	savefile->ReadFloat( turn_max );
   1618 	savefile->ReadFloat( clamp_dist );
   1619 	savefile->ReadAngles( angles );
   1620 	savefile->ReadBool( burstMode );
   1621 	savefile->ReadBool( unGuided );
   1622 	savefile->ReadFloat( burstDist );
   1623 	savefile->ReadFloat( burstVelocity );
   1624 }
   1625 
   1626 
   1627 /*
   1628 ================
   1629 idGuidedProjectile::GetSeekPos
   1630 ================
   1631 */
   1632 void idGuidedProjectile::GetSeekPos( idVec3 &out ) {
   1633 	idEntity *enemyEnt = enemy.GetEntity();
   1634 	if ( enemyEnt ) {
   1635 		if ( enemyEnt->IsType( idActor::Type ) ) {
   1636 			out = static_cast<idActor *>(enemyEnt)->GetEyePosition();
   1637 			out.z -= 12.0f;
   1638 		} else {
   1639 			out = enemyEnt->GetPhysics()->GetOrigin();
   1640 		}
   1641 	} else {
   1642 		out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f;
   1643 	}
   1644 }
   1645 
   1646 /*
   1647 ================
   1648 idGuidedProjectile::Think
   1649 ================
   1650 */
   1651 void idGuidedProjectile::Think() {
   1652 	idVec3		dir;
   1653 	idVec3		seekPos;
   1654 	idVec3		velocity;
   1655 	idVec3		nose;
   1656 	idVec3		tmp;
   1657 	idMat3		axis;
   1658 	idAngles	dirAng;
   1659 	idAngles	diff;
   1660 	float		dist;
   1661 	float		frac;
   1662 	int			i;
   1663 
   1664 	if ( state == LAUNCHED && !unGuided ) {
   1665 		
   1666 		GetSeekPos( seekPos );
   1667 
   1668 		if ( rndUpdateTime < gameLocal.time ) {
   1669 			rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat();
   1670 			rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat();
   1671 			rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat();
   1672 			rndUpdateTime = gameLocal.time + 200;
   1673 		}
   1674 
   1675 		nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0];
   1676 
   1677 		dir = seekPos - nose;
   1678 		dist = dir.Normalize();
   1679 		dirAng = dir.ToAngles();
   1680 
   1681 		// make it more accurate as it gets closer
   1682 		frac = dist / clamp_dist;
   1683 		if ( frac > 1.0f ) {
   1684 			frac = 1.0f;
   1685 		}
   1686 
   1687 		diff = dirAng - angles + rndAng * frac;
   1688 
   1689 		// clamp the to the max turn rate
   1690 		diff.Normalize180();
   1691 		for( i = 0; i < 3; i++ ) {
   1692 			if ( diff[ i ] > turn_max ) {
   1693 				diff[ i ] = turn_max;
   1694 			} else if ( diff[ i ] < -turn_max ) {
   1695 				diff[ i ] = -turn_max;
   1696 			}
   1697 		}
   1698 		angles += diff;
   1699 
   1700 		// make the visual model always points the dir we're traveling
   1701 		dir = angles.ToForward();
   1702 		velocity = dir * speed;
   1703 
   1704 		if ( burstMode && dist < burstDist ) {
   1705 			unGuided = true;
   1706 			velocity *= burstVelocity;
   1707 		}
   1708 
   1709 		physicsObj.SetLinearVelocity( velocity );
   1710 
   1711 		// align z-axis of model with the direction
   1712 		axis = dir.ToMat3();
   1713 		tmp = axis[2];
   1714 		axis[2] = axis[0];
   1715 		axis[0] = -tmp;
   1716 
   1717 		GetPhysics()->SetAxis( axis );
   1718 	}
   1719 
   1720 	idProjectile::Think();
   1721 }
   1722 
   1723 /*
   1724 =================
   1725 idGuidedProjectile::Launch
   1726 =================
   1727 */
   1728 void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
   1729 	idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
   1730 	if ( owner.GetEntity() ) {
   1731 		if ( owner.GetEntity()->IsType( idAI::Type ) ) {
   1732 			enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy();
   1733 		} else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) {
   1734 			trace_t tr;
   1735 			idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() );
   1736 			idVec3 start = player->GetEyePosition();
   1737 			idVec3 end = start + player->viewAxis[0] * 1000.0f;
   1738 			gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() );
   1739 			if ( tr.fraction < 1.0f ) {
   1740 				enemy = gameLocal.GetTraceEntity( tr );
   1741 			} 
   1742 			// ignore actors on the player's team
   1743 			if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) {
   1744 				enemy = player->EnemyWithMostHealth();
   1745 			}
   1746 		}
   1747 	}
   1748 	const idVec3 &vel = physicsObj.GetLinearVelocity();
   1749 	angles = vel.ToAngles();
   1750 	speed = vel.Length();
   1751 	rndScale = spawnArgs.GetAngles( "random", "15 15 0" );
   1752 	turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched;
   1753 	clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" );
   1754 	burstMode = spawnArgs.GetBool( "burstMode" );
   1755 	unGuided = false;
   1756 	burstDist = spawnArgs.GetFloat( "burstDist", "64" );
   1757 	burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" );
   1758 	UpdateVisuals();
   1759 }
   1760 
   1761 void idGuidedProjectile::SetEnemy( idEntity *ent ) {
   1762 	enemy = ent;
   1763 }
   1764 void idGuidedProjectile::Event_SetEnemy(idEntity *ent) {
   1765 	SetEnemy(ent);
   1766 }
   1767 
   1768 /*
   1769 ===============================================================================
   1770 
   1771 idSoulCubeMissile
   1772 
   1773 ===============================================================================
   1774 */
   1775 
   1776 CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile )
   1777 END_CLASS
   1778 
   1779 /*
   1780 ================
   1781 idSoulCubeMissile::Spawn()
   1782 ================
   1783 */
   1784 void idSoulCubeMissile::Spawn() {
   1785 	startingVelocity.Zero();
   1786 	endingVelocity.Zero();
   1787 	accelTime = 0.0f;
   1788 	launchTime = 0;
   1789 	killPhase = false;
   1790 	returnPhase = false;
   1791 	smokeKillTime = 0;
   1792 	smokeKill = NULL;
   1793 }
   1794 
   1795 /*
   1796 =================
   1797 idSoulCubeMissile::~idSoulCubeMissile
   1798 =================
   1799 */
   1800 idSoulCubeMissile::~idSoulCubeMissile() {
   1801 }
   1802 
   1803 /*
   1804 ================
   1805 idSoulCubeMissile::Save
   1806 ================
   1807 */
   1808 void idSoulCubeMissile::Save( idSaveGame *savefile ) const {
   1809 	savefile->WriteVec3( startingVelocity );
   1810 	savefile->WriteVec3( endingVelocity );
   1811 	savefile->WriteFloat( accelTime );
   1812 	savefile->WriteInt( launchTime );
   1813 	savefile->WriteBool( killPhase );
   1814 	savefile->WriteBool( returnPhase );
   1815 	savefile->WriteVec3( destOrg);
   1816 	savefile->WriteInt( orbitTime );
   1817 	savefile->WriteVec3( orbitOrg );
   1818 	savefile->WriteInt( smokeKillTime );
   1819 	savefile->WriteParticle( smokeKill );
   1820 }
   1821 
   1822 /*
   1823 ================
   1824 idSoulCubeMissile::Restore
   1825 ================
   1826 */
   1827 void idSoulCubeMissile::Restore( idRestoreGame *savefile ) {
   1828 	savefile->ReadVec3( startingVelocity );
   1829 	savefile->ReadVec3( endingVelocity );
   1830 	savefile->ReadFloat( accelTime );
   1831 	savefile->ReadInt( launchTime );
   1832 	savefile->ReadBool( killPhase );
   1833 	savefile->ReadBool( returnPhase );
   1834 	savefile->ReadVec3( destOrg);
   1835 	savefile->ReadInt( orbitTime );
   1836 	savefile->ReadVec3( orbitOrg );
   1837 	savefile->ReadInt( smokeKillTime );
   1838 	savefile->ReadParticle( smokeKill );
   1839 }
   1840 
   1841 /*
   1842 ================
   1843 idSoulCubeMissile::KillTarget
   1844 ================
   1845 */
   1846 void idSoulCubeMissile::KillTarget( const idVec3 &dir ) {
   1847 	idEntity	*ownerEnt;
   1848 	const char	*smokeName;
   1849 	idActor		*act;
   1850 
   1851 	ReturnToOwner();
   1852 	if ( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) {
   1853 		act = static_cast<idActor*>( enemy.GetEntity() );
   1854 		killPhase = true;
   1855 		orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter();
   1856 		orbitTime = gameLocal.time;
   1857 		smokeKillTime = 0;
   1858 		smokeName = spawnArgs.GetString( "smoke_kill" );
   1859 		if ( *smokeName != '\0' ) {
   1860 			smokeKill = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
   1861 			smokeKillTime = gameLocal.time;
   1862 		}
   1863 		ownerEnt = owner.GetEntity();
   1864 		if ( ( act->health > 0 ) && ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) {
   1865 			static_cast<idPlayer *>( ownerEnt )->GiveHealthPool( act->health );
   1866 		}
   1867 		act->Damage( this, owner.GetEntity(), dir,  spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT );
   1868 		act->GetAFPhysics()->SetTimeScale( 0.25 );
   1869 		StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
   1870 	}
   1871 }
   1872 
   1873 /*
   1874 ================
   1875 idSoulCubeMissile::Think
   1876 ================
   1877 */
   1878 void idSoulCubeMissile::Think() {
   1879 	float		pct;
   1880 	idVec3		seekPos;
   1881 	idEntity	*ownerEnt;
   1882 
   1883 	if ( state == LAUNCHED ) {
   1884 		if ( killPhase ) {
   1885 			// orbit the mob, cascading down
   1886 			if ( gameLocal.time < orbitTime + 1500 ) {
   1887 				if ( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity, timeGroup /*_D3XP*/ ) ) {
   1888 					smokeKillTime = gameLocal.time;
   1889 				}
   1890 			} 
   1891 		} else  {
   1892 			if ( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) {
   1893 				pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 );
   1894 				speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length();
   1895 			} 
   1896 		} 
   1897 		idGuidedProjectile::Think();
   1898 		GetSeekPos( seekPos );
   1899 		if ( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) {
   1900 			if ( returnPhase ) {
   1901 				StopSound( SND_CHANNEL_ANY, false );
   1902 				StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL );
   1903 				Hide();
   1904 				PostEventSec( &EV_Remove, 2.0f );
   1905 
   1906 				ownerEnt = owner.GetEntity();
   1907 				if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) {
   1908 					static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( NULL );
   1909 				}
   1910 
   1911 				state = FIZZLED;
   1912 			} else if ( !killPhase ){
   1913 				KillTarget( physicsObj.GetAxis()[0] );
   1914 			}
   1915 		}
   1916 	}
   1917 }
   1918 
   1919 /*
   1920 ================
   1921 idSoulCubeMissile::GetSeekPos
   1922 ================
   1923 */
   1924 void idSoulCubeMissile::GetSeekPos( idVec3 &out ) {
   1925 	if ( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) {
   1926 		idActor *act = static_cast<idActor*>( owner.GetEntity() );
   1927 		out = act->GetEyePosition();
   1928 		return;
   1929 	}
   1930 	if ( destOrg != vec3_zero ) {
   1931 		out = destOrg;
   1932 		return;
   1933 	} 
   1934 	idGuidedProjectile::GetSeekPos( out );
   1935 }
   1936 
   1937 
   1938 /*
   1939 ================
   1940 idSoulCubeMissile::Event_ReturnToOwner
   1941 ================
   1942 */
   1943 void idSoulCubeMissile::ReturnToOwner() {
   1944 	speed *= 0.65f;
   1945 	killPhase = false;
   1946 	returnPhase = true;
   1947 	smokeFlyTime = 0;
   1948 }
   1949 
   1950 
   1951 /*
   1952 =================
   1953 idSoulCubeMissile::Launch
   1954 =================
   1955 */
   1956 void idSoulCubeMissile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
   1957 	idVec3		newStart;
   1958 	idVec3		offs;
   1959 	idEntity	*ownerEnt;
   1960 
   1961 	// push it out a little
   1962 	newStart = start + dir * spawnArgs.GetFloat( "launchDist" );
   1963 	offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" );
   1964 	newStart += offs;
   1965 	idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
   1966 	if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) {
   1967 		destOrg = start + dir * 256.0f;
   1968 	} else {
   1969 		destOrg.Zero();
   1970 	}
   1971 	physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate
   1972 	startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" );
   1973 	endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" );
   1974 	accelTime = spawnArgs.GetFloat( "accelTime", "5" );
   1975 	physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] );
   1976 	launchTime = gameLocal.time;
   1977 	killPhase = false;
   1978 	UpdateVisuals();
   1979 	
   1980 	ownerEnt = owner.GetEntity();
   1981 	if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) {
   1982 		static_cast<idPlayer *>( ownerEnt )->SetSoulCubeProjectile( this );
   1983 	}
   1984 
   1985 }
   1986 
   1987 
   1988 /*
   1989 ===============================================================================
   1990 
   1991 idBFGProjectile
   1992 
   1993 ===============================================================================
   1994 */
   1995 const idEventDef EV_RemoveBeams( "<removeBeams>", NULL );
   1996 
   1997 CLASS_DECLARATION( idProjectile, idBFGProjectile )
   1998 	EVENT( EV_RemoveBeams,		idBFGProjectile::Event_RemoveBeams )
   1999 END_CLASS
   2000 
   2001 
   2002 /*
   2003 =================
   2004 idBFGProjectile::idBFGProjectile
   2005 =================
   2006 */
   2007 idBFGProjectile::idBFGProjectile() {
   2008 	memset( &secondModel, 0, sizeof( secondModel ) );
   2009 	secondModelDefHandle = -1;
   2010 	nextDamageTime = 0;
   2011 }
   2012 
   2013 /*
   2014 =================
   2015 idBFGProjectile::~idBFGProjectile
   2016 =================
   2017 */
   2018 idBFGProjectile::~idBFGProjectile() {
   2019 	FreeBeams();
   2020 
   2021 	if ( secondModelDefHandle >= 0 ) {
   2022 		gameRenderWorld->FreeEntityDef( secondModelDefHandle );
   2023 		secondModelDefHandle = -1;
   2024 	}
   2025 }
   2026 
   2027 /*
   2028 ================
   2029 idBFGProjectile::Spawn
   2030 ================
   2031 */
   2032 void idBFGProjectile::Spawn() {
   2033 	beamTargets.Clear();
   2034 	memset( &secondModel, 0, sizeof( secondModel ) );
   2035 	secondModelDefHandle = -1;
   2036 	const char *temp = spawnArgs.GetString( "model_two" );
   2037 	if ( temp != NULL && *temp != NULL ) {
   2038 		secondModel.hModel = renderModelManager->FindModel( temp );
   2039 		secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
   2040 		secondModel.shaderParms[ SHADERPARM_RED ] =
   2041 		secondModel.shaderParms[ SHADERPARM_GREEN ] =
   2042 		secondModel.shaderParms[ SHADERPARM_BLUE ] =
   2043 		secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
   2044 		secondModel.noSelfShadow = true;
   2045 		secondModel.noShadow = true;
   2046 	}
   2047 	nextDamageTime = 0;
   2048 	damageFreq = NULL;
   2049 }
   2050 
   2051 /*
   2052 ================
   2053 idBFGProjectile::Save
   2054 ================
   2055 */
   2056 void idBFGProjectile::Save( idSaveGame *savefile ) const {
   2057 	int i;
   2058 
   2059 	savefile->WriteInt( beamTargets.Num() );
   2060 	for ( i = 0; i < beamTargets.Num(); i++ ) {
   2061 		beamTargets[i].target.Save( savefile );
   2062 		savefile->WriteRenderEntity( beamTargets[i].renderEntity );
   2063 		savefile->WriteInt( beamTargets[i].modelDefHandle );
   2064 	}
   2065 
   2066 	savefile->WriteRenderEntity( secondModel );
   2067 	savefile->WriteInt( secondModelDefHandle );
   2068 	savefile->WriteInt( nextDamageTime );
   2069 	savefile->WriteString( damageFreq );
   2070 }
   2071 
   2072 /*
   2073 ================
   2074 idBFGProjectile::Restore
   2075 ================
   2076 */
   2077 void idBFGProjectile::Restore( idRestoreGame *savefile ) {
   2078 	int i, num;
   2079 
   2080 	savefile->ReadInt( num );
   2081 	beamTargets.SetNum( num );
   2082 	for ( i = 0; i < num; i++ ) {
   2083 		beamTargets[i].target.Restore( savefile );
   2084 		savefile->ReadRenderEntity( beamTargets[i].renderEntity );
   2085 		savefile->ReadInt( beamTargets[i].modelDefHandle );
   2086 
   2087 		if ( beamTargets[i].modelDefHandle >= 0 ) {
   2088 			beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity );
   2089 		}
   2090 	}
   2091 
   2092 	savefile->ReadRenderEntity( secondModel );
   2093 	savefile->ReadInt( secondModelDefHandle );
   2094 	savefile->ReadInt( nextDamageTime );
   2095 	savefile->ReadString( damageFreq );
   2096 
   2097 	if ( secondModelDefHandle >= 0 ) {
   2098 		secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
   2099 	}
   2100 }
   2101 
   2102 /*
   2103 =================
   2104 idBFGProjectile::FreeBeams
   2105 =================
   2106 */
   2107 void idBFGProjectile::FreeBeams() {
   2108 	for ( int i = 0; i < beamTargets.Num(); i++ ) {
   2109 		if ( beamTargets[i].modelDefHandle >= 0 ) {
   2110 			gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle );
   2111 			beamTargets[i].modelDefHandle = -1;
   2112 		}
   2113 	}
   2114 
   2115 	idPlayer *player = gameLocal.GetLocalPlayer();
   2116 	if ( player ) {
   2117 		player->playerView.EnableBFGVision( false );
   2118 	}
   2119 }
   2120 
   2121 /*
   2122 ================
   2123 idBFGProjectile::Think
   2124 ================
   2125 */
   2126 void idBFGProjectile::Think() {
   2127 	if ( state == LAUNCHED ) {
   2128 
   2129 		// update beam targets
   2130 		for ( int i = 0; i < beamTargets.Num(); i++ ) {
   2131 			if ( beamTargets[i].target.GetEntity() == NULL ) {
   2132 				continue;
   2133 			}
   2134 			idPlayer *player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast<idPlayer*>( beamTargets[i].target.GetEntity() ) : NULL;
   2135 			// Major hack for end boss.  :(
   2136 			idAnimatedEntity	*beamEnt;
   2137 			idVec3				org;
   2138 			bool				forceDamage = false;
   2139 
   2140 			beamEnt = static_cast<idAnimatedEntity*>(beamTargets[i].target.GetEntity());
   2141 			if ( !idStr::Cmp( beamEnt->GetEntityDefName(), "monster_boss_d3xp_maledict" ) ) {
   2142 				SetTimeState	ts( beamEnt->timeGroup );
   2143 				idMat3			temp;
   2144 				jointHandle_t	bodyJoint;
   2145 
   2146 				bodyJoint = beamEnt->GetAnimator()->GetJointHandle( "Chest1" );
   2147 				beamEnt->GetJointWorldTransform( bodyJoint, gameLocal.time, org, temp );
   2148 
   2149 				forceDamage = true;
   2150 			} else {
   2151 				org = beamEnt->GetPhysics()->GetAbsBounds().GetCenter();
   2152 			}
   2153 			beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin();
   2154 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x;
   2155 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y;
   2156 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z;
   2157 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = 
   2158 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = 
   2159 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = 
   2160 			beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
   2161 			if ( gameLocal.time > nextDamageTime ) {
   2162 				bool bfgVision = true;
   2163 				if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && ( forceDamage || beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) ) {
   2164 					org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
   2165 					org.Normalize();
   2166 					beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT );
   2167 				} else {
   2168 					beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = 
   2169 					beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = 
   2170 					beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = 
   2171 					beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f;
   2172 					bfgVision = false;
   2173 				}
   2174 				if ( player ) {
   2175 					player->playerView.EnableBFGVision( bfgVision );
   2176 				}
   2177 				nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
   2178 			}
   2179 			gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity );
   2180 		}
   2181 
   2182 		if ( secondModelDefHandle >= 0 ) {
   2183 			secondModel.origin = GetPhysics()->GetOrigin();
   2184 			gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel );
   2185 		}
   2186 
   2187 		idAngles ang;
   2188 
   2189 		ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f;
   2190 		ang.yaw = ang.pitch;
   2191 		ang.roll = 0.0f;
   2192 		SetAngles( ang );
   2193 
   2194 		ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f;
   2195 		ang.yaw = ang.pitch;
   2196 		ang.roll = 0.0f;
   2197 		secondModel.axis = ang.ToMat3();
   2198 
   2199 		UpdateVisuals();
   2200 	}
   2201 
   2202 	idProjectile::Think();
   2203 }
   2204 
   2205 /*
   2206 =================
   2207 idBFGProjectile::Launch
   2208 =================
   2209 */
   2210 void idBFGProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) {
   2211 	idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower );
   2212 
   2213 	// dmgPower * radius is the target acquisition area
   2214 	// acquisition should make sure that monsters are not dormant 
   2215 	// which will cut down on hitting monsters not actively fighting
   2216 	// but saves on the traces making sure they are visible
   2217 	// damage is not applied until the projectile explodes
   2218 
   2219 	idEntity *	ent;
   2220 	idEntity *	entityList[ MAX_GENTITIES ];
   2221 	int			numListedEntities;
   2222 	idBounds	bounds;
   2223 	idVec3		damagePoint;
   2224 
   2225 	float radius;
   2226 	spawnArgs.GetFloat( "damageRadius", "512", radius );
   2227 	bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius );
   2228 
   2229 	float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" );
   2230 	const char *skin = spawnArgs.GetString( "skin_beam" );
   2231 
   2232 	memset( &secondModel, 0, sizeof( secondModel ) );
   2233 	secondModelDefHandle = -1;
   2234 	const char *temp = spawnArgs.GetString( "model_two" );
   2235 	if ( temp != NULL && *temp != NULL ) {
   2236 		secondModel.hModel = renderModelManager->FindModel( temp );
   2237 		secondModel.bounds = secondModel.hModel->Bounds( &secondModel );
   2238 		secondModel.shaderParms[ SHADERPARM_RED ] =
   2239 		secondModel.shaderParms[ SHADERPARM_GREEN ] =
   2240 		secondModel.shaderParms[ SHADERPARM_BLUE ] =
   2241 		secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
   2242 		secondModel.noSelfShadow = true;
   2243 		secondModel.noShadow = true;
   2244 		secondModel.origin = GetPhysics()->GetOrigin();
   2245 		secondModel.axis = GetPhysics()->GetAxis();
   2246 		secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel );
   2247 	}
   2248 
   2249 	idVec3 delta( 15.0f, 15.0f, 15.0f );
   2250 	//physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero );
   2251 
   2252 	// get all entities touching the bounds
   2253 	numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES );
   2254 	for ( int e = 0; e < numListedEntities; e++ ) {
   2255 		ent = entityList[ e ];
   2256 		assert( ent );
   2257 
   2258 		if ( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) {
   2259 			continue;
   2260 		}
   2261 
   2262 		if ( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) {
   2263 			continue;
   2264 		}
   2265 
   2266 		if ( ent->IsType( idPlayer::Type ) ) {
   2267 			idPlayer *player = static_cast<idPlayer*>( ent );
   2268 			player->playerView.EnableBFGVision( true );
   2269 		}
   2270 
   2271 		beamTarget_t bt;
   2272 		memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) );
   2273 		bt.renderEntity.origin = GetPhysics()->GetOrigin();
   2274 		bt.renderEntity.axis = GetPhysics()->GetAxis();
   2275 		bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth;
   2276 		bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
   2277 		bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
   2278 		bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
   2279 		bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
   2280 		bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75;
   2281 		bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" );
   2282 		bt.renderEntity.callback = NULL;
   2283 		bt.renderEntity.numJoints = 0;
   2284 		bt.renderEntity.joints = NULL;
   2285 		bt.renderEntity.bounds.Clear();
   2286 		bt.renderEntity.customSkin = declManager->FindSkin( skin );
   2287 		bt.target = ent;
   2288 		bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity );
   2289 		beamTargets.Append( bt );
   2290 	}
   2291 
   2292 	// Major hack for end boss.  :(
   2293 	idAnimatedEntity *maledict = static_cast<idAnimatedEntity*>(gameLocal.FindEntity( "monster_boss_d3xp_maledict_1" ));
   2294 
   2295 	if ( maledict ) {
   2296 		SetTimeState	ts( maledict->timeGroup );
   2297 
   2298 		idVec3			realPoint;
   2299 		idMat3			temp;
   2300 		float			dist;
   2301 		jointHandle_t	bodyJoint;
   2302 
   2303 		bodyJoint = maledict->GetAnimator()->GetJointHandle( "Chest1" );
   2304 		maledict->GetJointWorldTransform( bodyJoint, gameLocal.time, realPoint, temp );
   2305 
   2306 		dist = idVec3( realPoint - GetPhysics()->GetOrigin() ).Length();
   2307 
   2308 		if ( dist < radius ) {
   2309 			beamTarget_t bt;
   2310 			memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) );
   2311 			bt.renderEntity.origin = GetPhysics()->GetOrigin();
   2312 			bt.renderEntity.axis = GetPhysics()->GetAxis();
   2313 			bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth;
   2314 			bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f;
   2315 			bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f;
   2316 			bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f;
   2317 			bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f;
   2318 			bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75;
   2319 			bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" );
   2320 			bt.renderEntity.callback = NULL;
   2321 			bt.renderEntity.numJoints = 0;
   2322 			bt.renderEntity.joints = NULL;
   2323 			bt.renderEntity.bounds.Clear();
   2324 			bt.renderEntity.customSkin = declManager->FindSkin( skin );
   2325 			bt.target = maledict;
   2326 			bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity );
   2327 			beamTargets.Append( bt );
   2328 
   2329 			numListedEntities++;
   2330 		}
   2331 	}
   2332 
   2333 	if ( numListedEntities ) {
   2334 		StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL );
   2335 	}
   2336 	damageFreq = spawnArgs.GetString( "def_damageFreq" );
   2337 	nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY;
   2338 	UpdateVisuals();
   2339 }
   2340 
   2341 /*
   2342 ================
   2343 idProjectile::Event_RemoveBeams
   2344 ================
   2345 */
   2346 void idBFGProjectile::Event_RemoveBeams() {
   2347 	FreeBeams();
   2348 	UpdateVisuals();
   2349 }
   2350 
   2351 /*
   2352 ================
   2353 idProjectile::Explode
   2354 ================
   2355 */
   2356 void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) {
   2357 	int			i;
   2358 	idVec3		dmgPoint;
   2359 	idVec3		dir;
   2360 	float		beamWidth;
   2361 	float		damageScale;
   2362 	const char *damage;
   2363 	idPlayer *	player;
   2364 	idEntity *	ownerEnt;
   2365 
   2366 	ownerEnt = owner.GetEntity();
   2367 	if ( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) {
   2368 		player = static_cast< idPlayer * >( ownerEnt );
   2369 	} else {
   2370 		player = NULL;
   2371 	}
   2372 
   2373 	beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" );
   2374 	damage = spawnArgs.GetString( "def_damage" );
   2375 
   2376 	for ( i = 0; i < beamTargets.Num(); i++ ) {
   2377 		if ( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) {
   2378 			continue;
   2379 		}
   2380 
   2381 		if ( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) {
   2382 			continue;
   2383 		}
   2384 
   2385 		beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth;
   2386 
   2387 		// if the hit entity takes damage
   2388 		if ( damagePower ) {
   2389 			damageScale = damagePower;
   2390 		} else {
   2391 			damageScale = 1.0f;
   2392 		}
   2393 
   2394 		// if the projectile owner is a player
   2395 		if ( player ) {
   2396 			// if the projectile hit an actor
   2397 			if ( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) {
   2398 				player->SetLastHitTime( gameLocal.time );
   2399 				player->AddProjectileHits( 1 );
   2400 				damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE );
   2401 			}
   2402 		}
   2403 
   2404 		if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) {
   2405 			dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
   2406 			dir.Normalize();
   2407 			beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT );
   2408 		}
   2409 	}
   2410 
   2411 	if ( secondModelDefHandle >= 0 ) {
   2412 		gameRenderWorld->FreeEntityDef( secondModelDefHandle );
   2413 		secondModelDefHandle = -1;
   2414 	}
   2415 
   2416 	if ( ignore == NULL ) {
   2417 		projectileFlags.noSplashDamage = true;
   2418 	}
   2419 
   2420 	if ( !common->IsClient() || fl.skipReplication ) {
   2421 		if ( ignore != NULL ) {
   2422 			PostEventMS( &EV_RemoveBeams, 750 );
   2423 		} else {
   2424 			PostEventMS( &EV_RemoveBeams, 0 );
   2425 		}
   2426 	}
   2427 
   2428 	return idProjectile::Explode( collision, ignore );
   2429 }
   2430 
   2431 
   2432 /*
   2433 ===============================================================================
   2434 
   2435 	idDebris
   2436 
   2437 ===============================================================================
   2438 */
   2439 
   2440 CLASS_DECLARATION( idEntity, idDebris )
   2441 EVENT( EV_Explode,			idDebris::Event_Explode )
   2442 EVENT( EV_Fizzle,			idDebris::Event_Fizzle )
   2443 END_CLASS
   2444 
   2445 /*
   2446 ================
   2447 idDebris::Spawn
   2448 ================
   2449 */
   2450 void idDebris::Spawn() {
   2451 	owner = NULL;
   2452 	smokeFly = NULL;
   2453 	smokeFlyTime = 0;
   2454 }
   2455 
   2456 /*
   2457 ================
   2458 idDebris::Create
   2459 ================
   2460 */
   2461 void idDebris::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) {
   2462 	Unbind();
   2463 	GetPhysics()->SetOrigin( start );
   2464 	GetPhysics()->SetAxis( axis );
   2465 	GetPhysics()->SetContents( 0 );
   2466 	this->owner = owner;
   2467 	smokeFly = NULL;
   2468 	smokeFlyTime = 0;
   2469 	sndBounce = NULL;
   2470 	noGrab = true;
   2471 	UpdateVisuals();
   2472 }
   2473 
   2474 /*
   2475 =================
   2476 idDebris::idDebris
   2477 =================
   2478 */
   2479 idDebris::idDebris() {
   2480 	owner = NULL;
   2481 	smokeFly = NULL;
   2482 	smokeFlyTime = 0;
   2483 	sndBounce = NULL;
   2484 }
   2485 
   2486 /*
   2487 =================
   2488 idDebris::~idDebris
   2489 =================
   2490 */
   2491 idDebris::~idDebris() {
   2492 }
   2493 
   2494 /*
   2495 =================
   2496 idDebris::Save
   2497 =================
   2498 */
   2499 void idDebris::Save( idSaveGame *savefile ) const {
   2500 	owner.Save( savefile );
   2501 
   2502 	savefile->WriteStaticObject( physicsObj );
   2503 
   2504 	savefile->WriteParticle( smokeFly );
   2505 	savefile->WriteInt( smokeFlyTime );
   2506 	savefile->WriteSoundShader( sndBounce );
   2507 }
   2508 
   2509 /*
   2510 =================
   2511 idDebris::Restore
   2512 =================
   2513 */
   2514 void idDebris::Restore( idRestoreGame *savefile ) {
   2515 	owner.Restore( savefile );
   2516 
   2517 	savefile->ReadStaticObject( physicsObj );
   2518 	RestorePhysics( &physicsObj );
   2519 
   2520 	savefile->ReadParticle( smokeFly );
   2521 	savefile->ReadInt( smokeFlyTime );
   2522 	savefile->ReadSoundShader( sndBounce );
   2523 }
   2524 
   2525 /*
   2526 =================
   2527 idDebris::Launch
   2528 =================
   2529 */
   2530 void idDebris::Launch() {
   2531 	float		fuse;
   2532 	idVec3		velocity;
   2533 	idAngles	angular_velocity;
   2534 	float		linear_friction;
   2535 	float		angular_friction;
   2536 	float		contact_friction;
   2537 	float		bounce;
   2538 	float		mass;
   2539 	float		gravity;
   2540 	idVec3		gravVec;
   2541 	bool		randomVelocity;
   2542 	idMat3		axis;
   2543 
   2544 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
   2545 
   2546 	spawnArgs.GetVector( "velocity", "0 0 0", velocity );
   2547 	spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity );
   2548 	
   2549 	linear_friction		= spawnArgs.GetFloat( "linear_friction" );
   2550 	angular_friction	= spawnArgs.GetFloat( "angular_friction" );
   2551 	contact_friction	= spawnArgs.GetFloat( "contact_friction" );
   2552 	bounce				= spawnArgs.GetFloat( "bounce" );
   2553 	mass				= spawnArgs.GetFloat( "mass" );
   2554 	gravity				= spawnArgs.GetFloat( "gravity" );
   2555 	fuse				= spawnArgs.GetFloat( "fuse" );
   2556 	randomVelocity		= spawnArgs.GetBool ( "random_velocity" );
   2557 
   2558 	if ( mass <= 0 ) {
   2559 		gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() );
   2560 	}
   2561 
   2562 	if ( randomVelocity ) {
   2563 		velocity.x *= gameLocal.random.RandomFloat() + 0.5f;
   2564 		velocity.y *= gameLocal.random.RandomFloat() + 0.5f;
   2565 		velocity.z *= gameLocal.random.RandomFloat() + 0.5f;
   2566 	}
   2567 
   2568 	if ( health ) {
   2569 		fl.takedamage = true;
   2570 	}
   2571 
   2572 	gravVec = gameLocal.GetGravity();
   2573 	gravVec.NormalizeFast();
   2574 	axis = GetPhysics()->GetAxis();
   2575 
   2576 	Unbind();
   2577 
   2578 	physicsObj.SetSelf( this );
   2579 
   2580 	// check if a clip model is set
   2581 	const char *clipModelName;
   2582 	idTraceModel trm;
   2583 	spawnArgs.GetString( "clipmodel", "", &clipModelName );
   2584 	if ( !clipModelName[0] ) {
   2585 		clipModelName = spawnArgs.GetString( "model" );		// use the visual model
   2586 	}
   2587 
   2588 	// load the trace model
   2589 	if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) {
   2590 		// default to a box
   2591 		physicsObj.SetClipBox( renderEntity.bounds, 1.0f );
   2592 	} else {
   2593 		physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( trm ), 1.0f );
   2594 	}
   2595 
   2596 	physicsObj.GetClipModel()->SetOwner( owner.GetEntity() );
   2597 	physicsObj.SetMass( mass );
   2598 	physicsObj.SetFriction( linear_friction, angular_friction, contact_friction );
   2599 	if ( contact_friction == 0.0f ) {
   2600 		physicsObj.NoContact();
   2601 	}
   2602 	physicsObj.SetBouncyness( bounce );
   2603 	physicsObj.SetGravity( gravVec * gravity );
   2604 	physicsObj.SetContents( 0 );
   2605 	physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP );
   2606 	physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] );
   2607 	physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis );
   2608 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   2609 	physicsObj.SetAxis( axis );
   2610 	SetPhysics( &physicsObj );
   2611 
   2612 	if ( !common->IsClient() ) {
   2613 		if ( fuse <= 0 ) {
   2614 			// run physics for 1 second
   2615 			RunPhysics();
   2616 			PostEventMS( &EV_Remove, 0 );
   2617 		} else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) {
   2618 			if ( fuse < 0.0f ) {
   2619 				fuse = 0.0f;
   2620 			}
   2621 			RunPhysics();
   2622 			PostEventSec( &EV_Explode, fuse );
   2623 		} else {
   2624 			if ( fuse < 0.0f ) {
   2625 				fuse = 0.0f;
   2626 			}
   2627 			PostEventSec( &EV_Fizzle, fuse );
   2628 		}
   2629 	}
   2630 
   2631 	StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL );
   2632 
   2633 	smokeFly = NULL;
   2634 	smokeFlyTime = 0;
   2635 	const char *smokeName = spawnArgs.GetString( "smoke_fly" );
   2636 	if ( *smokeName != '\0' ) {
   2637 		smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
   2638 		smokeFlyTime = gameLocal.time;
   2639 		gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
   2640 	}
   2641 
   2642 	const char *sndName = spawnArgs.GetString( "snd_bounce" );
   2643 	if ( *sndName != '\0' ) {
   2644 		sndBounce = declManager->FindSound( sndName );
   2645 	}
   2646 
   2647 	UpdateVisuals();
   2648 }
   2649 
   2650 /*
   2651 ================
   2652 idDebris::Think
   2653 ================
   2654 */
   2655 void idDebris::Think() {
   2656 
   2657 	// run physics
   2658 	RunPhysics();
   2659 	Present();
   2660 
   2661 	if ( smokeFly && smokeFlyTime ) {
   2662 		if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) {
   2663 			smokeFlyTime = 0;
   2664 		}
   2665 	}
   2666 }
   2667 
   2668 /*
   2669 ================
   2670 idDebris::Killed
   2671 ================
   2672 */
   2673 void idDebris::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
   2674 	if ( spawnArgs.GetBool( "detonate_on_death" ) ) {
   2675 		Explode();
   2676 	} else {
   2677 		Fizzle();
   2678 	}
   2679 }
   2680 
   2681 /*
   2682 =================
   2683 idDebris::Collide
   2684 =================
   2685 */
   2686 bool idDebris::Collide( const trace_t &collision, const idVec3 &velocity ) {
   2687 	if ( sndBounce != NULL ) {
   2688 		StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL );
   2689 	}
   2690 	sndBounce = NULL;
   2691 	return false;
   2692 }
   2693 
   2694 
   2695 /*
   2696 ================
   2697 idDebris::Fizzle
   2698 ================
   2699 */
   2700 void idDebris::Fizzle() {
   2701 	if ( IsHidden() ) {
   2702 		// already exploded
   2703 		return;
   2704 	}
   2705 
   2706 	StopSound( SND_CHANNEL_ANY, false );
   2707 	StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL );
   2708 
   2709 	// fizzle FX
   2710 	const char *smokeName = spawnArgs.GetString( "smoke_fuse" );
   2711 	if ( *smokeName != '\0' ) {
   2712 		smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
   2713 		smokeFlyTime = gameLocal.time;
   2714 		gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
   2715 	}
   2716 
   2717 	fl.takedamage = false;
   2718 	physicsObj.SetContents( 0 );
   2719 	physicsObj.PutToRest();
   2720 
   2721 	Hide();
   2722 
   2723 	if ( common->IsClient() && !fl.skipReplication ) {
   2724 		return;
   2725 	}
   2726 
   2727 	CancelEvents( &EV_Fizzle );
   2728 	PostEventMS( &EV_Remove, 0 );
   2729 }
   2730 
   2731 /*
   2732 ================
   2733 idDebris::Explode
   2734 ================
   2735 */
   2736 void idDebris::Explode() {
   2737 	if ( IsHidden() ) {
   2738 		// already exploded
   2739 		return;
   2740 	}
   2741 
   2742 	StopSound( SND_CHANNEL_ANY, false );
   2743 	StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL );
   2744 
   2745 	Hide();
   2746 
   2747 	// these must not be "live forever" particle systems
   2748 	smokeFly = NULL;
   2749 	smokeFlyTime = 0;
   2750 	const char *smokeName = spawnArgs.GetString( "smoke_detonate" );
   2751 	if ( *smokeName != '\0' ) {
   2752 		smokeFly = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
   2753 		smokeFlyTime = gameLocal.time;
   2754 		gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ );
   2755 	}
   2756 
   2757 	fl.takedamage = false;
   2758 	physicsObj.SetContents( 0 );
   2759 	physicsObj.PutToRest();
   2760 
   2761 	CancelEvents( &EV_Explode );
   2762 	PostEventMS( &EV_Remove, 0 );
   2763 }
   2764 
   2765 /*
   2766 ================
   2767 idDebris::Event_Explode
   2768 ================
   2769 */
   2770 void idDebris::Event_Explode() {
   2771 	Explode();
   2772 }
   2773 
   2774 /*
   2775 ================
   2776 idDebris::Event_Fizzle
   2777 ================
   2778 */
   2779 void idDebris::Event_Fizzle() {
   2780 	Fizzle();
   2781 }
   2782 
   2783 /*
   2784 ===============================================================================
   2785 
   2786 	idHomingProjectile
   2787 
   2788 ===============================================================================
   2789 */
   2790 
   2791 CLASS_DECLARATION( idProjectile, idHomingProjectile )
   2792 	EVENT( EV_SetEnemy,		idHomingProjectile::Event_SetEnemy )
   2793 END_CLASS
   2794 
   2795 /*
   2796 ================
   2797 idHomingProjectile::idHomingProjectile
   2798 ================
   2799 */
   2800 idHomingProjectile::idHomingProjectile() {
   2801 	enemy			= NULL;
   2802 	speed			= 0.0f;
   2803 	turn_max		= 0.0f;
   2804 	clamp_dist		= 0.0f;
   2805 	rndScale		= ang_zero;
   2806 	rndAng			= ang_zero;
   2807 	angles			= ang_zero;
   2808 	burstMode		= false;
   2809 	burstDist		= 0;
   2810 	burstVelocity	= 0.0f;
   2811 	unGuided		= false;
   2812 	seekPos			= vec3_origin;
   2813 }
   2814 
   2815 /*
   2816 =================
   2817 idHomingProjectile::~idHomingProjectile
   2818 =================
   2819 */
   2820 idHomingProjectile::~idHomingProjectile() {
   2821 }
   2822 
   2823 /*
   2824 ================
   2825 idHomingProjectile::Spawn
   2826 ================
   2827 */
   2828 void idHomingProjectile::Spawn() {
   2829 }
   2830 
   2831 /*
   2832 ================
   2833 idHomingProjectile::Save
   2834 ================
   2835 */
   2836 void idHomingProjectile::Save( idSaveGame *savefile ) const {
   2837 	enemy.Save( savefile );
   2838 	savefile->WriteFloat( speed );
   2839 	savefile->WriteAngles( rndScale );
   2840 	savefile->WriteAngles( rndAng );
   2841 	savefile->WriteFloat( turn_max );
   2842 	savefile->WriteFloat( clamp_dist );
   2843 	savefile->WriteAngles( angles );
   2844 	savefile->WriteBool( burstMode );
   2845 	savefile->WriteBool( unGuided );
   2846 	savefile->WriteFloat( burstDist );
   2847 	savefile->WriteFloat( burstVelocity );
   2848 	savefile->WriteVec3( seekPos );
   2849 }
   2850 
   2851 /*
   2852 ================
   2853 idHomingProjectile::Restore
   2854 ================
   2855 */
   2856 void idHomingProjectile::Restore( idRestoreGame *savefile ) {
   2857 	enemy.Restore( savefile );
   2858 	savefile->ReadFloat( speed );
   2859 	savefile->ReadAngles( rndScale );
   2860 	savefile->ReadAngles( rndAng );
   2861 	savefile->ReadFloat( turn_max );
   2862 	savefile->ReadFloat( clamp_dist );
   2863 	savefile->ReadAngles( angles );
   2864 	savefile->ReadBool( burstMode );
   2865 	savefile->ReadBool( unGuided );
   2866 	savefile->ReadFloat( burstDist );
   2867 	savefile->ReadFloat( burstVelocity );
   2868 	savefile->ReadVec3( seekPos );
   2869 }
   2870 
   2871 
   2872 /*
   2873 ================
   2874 idHomingProjectile::Think
   2875 ================
   2876 */
   2877 void idHomingProjectile::Think() {
   2878 	if ( seekPos == vec3_zero ) {
   2879 		// ai def uses a single def_projectile .. guardian has two projectile types so when seekPos is zero, just run regular projectile
   2880 		idProjectile::Think();
   2881 		return;
   2882 	}
   2883 
   2884 	idVec3		dir;
   2885 	idVec3		velocity;
   2886 	idVec3		nose;
   2887 	idVec3		tmp;
   2888 	idMat3		axis;
   2889 	idAngles	dirAng;
   2890 	idAngles	diff;
   2891 	float		dist;
   2892 	float		frac;
   2893 	int			i;
   2894 
   2895 		
   2896 	nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0];
   2897 
   2898 	dir = seekPos - nose;
   2899 	dist = dir.Normalize();
   2900 	dirAng = dir.ToAngles();
   2901 
   2902 	// make it more accurate as it gets closer
   2903 	frac = ( dist * 2.0f ) / clamp_dist;
   2904 	if ( frac > 1.0f ) {
   2905 		frac = 1.0f;
   2906 	}
   2907 
   2908 	diff = dirAng - angles * frac;
   2909 
   2910 	// clamp the to the max turn rate
   2911 	diff.Normalize180();
   2912 	for( i = 0; i < 3; i++ ) {
   2913 		if ( diff[ i ] > turn_max ) {
   2914 			diff[ i ] = turn_max;
   2915 		} else if ( diff[ i ] < -turn_max ) {
   2916 			diff[ i ] = -turn_max;
   2917 		}
   2918 	}
   2919 	angles += diff;
   2920 
   2921 	// make the visual model always points the dir we're traveling
   2922 	dir = angles.ToForward();
   2923 	velocity = dir * speed;
   2924 
   2925 	if ( burstMode && dist < burstDist ) {
   2926 		unGuided = true;
   2927 		velocity *= burstVelocity;
   2928 	}
   2929 
   2930 	physicsObj.SetLinearVelocity( velocity );
   2931 
   2932 	// align z-axis of model with the direction
   2933 	axis = dir.ToMat3();
   2934 	tmp = axis[2];
   2935 	axis[2] = axis[0];
   2936 	axis[0] = -tmp;
   2937 
   2938 	GetPhysics()->SetAxis( axis );
   2939 
   2940 	idProjectile::Think();
   2941 }
   2942 
   2943 /*
   2944 =================
   2945 idHomingProjectile::Launch
   2946 =================
   2947 */
   2948 void idHomingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) {
   2949 	idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower );
   2950 	if ( owner.GetEntity() ) {
   2951 		if ( owner.GetEntity()->IsType( idAI::Type ) ) {
   2952 			enemy = static_cast<idAI *>( owner.GetEntity() )->GetEnemy();
   2953 		} else if ( owner.GetEntity()->IsType( idPlayer::Type ) ) {
   2954 			trace_t tr;
   2955 			idPlayer *player = static_cast<idPlayer*>( owner.GetEntity() );
   2956 			idVec3 start = player->GetEyePosition();
   2957 			idVec3 end = start + player->viewAxis[0] * 1000.0f;
   2958 			gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() );
   2959 			if ( tr.fraction < 1.0f ) {
   2960 				enemy = gameLocal.GetTraceEntity( tr );
   2961 			} 
   2962 			// ignore actors on the player's team
   2963 			if ( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast<idActor *>( enemy.GetEntity() )->team == player->team ) ) {
   2964 				enemy = player->EnemyWithMostHealth();
   2965 			}
   2966 		}
   2967 	}
   2968 	const idVec3 &vel = physicsObj.GetLinearVelocity();
   2969 	angles = vel.ToAngles();
   2970 	speed = vel.Length();
   2971 	rndScale = spawnArgs.GetAngles( "random", "15 15 0" );
   2972 	turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched;
   2973 	clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" );
   2974 	burstMode = spawnArgs.GetBool( "burstMode" );
   2975 	unGuided = false;
   2976 	burstDist = spawnArgs.GetFloat( "burstDist", "64" );
   2977 	burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" );
   2978 	UpdateVisuals();
   2979 }
   2980 
   2981 void idHomingProjectile::SetEnemy( idEntity *ent ) {
   2982 	enemy = ent;
   2983 }
   2984 void idHomingProjectile::SetSeekPos( idVec3 pos ) {
   2985 	seekPos = pos;
   2986 }
   2987 void idHomingProjectile::Event_SetEnemy(idEntity *ent) {
   2988 	SetEnemy(ent);
   2989 }