DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Misc.cpp (92040B)


      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 
     30 Various utility objects and functions.
     31 
     32 */
     33 
     34 #include "../idlib/precompiled.h"
     35 #pragma hdrstop
     36 
     37 #include "Game_local.h"
     38 
     39 /*
     40 ===============================================================================
     41 
     42 idSpawnableEntity
     43 
     44 A simple, spawnable entity with a model and no functionable ability of it's own.
     45 For example, it can be used as a placeholder during development, for marking
     46 locations on maps for script, or for simple placed models without any behavior
     47 that can be bound to other entities.  Should not be subclassed.
     48 ===============================================================================
     49 */
     50 
     51 CLASS_DECLARATION( idEntity, idSpawnableEntity )
     52 END_CLASS
     53 
     54 /*
     55 ======================
     56 idSpawnableEntity::Spawn
     57 ======================
     58 */
     59 void idSpawnableEntity::Spawn() {
     60 	// this just holds dict information
     61 }
     62 
     63 /*
     64 ===============================================================================
     65 
     66 	idPlayerStart
     67 
     68 ===============================================================================
     69 */
     70 
     71 const idEventDef EV_TeleportStage( "<TeleportStage>", "e" );
     72 
     73 CLASS_DECLARATION( idEntity, idPlayerStart )
     74 	EVENT( EV_Activate,			idPlayerStart::Event_TeleportPlayer )
     75 	EVENT( EV_TeleportStage,	idPlayerStart::Event_TeleportStage )
     76 END_CLASS
     77 
     78 /*
     79 ===============
     80 idPlayerStart::idPlayerStart
     81 ================
     82 */
     83 idPlayerStart::idPlayerStart() {
     84 	teleportStage = 0;
     85 }
     86 
     87 /*
     88 ===============
     89 idPlayerStart::Spawn
     90 ================
     91 */
     92 void idPlayerStart::Spawn() {
     93 	teleportStage = 0;
     94 }
     95 
     96 /*
     97 ================
     98 idPlayerStart::Save
     99 ================
    100 */
    101 void idPlayerStart::Save( idSaveGame *savefile ) const {
    102 	savefile->WriteInt( teleportStage );
    103 }
    104 
    105 /*
    106 ================
    107 idPlayerStart::Restore
    108 ================
    109 */
    110 void idPlayerStart::Restore( idRestoreGame *savefile ) {
    111 	savefile->ReadInt( teleportStage );
    112 }
    113 
    114 /*
    115 ================
    116 idPlayerStart::ClientReceiveEvent
    117 ================
    118 */
    119 bool idPlayerStart::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
    120 	int entityNumber;
    121 
    122 	switch( event ) {
    123 		case EVENT_TELEPORTPLAYER: {
    124 			entityNumber = msg.ReadBits( GENTITYNUM_BITS );
    125 			idPlayer *player = static_cast<idPlayer *>( gameLocal.entities[entityNumber] );
    126 			if ( player != NULL && player->IsType( idPlayer::Type ) ) {
    127 				Event_TeleportPlayer( player );
    128 			}
    129 			return true;
    130 		}
    131 		default: {
    132 			return idEntity::ClientReceiveEvent( event, time, msg );
    133 		}
    134 	}
    135 }
    136 
    137 /*
    138 ===============
    139 idPlayerStart::Event_TeleportStage
    140 
    141 FIXME: add functionality to fx system ( could be done with player scripting too )
    142 ================
    143 */
    144 void idPlayerStart::Event_TeleportStage( idEntity *_player ) {
    145 	idPlayer *player;
    146 	if ( !_player->IsType( idPlayer::Type ) ) {
    147 		common->Warning( "idPlayerStart::Event_TeleportStage: entity is not an idPlayer\n" );
    148 		return;
    149 	}
    150 	player = static_cast<idPlayer*>(_player);
    151 	float teleportDelay = spawnArgs.GetFloat( "teleportDelay" );
    152 	switch ( teleportStage ) {
    153 		case 0:
    154 			player->playerView.Flash( colorWhite, 125 );
    155 			player->SetInfluenceLevel( INFLUENCE_LEVEL3 );
    156 			player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL );
    157 			gameSoundWorld->FadeSoundClasses( 0, -20.0f, teleportDelay );
    158 			player->StartSound( "snd_teleport_start", SND_CHANNEL_BODY2, 0, false, NULL );
    159 			teleportStage++;
    160 			PostEventSec( &EV_TeleportStage, teleportDelay, player );
    161 			break;
    162 		case 1:
    163 			gameSoundWorld->FadeSoundClasses( 0, 0.0f, 0.25f );
    164 			teleportStage++;
    165 			PostEventSec( &EV_TeleportStage, 0.25f, player );
    166 			break;
    167 		case 2:
    168 			player->SetInfluenceView( NULL, NULL, 0.0f, NULL );
    169 			TeleportPlayer( player );
    170 			player->StopSound( SND_CHANNEL_BODY2, false );
    171 			player->SetInfluenceLevel( INFLUENCE_NONE );
    172 			teleportStage = 0;
    173 			break;
    174 		default:
    175 			break;
    176 	}
    177 }
    178 
    179 /*
    180 ===============
    181 idPlayerStart::TeleportPlayer
    182 ================
    183 */
    184 void idPlayerStart::TeleportPlayer( idPlayer *player ) {
    185 	float pushVel = spawnArgs.GetFloat( "push", "300" );
    186 	float f = spawnArgs.GetFloat( "visualEffect", "0" );
    187 	const char *viewName = spawnArgs.GetString( "visualView", "" );
    188 	idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL;
    189 
    190 	SetTimeState ts( player->timeGroup );
    191 
    192 	if ( f && ent != NULL ) {
    193 		// place in private camera view for some time
    194 		// the entity needs to teleport to where the camera view is to have the PVS right
    195 		player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this );
    196 		player->StartSound( "snd_teleport_enter", SND_CHANNEL_ANY, 0, false, NULL );
    197 		player->SetPrivateCameraView( static_cast<idCamera*>(ent) );
    198 		// the player entity knows where to spawn from the previous Teleport call
    199 		if ( !common->IsClient() ) {
    200 			player->PostEventSec( &EV_Player_ExitTeleporter, f );
    201 		}
    202 	} else {
    203 		// direct to exit, Teleport will take care of the killbox
    204 		player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL );
    205 
    206 		// multiplayer hijacked this entity, so only push the player in multiplayer
    207 		if ( common->IsMultiplayer() ) {
    208 			player->GetPhysics()->SetLinearVelocity( GetPhysics()->GetAxis()[0] * pushVel );
    209 		}
    210 	}
    211 }
    212 
    213 /*
    214 ===============
    215 idPlayerStart::Event_TeleportPlayer
    216 ================
    217 */
    218 void idPlayerStart::Event_TeleportPlayer( idEntity *activator ) {
    219 	idPlayer *player;
    220 
    221 	if ( activator->IsType( idPlayer::Type ) ) {
    222 		player = static_cast<idPlayer*>( activator );
    223 	} else {
    224 		player = gameLocal.GetLocalPlayer();
    225 	}
    226 	if ( player ) {
    227 		if ( spawnArgs.GetBool( "visualFx" ) ) {
    228 
    229 			teleportStage = 0;
    230 			Event_TeleportStage( player );
    231 
    232 		} else {
    233 
    234 			if ( common->IsServer() ) {
    235 				idBitMsg	msg;
    236 				byte		msgBuf[MAX_EVENT_PARAM_SIZE];
    237 
    238 				msg.InitWrite( msgBuf, sizeof( msgBuf ) );
    239 				msg.BeginWriting();
    240 				msg.WriteBits( player->entityNumber, GENTITYNUM_BITS );
    241 				ServerSendEvent( EVENT_TELEPORTPLAYER, &msg, false );
    242 			}
    243 
    244 			TeleportPlayer( player );
    245 		}
    246 	}
    247 }
    248 
    249 /*
    250 ===============================================================================
    251 
    252 	idActivator
    253 
    254 ===============================================================================
    255 */
    256 
    257 CLASS_DECLARATION( idEntity, idActivator )
    258 	EVENT( EV_Activate,		idActivator::Event_Activate )
    259 END_CLASS
    260 
    261 /*
    262 ===============
    263 idActivator::Save
    264 ================
    265 */
    266 void idActivator::Save( idSaveGame *savefile ) const {
    267 	savefile->WriteBool( stay_on );
    268 }
    269 
    270 /*
    271 ===============
    272 idActivator::Restore
    273 ================
    274 */
    275 void idActivator::Restore( idRestoreGame *savefile ) {
    276 	savefile->ReadBool( stay_on );
    277 
    278 	if ( stay_on ) {
    279 		BecomeActive( TH_THINK );
    280 	}
    281 }
    282 
    283 /*
    284 ===============
    285 idActivator::Spawn
    286 ================
    287 */
    288 void idActivator::Spawn() {
    289 	bool start_off;
    290 
    291 	spawnArgs.GetBool( "stay_on", "0", stay_on );
    292 	spawnArgs.GetBool( "start_off", "0", start_off );
    293 
    294 	GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f );
    295 	GetPhysics()->SetContents( 0 );
    296 
    297 	if ( !start_off ) {
    298 		BecomeActive( TH_THINK );
    299 	}
    300 }
    301 
    302 /*
    303 ===============
    304 idActivator::Think
    305 ================
    306 */
    307 void idActivator::Think() {
    308 	RunPhysics();
    309 	if ( thinkFlags & TH_THINK ) {
    310 		if ( TouchTriggers() ) {
    311 			if ( !stay_on ) {
    312 				BecomeInactive( TH_THINK );
    313 			}
    314 		}
    315 	}
    316 	Present();
    317 }
    318 
    319 /*
    320 ===============
    321 idActivator::Activate
    322 ================
    323 */
    324 void idActivator::Event_Activate( idEntity *activator ) {
    325 	if ( thinkFlags & TH_THINK ) {
    326 		BecomeInactive( TH_THINK );
    327 	} else {
    328 		BecomeActive( TH_THINK );
    329 	}
    330 }
    331 
    332 
    333 /*
    334 ===============================================================================
    335 
    336 idPathCorner
    337 
    338 ===============================================================================
    339 */
    340 
    341 CLASS_DECLARATION( idEntity, idPathCorner )
    342 	EVENT( AI_RandomPath,		idPathCorner::Event_RandomPath )
    343 END_CLASS
    344 
    345 /*
    346 =====================
    347 idPathCorner::Spawn
    348 =====================
    349 */
    350 void idPathCorner::Spawn() {
    351 }
    352 
    353 /*
    354 =====================
    355 idPathCorner::DrawDebugInfo
    356 =====================
    357 */
    358 void idPathCorner::DrawDebugInfo() {
    359 	idEntity *ent;
    360 	idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) );
    361 
    362 	for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
    363 		if ( !ent->IsType( idPathCorner::Type ) ) {
    364 			continue;
    365 		}
    366 
    367 		idVec3 org = ent->GetPhysics()->GetOrigin();
    368 		gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 );
    369 	}
    370 }
    371 
    372 /*
    373 ============
    374 idPathCorner::RandomPath
    375 ============
    376 */
    377 idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) {
    378 	int	i;
    379 	int	num;
    380 	int which;
    381 	idEntity *ent;
    382 	idPathCorner *path[ MAX_GENTITIES ];
    383 
    384 	num = 0;
    385 	for( i = 0; i < source->targets.Num(); i++ ) {
    386 		ent = source->targets[ i ].GetEntity();
    387 		if ( ent != NULL && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) {
    388 			path[ num++ ] = static_cast<idPathCorner *>( ent );
    389 			if ( num >= MAX_GENTITIES ) {
    390 				break;
    391 			}
    392 		}
    393 	}
    394 
    395 	if ( !num ) {
    396 		return NULL;
    397 	}
    398 
    399 	which = gameLocal.random.RandomInt( num );
    400 	return path[ which ];
    401 }
    402 
    403 /*
    404 =====================
    405 idPathCorner::Event_RandomPath
    406 =====================
    407 */
    408 void idPathCorner::Event_RandomPath() {
    409 	idPathCorner *path;
    410 
    411 	path = RandomPath( this, NULL );
    412 	idThread::ReturnEntity( path );
    413 }
    414 
    415 /*
    416 ===============================================================================
    417 
    418   idDamagable
    419 	
    420 ===============================================================================
    421 */
    422 
    423 const idEventDef EV_RestoreDamagable( "<RestoreDamagable>" );
    424 
    425 CLASS_DECLARATION( idEntity, idDamagable )
    426 	EVENT( EV_Activate,			idDamagable::Event_BecomeBroken )
    427 	EVENT( EV_RestoreDamagable,	idDamagable::Event_RestoreDamagable )
    428 END_CLASS
    429 
    430 /*
    431 ================
    432 idDamagable::idDamagable
    433 ================
    434 */
    435 idDamagable::idDamagable() {
    436 	count = 0;
    437 	nextTriggerTime = 0;
    438 }
    439 
    440 /*
    441 ================
    442 idDamagable::Save
    443 ================
    444 */
    445 void idDamagable::Save( idSaveGame *savefile ) const {
    446 	savefile->WriteInt( count );
    447 	savefile->WriteInt( nextTriggerTime );
    448 }
    449 
    450 /*
    451 ================
    452 idDamagable::Restore
    453 ================
    454 */
    455 void idDamagable::Restore( idRestoreGame *savefile ) {
    456 	savefile->ReadInt( count );
    457 	savefile->ReadInt( nextTriggerTime );
    458 }
    459 
    460 /*
    461 ================
    462 idDamagable::Spawn
    463 ================
    464 */
    465 void idDamagable::Spawn() {
    466 	idStr broken;
    467 
    468 	health = spawnArgs.GetInt( "health", "5" );
    469 	spawnArgs.GetInt( "count", "1", count );	
    470 	nextTriggerTime = 0;
    471 	
    472 	// make sure the model gets cached
    473 	spawnArgs.GetString( "broken", "", broken );
    474 	if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) {
    475 		gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() );
    476 	}
    477 
    478 	fl.takedamage = true;
    479 	GetPhysics()->SetContents( CONTENTS_SOLID );
    480 }
    481 
    482 /*
    483 ================
    484 idDamagable::BecomeBroken
    485 ================
    486 */
    487 void idDamagable::BecomeBroken( idEntity *activator ) {
    488 	float	forceState;
    489 	int		numStates;
    490 	int		cycle;
    491 	float	wait;
    492 	
    493 	if ( gameLocal.time < nextTriggerTime ) {
    494 		return;
    495 	}
    496 
    497 	spawnArgs.GetFloat( "wait", "0.1", wait );
    498 	nextTriggerTime = gameLocal.time + SEC2MS( wait );
    499 	if ( count > 0 ) {
    500 		count--;
    501 		if ( !count ) {
    502 			fl.takedamage = false;
    503 		} else {
    504 			health = spawnArgs.GetInt( "health", "5" );
    505 		}
    506 	}
    507 
    508 	idStr	broken;
    509 
    510 	spawnArgs.GetString( "broken", "", broken );
    511 	if ( broken.Length() ) {
    512 		SetModel( broken );
    513 	}
    514 
    515 	// offset the start time of the shader to sync it to the gameLocal time
    516 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
    517 
    518 	spawnArgs.GetInt( "numstates", "1", numStates );
    519 	spawnArgs.GetInt( "cycle", "0", cycle );
    520 	spawnArgs.GetFloat( "forcestate", "0", forceState );
    521 
    522 	// set the state parm
    523 	if ( cycle ) {
    524 		renderEntity.shaderParms[ SHADERPARM_MODE ]++;
    525 		if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) {
    526 			renderEntity.shaderParms[ SHADERPARM_MODE ] = 0;
    527 		}
    528 	} else if ( forceState ) {
    529 		renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState;
    530 	} else {
    531 		renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1;
    532 	}
    533 
    534 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
    535 
    536 	ActivateTargets( activator );
    537 
    538 	if ( spawnArgs.GetBool( "hideWhenBroken" ) ) {
    539 		Hide();
    540 		PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time );
    541 		BecomeActive( TH_THINK );
    542 	}
    543 }
    544 
    545 /*
    546 ================
    547 idDamagable::Killed
    548 ================
    549 */
    550 void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
    551 	if ( gameLocal.time < nextTriggerTime ) {
    552 		health += damage;
    553 		return;
    554 	}
    555 
    556 	BecomeBroken( attacker );
    557 }
    558 
    559 /*
    560 ================
    561 idDamagable::Hide
    562 ================
    563 */
    564 void idDamagable::Hide() {
    565 	idEntity::Hide();
    566 	GetPhysics()->SetContents( 0 );
    567 }
    568 
    569 /*
    570 ================
    571 idDamagable::Show
    572 ================
    573 */
    574 void idDamagable::Show() {
    575 	idEntity::Show();
    576 	GetPhysics()->SetContents( CONTENTS_SOLID );
    577 }
    578 
    579 /*
    580 ================
    581 idDamagable::Event_BecomeBroken
    582 ================
    583 */
    584 void idDamagable::Event_BecomeBroken( idEntity *activator ) {
    585 	BecomeBroken( activator );
    586 }
    587 
    588 /*
    589 ================
    590 idDamagable::Event_RestoreDamagable
    591 ================
    592 */
    593 void idDamagable::Event_RestoreDamagable() {
    594 	health = spawnArgs.GetInt( "health", "5" );
    595 	Show();
    596 }
    597 
    598 
    599 /*
    600 ===============================================================================
    601 
    602   idExplodable
    603 	
    604 ===============================================================================
    605 */
    606 
    607 CLASS_DECLARATION( idEntity, idExplodable )
    608 	EVENT( EV_Activate,	idExplodable::Event_Explode )
    609 END_CLASS
    610 
    611 /*
    612 ================
    613 idExplodable::Spawn
    614 ================
    615 */
    616 void idExplodable::Spawn() {
    617 	Hide();
    618 }
    619 
    620 /*
    621 ================
    622 idExplodable::Event_Explode
    623 ================
    624 */
    625 void idExplodable::Event_Explode( idEntity *activator ) {
    626 	const char *temp;
    627 
    628 	if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) {
    629 		gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp );
    630 	}
    631 
    632 	StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL );
    633 
    634 	// Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms
    635 	renderEntity.shaderParms[SHADERPARM_RED]		= 1.0f;
    636 	renderEntity.shaderParms[SHADERPARM_GREEN]		= 1.0f;
    637 	renderEntity.shaderParms[SHADERPARM_BLUE]		= 1.0f;
    638 	renderEntity.shaderParms[SHADERPARM_ALPHA]		= 1.0f;
    639 	renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
    640 	renderEntity.shaderParms[SHADERPARM_DIVERSITY]	= 0.0f;
    641 	Show();
    642 
    643 	PostEventMS( &EV_Remove, 2000 );
    644 
    645 	ActivateTargets( activator );
    646 }
    647 
    648 
    649 /*
    650 ===============================================================================
    651 
    652   idSpring
    653 	
    654 ===============================================================================
    655 */
    656 
    657 CLASS_DECLARATION( idEntity, idSpring )
    658 	EVENT( EV_PostSpawn,	idSpring::Event_LinkSpring )
    659 END_CLASS
    660 
    661 /*
    662 ================
    663 idSpring::Think
    664 ================
    665 */
    666 void idSpring::Think() {
    667 	idVec3 start, end, origin;
    668 	idMat3 axis;
    669 
    670 	// run physics
    671 	RunPhysics();
    672 
    673 	if ( thinkFlags & TH_THINK ) {
    674 		// evaluate force
    675 		spring.Evaluate( gameLocal.time );
    676 
    677 		start = p1;
    678 		if ( ent1->GetPhysics() ) {
    679 			axis = ent1->GetPhysics()->GetAxis();
    680 			origin = ent1->GetPhysics()->GetOrigin();
    681 			start = origin + start * axis;
    682 		}
    683 
    684 		end = p2;
    685 		if ( ent2->GetPhysics() ) {
    686 			axis = ent2->GetPhysics()->GetAxis();
    687 			origin = ent2->GetPhysics()->GetOrigin();
    688 			end = origin + p2 * axis;
    689 		}
    690 		
    691 		gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true );
    692 	}
    693 
    694 	Present();
    695 }
    696 
    697 /*
    698 ================
    699 idSpring::Event_LinkSpring
    700 ================
    701 */
    702 void idSpring::Event_LinkSpring() {
    703 	idStr name1, name2;
    704 
    705 	spawnArgs.GetString( "ent1", "", name1 );
    706 	spawnArgs.GetString( "ent2", "", name2 );
    707 
    708 	if ( name1.Length() ) {
    709 		ent1 = gameLocal.FindEntity( name1 );
    710 		if ( ent1 == NULL ) {
    711 			gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() );
    712 			return;
    713 		}
    714 	}
    715 	else {
    716 		ent1 = gameLocal.entities[ENTITYNUM_WORLD];
    717 	}
    718 
    719 	if ( name2.Length() ) {
    720 		ent2 = gameLocal.FindEntity( name2 );
    721 		if ( ent2 == NULL ) {
    722 			gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() );
    723 			return;
    724 		}
    725 	}
    726 	else {
    727 		ent2 = gameLocal.entities[ENTITYNUM_WORLD];
    728 	}
    729 
    730 	spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 );
    731 	BecomeActive( TH_THINK );
    732 }
    733 
    734 /*
    735 ================
    736 idSpring::Spawn
    737 ================
    738 */
    739 void idSpring::Spawn() {
    740 	float Kstretch, damping, restLength;
    741 
    742 	spawnArgs.GetInt( "id1", "0", id1 );
    743 	spawnArgs.GetInt( "id2", "0", id2 );
    744 	spawnArgs.GetVector( "point1", "0 0 0", p1 );
    745 	spawnArgs.GetVector( "point2", "0 0 0", p2 );
    746 	spawnArgs.GetFloat( "constant", "100.0f", Kstretch );
    747 	spawnArgs.GetFloat( "damping", "10.0f", damping );
    748 	spawnArgs.GetFloat( "restlength", "0.0f", restLength );
    749 
    750 	spring.InitSpring( Kstretch, 0.0f, damping, restLength );
    751 
    752 	ent1 = ent2 = NULL;
    753 
    754 	PostEventMS( &EV_PostSpawn, 0 );
    755 }
    756 
    757 /*
    758 ===============================================================================
    759 
    760   idForceField
    761 	
    762 ===============================================================================
    763 */
    764 
    765 const idEventDef EV_Toggle( "Toggle", NULL );
    766 
    767 CLASS_DECLARATION( idEntity, idForceField )
    768 	EVENT( EV_Activate,		idForceField::Event_Activate )
    769 	EVENT( EV_Toggle,		idForceField::Event_Toggle )
    770 	EVENT( EV_FindTargets,	idForceField::Event_FindTargets )
    771 END_CLASS
    772 
    773 /*
    774 ===============
    775 idForceField::Toggle
    776 ================
    777 */
    778 void idForceField::Toggle() {
    779 	if ( thinkFlags & TH_THINK ) {
    780 		BecomeInactive( TH_THINK );
    781 	} else {
    782 		BecomeActive( TH_THINK );
    783 	}
    784 }
    785 
    786 
    787 
    788 /*
    789 ================
    790 idForceField::Think
    791 ================
    792 */
    793 void idForceField::ClientThink( const int curTime, const float fraction, const bool predict ) { 
    794 
    795 		// evaluate force
    796 		forceField.Evaluate( gameLocal.time );
    797 
    798 	Present();
    799 }
    800 
    801 /*
    802 ================
    803 idForceField::Think
    804 ================
    805 */
    806 void idForceField::Think() {
    807 	if ( thinkFlags & TH_THINK ) {
    808 		// evaluate force
    809 		forceField.Evaluate( gameLocal.time );
    810 	}
    811 	Present();
    812 }
    813 
    814 /*
    815 ================
    816 idForceField::Save
    817 ================
    818 */
    819 void idForceField::Save( idSaveGame *savefile ) const {
    820 	savefile->WriteStaticObject( forceField );
    821 }
    822 
    823 /*
    824 ================
    825 idForceField::Restore
    826 ================
    827 */
    828 void idForceField::Restore( idRestoreGame *savefile ) {
    829 	savefile->ReadStaticObject( forceField );
    830 }
    831 
    832 /*
    833 ================
    834 idForceField::Spawn
    835 ================
    836 */
    837 void idForceField::Spawn() {
    838 	idVec3 uniform;
    839 	float explosion, implosion, randomTorque;
    840 
    841 	if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) {
    842 		forceField.Uniform( uniform );
    843 	} else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) {
    844 		forceField.Explosion( explosion );
    845 	} else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) {
    846 		forceField.Implosion( implosion );
    847 	}
    848 
    849 	if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) {
    850 		forceField.RandomTorque( randomTorque );
    851 	}
    852 
    853 	if ( spawnArgs.GetBool( "applyForce", "0" ) ) {
    854 		forceField.SetApplyType( FORCEFIELD_APPLY_FORCE );
    855 	} else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) {
    856 		forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE );
    857 	} else {
    858 		forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY );
    859 	}
    860 
    861 	forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) );
    862 	forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) );
    863 
    864 	// set the collision model on the force field
    865 	forceField.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ) );
    866 
    867 	// remove the collision model from the physics object
    868 	GetPhysics()->SetClipModel( NULL, 1.0f );
    869 
    870 	if ( spawnArgs.GetBool( "start_on" ) ) {
    871 		BecomeActive( TH_THINK );
    872 	}
    873 }
    874 
    875 /*
    876 ===============
    877 idForceField::Event_Toggle
    878 ================
    879 */
    880 void idForceField::Event_Toggle() {
    881 	Toggle();
    882 }
    883 
    884 /*
    885 ================
    886 idForceField::Event_Activate
    887 ================
    888 */
    889 void idForceField::Event_Activate( idEntity *activator ) {
    890 	float wait;
    891 
    892 	Toggle();
    893 	if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) {
    894 		PostEventSec( &EV_Toggle, wait );
    895 	}
    896 }
    897 
    898 /*
    899 ================
    900 idForceField::Event_FindTargets
    901 ================
    902 */
    903 void idForceField::Event_FindTargets() {
    904 	FindTargets();
    905 	RemoveNullTargets();
    906 	if ( targets.Num() ) {
    907 		forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() );
    908 	}
    909 }
    910 
    911 
    912 /*
    913 ===============================================================================
    914 
    915 	idAnimated
    916 
    917 ===============================================================================
    918 */
    919 
    920 const idEventDef EV_Animated_Start( "<start>" );
    921 const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" );
    922 const idEventDef EV_LaunchMissilesUpdate( "<launchMissiles>", "dddd" );
    923 const idEventDef EV_AnimDone( "<AnimDone>", "d" );
    924 const idEventDef EV_StartRagdoll( "startRagdoll" );
    925 const idEventDef EV_SetAnimation( "setAnimation", "s" );
    926 const idEventDef EV_GetAnimationLength( "getAnimationLength", NULL, 'f' );
    927 
    928 CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated )
    929 	EVENT( EV_Activate,				idAnimated::Event_Activate )
    930 	EVENT( EV_Animated_Start,		idAnimated::Event_Start )
    931 	EVENT( EV_StartRagdoll,			idAnimated::Event_StartRagdoll )
    932 	EVENT( EV_AnimDone,				idAnimated::Event_AnimDone )
    933 	EVENT( EV_Footstep,				idAnimated::Event_Footstep )
    934 	EVENT( EV_FootstepLeft,			idAnimated::Event_Footstep )
    935 	EVENT( EV_FootstepRight,		idAnimated::Event_Footstep )
    936 	EVENT( EV_LaunchMissiles,		idAnimated::Event_LaunchMissiles )
    937 	EVENT( EV_LaunchMissilesUpdate,	idAnimated::Event_LaunchMissilesUpdate )
    938 	EVENT( EV_SetAnimation,			idAnimated::Event_SetAnimation )
    939 	EVENT( EV_GetAnimationLength,	idAnimated::Event_GetAnimationLength )
    940 END_CLASS
    941 
    942 /*
    943 ===============
    944 idAnimated::idAnimated
    945 ================
    946 */
    947 idAnimated::idAnimated() {
    948 	anim = 0;
    949 	blendFrames = 0;
    950 	soundJoint = INVALID_JOINT;
    951 	activated = false;
    952 	combatModel = NULL;
    953 	activator = NULL;
    954 	current_anim_index = 0;
    955 	num_anims = 0;
    956 	achievement = -1;
    957 
    958 }
    959 
    960 /*
    961 ===============
    962 idAnimated::idAnimated
    963 ================
    964 */
    965 idAnimated::~idAnimated() {
    966 	delete combatModel;
    967 	combatModel = NULL;
    968 }
    969 
    970 /*
    971 ===============
    972 idAnimated::Save
    973 ================
    974 */
    975 void idAnimated::Save( idSaveGame *savefile ) const {
    976 	savefile->WriteInt( current_anim_index );
    977 	savefile->WriteInt( num_anims );
    978 	savefile->WriteInt( anim );
    979 	savefile->WriteInt( blendFrames );
    980 	savefile->WriteJoint( soundJoint );
    981 	activator.Save( savefile );
    982 	savefile->WriteBool( activated );
    983 }
    984 
    985 /*
    986 ===============
    987 idAnimated::Restore
    988 ================
    989 */
    990 void idAnimated::Restore( idRestoreGame *savefile ) {
    991 	savefile->ReadInt( current_anim_index );
    992 	savefile->ReadInt( num_anims );
    993 	savefile->ReadInt( anim );
    994 	savefile->ReadInt( blendFrames );
    995 	savefile->ReadJoint( soundJoint );
    996 	activator.Restore( savefile );
    997 	savefile->ReadBool( activated );
    998 }
    999 
   1000 /*
   1001 ===============
   1002 idAnimated::Spawn
   1003 ================
   1004 */
   1005 void idAnimated::Spawn() {
   1006 	idStr		animname;
   1007 	int			anim2;
   1008 	float		wait;
   1009 	const char	*joint;
   1010 
   1011 	joint = spawnArgs.GetString( "sound_bone", "origin" ); 
   1012 	soundJoint = animator.GetJointHandle( joint );
   1013 	if ( soundJoint == INVALID_JOINT ) {
   1014 		gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint );
   1015 	}
   1016 
   1017 	LoadAF();
   1018 
   1019 	// allow bullets to collide with a combat model
   1020 	if ( spawnArgs.GetBool( "combatModel", "0" ) ) {
   1021 		combatModel = new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( modelDefHandle );
   1022 	}
   1023 
   1024 	// allow the entity to take damage
   1025 	if ( spawnArgs.GetBool( "takeDamage", "0" ) ) {
   1026 		fl.takedamage = true;
   1027 	}
   1028 
   1029 	current_anim_index = 0;
   1030 	spawnArgs.GetInt( "num_anims", "0", num_anims );
   1031 
   1032 	blendFrames = spawnArgs.GetInt( "blend_in" );
   1033 
   1034 	animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" );
   1035 	if ( !animname.Length() ) {
   1036 		anim = 0;
   1037 	} else {
   1038 		anim = animator.GetAnim( animname );
   1039 		if ( !anim ) {
   1040 			gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() );
   1041 		}
   1042 	}
   1043 
   1044 	if ( spawnArgs.GetBool( "hide" ) ) {
   1045 		Hide();
   1046 
   1047 		if ( !num_anims ) {
   1048 			blendFrames = 0;
   1049 		}
   1050 	} else if ( spawnArgs.GetString( "start_anim", "", animname ) ) {
   1051 		anim2 = animator.GetAnim( animname );
   1052 		if ( !anim2 ) {
   1053 			gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() );
   1054 		}
   1055 		animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 );
   1056 	} else if ( anim ) {
   1057 		// init joints to the first frame of the animation
   1058 		animator.SetFrame( ANIMCHANNEL_ALL, anim, 1, gameLocal.time, 0 );		
   1059 
   1060 		if ( !num_anims ) {
   1061 			blendFrames = 0;
   1062 		}
   1063 	}
   1064 
   1065 	spawnArgs.GetFloat( "wait", "-1", wait );
   1066 
   1067 	if ( wait >= 0 ) {
   1068 		PostEventSec( &EV_Activate, wait, this );
   1069 	}
   1070 }
   1071 
   1072 /*
   1073 ===============
   1074 idAnimated::LoadAF
   1075 ===============
   1076 */
   1077 bool idAnimated::LoadAF() {
   1078 	idStr fileName;
   1079 
   1080 	if ( !spawnArgs.GetString( "ragdoll", "*unknown*", fileName ) ) {
   1081 		return false;
   1082 	}
   1083 	af.SetAnimator( GetAnimator() );
   1084 	return af.Load( this, fileName );
   1085 }
   1086 
   1087 /*
   1088 ===============
   1089 idAnimated::GetPhysicsToSoundTransform
   1090 ===============
   1091 */
   1092 bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) {
   1093 	animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis );
   1094 	axis = renderEntity.axis;
   1095 	return true;
   1096 }
   1097 
   1098 /*
   1099 ================
   1100 idAnimated::StartRagdoll
   1101 ================
   1102 */
   1103 bool idAnimated::StartRagdoll() {
   1104 	// if no AF loaded
   1105 	if ( !af.IsLoaded() ) {
   1106 		return false;
   1107 	}
   1108 
   1109 	// if the AF is already active
   1110 	if ( af.IsActive() ) {
   1111 		return true;
   1112 	}
   1113 
   1114 	// disable any collision model used
   1115 	GetPhysics()->DisableClip();
   1116 
   1117 	// start using the AF
   1118 	af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) );
   1119 	
   1120 	return true;
   1121 }
   1122 
   1123 /*
   1124 =====================
   1125 idAnimated::PlayNextAnim
   1126 =====================
   1127 */
   1128 void idAnimated::PlayNextAnim() {
   1129 	const char *animname;
   1130 	int len;
   1131 	int cycle;
   1132 
   1133 	if ( current_anim_index >= num_anims ) {
   1134 		Hide();
   1135 		if ( spawnArgs.GetBool( "remove" ) ) {
   1136 			PostEventMS( &EV_Remove, 0 );
   1137 		} else {
   1138 			current_anim_index = 0;
   1139 		}
   1140 		return;
   1141 	}
   1142 
   1143 	Show();
   1144 	current_anim_index++;
   1145 
   1146 	spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname );
   1147 	if ( !animname ) {
   1148 		anim = 0;
   1149 		animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) );
   1150 		return;
   1151 	}
   1152 
   1153 	anim = animator.GetAnim( animname );
   1154 	if ( !anim ) {
   1155 		gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() );
   1156 		return;
   1157 	}
   1158 
   1159 	if ( g_debugCinematic.GetBool() ) {
   1160 		gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname );
   1161 	}
   1162 		
   1163 	spawnArgs.GetInt( "cycle", "1", cycle );
   1164 	if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) {
   1165 		cycle = -1;
   1166 	}
   1167 
   1168 	animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) );
   1169 	animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle );
   1170 
   1171 	len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength();
   1172 	if ( len >= 0 ) {
   1173 		PostEventMS( &EV_AnimDone, len, current_anim_index );
   1174 	}
   1175 
   1176 	// offset the start time of the shader to sync it to the game time
   1177 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
   1178 
   1179 	animator.ForceUpdate();
   1180 	UpdateAnimation();
   1181 	UpdateVisuals();
   1182 	Present();
   1183 }
   1184 
   1185 /*
   1186 ===============
   1187 idAnimated::Event_StartRagdoll
   1188 ================
   1189 */
   1190 void idAnimated::Event_StartRagdoll() {
   1191 	StartRagdoll();
   1192 }
   1193 
   1194 /*
   1195 ===============
   1196 idAnimated::Event_AnimDone
   1197 ================
   1198 */
   1199 void idAnimated::Event_AnimDone( int animindex ) {
   1200 	if ( g_debugCinematic.GetBool() ) {
   1201 		const idAnim *animPtr = animator.GetAnim( anim );
   1202 		gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" );
   1203 	}
   1204 
   1205 	if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) {
   1206 		Hide();
   1207 		PostEventMS( &EV_Remove, 0 );
   1208 	} else if ( spawnArgs.GetBool( "auto_advance" ) ) {
   1209 		PlayNextAnim();
   1210 	} else {
   1211 		activated = false;
   1212 	}
   1213 
   1214 	ActivateTargets( activator.GetEntity() );
   1215 }
   1216 
   1217 /*
   1218 ===============
   1219 idAnimated::Event_Activate
   1220 ================
   1221 */
   1222 void idAnimated::Event_Activate( idEntity *_activator ) {
   1223 	if ( num_anims ) {
   1224 		PlayNextAnim();
   1225 		activator = _activator;
   1226 		return;
   1227 	}
   1228 
   1229 	if ( activated ) {
   1230 		// already activated
   1231 		return;
   1232 	}
   1233 
   1234 	// achievement associated with this entity (given on activation)
   1235 	achievement = spawnArgs.GetInt( "achievement", "-1" );
   1236 	if ( achievement != -1 ) {
   1237 		idPlayer *player = gameLocal.GetLocalPlayer();
   1238 		if ( player != NULL ) {
   1239 			bool shouldCountAction = true;
   1240 			// only count unlocking lockers if we're in the base game
   1241 			if ( achievement == ACHIEVEMENT_OPEN_ALL_LOCKERS && player->GetExpansionType() != GAME_BASE ) {
   1242 				shouldCountAction = false;
   1243 			}
   1244 
   1245 			if ( shouldCountAction ) {
   1246 				player->GetAchievementManager().EventCompletesAchievement( (achievement_t)achievement );
   1247 			}
   1248 		}
   1249 	}
   1250 
   1251 	activated = true;
   1252 	activator = _activator;
   1253 	ProcessEvent( &EV_Animated_Start );
   1254 }
   1255 
   1256 /*
   1257 ===============
   1258 idAnimated::Event_Start
   1259 ================
   1260 */
   1261 void idAnimated::Event_Start() {
   1262 	int cycle;
   1263 	int len;
   1264 
   1265 	Show();
   1266 
   1267 	if ( num_anims ) {
   1268 		PlayNextAnim();
   1269 		return;
   1270 	}
   1271 
   1272 	if ( anim ) {
   1273 		if ( g_debugCinematic.GetBool() ) {
   1274 			const idAnim *animPtr = animator.GetAnim( anim );
   1275 			gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" );
   1276 		}
   1277 		spawnArgs.GetInt( "cycle", "1", cycle );
   1278 		animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) );
   1279 		animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle );
   1280 
   1281 		len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength();
   1282 		if ( len >= 0 ) {
   1283 			PostEventMS( &EV_AnimDone, len, 1 );
   1284 		}
   1285 	}
   1286 
   1287 	// offset the start time of the shader to sync it to the game time
   1288 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
   1289 
   1290 	animator.ForceUpdate();
   1291 	UpdateAnimation();
   1292 	UpdateVisuals();
   1293 	Present();
   1294 }
   1295 
   1296 /*
   1297 ===============
   1298 idAnimated::Event_Footstep
   1299 ===============
   1300 */
   1301 void idAnimated::Event_Footstep() {
   1302 	StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL );
   1303 }
   1304 
   1305 /*
   1306 =====================
   1307 idAnimated::Event_LaunchMissilesUpdate
   1308 =====================
   1309 */
   1310 void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) {
   1311 	idVec3			launchPos;
   1312 	idVec3			targetPos;
   1313 	idMat3			axis;
   1314 	idVec3			dir;
   1315 	idEntity *		ent;
   1316 	idProjectile *	projectile;
   1317 	const idDict *	projectileDef;
   1318 	const char *	projectilename;
   1319 
   1320 	projectilename = spawnArgs.GetString( "projectilename" );
   1321 	projectileDef = gameLocal.FindEntityDefDict( projectilename, false );
   1322 	if ( !projectileDef ) {
   1323 		gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename );
   1324 		return;
   1325 	}
   1326 
   1327 	StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL );
   1328 
   1329 	animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis );
   1330 	launchPos = renderEntity.origin + launchPos * renderEntity.axis;
   1331 	
   1332 	animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis );
   1333 	targetPos = renderEntity.origin + targetPos * renderEntity.axis;
   1334 
   1335 	dir = targetPos - launchPos;
   1336 	dir.Normalize();
   1337 
   1338 	gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
   1339 	if ( ent == NULL || !ent->IsType( idProjectile::Type ) ) {
   1340 		gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename );
   1341 		return;
   1342 	}
   1343 	projectile = ( idProjectile * )ent;
   1344 	projectile->Create( this, launchPos, dir );
   1345 	projectile->Launch( launchPos, dir, vec3_origin );
   1346 
   1347 	if ( numshots > 0 ) {
   1348 		PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay );
   1349 	}
   1350 }
   1351 
   1352 /*
   1353 =====================
   1354 idAnimated::Event_LaunchMissiles
   1355 =====================
   1356 */
   1357 void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) {
   1358 	const idDict *	projectileDef;
   1359 	jointHandle_t	launch;
   1360 	jointHandle_t	target;
   1361 
   1362 	projectileDef = gameLocal.FindEntityDefDict( projectilename, false );
   1363 	if ( !projectileDef ) {
   1364 		gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename );
   1365 		return;
   1366 	}
   1367 
   1368 	launch = animator.GetJointHandle( launchjoint );
   1369 	if ( launch == INVALID_JOINT ) {
   1370 		gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint );
   1371 		gameLocal.Error( "Unknown joint '%s'", launchjoint );
   1372 	}
   1373 
   1374 	target = animator.GetJointHandle( targetjoint );
   1375 	if ( target == INVALID_JOINT ) {
   1376 		gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint );
   1377 	}
   1378 
   1379 	spawnArgs.Set( "projectilename", projectilename );
   1380 	spawnArgs.Set( "missilesound", sound );
   1381 
   1382 	CancelEvents( &EV_LaunchMissilesUpdate );
   1383 	ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay );
   1384 }
   1385 
   1386 /*
   1387 =====================
   1388 idAnimated::Event_SetAnimation
   1389 =====================
   1390 */
   1391 void idAnimated::Event_SetAnimation( const char *animName ) {
   1392 
   1393 	//BSM Nerve: Need to add some error checking so we don't change the animation
   1394 	//in the middle of the existing animation
   1395 	anim = animator.GetAnim( animName );
   1396 	if ( !anim ) {
   1397 		gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animName );
   1398 	}
   1399 
   1400 }
   1401 
   1402 /*
   1403 =====================
   1404 idAnimated::Event_GetAnimationLength
   1405 =====================
   1406 */
   1407 void idAnimated::Event_GetAnimationLength() {
   1408 	float length = 0;
   1409 
   1410 	if(anim) {
   1411 		length = (float)(animator.AnimLength( anim )) / 1000.f;
   1412 	}
   1413 
   1414 	idThread::ReturnFloat(length);
   1415 }
   1416 
   1417 /*
   1418 ===============================================================================
   1419 
   1420 	idStaticEntity
   1421 
   1422 	Some static entities may be optimized into inline geometry by dmap
   1423 
   1424 ===============================================================================
   1425 */
   1426 
   1427 CLASS_DECLARATION( idEntity, idStaticEntity )
   1428 	EVENT( EV_Activate,				idStaticEntity::Event_Activate )
   1429 END_CLASS
   1430 
   1431 /*
   1432 ===============
   1433 idStaticEntity::idStaticEntity
   1434 ===============
   1435 */
   1436 idStaticEntity::idStaticEntity() {
   1437 	spawnTime = 0;
   1438 	active = false;
   1439 	fadeFrom.Set( 1, 1, 1, 1 );
   1440 	fadeTo.Set( 1, 1, 1, 1 );
   1441 	fadeStart = 0;
   1442 	fadeEnd	= 0;
   1443 	runGui = false;
   1444 }
   1445 
   1446 /*
   1447 ===============
   1448 idStaticEntity::Save
   1449 ===============
   1450 */
   1451 void idStaticEntity::Save( idSaveGame *savefile ) const {
   1452 	savefile->WriteInt( spawnTime );
   1453 	savefile->WriteBool( active );
   1454 	savefile->WriteVec4( fadeFrom );
   1455 	savefile->WriteVec4( fadeTo );
   1456 	savefile->WriteInt( fadeStart );
   1457 	savefile->WriteInt( fadeEnd );
   1458 	savefile->WriteBool( runGui );
   1459 }
   1460 
   1461 /*
   1462 ===============
   1463 idStaticEntity::Restore
   1464 ===============
   1465 */
   1466 void idStaticEntity::Restore( idRestoreGame *savefile ) {
   1467 	savefile->ReadInt( spawnTime );
   1468 	savefile->ReadBool( active );
   1469 	savefile->ReadVec4( fadeFrom );
   1470 	savefile->ReadVec4( fadeTo );
   1471 	savefile->ReadInt( fadeStart );
   1472 	savefile->ReadInt( fadeEnd );
   1473 	savefile->ReadBool( runGui );
   1474 }
   1475 
   1476 /*
   1477 ===============
   1478 idStaticEntity::Spawn
   1479 ===============
   1480 */
   1481 void idStaticEntity::Spawn() {
   1482 	bool solid;
   1483 	bool hidden;
   1484 
   1485 	// an inline static model will not do anything at all
   1486 	if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) {
   1487 		Hide();
   1488 		return;
   1489 	}
   1490 
   1491 	solid = spawnArgs.GetBool( "solid" );
   1492 	hidden = spawnArgs.GetBool( "hide" );
   1493 
   1494 	if ( solid && !hidden ) {
   1495 		GetPhysics()->SetContents( CONTENTS_SOLID );
   1496 	} else {
   1497 		GetPhysics()->SetContents( 0 );
   1498 	}
   1499 
   1500 	spawnTime = gameLocal.time;
   1501 	active = false;
   1502 
   1503 	idStr model = spawnArgs.GetString( "model" );
   1504 	if ( model.Find( ".prt" ) >= 0 ) {
   1505 		// we want the parametric particles out of sync with each other
   1506 		renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 );
   1507 	}
   1508 
   1509 	fadeFrom.Set( 1, 1, 1, 1 );
   1510 	fadeTo.Set( 1, 1, 1, 1 );
   1511 	fadeStart = 0;
   1512 	fadeEnd	= 0;
   1513 
   1514 	// NOTE: this should be used very rarely because it is expensive
   1515 	runGui = spawnArgs.GetBool( "runGui" );
   1516 	if ( runGui ) {
   1517 		BecomeActive( TH_THINK );
   1518 	}
   1519 }
   1520 
   1521 /*
   1522 ================
   1523 idStaticEntity::ShowEditingDialog
   1524 ================
   1525 */
   1526 void idStaticEntity::ShowEditingDialog() {
   1527 }
   1528 /*
   1529 ================
   1530 idStaticEntity::Think
   1531 ================
   1532 */
   1533 void idStaticEntity::Think() {
   1534 	idEntity::Think();
   1535 	if ( thinkFlags & TH_THINK ) {
   1536 		if ( runGui && renderEntity.gui[0] ) {
   1537 			idPlayer *player = gameLocal.GetLocalPlayer();
   1538 			if ( player ) {
   1539 				if ( !player->objectiveSystemOpen ) {
   1540 					renderEntity.gui[0]->StateChanged( gameLocal.time, true );
   1541 					if ( renderEntity.gui[1] ) {
   1542 						renderEntity.gui[1]->StateChanged( gameLocal.time, true );
   1543 					}
   1544 					if ( renderEntity.gui[2] ) {
   1545 						renderEntity.gui[2]->StateChanged( gameLocal.time, true );
   1546 					}
   1547 				}
   1548 			}
   1549 		}
   1550 		if ( fadeEnd > 0 ) {
   1551 			idVec4 color;
   1552 			if ( gameLocal.time < fadeEnd ) {
   1553 				color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) );
   1554 			} else {
   1555 				color = fadeTo;
   1556 				fadeEnd = 0;
   1557 				BecomeInactive( TH_THINK );
   1558 			}
   1559 			SetColor( color );
   1560 		}
   1561 	}
   1562 }
   1563 
   1564 /*
   1565 ================
   1566 idStaticEntity::Fade
   1567 ================
   1568 */
   1569 void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) {
   1570 	GetColor( fadeFrom );
   1571 	fadeTo = to;
   1572 	fadeStart = gameLocal.time;
   1573 	fadeEnd = gameLocal.time + SEC2MS( fadeTime );
   1574 	BecomeActive( TH_THINK );
   1575 }
   1576 
   1577 /*
   1578 ================
   1579 idStaticEntity::Hide
   1580 ================
   1581 */
   1582 void idStaticEntity::Hide() {
   1583 	idEntity::Hide();
   1584 	GetPhysics()->SetContents( 0 );
   1585 }
   1586 
   1587 /*
   1588 ================
   1589 idStaticEntity::Show
   1590 ================
   1591 */
   1592 void idStaticEntity::Show() {
   1593 	idEntity::Show();
   1594 	if ( spawnArgs.GetBool( "solid" ) ) {
   1595 		GetPhysics()->SetContents( CONTENTS_SOLID );
   1596 	}
   1597 }
   1598 
   1599 /*
   1600 ================
   1601 idStaticEntity::Event_Activate
   1602 ================
   1603 */
   1604 void idStaticEntity::Event_Activate( idEntity *activator ) {
   1605 	idStr activateGui;
   1606 
   1607 	spawnTime = gameLocal.time;
   1608 	active = !active;
   1609 
   1610 	const idKeyValue *kv = spawnArgs.FindKey( "hide" );
   1611 	if ( kv ) {
   1612 		if ( IsHidden() ) {
   1613 			Show();
   1614 		} else {
   1615 			Hide();
   1616 		}
   1617 	}
   1618 
   1619 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime );
   1620 	renderEntity.shaderParms[5] = active;
   1621 	// this change should be a good thing, it will automatically turn on 
   1622 	// lights etc.. when triggered so that does not have to be specifically done
   1623 	// with trigger parms.. it MIGHT break things so need to keep an eye on it
   1624 	renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ?  0.0f : 1.0f;
   1625 	BecomeActive( TH_UPDATEVISUALS );
   1626 }
   1627 
   1628 /*
   1629 ================
   1630 idStaticEntity::WriteToSnapshot
   1631 ================
   1632 */
   1633 void idStaticEntity::WriteToSnapshot( idBitMsg &msg ) const {
   1634 	GetPhysics()->WriteToSnapshot( msg );
   1635 	WriteBindToSnapshot( msg );
   1636 	WriteColorToSnapshot( msg );
   1637 	WriteGUIToSnapshot( msg );
   1638 	msg.WriteBits( IsHidden()?1:0, 1 );
   1639 }
   1640 
   1641 /*
   1642 ================
   1643 idStaticEntity::ReadFromSnapshot
   1644 ================
   1645 */
   1646 void idStaticEntity::ReadFromSnapshot( const idBitMsg &msg ) {
   1647 	bool hidden;
   1648 
   1649 	GetPhysics()->ReadFromSnapshot( msg );
   1650 	ReadBindFromSnapshot( msg );
   1651 	ReadColorFromSnapshot( msg );
   1652 	ReadGUIFromSnapshot( msg );
   1653 	hidden = msg.ReadBits( 1 ) == 1;
   1654 	if ( hidden != IsHidden() ) {
   1655 		if ( hidden ) {
   1656 			Hide();
   1657 		} else {
   1658 			Show();
   1659 		}
   1660 	}
   1661 	if ( msg.HasChanged() ) {
   1662 		UpdateVisuals();
   1663 	}
   1664 }
   1665 
   1666 
   1667 /*
   1668 ===============================================================================
   1669 
   1670 idFuncEmitter
   1671 
   1672 ===============================================================================
   1673 */
   1674 
   1675 
   1676 CLASS_DECLARATION( idStaticEntity, idFuncEmitter )
   1677 EVENT( EV_Activate,				idFuncEmitter::Event_Activate )
   1678 END_CLASS
   1679 
   1680 /*
   1681 ===============
   1682 idFuncEmitter::idFuncEmitter
   1683 ===============
   1684 */
   1685 idFuncEmitter::idFuncEmitter() {
   1686 	hidden = false;
   1687 }
   1688 
   1689 /*
   1690 ===============
   1691 idFuncEmitter::Spawn
   1692 ===============
   1693 */
   1694 void idFuncEmitter::Spawn() {
   1695 	if ( spawnArgs.GetBool( "start_off" ) ) {
   1696 		hidden = true;
   1697 		renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 );
   1698 		UpdateVisuals();
   1699 	} else {
   1700 		hidden = false;
   1701 	}
   1702 }
   1703 
   1704 /*
   1705 ===============
   1706 idFuncEmitter::Save
   1707 ===============
   1708 */
   1709 void idFuncEmitter::Save( idSaveGame *savefile ) const {
   1710 	savefile->WriteBool( hidden );
   1711 }
   1712 
   1713 /*
   1714 ===============
   1715 idFuncEmitter::Restore
   1716 ===============
   1717 */
   1718 void idFuncEmitter::Restore( idRestoreGame *savefile ) {
   1719 	savefile->ReadBool( hidden );
   1720 }
   1721 
   1722 /*
   1723 ================
   1724 idFuncEmitter::Event_Activate
   1725 ================
   1726 */
   1727 void idFuncEmitter::Event_Activate( idEntity *activator ) {
   1728 	if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) {
   1729 		renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0;
   1730 		renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
   1731 		hidden = false;
   1732 	} else {
   1733 		renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time );
   1734 		hidden = true;
   1735 	}
   1736 	UpdateVisuals();
   1737 }
   1738 
   1739 /*
   1740 ================
   1741 idFuncEmitter::WriteToSnapshot
   1742 ================
   1743 */
   1744 void idFuncEmitter::WriteToSnapshot( idBitMsg &msg ) const {
   1745 	msg.WriteBits( hidden ? 1 : 0, 1 );
   1746 	msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] );
   1747 	msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] );
   1748 }
   1749 
   1750 /*
   1751 ================
   1752 idFuncEmitter::ReadFromSnapshot
   1753 ================
   1754 */
   1755 void idFuncEmitter::ReadFromSnapshot( const idBitMsg &msg ) {
   1756 	hidden = msg.ReadBits( 1 ) != 0;
   1757 	renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat();
   1758 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat();
   1759 	if ( msg.HasChanged() ) {
   1760 		UpdateVisuals();
   1761 	}
   1762 }
   1763 
   1764 /*
   1765 ===============================================================================
   1766 
   1767 idFuncShootProjectile
   1768 
   1769 ===============================================================================
   1770 */
   1771 
   1772 
   1773 CLASS_DECLARATION( idStaticEntity, idFuncShootProjectile )
   1774 	EVENT( EV_Activate,				idFuncShootProjectile::Event_Activate )
   1775 	END_CLASS
   1776 
   1777 	/*
   1778 	===============
   1779 	idFuncShootProjectile::idFuncShootProjectile
   1780 	===============
   1781 	*/
   1782 	idFuncShootProjectile::idFuncShootProjectile() {
   1783 		mRespawnDelay = 1000;
   1784 		mRespawnTime = 0;
   1785 		mShootSpeed = 1000;
   1786 		mShootDir = idVec3( 0.0f, 0.0f, 1.0f );
   1787 }
   1788 
   1789 /*
   1790 ===============
   1791 idFuncShootProjectile::Spawn
   1792 ===============
   1793 */
   1794 void idFuncShootProjectile::Spawn() {
   1795 }
   1796 
   1797 /*
   1798 ===============
   1799 idFuncShootProjectile::Think
   1800 ===============
   1801 */
   1802 void idFuncShootProjectile::Think() {
   1803 	if ( thinkFlags & TH_THINK ) {
   1804 		// time to spawn a new projectile?
   1805 		if ( mRespawnTime > 0 && mRespawnTime <= gameLocal.GetTime() ) {
   1806 			const idDict *dict = gameLocal.FindEntityDefDict( mEntityDefName );
   1807 			idEntity *ent = NULL;
   1808 			gameLocal.SpawnEntityDef( *dict, &ent );
   1809 			if ( ent != NULL ) {
   1810 				idProjectile *proj = static_cast<idProjectile *>(ent);
   1811 
   1812 				idVec3 pushVel = mShootDir * mShootSpeed;
   1813 				proj->Create( this, GetPhysics()->GetOrigin(), mShootDir );
   1814 				proj->Launch( GetPhysics()->GetOrigin(), mShootDir, pushVel );
   1815 				if ( mShootSpeed == 0.0f ) {
   1816 					proj->GetPhysics()->SetLinearVelocity( vec3_zero );
   1817 				} else {
   1818 					proj->GetPhysics()->SetLinearVelocity( pushVel );
   1819 				}
   1820 
   1821 				mLastProjectile = proj;
   1822 			}
   1823 			if ( mShootSpeed == 0.0f ) {
   1824 				mRespawnTime = 0;	// stationary, respawn when triggered
   1825 			} else {
   1826 				mRespawnTime = gameLocal.GetTime() + mRespawnDelay;		// moving, respawn after delay
   1827 			}
   1828 		}
   1829 	}
   1830 }
   1831 
   1832 /*
   1833 ===============
   1834 idFuncShootProjectile::Save
   1835 ===============
   1836 */
   1837 void idFuncShootProjectile::Save( idSaveGame *savefile ) const {
   1838 	savefile->WriteInt( mRespawnDelay );
   1839 	savefile->WriteInt( mRespawnTime );
   1840 	savefile->WriteFloat( mShootSpeed );
   1841 	savefile->WriteVec3( mShootDir );
   1842 	savefile->WriteString( mEntityDefName );
   1843 }
   1844 
   1845 /*
   1846 ===============
   1847 idFuncShootProjectile::Restore
   1848 ===============
   1849 */
   1850 void idFuncShootProjectile::Restore( idRestoreGame *savefile ) {
   1851 	savefile->ReadInt( mRespawnDelay );
   1852 	savefile->ReadInt( mRespawnTime );
   1853 	savefile->ReadFloat( mShootSpeed );
   1854 	savefile->ReadVec3( mShootDir );
   1855 	savefile->ReadString( mEntityDefName );
   1856 }
   1857 
   1858 /*
   1859 ================
   1860 idFuncShootProjectile::Event_Activate
   1861 ================
   1862 */
   1863 void idFuncShootProjectile::Event_Activate( idEntity *activator ) {
   1864 	if ( ( thinkFlags & TH_THINK ) != 0 ) {
   1865 		if ( mShootSpeed == 0.0f && mRespawnTime == 0 ) {
   1866 			mRespawnTime = gameLocal.GetTime();
   1867 			return;
   1868 		}
   1869 	}
   1870 
   1871 	mRespawnDelay = spawnArgs.GetInt( "spawn_delay_ms" );
   1872 	mShootSpeed = spawnArgs.GetFloat( "speed" );
   1873 	mEntityDefName = spawnArgs.GetString( "def_projectile" );
   1874 	if ( targets.Num() > 0 && targets[0].IsValid() ) {
   1875 		mShootDir = targets[0]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
   1876 		mShootDir.Normalize();
   1877 	} else {
   1878 		// stationary projectile, doesn't move and only respawns when triggered
   1879 		mShootSpeed = 0.0f;
   1880 		mRespawnTime = 0;
   1881 	}
   1882 
   1883 	if ( ( thinkFlags & TH_THINK ) != 0 ) {
   1884 		// currently active, deactivate
   1885 		BecomeInactive( TH_THINK );
   1886 	} else {
   1887 		// currently inactive, activate
   1888 		BecomeActive( TH_THINK );
   1889 		mRespawnTime = gameLocal.GetTime();
   1890 	}
   1891 }
   1892 
   1893 /*
   1894 ================
   1895 idFuncShootProjectile::WriteToSnapshot
   1896 ================
   1897 */
   1898 void idFuncShootProjectile::WriteToSnapshot( idBitMsg &msg ) const {
   1899 	// 	msg.WriteBits( hidden ? 1 : 0, 1 );
   1900 	// 	msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] );
   1901 	// 	msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] );
   1902 }
   1903 
   1904 /*
   1905 ================
   1906 idFuncShootProjectile::ReadFromSnapshot
   1907 ================
   1908 */
   1909 void idFuncShootProjectile::ReadFromSnapshot( const idBitMsg &msg ) {
   1910 	// 	hidden = msg.ReadBits( 1 ) != 0;
   1911 	// 	renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat();
   1912 	// 	renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat();
   1913 	// 	if ( msg.HasChanged() ) {
   1914 	// 		UpdateVisuals();
   1915 	// 	}
   1916 }
   1917 
   1918 
   1919 /*
   1920 ===============================================================================
   1921 
   1922 idFuncSplat
   1923 
   1924 ===============================================================================
   1925 */
   1926 
   1927 
   1928 const idEventDef EV_Splat( "<Splat>" );
   1929 CLASS_DECLARATION( idFuncEmitter, idFuncSplat )
   1930 EVENT( EV_Activate,		idFuncSplat::Event_Activate )
   1931 EVENT( EV_Splat,		idFuncSplat::Event_Splat )
   1932 END_CLASS
   1933 
   1934 /*
   1935 ===============
   1936 idFuncSplat::idFuncSplat
   1937 ===============
   1938 */
   1939 idFuncSplat::idFuncSplat() {
   1940 }
   1941 
   1942 /*
   1943 ===============
   1944 idFuncSplat::Spawn
   1945 ===============
   1946 */
   1947 void idFuncSplat::Spawn() {
   1948 }
   1949 
   1950 /*
   1951 ================
   1952 idFuncSplat::Event_Splat
   1953 ================
   1954 */
   1955 void idFuncSplat::Event_Splat() {
   1956 	const char *splat = NULL;
   1957 	int count = spawnArgs.GetInt( "splatCount", "1" );
   1958 	for ( int i = 0; i < count; i++ ) {
   1959 		splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random );
   1960 		if ( splat != NULL && *splat != NULL ) {
   1961 			float size = spawnArgs.GetFloat( "splatSize", "128" );
   1962 			float dist = spawnArgs.GetFloat( "splatDistance", "128" );
   1963 			float angle = spawnArgs.GetFloat( "splatAngle", "0" );
   1964 			gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle );
   1965 		}
   1966 	}
   1967 	StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL );
   1968 }
   1969 
   1970 /*
   1971 ================
   1972 idFuncSplat::Event_Activate
   1973 ================
   1974 */
   1975 void idFuncSplat::Event_Activate( idEntity *activator ) {
   1976 	idFuncEmitter::Event_Activate( activator );
   1977 	PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) );
   1978 	StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL );
   1979 }
   1980 
   1981 /*
   1982 ===============================================================================
   1983 
   1984 idFuncSmoke
   1985 
   1986 ===============================================================================
   1987 */
   1988 
   1989 CLASS_DECLARATION( idEntity, idFuncSmoke )
   1990 EVENT( EV_Activate,				idFuncSmoke::Event_Activate )
   1991 END_CLASS
   1992 
   1993 /*
   1994 ===============
   1995 idFuncSmoke::idFuncSmoke
   1996 ===============
   1997 */
   1998 idFuncSmoke::idFuncSmoke() {
   1999 	smokeTime = 0;
   2000 	smoke = NULL;
   2001 	restart = false;
   2002 }
   2003 
   2004 /*
   2005 ===============
   2006 idFuncSmoke::Save
   2007 ===============
   2008 */
   2009 void idFuncSmoke::Save(	idSaveGame *savefile ) const {
   2010 	savefile->WriteInt( smokeTime );
   2011 	savefile->WriteParticle( smoke );
   2012 	savefile->WriteBool( restart );
   2013 }
   2014 
   2015 /*
   2016 ===============
   2017 idFuncSmoke::Restore
   2018 ===============
   2019 */
   2020 void idFuncSmoke::Restore( idRestoreGame *savefile ) {
   2021 	savefile->ReadInt( smokeTime );
   2022 	savefile->ReadParticle( smoke );
   2023 	savefile->ReadBool( restart );
   2024 }
   2025 
   2026 /*
   2027 ===============
   2028 idFuncSmoke::Spawn
   2029 ===============
   2030 */
   2031 void idFuncSmoke::Spawn() {
   2032 	const char *smokeName = spawnArgs.GetString( "smoke" );
   2033 	if ( *smokeName != '\0' ) {
   2034 		smoke = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
   2035 	} else {
   2036 		smoke = NULL;
   2037 	}
   2038 	if ( spawnArgs.GetBool( "start_off" ) ) {
   2039 		smokeTime = 0;
   2040 		restart = false;
   2041 	} else if ( smoke ) {
   2042 		smokeTime = gameLocal.time;
   2043 		BecomeActive( TH_UPDATEPARTICLES );
   2044 		restart = true;
   2045 	}
   2046 	GetPhysics()->SetContents( 0 );
   2047 }
   2048 
   2049 /*
   2050 ================
   2051 idFuncSmoke::Event_Activate
   2052 ================
   2053 */
   2054 void idFuncSmoke::Event_Activate( idEntity *activator ) {
   2055 	if ( thinkFlags & TH_UPDATEPARTICLES ) {
   2056 		restart = false;
   2057 		return;
   2058 	} else {
   2059 		BecomeActive( TH_UPDATEPARTICLES );
   2060 		restart = true;
   2061 		smokeTime = gameLocal.time;
   2062 	}
   2063 }
   2064 
   2065 /*
   2066 ===============
   2067 idFuncSmoke::Think
   2068 ================
   2069 */
   2070 void idFuncSmoke::Think() {
   2071 
   2072 	// if we are completely closed off from the player, don't do anything at all
   2073 	if ( CheckDormant() || smoke == NULL || smokeTime == -1 ) {
   2074 		return;
   2075 	}
   2076 
   2077 	if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
   2078 		if ( !gameLocal.smokeParticles->EmitSmoke( smoke, smokeTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) {
   2079 			if ( restart ) {
   2080 				smokeTime = gameLocal.time;
   2081 			} else {
   2082 				smokeTime = 0;
   2083 				BecomeInactive( TH_UPDATEPARTICLES );
   2084 			}
   2085 		}
   2086 	}
   2087 
   2088 }
   2089 
   2090 
   2091 /*
   2092 ===============================================================================
   2093 
   2094 	idTextEntity
   2095 
   2096 ===============================================================================
   2097 */
   2098 
   2099 CLASS_DECLARATION( idEntity, idTextEntity )
   2100 END_CLASS
   2101 
   2102 /*
   2103 ================
   2104 idTextEntity::Spawn
   2105 ================
   2106 */
   2107 void idTextEntity::Spawn() {
   2108 	// these are cached as the are used each frame
   2109 	text = spawnArgs.GetString( "text" );
   2110 	playerOriented = spawnArgs.GetBool( "playerOriented" );
   2111 	bool force = spawnArgs.GetBool( "force" );
   2112 	if ( developer.GetBool() || force ) {
   2113 		BecomeActive(TH_THINK);
   2114 	}
   2115 }
   2116 
   2117 /*
   2118 ================
   2119 idTextEntity::Save
   2120 ================
   2121 */
   2122 void idTextEntity::Save( idSaveGame *savefile ) const {
   2123 	savefile->WriteString( text );
   2124 	savefile->WriteBool( playerOriented );
   2125 }
   2126 
   2127 /*
   2128 ================
   2129 idTextEntity::Restore
   2130 ================
   2131 */
   2132 void idTextEntity::Restore( idRestoreGame *savefile ) {
   2133 	savefile->ReadString( text );
   2134 	savefile->ReadBool( playerOriented );
   2135 }
   2136 
   2137 /*
   2138 ================
   2139 idTextEntity::Think
   2140 ================
   2141 */
   2142 void idTextEntity::Think() {
   2143 	if ( thinkFlags & TH_THINK ) {
   2144 		gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 );
   2145 		for ( int i = 0; i < targets.Num(); i++ ) {
   2146 			if ( targets[i].GetEntity() ) {
   2147 				gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 );
   2148 			}
   2149 		}
   2150 	} else {
   2151 		BecomeInactive( TH_ALL );
   2152 	}
   2153 }
   2154 
   2155 
   2156 /*
   2157 ===============================================================================
   2158 
   2159 	idVacuumSeperatorEntity
   2160 
   2161 	Can be triggered to let vacuum through a portal (blown out window)
   2162 
   2163 ===============================================================================
   2164 */
   2165 
   2166 CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity )
   2167 	EVENT( EV_Activate,		idVacuumSeparatorEntity::Event_Activate )
   2168 END_CLASS
   2169 
   2170 
   2171 /*
   2172 ================
   2173 idVacuumSeparatorEntity::idVacuumSeparatorEntity
   2174 ================
   2175 */
   2176 idVacuumSeparatorEntity::idVacuumSeparatorEntity() {
   2177 	portal = 0;
   2178 }
   2179 
   2180 /*
   2181 ================
   2182 idVacuumSeparatorEntity::Save
   2183 ================
   2184 */
   2185 void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const {
   2186 	savefile->WriteInt( (int)portal );
   2187 	savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) );
   2188 }
   2189 
   2190 /*
   2191 ================
   2192 idVacuumSeparatorEntity::Restore
   2193 ================
   2194 */
   2195 void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) {
   2196 	int state;
   2197 
   2198 	savefile->ReadInt( (int &)portal );
   2199 	savefile->ReadInt( state );
   2200 
   2201 	gameLocal.SetPortalState( portal, state );
   2202 }
   2203 
   2204 /*
   2205 ================
   2206 idVacuumSeparatorEntity::Spawn
   2207 ================
   2208 */
   2209 void idVacuumSeparatorEntity::Spawn() {
   2210 	idBounds b;
   2211 
   2212 	b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 );
   2213 	portal = gameRenderWorld->FindPortal( b );
   2214 	if ( !portal ) {
   2215 		gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) );
   2216 		return;
   2217 	}
   2218 	gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION );
   2219 }
   2220 
   2221 /*
   2222 ================
   2223 idVacuumSeparatorEntity::Event_Activate
   2224 ================
   2225 */
   2226 void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) {
   2227 	if ( !portal ) {
   2228 		return;
   2229 	}
   2230 	gameLocal.SetPortalState( portal, PS_BLOCK_NONE );
   2231 }
   2232 
   2233 
   2234 /*
   2235 ===============================================================================
   2236 
   2237 idLocationSeparatorEntity
   2238 
   2239 ===============================================================================
   2240 */
   2241 
   2242 CLASS_DECLARATION( idEntity, idLocationSeparatorEntity )
   2243 END_CLASS
   2244 
   2245 /*
   2246 ================
   2247 idLocationSeparatorEntity::Spawn
   2248 ================
   2249 */
   2250 void idLocationSeparatorEntity::Spawn() {
   2251 	idBounds b;
   2252 
   2253 	b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 );
   2254 	qhandle_t portal = gameRenderWorld->FindPortal( b );
   2255 	if ( !portal ) {
   2256 		gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) );
   2257 	}
   2258 	gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION );
   2259 }
   2260 
   2261 
   2262 /*
   2263 ===============================================================================
   2264 
   2265 	idVacuumEntity
   2266 
   2267 	Levels should only have a single vacuum entity.
   2268 
   2269 ===============================================================================
   2270 */
   2271 
   2272 CLASS_DECLARATION( idEntity, idVacuumEntity )
   2273 END_CLASS
   2274 
   2275 /*
   2276 ================
   2277 idVacuumEntity::Spawn
   2278 ================
   2279 */
   2280 void idVacuumEntity::Spawn() {
   2281 	if ( gameLocal.vacuumAreaNum != -1 ) {
   2282 		gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" );
   2283 		return;
   2284 	}
   2285 
   2286 	idVec3 org = spawnArgs.GetVector( "origin" );
   2287 
   2288 	gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org );
   2289 }
   2290 
   2291 
   2292 /*
   2293 ===============================================================================
   2294 
   2295 idLocationEntity
   2296 
   2297 ===============================================================================
   2298 */
   2299 
   2300 CLASS_DECLARATION( idEntity, idLocationEntity )
   2301 END_CLASS
   2302 
   2303 /*
   2304 ======================
   2305 idLocationEntity::Spawn
   2306 ======================
   2307 */
   2308 void idLocationEntity::Spawn() {
   2309 	idStr realName;
   2310 
   2311 	// this just holds dict information
   2312 
   2313 	// if "location" not already set, use the entity name.
   2314 	if ( !spawnArgs.GetString( "location", "", realName ) ) {
   2315 		spawnArgs.Set( "location", name );
   2316 	}
   2317 }
   2318 
   2319 /*
   2320 ======================
   2321 idLocationEntity::GetLocation
   2322 ======================
   2323 */
   2324 const char *idLocationEntity::GetLocation() const {
   2325 	return spawnArgs.GetString( "location" );
   2326 }
   2327 
   2328 /*
   2329 ===============================================================================
   2330 
   2331 	idBeam
   2332 
   2333 ===============================================================================
   2334 */
   2335 
   2336 CLASS_DECLARATION( idEntity, idBeam )
   2337 	EVENT( EV_PostSpawn,			idBeam::Event_MatchTarget )
   2338 	EVENT( EV_Activate,				idBeam::Event_Activate )
   2339 END_CLASS
   2340 
   2341 /*
   2342 ===============
   2343 idBeam::idBeam
   2344 ===============
   2345 */
   2346 idBeam::idBeam() {
   2347 	target = NULL;
   2348 	master = NULL;
   2349 }
   2350 
   2351 /*
   2352 ===============
   2353 idBeam::Save
   2354 ===============
   2355 */
   2356 void idBeam::Save( idSaveGame *savefile ) const {
   2357 	target.Save( savefile );
   2358 	master.Save( savefile );
   2359 }
   2360 
   2361 /*
   2362 ===============
   2363 idBeam::Restore
   2364 ===============
   2365 */
   2366 void idBeam::Restore( idRestoreGame *savefile ) {
   2367 	target.Restore( savefile );
   2368 	master.Restore( savefile );
   2369 }
   2370 
   2371 /*
   2372 ===============
   2373 idBeam::Spawn
   2374 ===============
   2375 */
   2376 void idBeam::Spawn() {
   2377 	float width;
   2378 
   2379 	if ( spawnArgs.GetFloat( "width", "0", width ) ) {
   2380 		renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width;
   2381 	}
   2382 
   2383 	SetModel( "_BEAM" );
   2384 	Hide();
   2385 	PostEventMS( &EV_PostSpawn, 0 );
   2386 }
   2387 
   2388 /*
   2389 ================
   2390 idBeam::Think
   2391 ================
   2392 */
   2393 void idBeam::Think() {
   2394 	idBeam *masterEnt;
   2395 
   2396 	if ( !IsHidden() && !target.GetEntity() ) {
   2397 		// hide if our target is removed
   2398 		Hide();
   2399 	}
   2400 
   2401 	RunPhysics();
   2402 
   2403 	masterEnt = master.GetEntity();
   2404 	if ( masterEnt ) {
   2405 		const idVec3 &origin = GetPhysics()->GetOrigin();
   2406 		masterEnt->SetBeamTarget( origin );
   2407 	}
   2408 	Present();
   2409 }
   2410 
   2411 /*
   2412 ================
   2413 idBeam::SetMaster
   2414 ================
   2415 */
   2416 void idBeam::SetMaster( idBeam *masterbeam ) {
   2417 	master = masterbeam;
   2418 }
   2419 
   2420 /*
   2421 ================
   2422 idBeam::SetBeamTarget
   2423 ================
   2424 */
   2425 void idBeam::SetBeamTarget( const idVec3 &origin ) {
   2426 	if ( ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] != origin.x ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] != origin.y ) || ( renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] != origin.z ) ) {
   2427 		renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x;
   2428 		renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y;
   2429 		renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z;
   2430 		UpdateVisuals();
   2431 	}
   2432 }
   2433 
   2434 /*
   2435 ================
   2436 idBeam::Show
   2437 ================
   2438 */
   2439 void idBeam::Show() {
   2440 	idBeam *targetEnt;
   2441 
   2442 	idEntity::Show();
   2443 
   2444 	targetEnt = target.GetEntity();
   2445 	if ( targetEnt ) {
   2446 		const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin();
   2447 		SetBeamTarget( origin );
   2448 	}
   2449 }
   2450 
   2451 /*
   2452 ================
   2453 idBeam::Event_MatchTarget
   2454 ================
   2455 */
   2456 void idBeam::Event_MatchTarget() {
   2457 	int i;
   2458 	idEntity *targetEnt;
   2459 	idBeam *targetBeam;
   2460 
   2461 	if ( !targets.Num() ) {
   2462 		return;
   2463 	}
   2464 
   2465 	targetBeam = NULL;
   2466 	for( i = 0; i < targets.Num(); i++ ) {
   2467 		targetEnt = targets[ i ].GetEntity();
   2468 		if ( targetEnt != NULL && targetEnt->IsType( idBeam::Type ) ) {
   2469 			targetBeam = static_cast<idBeam *>( targetEnt );
   2470 			break;
   2471 		}
   2472 	}
   2473 
   2474 	if ( targetBeam == NULL ) {
   2475 		gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() );
   2476 		return;
   2477 	}
   2478 
   2479 	target = targetBeam;
   2480 	targetBeam->SetMaster( this );
   2481 	if ( !spawnArgs.GetBool( "start_off" ) ) {
   2482 		Show();
   2483 	}
   2484 }
   2485 
   2486 /*
   2487 ================
   2488 idBeam::Event_Activate
   2489 ================
   2490 */
   2491 void idBeam::Event_Activate( idEntity *activator ) {
   2492 	if ( IsHidden() ) {
   2493 		Show();
   2494 	} else {
   2495 		Hide();		
   2496 	}
   2497 }
   2498 
   2499 /*
   2500 ================
   2501 idBeam::WriteToSnapshot
   2502 ================
   2503 */
   2504 void idBeam::WriteToSnapshot( idBitMsg &msg ) const {
   2505 	GetPhysics()->WriteToSnapshot( msg );
   2506 	WriteBindToSnapshot( msg );
   2507 	WriteColorToSnapshot( msg );
   2508 	msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] );
   2509 	msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] );
   2510 	msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] );
   2511 }
   2512 
   2513 /*
   2514 ================
   2515 idBeam::ReadFromSnapshot
   2516 ================
   2517 */
   2518 void idBeam::ReadFromSnapshot( const idBitMsg &msg ) {
   2519 	GetPhysics()->ReadFromSnapshot( msg );
   2520 	ReadBindFromSnapshot( msg );
   2521 	ReadColorFromSnapshot( msg );
   2522 	renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat();
   2523 	renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat();
   2524 	renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat();
   2525 	if ( msg.HasChanged() ) {
   2526 		UpdateVisuals();
   2527 	}
   2528 }
   2529 
   2530 
   2531 /*
   2532 ===============================================================================
   2533 
   2534 	idLiquid
   2535 
   2536 ===============================================================================
   2537 */
   2538 
   2539 CLASS_DECLARATION( idEntity, idLiquid )
   2540 	EVENT( EV_Touch,			idLiquid::Event_Touch )
   2541 END_CLASS
   2542 
   2543 /*
   2544 ================
   2545 idLiquid::Save
   2546 ================
   2547 */
   2548 void idLiquid::Save( idSaveGame *savefile ) const {
   2549 	// Nothing to save
   2550 }
   2551 
   2552 /*
   2553 ================
   2554 idLiquid::Restore
   2555 ================
   2556 */
   2557 void idLiquid::Restore( idRestoreGame *savefile ) {
   2558 	//FIXME: NO!
   2559 	Spawn();
   2560 }
   2561 
   2562 /*
   2563 ================
   2564 idLiquid::Spawn
   2565 ================
   2566 */
   2567 void idLiquid::Spawn() {
   2568 /*
   2569 	model = dynamic_cast<idRenderModelLiquid *>( renderEntity.hModel );
   2570 	if ( !model ) {
   2571 		gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() );
   2572 	}
   2573 	model->Reset();
   2574 	GetPhysics()->SetContents( CONTENTS_TRIGGER );
   2575 */
   2576 }
   2577 
   2578 /*
   2579 ================
   2580 idLiquid::Event_Touch
   2581 ================
   2582 */
   2583 void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) {
   2584 	// FIXME: for QuakeCon
   2585 /*
   2586 	if ( common->IsClient() ) {
   2587 		return;
   2588 	}
   2589 
   2590 	idVec3 pos;
   2591 
   2592 	pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
   2593 	model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f );
   2594 */
   2595 }
   2596 
   2597 
   2598 /*
   2599 ===============================================================================
   2600 
   2601 	idShaking
   2602 
   2603 ===============================================================================
   2604 */
   2605 
   2606 CLASS_DECLARATION( idEntity, idShaking )
   2607 	EVENT( EV_Activate,				idShaking::Event_Activate )
   2608 END_CLASS
   2609 
   2610 /*
   2611 ===============
   2612 idShaking::idShaking
   2613 ===============
   2614 */
   2615 idShaking::idShaking() {
   2616 	active = false;
   2617 }
   2618 
   2619 /*
   2620 ===============
   2621 idShaking::Save
   2622 ===============
   2623 */
   2624 void idShaking::Save( idSaveGame *savefile ) const {
   2625 	savefile->WriteBool( active );
   2626 	savefile->WriteStaticObject( physicsObj );
   2627 }
   2628 
   2629 /*
   2630 ===============
   2631 idShaking::Restore
   2632 ===============
   2633 */
   2634 void idShaking::Restore( idRestoreGame *savefile ) {
   2635 	savefile->ReadBool( active );
   2636 	savefile->ReadStaticObject( physicsObj );
   2637 	RestorePhysics( &physicsObj );
   2638 }
   2639 
   2640 /*
   2641 ===============
   2642 idShaking::Spawn
   2643 ===============
   2644 */
   2645 void idShaking::Spawn() {
   2646 	physicsObj.SetSelf( this );
   2647 	physicsObj.SetClipModel( new (TAG_PHYSICS_CLIP_ENTITY) idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
   2648 	physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
   2649 	physicsObj.SetAxis( GetPhysics()->GetAxis() );
   2650 	physicsObj.SetClipMask( MASK_SOLID );
   2651 	SetPhysics( &physicsObj );
   2652 	
   2653 	active = false;
   2654 	if ( !spawnArgs.GetBool( "start_off" ) ) {
   2655 		BeginShaking();
   2656 	}
   2657 }
   2658 
   2659 /*
   2660 ================
   2661 idShaking::BeginShaking
   2662 ================
   2663 */
   2664 void idShaking::BeginShaking() {
   2665 	int			phase;
   2666 	idAngles	shake;
   2667 	int			period;
   2668 
   2669 	active = true;
   2670 	phase = gameLocal.random.RandomInt( 1000 );
   2671 	shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" );
   2672 	period = spawnArgs.GetFloat( "period", "0.05" ) * 1000;
   2673 	physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero );
   2674 }
   2675 
   2676 /*
   2677 ================
   2678 idShaking::Event_Activate
   2679 ================
   2680 */
   2681 void idShaking::Event_Activate( idEntity *activator ) {
   2682 	if ( !active ) {
   2683 		BeginShaking();
   2684 	} else {
   2685 		active = false;
   2686 		physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero );
   2687 	}
   2688 }
   2689 
   2690 /*
   2691 ===============================================================================
   2692 
   2693 	idEarthQuake
   2694 
   2695 ===============================================================================
   2696 */
   2697 
   2698 CLASS_DECLARATION( idEntity, idEarthQuake )
   2699 	EVENT( EV_Activate,				idEarthQuake::Event_Activate )
   2700 END_CLASS
   2701 
   2702 /*
   2703 ===============
   2704 idEarthQuake::idEarthQuake
   2705 ===============
   2706 */
   2707 idEarthQuake::idEarthQuake() {
   2708 	wait = 0.0f;
   2709 	random = 0.0f;
   2710 	nextTriggerTime = 0;
   2711 	shakeStopTime = 0;
   2712 	triggered = false;
   2713 	playerOriented = false;
   2714 	disabled = false;
   2715 	shakeTime = 0.0f;
   2716 }
   2717 
   2718 /*
   2719 ===============
   2720 idEarthQuake::Save
   2721 ===============
   2722 */
   2723 void idEarthQuake::Save( idSaveGame *savefile ) const {
   2724 	savefile->WriteInt( nextTriggerTime );
   2725 	savefile->WriteInt( shakeStopTime );
   2726 	savefile->WriteFloat( wait );
   2727 	savefile->WriteFloat( random );
   2728 	savefile->WriteBool( triggered );
   2729 	savefile->WriteBool( playerOriented );
   2730 	savefile->WriteBool( disabled );
   2731 	savefile->WriteFloat( shakeTime );
   2732 }
   2733 
   2734 /*
   2735 ===============
   2736 idEarthQuake::Restore
   2737 ===============
   2738 */
   2739 void idEarthQuake::Restore( idRestoreGame *savefile ) {
   2740 	savefile->ReadInt( nextTriggerTime );
   2741 	savefile->ReadInt( shakeStopTime );
   2742 	savefile->ReadFloat( wait );
   2743 	savefile->ReadFloat( random );
   2744 	savefile->ReadBool( triggered );
   2745 	savefile->ReadBool( playerOriented );
   2746 	savefile->ReadBool( disabled );
   2747 	savefile->ReadFloat( shakeTime );
   2748 
   2749 	if ( shakeStopTime > gameLocal.time ) {
   2750 		BecomeActive( TH_THINK );
   2751 	}
   2752 }
   2753 
   2754 /*
   2755 ===============
   2756 idEarthQuake::Spawn
   2757 ===============
   2758 */
   2759 void idEarthQuake::Spawn() {
   2760 	nextTriggerTime = 0;
   2761 	shakeStopTime = 0;
   2762 	wait = spawnArgs.GetFloat( "wait", "15" );
   2763 	random = spawnArgs.GetFloat( "random", "5" );
   2764 	triggered = spawnArgs.GetBool( "triggered" );
   2765 	playerOriented = spawnArgs.GetBool( "playerOriented" );
   2766 	disabled = false;
   2767 	shakeTime = spawnArgs.GetFloat( "shakeTime", "0" );
   2768 
   2769 	if ( !triggered ){
   2770 		PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this );
   2771 	}
   2772 	BecomeInactive( TH_THINK );
   2773 }
   2774 
   2775 /*
   2776 ================
   2777 idEarthQuake::Event_Activate
   2778 ================
   2779 */
   2780 void idEarthQuake::Event_Activate( idEntity *activator ) {
   2781 	
   2782 	if ( nextTriggerTime > gameLocal.time ) {
   2783 		return;
   2784 	}
   2785 
   2786 	if ( disabled && activator == this ) {
   2787 		return;
   2788 	}
   2789 
   2790 	idPlayer *player = gameLocal.GetLocalPlayer();
   2791 	if ( player == NULL ) {
   2792 		return;
   2793 	}
   2794 
   2795 	nextTriggerTime = 0;
   2796 
   2797 	if ( !triggered && activator != this ){
   2798 		// if we are not triggered ( i.e. random ), disable or enable
   2799 		disabled ^= 1;
   2800 		if (disabled) {
   2801 			return;
   2802 		} else {
   2803 			PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this );
   2804 		}
   2805 	}
   2806 
   2807 	ActivateTargets( activator );
   2808 
   2809 	const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) );
   2810 	if ( playerOriented ) {
   2811 		player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
   2812 	} else {
   2813 		StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
   2814 	}
   2815 
   2816 	if ( shakeTime > 0.0f ) {
   2817 		shakeStopTime = gameLocal.time + SEC2MS( shakeTime );
   2818 		BecomeActive( TH_THINK );
   2819 	}
   2820 
   2821 	if ( wait > 0.0f ) {
   2822 		if ( !triggered ) {
   2823 			PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this );
   2824 		} else {
   2825 			nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() );
   2826 		}
   2827 	} else if ( shakeTime == 0.0f ) {
   2828 		PostEventMS( &EV_Remove, 0 );
   2829 	}
   2830 }
   2831 
   2832 
   2833 /*
   2834 ===============
   2835 idEarthQuake::Think
   2836 ================
   2837 */
   2838 void idEarthQuake::Think() {
   2839 	if ( thinkFlags & TH_THINK ) {
   2840 		if ( gameLocal.time > shakeStopTime ) {
   2841 			BecomeInactive( TH_THINK );
   2842 			if ( wait <= 0.0f ) {
   2843 				PostEventMS( &EV_Remove, 0 );
   2844 			}
   2845 			return;
   2846 		}
   2847 		float shakeVolume = gameSoundWorld->CurrentShakeAmplitude();
   2848 		gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true );
   2849 	}
   2850 	BecomeInactive( TH_UPDATEVISUALS );
   2851 }
   2852 
   2853 /*
   2854 ===============================================================================
   2855 
   2856 	idFuncPortal
   2857 
   2858 ===============================================================================
   2859 */
   2860 
   2861 CLASS_DECLARATION( idEntity, idFuncPortal )
   2862 	EVENT( EV_Activate,				idFuncPortal::Event_Activate )
   2863 END_CLASS
   2864 
   2865 /*
   2866 ===============
   2867 idFuncPortal::idFuncPortal
   2868 ===============
   2869 */
   2870 idFuncPortal::idFuncPortal() {
   2871 	portal = 0;
   2872 	state = false;
   2873 }
   2874 
   2875 /*
   2876 ===============
   2877 idFuncPortal::Save
   2878 ===============
   2879 */
   2880 void idFuncPortal::Save( idSaveGame *savefile ) const {
   2881 	savefile->WriteInt( (int)portal );
   2882 	savefile->WriteBool( state );
   2883 }
   2884 
   2885 /*
   2886 ===============
   2887 idFuncPortal::Restore
   2888 ===============
   2889 */
   2890 void idFuncPortal::Restore( idRestoreGame *savefile ) {
   2891 	savefile->ReadInt( (int &)portal );
   2892 	savefile->ReadBool( state );
   2893 	gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE );
   2894 }
   2895 
   2896 /*
   2897 ===============
   2898 idFuncPortal::Spawn
   2899 ===============
   2900 */
   2901 void idFuncPortal::Spawn() {
   2902 	portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) );
   2903 	if ( portal > 0 ) {
   2904 		state = spawnArgs.GetBool( "start_on" );
   2905 		gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE );
   2906 	}
   2907 }
   2908 
   2909 /*
   2910 ================
   2911 idFuncPortal::Event_Activate
   2912 ================
   2913 */
   2914 void idFuncPortal::Event_Activate( idEntity *activator ) {
   2915 	if ( portal > 0 ) {
   2916 		state = !state;
   2917 		gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE );
   2918 	}
   2919 }
   2920 
   2921 /*
   2922 ===============================================================================
   2923 
   2924 	idFuncAASPortal
   2925 
   2926 ===============================================================================
   2927 */
   2928 
   2929 CLASS_DECLARATION( idEntity, idFuncAASPortal )
   2930 	EVENT( EV_Activate,				idFuncAASPortal::Event_Activate )
   2931 END_CLASS
   2932 
   2933 /*
   2934 ===============
   2935 idFuncAASPortal::idFuncAASPortal
   2936 ===============
   2937 */
   2938 idFuncAASPortal::idFuncAASPortal() {
   2939 	state = false;
   2940 }
   2941 
   2942 /*
   2943 ===============
   2944 idFuncAASPortal::Save
   2945 ===============
   2946 */
   2947 void idFuncAASPortal::Save( idSaveGame *savefile ) const {
   2948 	savefile->WriteBool( state );
   2949 }
   2950 
   2951 /*
   2952 ===============
   2953 idFuncAASPortal::Restore
   2954 ===============
   2955 */
   2956 void idFuncAASPortal::Restore( idRestoreGame *savefile ) {
   2957 	savefile->ReadBool( state );
   2958 	gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state );
   2959 }
   2960 
   2961 /*
   2962 ===============
   2963 idFuncAASPortal::Spawn
   2964 ===============
   2965 */
   2966 void idFuncAASPortal::Spawn() {
   2967 	state = spawnArgs.GetBool( "start_on" );
   2968 	gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state );
   2969 }
   2970 
   2971 /*
   2972 ================
   2973 idFuncAASPortal::Event_Activate
   2974 ================
   2975 */
   2976 void idFuncAASPortal::Event_Activate( idEntity *activator ) {
   2977 	state ^= 1;
   2978 	gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state );
   2979 }
   2980 
   2981 /*
   2982 ===============================================================================
   2983 
   2984 	idFuncAASObstacle
   2985 
   2986 ===============================================================================
   2987 */
   2988 
   2989 CLASS_DECLARATION( idEntity, idFuncAASObstacle )
   2990 	EVENT( EV_Activate,				idFuncAASObstacle::Event_Activate )
   2991 END_CLASS
   2992 
   2993 /*
   2994 ===============
   2995 idFuncAASObstacle::idFuncAASObstacle
   2996 ===============
   2997 */
   2998 idFuncAASObstacle::idFuncAASObstacle() {
   2999 	state = false;
   3000 }
   3001 
   3002 /*
   3003 ===============
   3004 idFuncAASObstacle::Save
   3005 ===============
   3006 */
   3007 void idFuncAASObstacle::Save( idSaveGame *savefile ) const {
   3008 	savefile->WriteBool( state );
   3009 }
   3010 
   3011 /*
   3012 ===============
   3013 idFuncAASObstacle::Restore
   3014 ===============
   3015 */
   3016 void idFuncAASObstacle::Restore( idRestoreGame *savefile ) {
   3017 	savefile->ReadBool( state );
   3018 	gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state );
   3019 }
   3020 
   3021 /*
   3022 ===============
   3023 idFuncAASObstacle::Spawn
   3024 ===============
   3025 */
   3026 void idFuncAASObstacle::Spawn() {
   3027 	state = spawnArgs.GetBool( "start_on" );
   3028 	gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state );
   3029 }
   3030 
   3031 /*
   3032 ================
   3033 idFuncAASObstacle::Event_Activate
   3034 ================
   3035 */
   3036 void idFuncAASObstacle::Event_Activate( idEntity *activator ) {
   3037 	state ^= 1;
   3038 	gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state );
   3039 }
   3040 
   3041 
   3042 
   3043 /*
   3044 ===============================================================================
   3045 
   3046 idFuncRadioChatter
   3047 
   3048 ===============================================================================
   3049 */
   3050 
   3051 const idEventDef EV_ResetRadioHud( "<resetradiohud>", "e" );
   3052 
   3053 
   3054 CLASS_DECLARATION( idEntity, idFuncRadioChatter )
   3055 EVENT( EV_Activate,				idFuncRadioChatter::Event_Activate )
   3056 EVENT( EV_ResetRadioHud,		idFuncRadioChatter::Event_ResetRadioHud )
   3057 END_CLASS
   3058 
   3059 /*
   3060 ===============
   3061 idFuncRadioChatter::idFuncRadioChatter
   3062 ===============
   3063 */
   3064 idFuncRadioChatter::idFuncRadioChatter() {
   3065 	time = 0.0;
   3066 }
   3067 
   3068 /*
   3069 ===============
   3070 idFuncRadioChatter::Save
   3071 ===============
   3072 */
   3073 void idFuncRadioChatter::Save( idSaveGame *savefile ) const {
   3074 	savefile->WriteFloat( time );
   3075 }
   3076 
   3077 /*
   3078 ===============
   3079 idFuncRadioChatter::Restore
   3080 ===============
   3081 */
   3082 void idFuncRadioChatter::Restore( idRestoreGame *savefile ) {
   3083 	savefile->ReadFloat( time );
   3084 }
   3085 
   3086 /*
   3087 ===============
   3088 idFuncRadioChatter::Spawn
   3089 ===============
   3090 */
   3091 void idFuncRadioChatter::Spawn() {
   3092 	time = spawnArgs.GetFloat( "time", "5.0" );
   3093 }
   3094 
   3095 /*
   3096 ================
   3097 idFuncRadioChatter::Event_Activate
   3098 ================
   3099 */
   3100 void idFuncRadioChatter::Event_Activate( idEntity *activator ) {
   3101 	idPlayer * player = gameLocal.GetLocalPlayer();
   3102 
   3103 	if ( player != NULL && player->hudManager ) {
   3104 		player->hudManager->SetRadioMessage( true );
   3105 	}
   3106 
   3107 	const char * sound = spawnArgs.GetString( "snd_radiochatter", "" );
   3108 	if ( sound != NULL && *sound != NULL ) {
   3109 		const idSoundShader * shader = declManager->FindSound( sound );
   3110 		int length = 0;
   3111 		player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length );
   3112 		time = MS2SEC( length + 150 );
   3113 	}
   3114 	// we still put the hud up because this is used with no sound on 
   3115 	// certain frame commands when the chatter is triggered
   3116 	PostEventSec( &EV_ResetRadioHud, time, player );
   3117 }
   3118 
   3119 /*
   3120 ================
   3121 idFuncRadioChatter::Event_ResetRadioHud
   3122 ================
   3123 */
   3124 void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) {
   3125 	idPlayer *player = ( activator->IsType( idPlayer::Type ) ) ? static_cast<idPlayer *>( activator ) : gameLocal.GetLocalPlayer();
   3126 
   3127 	if ( player != NULL && player->hudManager ) {
   3128 		player->hudManager->SetRadioMessage( false );
   3129 	}
   3130 
   3131 	ActivateTargets( activator );
   3132 }
   3133 
   3134 
   3135 /*
   3136 ===============================================================================
   3137 
   3138 	idPhantomObjects
   3139 
   3140 ===============================================================================
   3141 */
   3142 
   3143 CLASS_DECLARATION( idEntity, idPhantomObjects )
   3144 	EVENT( EV_Activate,				idPhantomObjects::Event_Activate )
   3145 END_CLASS
   3146 
   3147 /*
   3148 ===============
   3149 idPhantomObjects::idPhantomObjects
   3150 ===============
   3151 */
   3152 idPhantomObjects::idPhantomObjects() {
   3153 	target			= NULL;
   3154 	end_time		= 0;
   3155 	throw_time 		= 0.0f;
   3156 	shake_time 		= 0.0f;
   3157 	shake_ang.Zero();
   3158 	speed			= 0.0f;
   3159 	min_wait		= 0;
   3160 	max_wait		= 0;
   3161 	fl.neverDormant	= false;
   3162 }
   3163 
   3164 /*
   3165 ===============
   3166 idPhantomObjects::Save
   3167 ===============
   3168 */
   3169 void idPhantomObjects::Save( idSaveGame *savefile ) const {
   3170 	int i;
   3171 
   3172 	savefile->WriteInt( end_time );
   3173 	savefile->WriteFloat( throw_time );
   3174 	savefile->WriteFloat( shake_time );
   3175 	savefile->WriteVec3( shake_ang );
   3176 	savefile->WriteFloat( speed );
   3177 	savefile->WriteInt( min_wait );
   3178 	savefile->WriteInt( max_wait );
   3179 	target.Save( savefile );
   3180 
   3181 	savefile->WriteInt( targetTime.Num() );
   3182 	for( i = 0; i < targetTime.Num(); i++ ) {
   3183 		savefile->WriteInt( targetTime[ i ] );
   3184 	}
   3185 	for( i = 0; i < lastTargetPos.Num(); i++ ) {
   3186 		savefile->WriteVec3( lastTargetPos[ i ] );
   3187 	}
   3188 }
   3189 
   3190 /*
   3191 ===============
   3192 idPhantomObjects::Restore
   3193 ===============
   3194 */
   3195 void idPhantomObjects::Restore( idRestoreGame *savefile ) {
   3196 	int num;
   3197 	int i;
   3198 
   3199 	savefile->ReadInt( end_time );
   3200 	savefile->ReadFloat( throw_time );
   3201 	savefile->ReadFloat( shake_time );
   3202 	savefile->ReadVec3( shake_ang );
   3203 	savefile->ReadFloat( speed );
   3204 	savefile->ReadInt( min_wait );
   3205 	savefile->ReadInt( max_wait );
   3206 	target.Restore( savefile );
   3207 	
   3208 	savefile->ReadInt( num );	
   3209 	targetTime.SetGranularity( 1 );
   3210 	targetTime.SetNum( num );
   3211 	lastTargetPos.SetGranularity( 1 );
   3212 	lastTargetPos.SetNum( num );
   3213 
   3214 	for( i = 0; i < num; i++ ) {
   3215 		savefile->ReadInt( targetTime[ i ] );
   3216 	}
   3217 	for( i = 0; i < num; i++ ) {
   3218 		savefile->ReadVec3( lastTargetPos[ i ] );
   3219 	}
   3220 }
   3221 
   3222 /*
   3223 ===============
   3224 idPhantomObjects::Spawn
   3225 ===============
   3226 */
   3227 void idPhantomObjects::Spawn() {
   3228 	throw_time = spawnArgs.GetFloat( "time", "5" );
   3229 	speed = spawnArgs.GetFloat( "speed", "1200" );
   3230 	shake_time = spawnArgs.GetFloat( "shake_time", "1" );
   3231 	throw_time -= shake_time;
   3232 	if ( throw_time < 0.0f ) {
   3233 		throw_time = 0.0f;
   3234 	}
   3235 	min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) );
   3236 	max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) );
   3237 
   3238 	shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" );
   3239 	Hide();
   3240 	GetPhysics()->SetContents( 0 );
   3241 }
   3242 
   3243 /*
   3244 ================
   3245 idPhantomObjects::Event_Activate
   3246 ================
   3247 */
   3248 void idPhantomObjects::Event_Activate( idEntity *activator ) {
   3249 	int i;
   3250 	float time;
   3251 	float frac;
   3252 	float scale;
   3253 
   3254 	if ( thinkFlags & TH_THINK ) {
   3255 		BecomeInactive( TH_THINK );
   3256 		return;
   3257 	}
   3258 
   3259 	RemoveNullTargets();
   3260 	if ( !targets.Num() ) {
   3261 		return;
   3262 	}
   3263 
   3264 	if ( !activator || !activator->IsType( idActor::Type ) ) {
   3265 		target = gameLocal.GetLocalPlayer();
   3266 	} else {
   3267 		target = static_cast<idActor *>( activator );
   3268 	}
   3269 	
   3270 	end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) );
   3271 
   3272 	targetTime.SetNum( targets.Num() );
   3273 	lastTargetPos.SetNum( targets.Num() );
   3274 
   3275 	const idVec3 &toPos = target.GetEntity()->GetEyePosition();
   3276 
   3277     // calculate the relative times of all the objects
   3278 	time = 0.0f;
   3279 	for( i = 0; i < targetTime.Num(); i++ ) {
   3280 		targetTime[ i ] = SEC2MS( time );
   3281 		lastTargetPos[ i ] = toPos;
   3282 
   3283 		frac = 1.0f - ( float )i / ( float )targetTime.Num();
   3284 		time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f;
   3285 	}
   3286 
   3287 	// scale up the times to fit within throw_time
   3288 	scale = throw_time / time;
   3289 	for( i = 0; i < targetTime.Num(); i++ ) {
   3290 		targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale;
   3291 	}
   3292 
   3293 	BecomeActive( TH_THINK );
   3294 }
   3295 
   3296 /*
   3297 ===============
   3298 idPhantomObjects::Think
   3299 ================
   3300 */
   3301 void idPhantomObjects::Think() {
   3302 	int			i;
   3303 	int			num;
   3304 	float		time;
   3305 	idVec3		vel;
   3306 	idVec3		ang;
   3307 	idEntity	*ent;
   3308 	idActor		*targetEnt;
   3309 	idPhysics	*entPhys;
   3310 	trace_t		tr;
   3311 
   3312 	// if we are completely closed off from the player, don't do anything at all
   3313 	if ( CheckDormant() ) {
   3314 		return;
   3315 	}
   3316 
   3317 	if ( !( thinkFlags & TH_THINK ) ) {
   3318 		BecomeInactive( thinkFlags & ~TH_THINK );
   3319 		return;
   3320 	}
   3321 
   3322 	targetEnt = target.GetEntity();
   3323 	if ( targetEnt == NULL || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) {
   3324 		BecomeInactive( TH_THINK );
   3325 		return;
   3326 	}
   3327 
   3328 	const idVec3 &toPos = targetEnt->GetEyePosition();
   3329 
   3330 	num = 0;
   3331 	for ( i = 0; i < targets.Num(); i++ ) {
   3332 		ent = targets[ i ].GetEntity();
   3333 		if ( !ent ) {
   3334 			continue;
   3335 		}
   3336 		
   3337 		if ( ent->fl.hidden ) {
   3338 			// don't throw hidden objects
   3339 			continue;
   3340 		}
   3341 
   3342 		if ( !targetTime[ i ] ) {
   3343 			// already threw this object
   3344 			continue;
   3345 		}
   3346 
   3347 		num++;
   3348 
   3349 		time = MS2SEC( targetTime[ i ] - gameLocal.time );
   3350 		if ( time > shake_time ) {
   3351 			continue;
   3352 		}
   3353 
   3354 		entPhys = ent->GetPhysics();
   3355 		const idVec3 &entOrg = entPhys->GetOrigin();
   3356 
   3357 		gameLocal.clip.TracePoint( tr, entOrg, toPos, MASK_OPAQUE, ent );
   3358 		if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) {
   3359 			lastTargetPos[ i ] = toPos;
   3360 		}
   3361 
   3362 		if ( time < 0.0f ) {
   3363 			idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), 
   3364 				entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel );
   3365 			vel *= speed;
   3366 			entPhys->SetLinearVelocity( vel );
   3367 			if ( !end_time ) {
   3368 				targetTime[ i ] = 0;
   3369 			} else {
   3370 				targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait;
   3371 			}
   3372 			if ( ent->IsType( idMoveable::Type ) ) {
   3373 				idMoveable *ment = static_cast<idMoveable*>( ent );
   3374 				ment->EnableDamage( true, 2.5f );
   3375 			}
   3376 		} else {
   3377 			// this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :)
   3378 			ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z );
   3379 			ang *= ( 1.0f - time / shake_time );
   3380 			entPhys->SetAngularVelocity( ang );
   3381 		}
   3382 	}
   3383 
   3384 	if ( !num ) {
   3385 		BecomeInactive( TH_THINK );
   3386 	}
   3387 }
   3388 
   3389 /*
   3390 ===============================================================================
   3391 
   3392 idShockwave
   3393 
   3394 ===============================================================================
   3395 */
   3396 CLASS_DECLARATION( idEntity, idShockwave )
   3397 EVENT( EV_Activate,			idShockwave::Event_Activate )
   3398 END_CLASS
   3399 
   3400 /*
   3401 ===============
   3402 idShockwave::idShockwave
   3403 ===============
   3404 */
   3405 idShockwave::idShockwave() {
   3406 	isActive = false;
   3407 	startTime = 0;
   3408 	duration = 0;
   3409 	startSize = 0.f;
   3410 	endSize = 0.f;
   3411 	currentSize = 0.f;
   3412 	magnitude = 0.f;
   3413 
   3414 	height = 0.0f;
   3415 	playerDamaged = false;
   3416 	playerDamageSize = 0.0f;
   3417 }
   3418 
   3419 /*
   3420 ===============
   3421 idShockwave::~idShockwave
   3422 ===============
   3423 */
   3424 idShockwave::~idShockwave() {
   3425 }
   3426 
   3427 /*
   3428 ===============
   3429 idShockwave::Save
   3430 ===============
   3431 */
   3432 void idShockwave::Save( idSaveGame *savefile ) const {
   3433 	savefile->WriteBool( isActive );
   3434 	savefile->WriteInt( startTime );
   3435 	savefile->WriteInt( duration );
   3436 
   3437 	savefile->WriteFloat( startSize );
   3438 	savefile->WriteFloat( endSize );
   3439 	savefile->WriteFloat( currentSize );
   3440 
   3441 	savefile->WriteFloat( magnitude );
   3442 
   3443 	savefile->WriteFloat( height );
   3444 	savefile->WriteBool( playerDamaged );
   3445 	savefile->WriteFloat( playerDamageSize );
   3446 }
   3447 
   3448 /*
   3449 ===============
   3450 idShockwave::Restore
   3451 ===============
   3452 */
   3453 void idShockwave::Restore( idRestoreGame *savefile ) {
   3454 	savefile->ReadBool( isActive );
   3455 	savefile->ReadInt( startTime );
   3456 	savefile->ReadInt( duration );
   3457 
   3458 	savefile->ReadFloat( startSize );
   3459 	savefile->ReadFloat( endSize );
   3460 	savefile->ReadFloat( currentSize );
   3461 
   3462 	savefile->ReadFloat( magnitude );
   3463 
   3464 	savefile->ReadFloat( height );
   3465 	savefile->ReadBool( playerDamaged );
   3466 	savefile->ReadFloat( playerDamageSize );
   3467 	
   3468 }
   3469 
   3470 /*
   3471 ===============
   3472 idShockwave::Spawn
   3473 ===============
   3474 */
   3475 void idShockwave::Spawn() {
   3476 
   3477 	spawnArgs.GetInt( "duration", "1000", duration );
   3478 	spawnArgs.GetFloat( "startsize", "8", startSize );
   3479 	spawnArgs.GetFloat( "endsize", "512", endSize );
   3480 	spawnArgs.GetFloat( "magnitude", "100", magnitude );
   3481 
   3482 	spawnArgs.GetFloat( "height", "0", height);
   3483 	spawnArgs.GetFloat( "player_damage_size", "20", playerDamageSize);
   3484 
   3485 	if ( spawnArgs.GetBool( "start_on" ) ) {
   3486 		ProcessEvent( &EV_Activate, this );
   3487 	}
   3488 }
   3489 
   3490 /*
   3491 ===============
   3492 idShockwave::Think
   3493 ===============
   3494 */
   3495 void idShockwave::Think() {
   3496 	int endTime;
   3497 
   3498 	if ( !isActive ) {
   3499 		BecomeInactive( TH_THINK );
   3500 		return;
   3501 	}
   3502 
   3503 	endTime = startTime + duration;
   3504 
   3505 	if ( gameLocal.time < endTime ) {
   3506 		float u;
   3507 		float newSize;
   3508 
   3509 		// Expand shockwave
   3510 		u = (float)(gameLocal.time - startTime) / (float)duration;
   3511 		newSize = startSize + u * (endSize - startSize);
   3512 
   3513 		// Find all clipmodels between currentSize and newSize
   3514 		idVec3		pos, end;
   3515 		idClipModel *clipModelList[ MAX_GENTITIES ];
   3516 		idClipModel *clip;
   3517 		idEntity	*ent;
   3518 		int			i, listedClipModels;
   3519 
   3520 		// Set bounds
   3521 		pos = GetPhysics()->GetOrigin();
   3522 
   3523 		float zVal;
   3524 		if(!height) {
   3525 			zVal = newSize;
   3526 		} else {
   3527 			zVal = height/2.0f;
   3528 		}
   3529 	
   3530 		//Expand in a sphere
   3531 		end = pos + idVec3( newSize, newSize, zVal );
   3532 		idBounds bounds( end );
   3533 		end = pos + idVec3( -newSize, -newSize, -zVal );
   3534 		bounds.AddPoint( end );
   3535 		
   3536 		if(g_debugShockwave.GetBool()) {
   3537 			gameRenderWorld->DebugBounds(colorRed,  bounds, vec3_origin);
   3538 		}
   3539 
   3540 		listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
   3541 
   3542 		for ( i = 0; i < listedClipModels; i++ ) {
   3543 			clip = clipModelList[ i ];
   3544 			ent = clip->GetEntity();
   3545 
   3546 			if ( ent->IsHidden() ) {
   3547 				continue;
   3548 			}
   3549 
   3550 			if ( !ent->IsType( idMoveable::Type ) && !ent->IsType( idAFEntity_Base::Type ) && !ent->IsType( idPlayer::Type )) {
   3551 				continue;
   3552 			}
   3553 
   3554 			idVec3 point = ent->GetPhysics()->GetOrigin();
   3555 			idVec3 force = point - pos;
   3556 
   3557 			float dist = force.Normalize();
   3558 
   3559 			if(ent->IsType( idPlayer::Type )) {
   3560 
   3561 				if(ent->GetPhysics()->GetAbsBounds().IntersectsBounds(bounds)) {
   3562 				
   3563 				//For player damage we check the current radius and a specified player damage ring size
   3564 					if ( dist <= newSize && dist > newSize-playerDamageSize ) {
   3565 
   3566 						idStr damageDef = spawnArgs.GetString("def_player_damage", "");
   3567 						if(damageDef.Length() > 0 && !playerDamaged) {
   3568 
   3569 							playerDamaged = true;	//Only damage once per shockwave
   3570 							idPlayer* player = static_cast< idPlayer* >( ent );
   3571 							idVec3 dir = ent->GetPhysics()->GetOrigin() - pos;
   3572 							dir.NormalizeFast();
   3573 							player->Damage(NULL, NULL, dir, damageDef, 1.0f, INVALID_JOINT);
   3574 						}
   3575 					}
   3576 				}
   3577 
   3578 			} else {
   3579 
   3580 				// If the object is inside the current expansion...
   3581 				if ( dist <= newSize && dist > currentSize ) {
   3582 					force.z += 4.f;
   3583 					force.NormalizeFast();
   3584 
   3585 					if ( ent->IsType( idAFEntity_Base::Type ) ) {
   3586 						force = force * (ent->GetPhysics()->GetMass() * magnitude * 0.01f);
   3587 					} else {
   3588 						force = force * ent->GetPhysics()->GetMass() * magnitude;
   3589 					}
   3590 
   3591 					// Kick it up, move force point off object origin
   3592 					float rad = ent->GetPhysics()->GetBounds().GetRadius();
   3593 					point.x += gameLocal.random.CRandomFloat() * rad;
   3594 					point.y += gameLocal.random.CRandomFloat() * rad;
   3595 
   3596 					int j;
   3597 					for( j=0; j < ent->GetPhysics()->GetNumClipModels(); j++ ) {
   3598 						ent->GetPhysics()->AddForce( j, point, force );
   3599 					}
   3600 				}
   3601 			}
   3602 		}
   3603 
   3604 		// Update currentSize for next frame
   3605 		currentSize = newSize;
   3606 
   3607 	} else {
   3608 
   3609 		// turn off
   3610 		isActive = false;
   3611 	}
   3612 }
   3613 
   3614 /*
   3615 ===============
   3616 idShockwave::Event_Activate
   3617 ===============
   3618 */
   3619 void idShockwave::Event_Activate( idEntity *activator ) {
   3620 
   3621 	isActive = true;
   3622 	startTime = gameLocal.time;
   3623 	playerDamaged = false;
   3624 
   3625 	BecomeActive( TH_THINK );
   3626 }
   3627 
   3628 
   3629 /*
   3630 ===============================================================================
   3631 
   3632 idFuncMountedObject
   3633 
   3634 ===============================================================================
   3635 */
   3636 
   3637 CLASS_DECLARATION( idEntity, idFuncMountedObject )
   3638 EVENT( EV_Touch,			idFuncMountedObject::Event_Touch )
   3639 EVENT( EV_Activate,			idFuncMountedObject::Event_Activate )
   3640 END_CLASS
   3641 
   3642 /*
   3643 ===============
   3644 idFuncMountedObject::idFuncMountedObject
   3645 ===============
   3646 */
   3647 idFuncMountedObject::idFuncMountedObject() {
   3648 	isMounted = false;
   3649 	scriptFunction = NULL;
   3650 	mountedPlayer = NULL;
   3651 	harc = 0;
   3652 	varc = 0;
   3653 }
   3654 
   3655 /*
   3656 ===============
   3657 idFuncMountedObject::idFuncMountedObject
   3658 ===============
   3659 */
   3660 idFuncMountedObject::~idFuncMountedObject() {
   3661 }
   3662 
   3663 /*
   3664 ===============
   3665 idFuncMountedObject::Spawn
   3666 ===============
   3667 */
   3668 void idFuncMountedObject::Spawn() {
   3669 	// Get viewOffset
   3670 	spawnArgs.GetInt( "harc", "45", harc );
   3671 	spawnArgs.GetInt( "varc", "30", varc );
   3672 
   3673 	// Get script function
   3674 	idStr funcName = spawnArgs.GetString( "call", "" );
   3675 	if ( funcName.Length() ) {
   3676 		scriptFunction = gameLocal.program.FindFunction( funcName );
   3677 		if ( scriptFunction == NULL ) {
   3678 			gameLocal.Warning( "idFuncMountedObject '%s' at (%s) calls unknown function '%s'\n", name.c_str(), GetPhysics()->GetOrigin().ToString(0), funcName.c_str() );
   3679 		}
   3680 	}
   3681 
   3682 	BecomeActive( TH_THINK );
   3683 }
   3684 
   3685 /*
   3686 ================
   3687 idFuncMountedObject::Think
   3688 ================
   3689 */
   3690 void idFuncMountedObject::Think() {
   3691 
   3692 	idEntity::Think();
   3693 }
   3694 
   3695 /*
   3696 ================
   3697 idFuncMountedObject::GetViewInfo
   3698 ================
   3699 */
   3700 void idFuncMountedObject::GetAngleRestrictions( int &yaw_min, int &yaw_max, int &pitch ) {
   3701 	idMat3		axis;
   3702 	idAngles	angs;
   3703 
   3704 	axis = GetPhysics()->GetAxis();
   3705 	angs = axis.ToAngles();
   3706 
   3707 	yaw_min = angs.yaw - harc;
   3708 	yaw_min = idMath::AngleNormalize180( yaw_min );
   3709 
   3710 	yaw_max = angs.yaw + harc;
   3711 	yaw_max = idMath::AngleNormalize180( yaw_max );
   3712 
   3713 	pitch = varc;
   3714 }
   3715 
   3716 /*
   3717 ================
   3718 idFuncMountedObject::Event_Touch
   3719 ================
   3720 */
   3721 void idFuncMountedObject::Event_Touch( idEntity *other, trace_t *trace ) {
   3722 	if ( common->IsClient() ) {
   3723 		return;
   3724 	}
   3725 
   3726 	ProcessEvent( &EV_Activate, other );
   3727 }
   3728 
   3729 /*
   3730 ================
   3731 idFuncMountedObject::Event_Activate
   3732 ================
   3733 */
   3734 void idFuncMountedObject::Event_Activate( idEntity *activator ) {
   3735 	if ( !isMounted && activator->IsType( idPlayer::Type ) ) {
   3736 		idPlayer *client = (idPlayer *)activator;
   3737 
   3738 		mountedPlayer = client;
   3739 
   3740 		/*
   3741 		// Place player at path_corner targeted by mounted object
   3742 		int i;
   3743 		idPathCorner	*spot;
   3744 
   3745 		for ( i = 0; i < targets.Num(); i++ ) {
   3746 		if ( targets[i]->IsType( idPathCorner::Type ) ) {
   3747 		spot = (idPathCorner*)targets[i];
   3748 		break;
   3749 		}
   3750 		}
   3751 
   3752 		mountedPlayer->GetPhysics()->SetOrigin( spot->GetPhysics()->GetOrigin() );
   3753 		mountedPlayer->GetPhysics()->SetAxis( spot->GetPhysics()->GetAxis() );
   3754 		*/
   3755 
   3756 		mountedPlayer->Bind( this, true );
   3757 		mountedPlayer->mountedObject = this;
   3758 
   3759 		// Call a script function
   3760 		idThread	*mountthread;
   3761 		if ( scriptFunction ) {
   3762 			mountthread = new idThread( scriptFunction );
   3763 			mountthread->DelayedStart( 0 );
   3764 		}
   3765 
   3766 		isMounted = true;
   3767 	}
   3768 }
   3769 
   3770 /*
   3771 ===============================================================================
   3772 
   3773 idFuncMountedWeapon
   3774 
   3775 ===============================================================================
   3776 */
   3777 CLASS_DECLARATION( idFuncMountedObject, idFuncMountedWeapon )
   3778 EVENT( EV_PostSpawn,		idFuncMountedWeapon::Event_PostSpawn )
   3779 END_CLASS
   3780 
   3781 idFuncMountedWeapon::idFuncMountedWeapon() {
   3782 	turret = NULL;
   3783 	weaponLastFireTime = 0;
   3784 	weaponFireDelay = 0;
   3785 	projectile = NULL;
   3786 }
   3787 
   3788 idFuncMountedWeapon::~idFuncMountedWeapon() {
   3789 }
   3790 
   3791 
   3792 void idFuncMountedWeapon::Spawn() {
   3793 
   3794 	// Get projectile info
   3795 	projectile = gameLocal.FindEntityDefDict( spawnArgs.GetString( "def_projectile" ), false );
   3796 	if ( !projectile ) {
   3797 		gameLocal.Warning( "Invalid projectile on func_mountedweapon." );
   3798 	}
   3799 
   3800 	float firerate;
   3801 	spawnArgs.GetFloat( "firerate", "3", firerate );
   3802 	weaponFireDelay = 1000.f / firerate;
   3803 
   3804 	// Get the firing sound
   3805 	idStr fireSound;
   3806 	spawnArgs.GetString( "snd_fire", "", fireSound );
   3807 	soundFireWeapon = declManager->FindSound( fireSound );
   3808 
   3809 	PostEventMS( &EV_PostSpawn, 0 );   
   3810 }
   3811 
   3812 void idFuncMountedWeapon::Think() {
   3813 
   3814 	if ( isMounted && turret ) {
   3815 		idVec3		vec = mountedPlayer->viewAngles.ToForward();
   3816 		idAngles	ang = mountedPlayer->GetLocalVector( vec ).ToAngles();
   3817 
   3818 		turret->GetPhysics()->SetAxis( ang.ToMat3() );
   3819 		turret->UpdateVisuals();
   3820 
   3821 		// Check for firing
   3822 		if ( mountedPlayer->usercmd.buttons & BUTTON_ATTACK && ( gameLocal.time > weaponLastFireTime + weaponFireDelay ) ) {
   3823 			// FIRE!
   3824 			idEntity		*ent;
   3825 			idProjectile	*proj;
   3826 			idBounds		projBounds;
   3827 			idVec3			dir;
   3828 
   3829 			gameLocal.SpawnEntityDef( *projectile, &ent );
   3830 			if ( !ent || !ent->IsType( idProjectile::Type ) ) {
   3831 				const char *projectileName = spawnArgs.GetString( "def_projectile" );
   3832 				gameLocal.Error( "'%s' is not an idProjectile", projectileName );
   3833 			}
   3834 
   3835 			mountedPlayer->GetViewPos( muzzleOrigin, muzzleAxis );
   3836 
   3837 			muzzleOrigin += ( muzzleAxis[0] * 128 );
   3838 			muzzleOrigin -= ( muzzleAxis[2] * 20 );
   3839 
   3840 			dir = muzzleAxis[0];
   3841 
   3842 			proj = static_cast<idProjectile *>(ent);
   3843 			proj->Create( this, muzzleOrigin, dir );
   3844 
   3845 			projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() );
   3846 
   3847 			proj->Launch( muzzleOrigin, dir, vec3_origin );
   3848 			StartSoundShader( soundFireWeapon, SND_CHANNEL_WEAPON, SSF_GLOBAL, false, NULL );
   3849 
   3850 			weaponLastFireTime = gameLocal.time;
   3851 		}
   3852 	}
   3853 
   3854 	idFuncMountedObject::Think();
   3855 }
   3856 
   3857 void idFuncMountedWeapon::Event_PostSpawn() {
   3858 
   3859 	if ( targets.Num() >= 1 ) {
   3860 		for ( int i=0; i < targets.Num(); i++ ) {
   3861 			if ( targets[i].GetEntity()->IsType( idStaticEntity::Type ) ) {
   3862 				turret = targets[i].GetEntity();
   3863 				break;
   3864 			}
   3865 		}
   3866 	} else {
   3867 		gameLocal.Warning( "idFuncMountedWeapon::Spawn:  Please target one model for a turret\n" );
   3868 	}
   3869 }
   3870 
   3871 
   3872 
   3873 
   3874 
   3875 
   3876 /*
   3877 ===============================================================================
   3878 
   3879 idPortalSky
   3880 
   3881 ===============================================================================
   3882 */
   3883 
   3884 CLASS_DECLARATION( idEntity, idPortalSky )
   3885 	EVENT( EV_PostSpawn,			idPortalSky::Event_PostSpawn )
   3886 	EVENT( EV_Activate,				idPortalSky::Event_Activate )
   3887 END_CLASS
   3888 
   3889 /*
   3890 ===============
   3891 idPortalSky::idPortalSky
   3892 ===============
   3893 */
   3894 idPortalSky::idPortalSky() {
   3895 
   3896 }
   3897 
   3898 /*
   3899 ===============
   3900 idPortalSky::~idPortalSky
   3901 ===============
   3902 */
   3903 idPortalSky::~idPortalSky() {
   3904 
   3905 }
   3906 
   3907 /*
   3908 ===============
   3909 idPortalSky::Spawn
   3910 ===============
   3911 */
   3912 void idPortalSky::Spawn() {
   3913 	if ( !spawnArgs.GetBool( "triggered" ) ) {
   3914 		PostEventMS( &EV_PostSpawn, 1 );
   3915 	}
   3916 }
   3917 
   3918 /*
   3919 ================
   3920 idPortalSky::Event_PostSpawn
   3921 ================
   3922 */
   3923 void idPortalSky::Event_PostSpawn() {
   3924 	gameLocal.SetPortalSkyEnt( this );
   3925 }
   3926 
   3927 /*
   3928 ================
   3929 idPortalSky::Event_Activate
   3930 ================
   3931 */
   3932 void idPortalSky::Event_Activate( idEntity *activator ) {
   3933 	gameLocal.SetPortalSkyEnt( this );
   3934 }