]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/game/Misc.cpp
Use the same OpenAL headers on all platforms.
[icculus/iodoom3.git] / neo / game / Misc.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
9 Doom 3 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 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 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the Doom 3 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 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( void ) {
84         teleportStage = 0;
85 }
86
87 /*
88 ===============
89 idPlayerStart::Spawn
90 ================
91 */
92 void idPlayerStart::Spawn( void ) {
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         return false;
136 }
137
138 /*
139 ===============
140 idPlayerStart::Event_TeleportStage
141
142 FIXME: add functionality to fx system ( could be done with player scripting too )
143 ================
144 */
145 void idPlayerStart::Event_TeleportStage( idEntity *_player ) {
146         idPlayer *player;
147         if ( !_player->IsType( idPlayer::Type ) ) {
148                 common->Warning( "idPlayerStart::Event_TeleportStage: entity is not an idPlayer\n" );
149                 return;
150         }
151         player = static_cast<idPlayer*>(_player);
152         float teleportDelay = spawnArgs.GetFloat( "teleportDelay" );
153         switch ( teleportStage ) {
154                 case 0:
155                         player->playerView.Flash( colorWhite, 125 );
156                         player->SetInfluenceLevel( INFLUENCE_LEVEL3 );
157                         player->SetInfluenceView( spawnArgs.GetString( "mtr_teleportFx" ), NULL, 0.0f, NULL );
158                         gameSoundWorld->FadeSoundClasses( 0, -20.0f, teleportDelay );
159                         player->StartSound( "snd_teleport_start", SND_CHANNEL_BODY2, 0, false, NULL );
160                         teleportStage++;
161                         PostEventSec( &EV_TeleportStage, teleportDelay, player );
162                         break;
163                 case 1:
164                         gameSoundWorld->FadeSoundClasses( 0, 0.0f, 0.25f );
165                         teleportStage++;
166                         PostEventSec( &EV_TeleportStage, 0.25f, player );
167                         break;
168                 case 2:
169                         player->SetInfluenceView( NULL, NULL, 0.0f, NULL );
170                         TeleportPlayer( player );
171                         player->StopSound( SND_CHANNEL_BODY2, false );
172                         player->SetInfluenceLevel( INFLUENCE_NONE );
173                         teleportStage = 0;
174                         break;
175                 default:
176                         break;
177         }
178 }
179
180 /*
181 ===============
182 idPlayerStart::TeleportPlayer
183 ================
184 */
185 void idPlayerStart::TeleportPlayer( idPlayer *player ) {
186         float pushVel = spawnArgs.GetFloat( "push", "300" );
187         float f = spawnArgs.GetFloat( "visualEffect", "0" );
188         const char *viewName = spawnArgs.GetString( "visualView", "" );
189         idEntity *ent = viewName ? gameLocal.FindEntity( viewName ) : NULL;
190
191         if ( f && ent ) {
192                 // place in private camera view for some time
193                 // the entity needs to teleport to where the camera view is to have the PVS right
194                 player->Teleport( ent->GetPhysics()->GetOrigin(), ang_zero, this );
195                 player->StartSound( "snd_teleport_enter", SND_CHANNEL_ANY, 0, false, NULL );
196                 player->SetPrivateCameraView( static_cast<idCamera*>(ent) );
197                 // the player entity knows where to spawn from the previous Teleport call
198                 if ( !gameLocal.isClient ) {
199                         player->PostEventSec( &EV_Player_ExitTeleporter, f );
200                 }
201         } else {
202                 // direct to exit, Teleport will take care of the killbox
203                 player->Teleport( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis().ToAngles(), NULL );
204
205                 // multiplayer hijacked this entity, so only push the player in multiplayer
206                 if ( gameLocal.isMultiplayer ) {
207                         player->GetPhysics()->SetLinearVelocity( GetPhysics()->GetAxis()[0] * pushVel );
208                 }
209         }
210 }
211
212 /*
213 ===============
214 idPlayerStart::Event_TeleportPlayer
215 ================
216 */
217 void idPlayerStart::Event_TeleportPlayer( idEntity *activator ) {
218         idPlayer *player;
219
220         if ( activator->IsType( idPlayer::Type ) ) {
221                 player = static_cast<idPlayer*>( activator );
222         } else {
223                 player = gameLocal.GetLocalPlayer();
224         }
225         if ( player ) {
226                 if ( spawnArgs.GetBool( "visualFx" ) ) {
227
228                         teleportStage = 0;
229                         Event_TeleportStage( player );
230
231                 } else {
232
233                         if ( gameLocal.isServer ) {
234                                 idBitMsg        msg;
235                                 byte            msgBuf[MAX_EVENT_PARAM_SIZE];
236
237                                 msg.Init( msgBuf, sizeof( msgBuf ) );
238                                 msg.BeginWriting();
239                                 msg.WriteBits( player->entityNumber, GENTITYNUM_BITS );
240                                 ServerSendEvent( EVENT_TELEPORTPLAYER, &msg, false, -1 );
241                         }
242
243                         TeleportPlayer( player );
244                 }
245         }
246 }
247
248 /*
249 ===============================================================================
250
251         idActivator
252
253 ===============================================================================
254 */
255
256 CLASS_DECLARATION( idEntity, idActivator )
257         EVENT( EV_Activate,             idActivator::Event_Activate )
258 END_CLASS
259
260 /*
261 ===============
262 idActivator::Save
263 ================
264 */
265 void idActivator::Save( idSaveGame *savefile ) const {
266         savefile->WriteBool( stay_on );
267 }
268
269 /*
270 ===============
271 idActivator::Restore
272 ================
273 */
274 void idActivator::Restore( idRestoreGame *savefile ) {
275         savefile->ReadBool( stay_on );
276
277         if ( stay_on ) {
278                 BecomeActive( TH_THINK );
279         }
280 }
281
282 /*
283 ===============
284 idActivator::Spawn
285 ================
286 */
287 void idActivator::Spawn( void ) {
288         bool start_off;
289
290         spawnArgs.GetBool( "stay_on", "0", stay_on );
291         spawnArgs.GetBool( "start_off", "0", start_off );
292
293         GetPhysics()->SetClipBox( idBounds( vec3_origin ).Expand( 4 ), 1.0f );
294         GetPhysics()->SetContents( 0 );
295
296         if ( !start_off ) {
297                 BecomeActive( TH_THINK );
298         }
299 }
300
301 /*
302 ===============
303 idActivator::Think
304 ================
305 */
306 void idActivator::Think( void ) {
307         RunPhysics();
308         if ( thinkFlags & TH_THINK ) {
309                 if ( TouchTriggers() ) {
310                         if ( !stay_on ) {
311                                 BecomeInactive( TH_THINK );
312                         }
313                 }
314         }
315         Present();
316 }
317
318 /*
319 ===============
320 idActivator::Activate
321 ================
322 */
323 void idActivator::Event_Activate( idEntity *activator ) {
324         if ( thinkFlags & TH_THINK ) {
325                 BecomeInactive( TH_THINK );
326         } else {
327                 BecomeActive( TH_THINK );
328         }
329 }
330
331
332 /*
333 ===============================================================================
334
335 idPathCorner
336
337 ===============================================================================
338 */
339
340 CLASS_DECLARATION( idEntity, idPathCorner )
341         EVENT( AI_RandomPath,           idPathCorner::Event_RandomPath )
342 END_CLASS
343
344 /*
345 =====================
346 idPathCorner::Spawn
347 =====================
348 */
349 void idPathCorner::Spawn( void ) {
350 }
351
352 /*
353 =====================
354 idPathCorner::DrawDebugInfo
355 =====================
356 */
357 void idPathCorner::DrawDebugInfo( void ) {
358         idEntity *ent;
359         idBounds bnds( idVec3( -4.0, -4.0f, -8.0f ), idVec3( 4.0, 4.0f, 64.0f ) );
360
361         for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
362                 if ( !ent->IsType( idPathCorner::Type ) ) {
363                         continue;
364                 }
365
366                 idVec3 org = ent->GetPhysics()->GetOrigin();
367                 gameRenderWorld->DebugBounds( colorRed, bnds, org, 0 );
368         }
369 }
370
371 /*
372 ============
373 idPathCorner::RandomPath
374 ============
375 */
376 idPathCorner *idPathCorner::RandomPath( const idEntity *source, const idEntity *ignore ) {
377         int     i;
378         int     num;
379         int which;
380         idEntity *ent;
381         idPathCorner *path[ MAX_GENTITIES ];
382
383         num = 0;
384         for( i = 0; i < source->targets.Num(); i++ ) {
385                 ent = source->targets[ i ].GetEntity();
386                 if ( ent && ( ent != ignore ) && ent->IsType( idPathCorner::Type ) ) {
387                         path[ num++ ] = static_cast<idPathCorner *>( ent );
388                         if ( num >= MAX_GENTITIES ) {
389                                 break;
390                         }
391                 }
392         }
393
394         if ( !num ) {
395                 return NULL;
396         }
397
398         which = gameLocal.random.RandomInt( num );
399         return path[ which ];
400 }
401
402 /*
403 =====================
404 idPathCorner::Event_RandomPath
405 =====================
406 */
407 void idPathCorner::Event_RandomPath( void ) {
408         idPathCorner *path;
409
410         path = RandomPath( this, NULL );
411         idThread::ReturnEntity( path );
412 }
413
414 /*
415 ===============================================================================
416
417   idDamagable
418         
419 ===============================================================================
420 */
421
422 const idEventDef EV_RestoreDamagable( "<RestoreDamagable>" );
423
424 CLASS_DECLARATION( idEntity, idDamagable )
425         EVENT( EV_Activate,                     idDamagable::Event_BecomeBroken )
426         EVENT( EV_RestoreDamagable,     idDamagable::Event_RestoreDamagable )
427 END_CLASS
428
429 /*
430 ================
431 idDamagable::idDamagable
432 ================
433 */
434 idDamagable::idDamagable( void ) {
435         count = 0;
436         nextTriggerTime = 0;
437 }
438
439 /*
440 ================
441 idDamagable::Save
442 ================
443 */
444 void idDamagable::Save( idSaveGame *savefile ) const {
445         savefile->WriteInt( count );
446         savefile->WriteInt( nextTriggerTime );
447 }
448
449 /*
450 ================
451 idDamagable::Restore
452 ================
453 */
454 void idDamagable::Restore( idRestoreGame *savefile ) {
455         savefile->ReadInt( count );
456         savefile->ReadInt( nextTriggerTime );
457 }
458
459 /*
460 ================
461 idDamagable::Spawn
462 ================
463 */
464 void idDamagable::Spawn( void ) {
465         idStr broken;
466
467         health = spawnArgs.GetInt( "health", "5" );
468         spawnArgs.GetInt( "count", "1", count );        
469         nextTriggerTime = 0;
470         
471         // make sure the model gets cached
472         spawnArgs.GetString( "broken", "", broken );
473         if ( broken.Length() && !renderModelManager->CheckModel( broken ) ) {
474                 gameLocal.Error( "idDamagable '%s' at (%s): cannot load broken model '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), broken.c_str() );
475         }
476
477         fl.takedamage = true;
478         GetPhysics()->SetContents( CONTENTS_SOLID );
479 }
480
481 /*
482 ================
483 idDamagable::BecomeBroken
484 ================
485 */
486 void idDamagable::BecomeBroken( idEntity *activator ) {
487         float   forceState;
488         int             numStates;
489         int             cycle;
490         float   wait;
491         
492         if ( gameLocal.time < nextTriggerTime ) {
493                 return;
494         }
495
496         spawnArgs.GetFloat( "wait", "0.1", wait );
497         nextTriggerTime = gameLocal.time + SEC2MS( wait );
498         if ( count > 0 ) {
499                 count--;
500                 if ( !count ) {
501                         fl.takedamage = false;
502                 } else {
503                         health = spawnArgs.GetInt( "health", "5" );
504                 }
505         }
506
507         idStr   broken;
508
509         spawnArgs.GetString( "broken", "", broken );
510         if ( broken.Length() ) {
511                 SetModel( broken );
512         }
513
514         // offset the start time of the shader to sync it to the gameLocal time
515         renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
516
517         spawnArgs.GetInt( "numstates", "1", numStates );
518         spawnArgs.GetInt( "cycle", "0", cycle );
519         spawnArgs.GetFloat( "forcestate", "0", forceState );
520
521         // set the state parm
522         if ( cycle ) {
523                 renderEntity.shaderParms[ SHADERPARM_MODE ]++;
524                 if ( renderEntity.shaderParms[ SHADERPARM_MODE ] > numStates ) {
525                         renderEntity.shaderParms[ SHADERPARM_MODE ] = 0;
526                 }
527         } else if ( forceState ) {
528                 renderEntity.shaderParms[ SHADERPARM_MODE ] = forceState;
529         } else {
530                 renderEntity.shaderParms[ SHADERPARM_MODE ] = gameLocal.random.RandomInt( numStates ) + 1;
531         }
532
533         renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
534
535         ActivateTargets( activator );
536
537         if ( spawnArgs.GetBool( "hideWhenBroken" ) ) {
538                 Hide();
539                 PostEventMS( &EV_RestoreDamagable, nextTriggerTime - gameLocal.time );
540                 BecomeActive( TH_THINK );
541         }
542 }
543
544 /*
545 ================
546 idDamagable::Killed
547 ================
548 */
549 void idDamagable::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
550         if ( gameLocal.time < nextTriggerTime ) {
551                 health += damage;
552                 return;
553         }
554
555         BecomeBroken( attacker );
556 }
557
558 /*
559 ================
560 idDamagable::Event_BecomeBroken
561 ================
562 */
563 void idDamagable::Event_BecomeBroken( idEntity *activator ) {
564         BecomeBroken( activator );
565 }
566
567 /*
568 ================
569 idDamagable::Event_RestoreDamagable
570 ================
571 */
572 void idDamagable::Event_RestoreDamagable( void ) {
573         health = spawnArgs.GetInt( "health", "5" );
574         Show();
575 }
576
577
578 /*
579 ===============================================================================
580
581   idExplodable
582         
583 ===============================================================================
584 */
585
586 CLASS_DECLARATION( idEntity, idExplodable )
587         EVENT( EV_Activate,     idExplodable::Event_Explode )
588 END_CLASS
589
590 /*
591 ================
592 idExplodable::Spawn
593 ================
594 */
595 void idExplodable::Spawn( void ) {
596         Hide();
597 }
598
599 /*
600 ================
601 idExplodable::Event_Explode
602 ================
603 */
604 void idExplodable::Event_Explode( idEntity *activator ) {
605         const char *temp;
606
607         if ( spawnArgs.GetString( "def_damage", "damage_explosion", &temp ) ) {
608                 gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), activator, activator, this, this, temp );
609         }
610
611         StartSound( "snd_explode", SND_CHANNEL_ANY, 0, false, NULL );
612
613         // Show() calls UpdateVisuals, so we don't need to call it ourselves after setting the shaderParms
614         renderEntity.shaderParms[SHADERPARM_RED]                = 1.0f;
615         renderEntity.shaderParms[SHADERPARM_GREEN]              = 1.0f;
616         renderEntity.shaderParms[SHADERPARM_BLUE]               = 1.0f;
617         renderEntity.shaderParms[SHADERPARM_ALPHA]              = 1.0f;
618         renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
619         renderEntity.shaderParms[SHADERPARM_DIVERSITY]  = 0.0f;
620         Show();
621
622         PostEventMS( &EV_Remove, 2000 );
623
624         ActivateTargets( activator );
625 }
626
627
628 /*
629 ===============================================================================
630
631   idSpring
632         
633 ===============================================================================
634 */
635
636 CLASS_DECLARATION( idEntity, idSpring )
637         EVENT( EV_PostSpawn,    idSpring::Event_LinkSpring )
638 END_CLASS
639
640 /*
641 ================
642 idSpring::Think
643 ================
644 */
645 void idSpring::Think( void ) {
646         idVec3 start, end, origin;
647         idMat3 axis;
648
649         // run physics
650         RunPhysics();
651
652         if ( thinkFlags & TH_THINK ) {
653                 // evaluate force
654                 spring.Evaluate( gameLocal.time );
655
656                 start = p1;
657                 if ( ent1->GetPhysics() ) {
658                         axis = ent1->GetPhysics()->GetAxis();
659                         origin = ent1->GetPhysics()->GetOrigin();
660                         start = origin + start * axis;
661                 }
662
663                 end = p2;
664                 if ( ent2->GetPhysics() ) {
665                         axis = ent2->GetPhysics()->GetAxis();
666                         origin = ent2->GetPhysics()->GetOrigin();
667                         end = origin + p2 * axis;
668                 }
669                 
670                 gameRenderWorld->DebugLine( idVec4(1, 1, 0, 1), start, end, 0, true );
671         }
672
673         Present();
674 }
675
676 /*
677 ================
678 idSpring::Event_LinkSpring
679 ================
680 */
681 void idSpring::Event_LinkSpring( void ) {
682         idStr name1, name2;
683
684         spawnArgs.GetString( "ent1", "", name1 );
685         spawnArgs.GetString( "ent2", "", name2 );
686
687         if ( name1.Length() ) {
688                 ent1 = gameLocal.FindEntity( name1 );
689                 if ( !ent1 ) {
690                         gameLocal.Error( "idSpring '%s' at (%s): cannot find first entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name1.c_str() );
691                 }
692         }
693         else {
694                 ent1 = gameLocal.entities[ENTITYNUM_WORLD];
695         }
696
697         if ( name2.Length() ) {
698                 ent2 = gameLocal.FindEntity( name2 );
699                 if ( !ent2 ) {
700                         gameLocal.Error( "idSpring '%s' at (%s): cannot find second entity '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), name2.c_str() );
701                 }
702         }
703         else {
704                 ent2 = gameLocal.entities[ENTITYNUM_WORLD];
705         }
706         spring.SetPosition( ent1->GetPhysics(), id1, p1, ent2->GetPhysics(), id2, p2 );
707         BecomeActive( TH_THINK );
708 }
709
710 /*
711 ================
712 idSpring::Spawn
713 ================
714 */
715 void idSpring::Spawn( void ) {
716         float Kstretch, damping, restLength;
717
718         spawnArgs.GetInt( "id1", "0", id1 );
719         spawnArgs.GetInt( "id2", "0", id2 );
720         spawnArgs.GetVector( "point1", "0 0 0", p1 );
721         spawnArgs.GetVector( "point2", "0 0 0", p2 );
722         spawnArgs.GetFloat( "constant", "100.0f", Kstretch );
723         spawnArgs.GetFloat( "damping", "10.0f", damping );
724         spawnArgs.GetFloat( "restlength", "0.0f", restLength );
725
726         spring.InitSpring( Kstretch, 0.0f, damping, restLength );
727
728         ent1 = ent2 = NULL;
729
730         PostEventMS( &EV_PostSpawn, 0 );
731 }
732
733 /*
734 ===============================================================================
735
736   idForceField
737         
738 ===============================================================================
739 */
740
741 const idEventDef EV_Toggle( "Toggle", NULL );
742
743 CLASS_DECLARATION( idEntity, idForceField )
744         EVENT( EV_Activate,             idForceField::Event_Activate )
745         EVENT( EV_Toggle,               idForceField::Event_Toggle )
746         EVENT( EV_FindTargets,  idForceField::Event_FindTargets )
747 END_CLASS
748
749 /*
750 ===============
751 idForceField::Toggle
752 ================
753 */
754 void idForceField::Toggle( void ) {
755         if ( thinkFlags & TH_THINK ) {
756                 BecomeInactive( TH_THINK );
757         } else {
758                 BecomeActive( TH_THINK );
759         }
760 }
761
762 /*
763 ================
764 idForceField::Think
765 ================
766 */
767 void idForceField::Think( void ) {
768         if ( thinkFlags & TH_THINK ) {
769                 // evaluate force
770                 forceField.Evaluate( gameLocal.time );
771         }
772         Present();
773 }
774
775 /*
776 ================
777 idForceField::Save
778 ================
779 */
780 void idForceField::Save( idSaveGame *savefile ) const {
781         savefile->WriteStaticObject( forceField );
782 }
783
784 /*
785 ================
786 idForceField::Restore
787 ================
788 */
789 void idForceField::Restore( idRestoreGame *savefile ) {
790         savefile->ReadStaticObject( forceField );
791 }
792
793 /*
794 ================
795 idForceField::Spawn
796 ================
797 */
798 void idForceField::Spawn( void ) {
799         idVec3 uniform;
800         float explosion, implosion, randomTorque;
801
802         if ( spawnArgs.GetVector( "uniform", "0 0 0", uniform ) ) {
803                 forceField.Uniform( uniform );
804         } else if ( spawnArgs.GetFloat( "explosion", "0", explosion ) ) {
805                 forceField.Explosion( explosion );
806         } else if ( spawnArgs.GetFloat( "implosion", "0", implosion ) ) {
807                 forceField.Implosion( implosion );
808         }
809
810         if ( spawnArgs.GetFloat( "randomTorque", "0", randomTorque ) ) {
811                 forceField.RandomTorque( randomTorque );
812         }
813
814         if ( spawnArgs.GetBool( "applyForce", "0" ) ) {
815                 forceField.SetApplyType( FORCEFIELD_APPLY_FORCE );
816         } else if ( spawnArgs.GetBool( "applyImpulse", "0" ) ) {
817                 forceField.SetApplyType( FORCEFIELD_APPLY_IMPULSE );
818         } else {
819                 forceField.SetApplyType( FORCEFIELD_APPLY_VELOCITY );
820         }
821
822         forceField.SetPlayerOnly( spawnArgs.GetBool( "playerOnly", "0" ) );
823         forceField.SetMonsterOnly( spawnArgs.GetBool( "monsterOnly", "0" ) );
824
825         // set the collision model on the force field
826         forceField.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ) );
827
828         // remove the collision model from the physics object
829         GetPhysics()->SetClipModel( NULL, 1.0f );
830
831         if ( spawnArgs.GetBool( "start_on" ) ) {
832                 BecomeActive( TH_THINK );
833         }
834 }
835
836 /*
837 ===============
838 idForceField::Event_Toggle
839 ================
840 */
841 void idForceField::Event_Toggle( void ) {
842         Toggle();
843 }
844
845 /*
846 ================
847 idForceField::Event_Activate
848 ================
849 */
850 void idForceField::Event_Activate( idEntity *activator ) {
851         float wait;
852
853         Toggle();
854         if ( spawnArgs.GetFloat( "wait", "0.01", wait ) ) {
855                 PostEventSec( &EV_Toggle, wait );
856         }
857 }
858
859 /*
860 ================
861 idForceField::Event_FindTargets
862 ================
863 */
864 void idForceField::Event_FindTargets( void ) {
865         FindTargets();
866         RemoveNullTargets();
867         if ( targets.Num() ) {
868                 forceField.Uniform( targets[0].GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() );
869         }
870 }
871
872
873 /*
874 ===============================================================================
875
876         idAnimated
877
878 ===============================================================================
879 */
880
881 const idEventDef EV_Animated_Start( "<start>" );
882 const idEventDef EV_LaunchMissiles( "launchMissiles", "ssssdf" );
883 const idEventDef EV_LaunchMissilesUpdate( "<launchMissiles>", "dddd" );
884 const idEventDef EV_AnimDone( "<AnimDone>", "d" );
885 const idEventDef EV_StartRagdoll( "startRagdoll" );
886
887 CLASS_DECLARATION( idAFEntity_Gibbable, idAnimated )
888         EVENT( EV_Activate,                             idAnimated::Event_Activate )
889         EVENT( EV_Animated_Start,               idAnimated::Event_Start )
890         EVENT( EV_StartRagdoll,                 idAnimated::Event_StartRagdoll )
891         EVENT( EV_AnimDone,                             idAnimated::Event_AnimDone )
892         EVENT( EV_Footstep,                             idAnimated::Event_Footstep )
893         EVENT( EV_FootstepLeft,                 idAnimated::Event_Footstep )
894         EVENT( EV_FootstepRight,                idAnimated::Event_Footstep )
895         EVENT( EV_LaunchMissiles,               idAnimated::Event_LaunchMissiles )
896         EVENT( EV_LaunchMissilesUpdate, idAnimated::Event_LaunchMissilesUpdate )
897 END_CLASS
898
899 /*
900 ===============
901 idAnimated::idAnimated
902 ================
903 */
904 idAnimated::idAnimated() {
905         anim = 0;
906         blendFrames = 0;
907         soundJoint = INVALID_JOINT;
908         activated = false;
909         combatModel = NULL;
910         activator = NULL;
911         current_anim_index = 0;
912         num_anims = 0;
913
914 }
915
916 /*
917 ===============
918 idAnimated::idAnimated
919 ================
920 */
921 idAnimated::~idAnimated() {
922         delete combatModel;
923         combatModel = NULL;
924 }
925
926 /*
927 ===============
928 idAnimated::Save
929 ================
930 */
931 void idAnimated::Save( idSaveGame *savefile ) const {
932         savefile->WriteInt( current_anim_index );
933         savefile->WriteInt( num_anims );
934         savefile->WriteInt( anim );
935         savefile->WriteInt( blendFrames );
936         savefile->WriteJoint( soundJoint );
937         activator.Save( savefile );
938         savefile->WriteBool( activated );
939 }
940
941 /*
942 ===============
943 idAnimated::Restore
944 ================
945 */
946 void idAnimated::Restore( idRestoreGame *savefile ) {
947         savefile->ReadInt( current_anim_index );
948         savefile->ReadInt( num_anims );
949         savefile->ReadInt( anim );
950         savefile->ReadInt( blendFrames );
951         savefile->ReadJoint( soundJoint );
952         activator.Restore( savefile );
953         savefile->ReadBool( activated );
954 }
955
956 /*
957 ===============
958 idAnimated::Spawn
959 ================
960 */
961 void idAnimated::Spawn( void ) {
962         idStr           animname;
963         int                     anim2;
964         float           wait;
965         const char      *joint;
966
967         joint = spawnArgs.GetString( "sound_bone", "origin" ); 
968         soundJoint = animator.GetJointHandle( joint );
969         if ( soundJoint == INVALID_JOINT ) {
970                 gameLocal.Warning( "idAnimated '%s' at (%s): cannot find joint '%s' for sound playback", name.c_str(), GetPhysics()->GetOrigin().ToString(0), joint );
971         }
972
973         LoadAF();
974
975         // allow bullets to collide with a combat model
976         if ( spawnArgs.GetBool( "combatModel", "0" ) ) {
977                 combatModel = new idClipModel( modelDefHandle );
978         }
979
980         // allow the entity to take damage
981         if ( spawnArgs.GetBool( "takeDamage", "0" ) ) {
982                 fl.takedamage = true;
983         }
984
985         blendFrames = 0;
986
987         current_anim_index = 0;
988         spawnArgs.GetInt( "num_anims", "0", num_anims );
989
990         blendFrames = spawnArgs.GetInt( "blend_in" );
991
992         animname = spawnArgs.GetString( num_anims ? "anim1" : "anim" );
993         if ( !animname.Length() ) {
994                 anim = 0;
995         } else {
996                 anim = animator.GetAnim( animname );
997                 if ( !anim ) {
998                         gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() );
999                 }
1000         }
1001
1002         if ( spawnArgs.GetBool( "hide" ) ) {
1003                 Hide();
1004
1005                 if ( !num_anims ) {
1006                         blendFrames = 0;
1007                 }
1008         } else if ( spawnArgs.GetString( "start_anim", "", animname ) ) {
1009                 anim2 = animator.GetAnim( animname );
1010                 if ( !anim2 ) {
1011                         gameLocal.Error( "idAnimated '%s' at (%s): cannot find anim '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), animname.c_str() );
1012                 }
1013                 animator.CycleAnim( ANIMCHANNEL_ALL, anim2, gameLocal.time, 0 );
1014         } else if ( anim ) {
1015                 // init joints to the first frame of the animation
1016                 animator.SetFrame( ANIMCHANNEL_ALL, anim, 1, gameLocal.time, 0 );               
1017
1018                 if ( !num_anims ) {
1019                         blendFrames = 0;
1020                 }
1021         }
1022
1023         spawnArgs.GetFloat( "wait", "-1", wait );
1024
1025         if ( wait >= 0 ) {
1026                 PostEventSec( &EV_Activate, wait, this );
1027         }
1028 }
1029
1030 /*
1031 ===============
1032 idAnimated::LoadAF
1033 ===============
1034 */
1035 bool idAnimated::LoadAF( void ) {
1036         idStr fileName;
1037
1038         if ( !spawnArgs.GetString( "ragdoll", "*unknown*", fileName ) ) {
1039                 return false;
1040         }
1041         af.SetAnimator( GetAnimator() );
1042         return af.Load( this, fileName );
1043 }
1044
1045 /*
1046 ===============
1047 idAnimated::GetPhysicsToSoundTransform
1048 ===============
1049 */
1050 bool idAnimated::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) {
1051         animator.GetJointTransform( soundJoint, gameLocal.time, origin, axis );
1052         axis = renderEntity.axis;
1053         return true;
1054 }
1055
1056 /*
1057 ================
1058 idAnimated::StartRagdoll
1059 ================
1060 */
1061 bool idAnimated::StartRagdoll( void ) {
1062         // if no AF loaded
1063         if ( !af.IsLoaded() ) {
1064                 return false;
1065         }
1066
1067         // if the AF is already active
1068         if ( af.IsActive() ) {
1069                 return true;
1070         }
1071
1072         // disable any collision model used
1073         GetPhysics()->DisableClip();
1074
1075         // start using the AF
1076         af.StartFromCurrentPose( spawnArgs.GetInt( "velocityTime", "0" ) );
1077         
1078         return true;
1079 }
1080
1081 /*
1082 =====================
1083 idAnimated::PlayNextAnim
1084 =====================
1085 */
1086 void idAnimated::PlayNextAnim( void ) {
1087         const char *animname;
1088         int len;
1089         int cycle;
1090
1091         if ( current_anim_index >= num_anims ) {
1092                 Hide();
1093                 if ( spawnArgs.GetBool( "remove" ) ) {
1094                         PostEventMS( &EV_Remove, 0 );
1095                 } else {
1096                         current_anim_index = 0;
1097                 }
1098                 return;
1099         }
1100
1101         Show();
1102         current_anim_index++;
1103
1104         spawnArgs.GetString( va( "anim%d", current_anim_index ), NULL, &animname );
1105         if ( !animname ) {
1106                 anim = 0;
1107                 animator.Clear( ANIMCHANNEL_ALL, gameLocal.time, FRAME2MS( blendFrames ) );
1108                 return;
1109         }
1110
1111         anim = animator.GetAnim( animname );
1112         if ( !anim ) {
1113                 gameLocal.Warning( "missing anim '%s' on %s", animname, name.c_str() );
1114                 return;
1115         }
1116
1117         if ( g_debugCinematic.GetBool() ) {
1118                 gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animname );
1119         }
1120                 
1121         spawnArgs.GetInt( "cycle", "1", cycle );
1122         if ( ( current_anim_index == num_anims ) && spawnArgs.GetBool( "loop_last_anim" ) ) {
1123                 cycle = -1;
1124         }
1125
1126         animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) );
1127         animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle );
1128
1129         len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength();
1130         if ( len >= 0 ) {
1131                 PostEventMS( &EV_AnimDone, len, current_anim_index );
1132         }
1133
1134         // offset the start time of the shader to sync it to the game time
1135         renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
1136
1137         animator.ForceUpdate();
1138         UpdateAnimation();
1139         UpdateVisuals();
1140         Present();
1141 }
1142
1143 /*
1144 ===============
1145 idAnimated::Event_StartRagdoll
1146 ================
1147 */
1148 void idAnimated::Event_StartRagdoll( void ) {
1149         StartRagdoll();
1150 }
1151
1152 /*
1153 ===============
1154 idAnimated::Event_AnimDone
1155 ================
1156 */
1157 void idAnimated::Event_AnimDone( int animindex ) {
1158         if ( g_debugCinematic.GetBool() ) {
1159                 const idAnim *animPtr = animator.GetAnim( anim );
1160                 gameLocal.Printf( "%d: '%s' end anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" );
1161         }
1162
1163         if ( ( animindex >= num_anims ) && spawnArgs.GetBool( "remove" ) ) {
1164                 Hide();
1165                 PostEventMS( &EV_Remove, 0 );
1166         } else if ( spawnArgs.GetBool( "auto_advance" ) ) {
1167                 PlayNextAnim();
1168         } else {
1169                 activated = false;
1170         }
1171
1172         ActivateTargets( activator.GetEntity() );
1173 }
1174
1175 /*
1176 ===============
1177 idAnimated::Event_Activate
1178 ================
1179 */
1180 void idAnimated::Event_Activate( idEntity *_activator ) {
1181         if ( num_anims ) {
1182                 PlayNextAnim();
1183                 activator = _activator;
1184                 return;
1185         }
1186
1187         if ( activated ) {
1188                 // already activated
1189                 return;
1190         }
1191
1192         activated = true;
1193         activator = _activator;
1194         ProcessEvent( &EV_Animated_Start );
1195 }
1196
1197 /*
1198 ===============
1199 idAnimated::Event_Start
1200 ================
1201 */
1202 void idAnimated::Event_Start( void ) {
1203         int cycle;
1204         int len;
1205
1206         Show();
1207
1208         if ( num_anims ) {
1209                 PlayNextAnim();
1210                 return;
1211         }
1212
1213         if ( anim ) {
1214                 if ( g_debugCinematic.GetBool() ) {
1215                         const idAnim *animPtr = animator.GetAnim( anim );
1216                         gameLocal.Printf( "%d: '%s' start anim '%s'\n", gameLocal.framenum, GetName(), animPtr ? animPtr->Name() : "" );
1217                 }
1218                 spawnArgs.GetInt( "cycle", "1", cycle );
1219                 animator.CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, FRAME2MS( blendFrames ) );
1220                 animator.CurrentAnim( ANIMCHANNEL_ALL )->SetCycleCount( cycle );
1221
1222                 len = animator.CurrentAnim( ANIMCHANNEL_ALL )->PlayLength();
1223                 if ( len >= 0 ) {
1224                         PostEventMS( &EV_AnimDone, len, 1 );
1225                 }
1226         }
1227
1228         // offset the start time of the shader to sync it to the game time
1229         renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
1230
1231         animator.ForceUpdate();
1232         UpdateAnimation();
1233         UpdateVisuals();
1234         Present();
1235 }
1236
1237 /*
1238 ===============
1239 idAnimated::Event_Footstep
1240 ===============
1241 */
1242 void idAnimated::Event_Footstep( void ) {
1243         StartSound( "snd_footstep", SND_CHANNEL_BODY, 0, false, NULL );
1244 }
1245
1246 /*
1247 =====================
1248 idAnimated::Event_LaunchMissilesUpdate
1249 =====================
1250 */
1251 void idAnimated::Event_LaunchMissilesUpdate( int launchjoint, int targetjoint, int numshots, int framedelay ) {
1252         idVec3                  launchPos;
1253         idVec3                  targetPos;
1254         idMat3                  axis;
1255         idVec3                  dir;
1256         idEntity *              ent;
1257         idProjectile *  projectile;
1258         const idDict *  projectileDef;
1259         const char *    projectilename;
1260
1261         projectilename = spawnArgs.GetString( "projectilename" );
1262         projectileDef = gameLocal.FindEntityDefDict( projectilename, false );
1263         if ( !projectileDef ) {
1264                 gameLocal.Warning( "idAnimated '%s' at (%s): 'launchMissiles' called with unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename );
1265                 return;
1266         }
1267
1268         StartSound( "snd_missile", SND_CHANNEL_WEAPON, 0, false, NULL );
1269
1270         animator.GetJointTransform( ( jointHandle_t )launchjoint, gameLocal.time, launchPos, axis );
1271         launchPos = renderEntity.origin + launchPos * renderEntity.axis;
1272         
1273         animator.GetJointTransform( ( jointHandle_t )targetjoint, gameLocal.time, targetPos, axis );
1274         targetPos = renderEntity.origin + targetPos * renderEntity.axis;
1275
1276         dir = targetPos - launchPos;
1277         dir.Normalize();
1278
1279         gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
1280         if ( !ent || !ent->IsType( idProjectile::Type ) ) {
1281                 gameLocal.Error( "idAnimated '%s' at (%s): in 'launchMissiles' call '%s' is not an idProjectile", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename );
1282         }
1283         projectile = ( idProjectile * )ent;
1284         projectile->Create( this, launchPos, dir );
1285         projectile->Launch( launchPos, dir, vec3_origin );
1286
1287         if ( numshots > 0 ) {
1288                 PostEventMS( &EV_LaunchMissilesUpdate, FRAME2MS( framedelay ), launchjoint, targetjoint, numshots - 1, framedelay );
1289         }
1290 }
1291
1292 /*
1293 =====================
1294 idAnimated::Event_LaunchMissiles
1295 =====================
1296 */
1297 void idAnimated::Event_LaunchMissiles( const char *projectilename, const char *sound, const char *launchjoint, const char *targetjoint, int numshots, int framedelay ) {
1298         const idDict *  projectileDef;
1299         jointHandle_t   launch;
1300         jointHandle_t   target;
1301
1302         projectileDef = gameLocal.FindEntityDefDict( projectilename, false );
1303         if ( !projectileDef ) {
1304                 gameLocal.Warning( "idAnimated '%s' at (%s): unknown projectile '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), projectilename );
1305                 return;
1306         }
1307
1308         launch = animator.GetJointHandle( launchjoint );
1309         if ( launch == INVALID_JOINT ) {
1310                 gameLocal.Warning( "idAnimated '%s' at (%s): unknown launch joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), launchjoint );
1311                 gameLocal.Error( "Unknown joint '%s'", launchjoint );
1312         }
1313
1314         target = animator.GetJointHandle( targetjoint );
1315         if ( target == INVALID_JOINT ) {
1316                 gameLocal.Warning( "idAnimated '%s' at (%s): unknown target joint '%s'", name.c_str(), GetPhysics()->GetOrigin().ToString(0), targetjoint );
1317         }
1318
1319         spawnArgs.Set( "projectilename", projectilename );
1320         spawnArgs.Set( "missilesound", sound );
1321
1322         CancelEvents( &EV_LaunchMissilesUpdate );
1323         ProcessEvent( &EV_LaunchMissilesUpdate, launch, target, numshots - 1, framedelay );
1324 }
1325
1326
1327 /*
1328 ===============================================================================
1329
1330         idStaticEntity
1331
1332         Some static entities may be optimized into inline geometry by dmap
1333
1334 ===============================================================================
1335 */
1336
1337 CLASS_DECLARATION( idEntity, idStaticEntity )
1338         EVENT( EV_Activate,                             idStaticEntity::Event_Activate )
1339 END_CLASS
1340
1341 /*
1342 ===============
1343 idStaticEntity::idStaticEntity
1344 ===============
1345 */
1346 idStaticEntity::idStaticEntity( void ) {
1347         spawnTime = 0;
1348         active = false;
1349         fadeFrom.Set( 1, 1, 1, 1 );
1350         fadeTo.Set( 1, 1, 1, 1 );
1351         fadeStart = 0;
1352         fadeEnd = 0;
1353         runGui = false;
1354 }
1355
1356 /*
1357 ===============
1358 idStaticEntity::Save
1359 ===============
1360 */
1361 void idStaticEntity::Save( idSaveGame *savefile ) const {
1362         savefile->WriteInt( spawnTime );
1363         savefile->WriteBool( active );
1364         savefile->WriteVec4( fadeFrom );
1365         savefile->WriteVec4( fadeTo );
1366         savefile->WriteInt( fadeStart );
1367         savefile->WriteInt( fadeEnd );
1368         savefile->WriteBool( runGui );
1369 }
1370
1371 /*
1372 ===============
1373 idStaticEntity::Restore
1374 ===============
1375 */
1376 void idStaticEntity::Restore( idRestoreGame *savefile ) {
1377         savefile->ReadInt( spawnTime );
1378         savefile->ReadBool( active );
1379         savefile->ReadVec4( fadeFrom );
1380         savefile->ReadVec4( fadeTo );
1381         savefile->ReadInt( fadeStart );
1382         savefile->ReadInt( fadeEnd );
1383         savefile->ReadBool( runGui );
1384 }
1385
1386 /*
1387 ===============
1388 idStaticEntity::Spawn
1389 ===============
1390 */
1391 void idStaticEntity::Spawn( void ) {
1392         bool solid;
1393         bool hidden;
1394
1395         // an inline static model will not do anything at all
1396         if ( spawnArgs.GetBool( "inline" ) || gameLocal.world->spawnArgs.GetBool( "inlineAllStatics" ) ) {
1397                 Hide();
1398                 return;
1399         }
1400
1401         solid = spawnArgs.GetBool( "solid" );
1402         hidden = spawnArgs.GetBool( "hide" );
1403
1404         if ( solid && !hidden ) {
1405                 GetPhysics()->SetContents( CONTENTS_SOLID );
1406         } else {
1407                 GetPhysics()->SetContents( 0 );
1408         }
1409
1410         spawnTime = gameLocal.time;
1411         active = false;
1412
1413         idStr model = spawnArgs.GetString( "model" );
1414         if ( model.Find( ".prt" ) >= 0 ) {
1415                 // we want the parametric particles out of sync with each other
1416                 renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = gameLocal.random.RandomInt( 32767 );
1417         }
1418
1419         fadeFrom.Set( 1, 1, 1, 1 );
1420         fadeTo.Set( 1, 1, 1, 1 );
1421         fadeStart = 0;
1422         fadeEnd = 0;
1423
1424         // NOTE: this should be used very rarely because it is expensive
1425         runGui = spawnArgs.GetBool( "runGui" );
1426         if ( runGui ) {
1427                 BecomeActive( TH_THINK );
1428         }
1429 }
1430
1431 /*
1432 ================
1433 idStaticEntity::ShowEditingDialog
1434 ================
1435 */
1436 void idStaticEntity::ShowEditingDialog( void ) {
1437         common->InitTool( EDITOR_PARTICLE, &spawnArgs );
1438 }
1439 /*
1440 ================
1441 idStaticEntity::Think
1442 ================
1443 */
1444 void idStaticEntity::Think( void ) {
1445         idEntity::Think();
1446         if ( thinkFlags & TH_THINK ) {
1447                 if ( runGui && renderEntity.gui[0] ) {
1448                         idPlayer *player = gameLocal.GetLocalPlayer();
1449                         if ( player ) {
1450                                 if ( !player->objectiveSystemOpen ) {
1451                                         renderEntity.gui[0]->StateChanged( gameLocal.time, true );
1452                                         if ( renderEntity.gui[1] ) {
1453                                                 renderEntity.gui[1]->StateChanged( gameLocal.time, true );
1454                                         }
1455                                         if ( renderEntity.gui[2] ) {
1456                                                 renderEntity.gui[2]->StateChanged( gameLocal.time, true );
1457                                         }
1458                                 }
1459                         }
1460                 }
1461                 if ( fadeEnd > 0 ) {
1462                         idVec4 color;
1463                         if ( gameLocal.time < fadeEnd ) {
1464                                 color.Lerp( fadeFrom, fadeTo, ( float )( gameLocal.time - fadeStart ) / ( float )( fadeEnd - fadeStart ) );
1465                         } else {
1466                                 color = fadeTo;
1467                                 fadeEnd = 0;
1468                                 BecomeInactive( TH_THINK );
1469                         }
1470                         SetColor( color );
1471                 }
1472         }
1473 }
1474
1475 /*
1476 ================
1477 idStaticEntity::Fade
1478 ================
1479 */
1480 void idStaticEntity::Fade( const idVec4 &to, float fadeTime ) {
1481         GetColor( fadeFrom );
1482         fadeTo = to;
1483         fadeStart = gameLocal.time;
1484         fadeEnd = gameLocal.time + SEC2MS( fadeTime );
1485         BecomeActive( TH_THINK );
1486 }
1487
1488 /*
1489 ================
1490 idStaticEntity::Hide
1491 ================
1492 */
1493 void idStaticEntity::Hide( void ) {
1494         idEntity::Hide();
1495         GetPhysics()->SetContents( 0 );
1496 }
1497
1498 /*
1499 ================
1500 idStaticEntity::Show
1501 ================
1502 */
1503 void idStaticEntity::Show( void ) {
1504         idEntity::Show();
1505         if ( spawnArgs.GetBool( "solid" ) ) {
1506                 GetPhysics()->SetContents( CONTENTS_SOLID );
1507         }
1508 }
1509
1510 /*
1511 ================
1512 idStaticEntity::Event_Activate
1513 ================
1514 */
1515 void idStaticEntity::Event_Activate( idEntity *activator ) {
1516         idStr activateGui;
1517
1518         spawnTime = gameLocal.time;
1519         active = !active;
1520
1521         const idKeyValue *kv = spawnArgs.FindKey( "hide" );
1522         if ( kv ) {
1523                 if ( IsHidden() ) {
1524                         Show();
1525                 } else {
1526                         Hide();
1527                 }
1528         }
1529
1530         renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( spawnTime );
1531         renderEntity.shaderParms[5] = active;
1532         // this change should be a good thing, it will automatically turn on 
1533         // lights etc.. when triggered so that does not have to be specifically done
1534         // with trigger parms.. it MIGHT break things so need to keep an eye on it
1535         renderEntity.shaderParms[ SHADERPARM_MODE ] = ( renderEntity.shaderParms[ SHADERPARM_MODE ] ) ?  0.0f : 1.0f;
1536         BecomeActive( TH_UPDATEVISUALS );
1537 }
1538
1539 /*
1540 ================
1541 idStaticEntity::WriteToSnapshot
1542 ================
1543 */
1544 void idStaticEntity::WriteToSnapshot( idBitMsgDelta &msg ) const {
1545         GetPhysics()->WriteToSnapshot( msg );
1546         WriteBindToSnapshot( msg );
1547         WriteColorToSnapshot( msg );
1548         WriteGUIToSnapshot( msg );
1549         msg.WriteBits( IsHidden()?1:0, 1 );
1550 }
1551
1552 /*
1553 ================
1554 idStaticEntity::ReadFromSnapshot
1555 ================
1556 */
1557 void idStaticEntity::ReadFromSnapshot( const idBitMsgDelta &msg ) {
1558         bool hidden;
1559
1560         GetPhysics()->ReadFromSnapshot( msg );
1561         ReadBindFromSnapshot( msg );
1562         ReadColorFromSnapshot( msg );
1563         ReadGUIFromSnapshot( msg );
1564         hidden = msg.ReadBits( 1 ) == 1;
1565         if ( hidden != IsHidden() ) {
1566                 if ( hidden ) {
1567                         Hide();
1568                 } else {
1569                         Show();
1570                 }
1571         }
1572         if ( msg.HasChanged() ) {
1573                 UpdateVisuals();
1574         }
1575 }
1576
1577
1578 /*
1579 ===============================================================================
1580
1581 idFuncEmitter
1582
1583 ===============================================================================
1584 */
1585
1586
1587 CLASS_DECLARATION( idStaticEntity, idFuncEmitter )
1588 EVENT( EV_Activate,                             idFuncEmitter::Event_Activate )
1589 END_CLASS
1590
1591 /*
1592 ===============
1593 idFuncEmitter::idFuncEmitter
1594 ===============
1595 */
1596 idFuncEmitter::idFuncEmitter( void ) {
1597         hidden = false;
1598 }
1599
1600 /*
1601 ===============
1602 idFuncEmitter::Spawn
1603 ===============
1604 */
1605 void idFuncEmitter::Spawn( void ) {
1606         if ( spawnArgs.GetBool( "start_off" ) ) {
1607                 hidden = true;
1608                 renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( 1 );
1609                 UpdateVisuals();
1610         } else {
1611                 hidden = false;
1612         }
1613 }
1614
1615 /*
1616 ===============
1617 idFuncEmitter::Save
1618 ===============
1619 */
1620 void idFuncEmitter::Save( idSaveGame *savefile ) const {
1621         savefile->WriteBool( hidden );
1622 }
1623
1624 /*
1625 ===============
1626 idFuncEmitter::Restore
1627 ===============
1628 */
1629 void idFuncEmitter::Restore( idRestoreGame *savefile ) {
1630         savefile->ReadBool( hidden );
1631 }
1632
1633 /*
1634 ================
1635 idFuncEmitter::Event_Activate
1636 ================
1637 */
1638 void idFuncEmitter::Event_Activate( idEntity *activator ) {
1639         if ( hidden || spawnArgs.GetBool( "cycleTrigger" ) ) {
1640                 renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = 0;
1641                 renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
1642                 hidden = false;
1643         } else {
1644                 renderEntity.shaderParms[SHADERPARM_PARTICLE_STOPTIME] = MS2SEC( gameLocal.time );
1645                 hidden = true;
1646         }
1647         UpdateVisuals();
1648 }
1649
1650 /*
1651 ================
1652 idFuncEmitter::WriteToSnapshot
1653 ================
1654 */
1655 void idFuncEmitter::WriteToSnapshot( idBitMsgDelta &msg ) const {
1656         msg.WriteBits( hidden ? 1 : 0, 1 );
1657         msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] );
1658         msg.WriteFloat( renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] );
1659 }
1660
1661 /*
1662 ================
1663 idFuncEmitter::ReadFromSnapshot
1664 ================
1665 */
1666 void idFuncEmitter::ReadFromSnapshot( const idBitMsgDelta &msg ) {
1667         hidden = msg.ReadBits( 1 ) != 0;
1668         renderEntity.shaderParms[ SHADERPARM_PARTICLE_STOPTIME ] = msg.ReadFloat();
1669         renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = msg.ReadFloat();
1670         if ( msg.HasChanged() ) {
1671                 UpdateVisuals();
1672         }
1673 }
1674
1675
1676 /*
1677 ===============================================================================
1678
1679 idFuncSplat
1680
1681 ===============================================================================
1682 */
1683
1684
1685 const idEventDef EV_Splat( "<Splat>" );
1686 CLASS_DECLARATION( idFuncEmitter, idFuncSplat )
1687 EVENT( EV_Activate,             idFuncSplat::Event_Activate )
1688 EVENT( EV_Splat,                idFuncSplat::Event_Splat )
1689 END_CLASS
1690
1691 /*
1692 ===============
1693 idFuncSplat::idFuncSplat
1694 ===============
1695 */
1696 idFuncSplat::idFuncSplat( void ) {
1697 }
1698
1699 /*
1700 ===============
1701 idFuncSplat::Spawn
1702 ===============
1703 */
1704 void idFuncSplat::Spawn( void ) {
1705 }
1706
1707 /*
1708 ================
1709 idFuncSplat::Event_Splat
1710 ================
1711 */
1712 void idFuncSplat::Event_Splat( void ) {
1713         const char *splat = NULL;
1714         int count = spawnArgs.GetInt( "splatCount", "1" );
1715         for ( int i = 0; i < count; i++ ) {
1716                 splat = spawnArgs.RandomPrefix( "mtr_splat", gameLocal.random );
1717                 if ( splat && *splat ) {
1718                         float size = spawnArgs.GetFloat( "splatSize", "128" );
1719                         float dist = spawnArgs.GetFloat( "splatDistance", "128" );
1720                         float angle = spawnArgs.GetFloat( "splatAngle", "0" );
1721                         gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[2], dist, true, size, splat, angle );
1722                 }
1723         }
1724         StartSound( "snd_splat", SND_CHANNEL_ANY, 0, false, NULL );
1725 }
1726
1727 /*
1728 ================
1729 idFuncSplat::Event_Activate
1730 ================
1731 */
1732 void idFuncSplat::Event_Activate( idEntity *activator ) {
1733         idFuncEmitter::Event_Activate( activator );
1734         PostEventSec( &EV_Splat, spawnArgs.GetFloat( "splatDelay", "0.25" ) );
1735         StartSound( "snd_spurt", SND_CHANNEL_ANY, 0, false, NULL );
1736 }
1737
1738 /*
1739 ===============================================================================
1740
1741 idFuncSmoke
1742
1743 ===============================================================================
1744 */
1745
1746 CLASS_DECLARATION( idEntity, idFuncSmoke )
1747 EVENT( EV_Activate,                             idFuncSmoke::Event_Activate )
1748 END_CLASS
1749
1750 /*
1751 ===============
1752 idFuncSmoke::idFuncSmoke
1753 ===============
1754 */
1755 idFuncSmoke::idFuncSmoke() {
1756         smokeTime = 0;
1757         smoke = NULL;
1758         restart = false;
1759 }
1760
1761 /*
1762 ===============
1763 idFuncSmoke::Save
1764 ===============
1765 */
1766 void idFuncSmoke::Save( idSaveGame *savefile ) const {
1767         savefile->WriteInt( smokeTime );
1768         savefile->WriteParticle( smoke );
1769         savefile->WriteBool( restart );
1770 }
1771
1772 /*
1773 ===============
1774 idFuncSmoke::Restore
1775 ===============
1776 */
1777 void idFuncSmoke::Restore( idRestoreGame *savefile ) {
1778         savefile->ReadInt( smokeTime );
1779         savefile->ReadParticle( smoke );
1780         savefile->ReadBool( restart );
1781 }
1782
1783 /*
1784 ===============
1785 idFuncSmoke::Spawn
1786 ===============
1787 */
1788 void idFuncSmoke::Spawn( void ) {
1789         const char *smokeName = spawnArgs.GetString( "smoke" );
1790         if ( *smokeName != '\0' ) {
1791                 smoke = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) );
1792         } else {
1793                 smoke = NULL;
1794         }
1795         if ( spawnArgs.GetBool( "start_off" ) ) {
1796                 smokeTime = 0;
1797                 restart = false;
1798         } else if ( smoke ) {
1799                 smokeTime = gameLocal.time;
1800                 BecomeActive( TH_UPDATEPARTICLES );
1801                 restart = true;
1802         }
1803         GetPhysics()->SetContents( 0 );
1804 }
1805
1806 /*
1807 ================
1808 idFuncSmoke::Event_Activate
1809 ================
1810 */
1811 void idFuncSmoke::Event_Activate( idEntity *activator ) {
1812         if ( thinkFlags & TH_UPDATEPARTICLES ) {
1813                 restart = false;
1814                 return;
1815         } else {
1816                 BecomeActive( TH_UPDATEPARTICLES );
1817                 restart = true;
1818                 smokeTime = gameLocal.time;
1819         }
1820 }
1821
1822 /*
1823 ===============
1824 idFuncSmoke::Think
1825 ================
1826 */
1827 void idFuncSmoke::Think( void ) {
1828
1829         // if we are completely closed off from the player, don't do anything at all
1830         if ( CheckDormant() || smoke == NULL || smokeTime == -1 ) {
1831                 return;
1832         }
1833
1834         if ( ( thinkFlags & TH_UPDATEPARTICLES) && !IsHidden() ) {
1835                 if ( !gameLocal.smokeParticles->EmitSmoke( smoke, smokeTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ) ) {
1836                         if ( restart ) {
1837                                 smokeTime = gameLocal.time;
1838                         } else {
1839                                 smokeTime = 0;
1840                                 BecomeInactive( TH_UPDATEPARTICLES );
1841                         }
1842                 }
1843         }
1844
1845 }
1846
1847
1848 /*
1849 ===============================================================================
1850
1851         idTextEntity
1852
1853 ===============================================================================
1854 */
1855
1856 CLASS_DECLARATION( idEntity, idTextEntity )
1857 END_CLASS
1858
1859 /*
1860 ================
1861 idTextEntity::Spawn
1862 ================
1863 */
1864 void idTextEntity::Spawn( void ) {
1865         // these are cached as the are used each frame
1866         text = spawnArgs.GetString( "text" );
1867         playerOriented = spawnArgs.GetBool( "playerOriented" );
1868         bool force = spawnArgs.GetBool( "force" );
1869         if ( developer.GetBool() || force ) {
1870                 BecomeActive(TH_THINK);
1871         }
1872 }
1873
1874 /*
1875 ================
1876 idTextEntity::Save
1877 ================
1878 */
1879 void idTextEntity::Save( idSaveGame *savefile ) const {
1880         savefile->WriteString( text );
1881         savefile->WriteBool( playerOriented );
1882 }
1883
1884 /*
1885 ================
1886 idTextEntity::Restore
1887 ================
1888 */
1889 void idTextEntity::Restore( idRestoreGame *savefile ) {
1890         savefile->ReadString( text );
1891         savefile->ReadBool( playerOriented );
1892 }
1893
1894 /*
1895 ================
1896 idTextEntity::Think
1897 ================
1898 */
1899 void idTextEntity::Think( void ) {
1900         if ( thinkFlags & TH_THINK ) {
1901                 gameRenderWorld->DrawText( text, GetPhysics()->GetOrigin(), 0.25, colorWhite, playerOriented ? gameLocal.GetLocalPlayer()->viewAngles.ToMat3() : GetPhysics()->GetAxis().Transpose(), 1 );
1902                 for ( int i = 0; i < targets.Num(); i++ ) {
1903                         if ( targets[i].GetEntity() ) {
1904                                 gameRenderWorld->DebugArrow( colorBlue, GetPhysics()->GetOrigin(), targets[i].GetEntity()->GetPhysics()->GetOrigin(), 1 );
1905                         }
1906                 }
1907         } else {
1908                 BecomeInactive( TH_ALL );
1909         }
1910 }
1911
1912
1913 /*
1914 ===============================================================================
1915
1916         idVacuumSeperatorEntity
1917
1918         Can be triggered to let vacuum through a portal (blown out window)
1919
1920 ===============================================================================
1921 */
1922
1923 CLASS_DECLARATION( idEntity, idVacuumSeparatorEntity )
1924         EVENT( EV_Activate,             idVacuumSeparatorEntity::Event_Activate )
1925 END_CLASS
1926
1927
1928 /*
1929 ================
1930 idVacuumSeparatorEntity::idVacuumSeparatorEntity
1931 ================
1932 */
1933 idVacuumSeparatorEntity::idVacuumSeparatorEntity( void ) {
1934         portal = 0;
1935 }
1936
1937 /*
1938 ================
1939 idVacuumSeparatorEntity::Save
1940 ================
1941 */
1942 void idVacuumSeparatorEntity::Save( idSaveGame *savefile ) const {
1943         savefile->WriteInt( (int)portal );
1944         savefile->WriteInt( gameRenderWorld->GetPortalState( portal ) );
1945 }
1946
1947 /*
1948 ================
1949 idVacuumSeparatorEntity::Restore
1950 ================
1951 */
1952 void idVacuumSeparatorEntity::Restore( idRestoreGame *savefile ) {
1953         int state;
1954
1955         savefile->ReadInt( (int &)portal );
1956         savefile->ReadInt( state );
1957
1958         gameLocal.SetPortalState( portal, state );
1959 }
1960
1961 /*
1962 ================
1963 idVacuumSeparatorEntity::Spawn
1964 ================
1965 */
1966 void idVacuumSeparatorEntity::Spawn() {
1967         idBounds b;
1968
1969         b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 );
1970         portal = gameRenderWorld->FindPortal( b );
1971         if ( !portal ) {
1972                 gameLocal.Warning( "VacuumSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) );
1973                 return;
1974         }
1975         gameLocal.SetPortalState( portal, PS_BLOCK_AIR | PS_BLOCK_LOCATION );
1976 }
1977
1978 /*
1979 ================
1980 idVacuumSeparatorEntity::Event_Activate
1981 ================
1982 */
1983 void idVacuumSeparatorEntity::Event_Activate( idEntity *activator ) {
1984         if ( !portal ) {
1985                 return;
1986         }
1987         gameLocal.SetPortalState( portal, PS_BLOCK_NONE );
1988 }
1989
1990
1991 /*
1992 ===============================================================================
1993
1994 idLocationSeparatorEntity
1995
1996 ===============================================================================
1997 */
1998
1999 CLASS_DECLARATION( idEntity, idLocationSeparatorEntity )
2000 END_CLASS
2001
2002 /*
2003 ================
2004 idLocationSeparatorEntity::Spawn
2005 ================
2006 */
2007 void idLocationSeparatorEntity::Spawn() {
2008         idBounds b;
2009
2010         b = idBounds( spawnArgs.GetVector( "origin" ) ).Expand( 16 );
2011         qhandle_t portal = gameRenderWorld->FindPortal( b );
2012         if ( !portal ) {
2013                 gameLocal.Warning( "LocationSeparator '%s' didn't contact a portal", spawnArgs.GetString( "name" ) );
2014         }
2015         gameLocal.SetPortalState( portal, PS_BLOCK_LOCATION );
2016 }
2017
2018
2019 /*
2020 ===============================================================================
2021
2022         idVacuumEntity
2023
2024         Levels should only have a single vacuum entity.
2025
2026 ===============================================================================
2027 */
2028
2029 CLASS_DECLARATION( idEntity, idVacuumEntity )
2030 END_CLASS
2031
2032 /*
2033 ================
2034 idVacuumEntity::Spawn
2035 ================
2036 */
2037 void idVacuumEntity::Spawn() {
2038         if ( gameLocal.vacuumAreaNum != -1 ) {
2039                 gameLocal.Warning( "idVacuumEntity::Spawn: multiple idVacuumEntity in level" );
2040                 return;
2041         }
2042
2043         idVec3 org = spawnArgs.GetVector( "origin" );
2044
2045         gameLocal.vacuumAreaNum = gameRenderWorld->PointInArea( org );
2046 }
2047
2048
2049 /*
2050 ===============================================================================
2051
2052 idLocationEntity
2053
2054 ===============================================================================
2055 */
2056
2057 CLASS_DECLARATION( idEntity, idLocationEntity )
2058 END_CLASS
2059
2060 /*
2061 ======================
2062 idLocationEntity::Spawn
2063 ======================
2064 */
2065 void idLocationEntity::Spawn() {
2066         idStr realName;
2067
2068         // this just holds dict information
2069
2070         // if "location" not already set, use the entity name.
2071         if ( !spawnArgs.GetString( "location", "", realName ) ) {
2072                 spawnArgs.Set( "location", name );
2073         }
2074 }
2075
2076 /*
2077 ======================
2078 idLocationEntity::GetLocation
2079 ======================
2080 */
2081 const char *idLocationEntity::GetLocation( void ) const {
2082         return spawnArgs.GetString( "location" );
2083 }
2084
2085 /*
2086 ===============================================================================
2087
2088         idBeam
2089
2090 ===============================================================================
2091 */
2092
2093 CLASS_DECLARATION( idEntity, idBeam )
2094         EVENT( EV_PostSpawn,                    idBeam::Event_MatchTarget )
2095         EVENT( EV_Activate,                             idBeam::Event_Activate )
2096 END_CLASS
2097
2098 /*
2099 ===============
2100 idBeam::idBeam
2101 ===============
2102 */
2103 idBeam::idBeam() {
2104         target = NULL;
2105         master = NULL;
2106 }
2107
2108 /*
2109 ===============
2110 idBeam::Save
2111 ===============
2112 */
2113 void idBeam::Save( idSaveGame *savefile ) const {
2114         target.Save( savefile );
2115         master.Save( savefile );
2116 }
2117
2118 /*
2119 ===============
2120 idBeam::Restore
2121 ===============
2122 */
2123 void idBeam::Restore( idRestoreGame *savefile ) {
2124         target.Restore( savefile );
2125         master.Restore( savefile );
2126 }
2127
2128 /*
2129 ===============
2130 idBeam::Spawn
2131 ===============
2132 */
2133 void idBeam::Spawn( void ) {
2134         float width;
2135
2136         if ( spawnArgs.GetFloat( "width", "0", width ) ) {
2137                 renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = width;
2138         }
2139
2140         SetModel( "_BEAM" );
2141         Hide();
2142         PostEventMS( &EV_PostSpawn, 0 );
2143 }
2144
2145 /*
2146 ================
2147 idBeam::Think
2148 ================
2149 */
2150 void idBeam::Think( void ) {
2151         idBeam *masterEnt;
2152
2153         if ( !IsHidden() && !target.GetEntity() ) {
2154                 // hide if our target is removed
2155                 Hide();
2156         }
2157
2158         RunPhysics();
2159
2160         masterEnt = master.GetEntity();
2161         if ( masterEnt ) {
2162                 const idVec3 &origin = GetPhysics()->GetOrigin();
2163                 masterEnt->SetBeamTarget( origin );
2164         }
2165         Present();
2166 }
2167
2168 /*
2169 ================
2170 idBeam::SetMaster
2171 ================
2172 */
2173 void idBeam::SetMaster( idBeam *masterbeam ) {
2174         master = masterbeam;
2175 }
2176
2177 /*
2178 ================
2179 idBeam::SetBeamTarget
2180 ================
2181 */
2182 void idBeam::SetBeamTarget( const idVec3 &origin ) {
2183         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 ) ) {
2184                 renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = origin.x;
2185                 renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = origin.y;
2186                 renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = origin.z;
2187                 UpdateVisuals();
2188         }
2189 }
2190
2191 /*
2192 ================
2193 idBeam::Show
2194 ================
2195 */
2196 void idBeam::Show( void ) {
2197         idBeam *targetEnt;
2198
2199         idEntity::Show();
2200
2201         targetEnt = target.GetEntity();
2202         if ( targetEnt ) {
2203                 const idVec3 &origin = targetEnt->GetPhysics()->GetOrigin();
2204                 SetBeamTarget( origin );
2205         }
2206 }
2207
2208 /*
2209 ================
2210 idBeam::Event_MatchTarget
2211 ================
2212 */
2213 void idBeam::Event_MatchTarget( void ) {
2214         int i;
2215         idEntity *targetEnt;
2216         idBeam *targetBeam;
2217
2218         if ( !targets.Num() ) {
2219                 return;
2220         }
2221
2222         targetBeam = NULL;
2223         for( i = 0; i < targets.Num(); i++ ) {
2224                 targetEnt = targets[ i ].GetEntity();
2225                 if ( targetEnt && targetEnt->IsType( idBeam::Type ) ) {
2226                         targetBeam = static_cast<idBeam *>( targetEnt );
2227                         break;
2228                 }
2229         }
2230
2231         if ( !targetBeam ) {
2232                 gameLocal.Error( "Could not find valid beam target for '%s'", name.c_str() );
2233         }
2234
2235         target = targetBeam;
2236         targetBeam->SetMaster( this );
2237         if ( !spawnArgs.GetBool( "start_off" ) ) {
2238                 Show();
2239         }
2240 }
2241
2242 /*
2243 ================
2244 idBeam::Event_Activate
2245 ================
2246 */
2247 void idBeam::Event_Activate( idEntity *activator ) {
2248         if ( IsHidden() ) {
2249                 Show();
2250         } else {
2251                 Hide();         
2252         }
2253 }
2254
2255 /*
2256 ================
2257 idBeam::WriteToSnapshot
2258 ================
2259 */
2260 void idBeam::WriteToSnapshot( idBitMsgDelta &msg ) const {
2261         GetPhysics()->WriteToSnapshot( msg );
2262         WriteBindToSnapshot( msg );
2263         WriteColorToSnapshot( msg );
2264         msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_X] );
2265         msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] );
2266         msg.WriteFloat( renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] );
2267 }
2268
2269 /*
2270 ================
2271 idBeam::ReadFromSnapshot
2272 ================
2273 */
2274 void idBeam::ReadFromSnapshot( const idBitMsgDelta &msg ) {
2275         GetPhysics()->ReadFromSnapshot( msg );
2276         ReadBindFromSnapshot( msg );
2277         ReadColorFromSnapshot( msg );
2278         renderEntity.shaderParms[SHADERPARM_BEAM_END_X] = msg.ReadFloat();
2279         renderEntity.shaderParms[SHADERPARM_BEAM_END_Y] = msg.ReadFloat();
2280         renderEntity.shaderParms[SHADERPARM_BEAM_END_Z] = msg.ReadFloat();
2281         if ( msg.HasChanged() ) {
2282                 UpdateVisuals();
2283         }
2284 }
2285
2286
2287 /*
2288 ===============================================================================
2289
2290         idLiquid
2291
2292 ===============================================================================
2293 */
2294
2295 CLASS_DECLARATION( idEntity, idLiquid )
2296         EVENT( EV_Touch,                        idLiquid::Event_Touch )
2297 END_CLASS
2298
2299 /*
2300 ================
2301 idLiquid::Save
2302 ================
2303 */
2304 void idLiquid::Save( idSaveGame *savefile ) const {
2305         // Nothing to save
2306 }
2307
2308 /*
2309 ================
2310 idLiquid::Restore
2311 ================
2312 */
2313 void idLiquid::Restore( idRestoreGame *savefile ) {
2314         //FIXME: NO!
2315         Spawn();
2316 }
2317
2318 /*
2319 ================
2320 idLiquid::Spawn
2321 ================
2322 */
2323 void idLiquid::Spawn() {
2324 /*
2325         model = dynamic_cast<idRenderModelLiquid *>( renderEntity.hModel );
2326         if ( !model ) {
2327                 gameLocal.Error( "Entity '%s' must have liquid model", name.c_str() );
2328         }
2329         model->Reset();
2330         GetPhysics()->SetContents( CONTENTS_TRIGGER );
2331 */
2332 }
2333
2334 /*
2335 ================
2336 idLiquid::Event_Touch
2337 ================
2338 */
2339 void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) {
2340         // FIXME: for QuakeCon
2341 /*
2342         idVec3 pos;
2343
2344         pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
2345         model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f );
2346 */
2347 }
2348
2349
2350 /*
2351 ===============================================================================
2352
2353         idShaking
2354
2355 ===============================================================================
2356 */
2357
2358 CLASS_DECLARATION( idEntity, idShaking )
2359         EVENT( EV_Activate,                             idShaking::Event_Activate )
2360 END_CLASS
2361
2362 /*
2363 ===============
2364 idShaking::idShaking
2365 ===============
2366 */
2367 idShaking::idShaking() {
2368         active = false;
2369 }
2370
2371 /*
2372 ===============
2373 idShaking::Save
2374 ===============
2375 */
2376 void idShaking::Save( idSaveGame *savefile ) const {
2377         savefile->WriteBool( active );
2378         savefile->WriteStaticObject( physicsObj );
2379 }
2380
2381 /*
2382 ===============
2383 idShaking::Restore
2384 ===============
2385 */
2386 void idShaking::Restore( idRestoreGame *savefile ) {
2387         savefile->ReadBool( active );
2388         savefile->ReadStaticObject( physicsObj );
2389         RestorePhysics( &physicsObj );
2390 }
2391
2392 /*
2393 ===============
2394 idShaking::Spawn
2395 ===============
2396 */
2397 void idShaking::Spawn( void ) {
2398         physicsObj.SetSelf( this );
2399         physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
2400         physicsObj.SetOrigin( GetPhysics()->GetOrigin() );
2401         physicsObj.SetAxis( GetPhysics()->GetAxis() );
2402         physicsObj.SetClipMask( MASK_SOLID );
2403         SetPhysics( &physicsObj );
2404         
2405         active = false;
2406         if ( !spawnArgs.GetBool( "start_off" ) ) {
2407                 BeginShaking();
2408         }
2409 }
2410
2411 /*
2412 ================
2413 idShaking::BeginShaking
2414 ================
2415 */
2416 void idShaking::BeginShaking( void ) {
2417         int                     phase;
2418         idAngles        shake;
2419         int                     period;
2420
2421         active = true;
2422         phase = gameLocal.random.RandomInt( 1000 );
2423         shake = spawnArgs.GetAngles( "shake", "0.5 0.5 0.5" );
2424         period = spawnArgs.GetFloat( "period", "0.05" ) * 1000;
2425         physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_DECELSINE|EXTRAPOLATION_NOSTOP), phase, period * 0.25f, GetPhysics()->GetAxis().ToAngles(), shake, ang_zero );
2426 }
2427
2428 /*
2429 ================
2430 idShaking::Event_Activate
2431 ================
2432 */
2433 void idShaking::Event_Activate( idEntity *activator ) {
2434         if ( !active ) {
2435                 BeginShaking();
2436         } else {
2437                 active = false;
2438                 physicsObj.SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, physicsObj.GetAxis().ToAngles(), ang_zero, ang_zero );
2439         }
2440 }
2441
2442 /*
2443 ===============================================================================
2444
2445         idEarthQuake
2446
2447 ===============================================================================
2448 */
2449
2450 CLASS_DECLARATION( idEntity, idEarthQuake )
2451         EVENT( EV_Activate,                             idEarthQuake::Event_Activate )
2452 END_CLASS
2453
2454 /*
2455 ===============
2456 idEarthQuake::idEarthQuake
2457 ===============
2458 */
2459 idEarthQuake::idEarthQuake() {
2460         wait = 0.0f;
2461         random = 0.0f;
2462         nextTriggerTime = 0;
2463         shakeStopTime = 0;
2464         triggered = false;
2465         playerOriented = false;
2466         disabled = false;
2467         shakeTime = 0.0f;
2468 }
2469
2470 /*
2471 ===============
2472 idEarthQuake::Save
2473 ===============
2474 */
2475 void idEarthQuake::Save( idSaveGame *savefile ) const {
2476         savefile->WriteInt( nextTriggerTime );
2477         savefile->WriteInt( shakeStopTime );
2478         savefile->WriteFloat( wait );
2479         savefile->WriteFloat( random );
2480         savefile->WriteBool( triggered );
2481         savefile->WriteBool( playerOriented );
2482         savefile->WriteBool( disabled );
2483         savefile->WriteFloat( shakeTime );
2484 }
2485
2486 /*
2487 ===============
2488 idEarthQuake::Restore
2489 ===============
2490 */
2491 void idEarthQuake::Restore( idRestoreGame *savefile ) {
2492         savefile->ReadInt( nextTriggerTime );
2493         savefile->ReadInt( shakeStopTime );
2494         savefile->ReadFloat( wait );
2495         savefile->ReadFloat( random );
2496         savefile->ReadBool( triggered );
2497         savefile->ReadBool( playerOriented );
2498         savefile->ReadBool( disabled );
2499         savefile->ReadFloat( shakeTime );
2500
2501         if ( shakeStopTime > gameLocal.time ) {
2502                 BecomeActive( TH_THINK );
2503         }
2504 }
2505
2506 /*
2507 ===============
2508 idEarthQuake::Spawn
2509 ===============
2510 */
2511 void idEarthQuake::Spawn( void ) {
2512         nextTriggerTime = 0;
2513         shakeStopTime = 0;
2514         wait = spawnArgs.GetFloat( "wait", "15" );
2515         random = spawnArgs.GetFloat( "random", "5" );
2516         triggered = spawnArgs.GetBool( "triggered" );
2517         playerOriented = spawnArgs.GetBool( "playerOriented" );
2518         disabled = false;
2519         shakeTime = spawnArgs.GetFloat( "shakeTime", "0" );
2520
2521         if ( !triggered ){
2522                 PostEventSec( &EV_Activate, spawnArgs.GetFloat( "wait" ), this );
2523         }
2524         BecomeInactive( TH_THINK );
2525 }
2526
2527 /*
2528 ================
2529 idEarthQuake::Event_Activate
2530 ================
2531 */
2532 void idEarthQuake::Event_Activate( idEntity *activator ) {
2533         
2534         if ( nextTriggerTime > gameLocal.time ) {
2535                 return;
2536         }
2537
2538         if ( disabled && activator == this ) {
2539                 return;
2540         }
2541
2542         idPlayer *player = gameLocal.GetLocalPlayer();
2543         if ( player == NULL ) {
2544                 return;
2545         }
2546
2547         nextTriggerTime = 0;
2548
2549         if ( !triggered && activator != this ){
2550                 // if we are not triggered ( i.e. random ), disable or enable
2551                 disabled ^= 1;
2552                 if (disabled) {
2553                         return;
2554                 } else {
2555                         PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this );
2556                 }
2557         }
2558
2559         ActivateTargets( activator );
2560
2561         const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_quake" ) );
2562         if ( playerOriented ) {
2563                 player->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
2564         } else {
2565                 StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
2566         }
2567
2568         if ( shakeTime > 0.0f ) {
2569                 shakeStopTime = gameLocal.time + SEC2MS( shakeTime );
2570                 BecomeActive( TH_THINK );
2571         }
2572
2573         if ( wait > 0.0f ) {
2574                 if ( !triggered ) {
2575                         PostEventSec( &EV_Activate, wait + random * gameLocal.random.CRandomFloat(), this );
2576                 } else {
2577                         nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() );
2578                 }
2579         } else if ( shakeTime == 0.0f ) {
2580                 PostEventMS( &EV_Remove, 0 );
2581         }
2582 }
2583
2584
2585 /*
2586 ===============
2587 idEarthQuake::Think
2588 ================
2589 */
2590 void idEarthQuake::Think( void ) {
2591         if ( thinkFlags & TH_THINK ) {
2592                 if ( gameLocal.time > shakeStopTime ) {
2593                         BecomeInactive( TH_THINK );
2594                         if ( wait <= 0.0f ) {
2595                                 PostEventMS( &EV_Remove, 0 );
2596                         }
2597                         return;
2598                 }
2599                 float shakeVolume = gameSoundWorld->CurrentShakeAmplitudeForPosition( gameLocal.time, gameLocal.GetLocalPlayer()->firstPersonViewOrigin );
2600                 gameLocal.RadiusPush( GetPhysics()->GetOrigin(), 256, 1500 * shakeVolume, this, this, 1.0f, true );
2601         }
2602         BecomeInactive( TH_UPDATEVISUALS );
2603 }
2604
2605 /*
2606 ===============================================================================
2607
2608         idFuncPortal
2609
2610 ===============================================================================
2611 */
2612
2613 CLASS_DECLARATION( idEntity, idFuncPortal )
2614         EVENT( EV_Activate,                             idFuncPortal::Event_Activate )
2615 END_CLASS
2616
2617 /*
2618 ===============
2619 idFuncPortal::idFuncPortal
2620 ===============
2621 */
2622 idFuncPortal::idFuncPortal() {
2623         portal = 0;
2624         state = false;
2625 }
2626
2627 /*
2628 ===============
2629 idFuncPortal::Save
2630 ===============
2631 */
2632 void idFuncPortal::Save( idSaveGame *savefile ) const {
2633         savefile->WriteInt( (int)portal );
2634         savefile->WriteBool( state );
2635 }
2636
2637 /*
2638 ===============
2639 idFuncPortal::Restore
2640 ===============
2641 */
2642 void idFuncPortal::Restore( idRestoreGame *savefile ) {
2643         savefile->ReadInt( (int &)portal );
2644         savefile->ReadBool( state );
2645         gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE );
2646 }
2647
2648 /*
2649 ===============
2650 idFuncPortal::Spawn
2651 ===============
2652 */
2653 void idFuncPortal::Spawn( void ) {
2654         portal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds().Expand( 32.0f ) );
2655         if ( portal > 0 ) {
2656                 state = spawnArgs.GetBool( "start_on" );
2657                 gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE );
2658         }
2659 }
2660
2661 /*
2662 ================
2663 idFuncPortal::Event_Activate
2664 ================
2665 */
2666 void idFuncPortal::Event_Activate( idEntity *activator ) {
2667         if ( portal > 0 ) {
2668                 state = !state;
2669                 gameLocal.SetPortalState( portal, state ? PS_BLOCK_ALL : PS_BLOCK_NONE );
2670         }
2671 }
2672
2673 /*
2674 ===============================================================================
2675
2676         idFuncAASPortal
2677
2678 ===============================================================================
2679 */
2680
2681 CLASS_DECLARATION( idEntity, idFuncAASPortal )
2682         EVENT( EV_Activate,                             idFuncAASPortal::Event_Activate )
2683 END_CLASS
2684
2685 /*
2686 ===============
2687 idFuncAASPortal::idFuncAASPortal
2688 ===============
2689 */
2690 idFuncAASPortal::idFuncAASPortal() {
2691         state = false;
2692 }
2693
2694 /*
2695 ===============
2696 idFuncAASPortal::Save
2697 ===============
2698 */
2699 void idFuncAASPortal::Save( idSaveGame *savefile ) const {
2700         savefile->WriteBool( state );
2701 }
2702
2703 /*
2704 ===============
2705 idFuncAASPortal::Restore
2706 ===============
2707 */
2708 void idFuncAASPortal::Restore( idRestoreGame *savefile ) {
2709         savefile->ReadBool( state );
2710         gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state );
2711 }
2712
2713 /*
2714 ===============
2715 idFuncAASPortal::Spawn
2716 ===============
2717 */
2718 void idFuncAASPortal::Spawn( void ) {
2719         state = spawnArgs.GetBool( "start_on" );
2720         gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state );
2721 }
2722
2723 /*
2724 ================
2725 idFuncAASPortal::Event_Activate
2726 ================
2727 */
2728 void idFuncAASPortal::Event_Activate( idEntity *activator ) {
2729         state ^= 1;
2730         gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_CLUSTERPORTAL, state );
2731 }
2732
2733 /*
2734 ===============================================================================
2735
2736         idFuncAASObstacle
2737
2738 ===============================================================================
2739 */
2740
2741 CLASS_DECLARATION( idEntity, idFuncAASObstacle )
2742         EVENT( EV_Activate,                             idFuncAASObstacle::Event_Activate )
2743 END_CLASS
2744
2745 /*
2746 ===============
2747 idFuncAASObstacle::idFuncAASObstacle
2748 ===============
2749 */
2750 idFuncAASObstacle::idFuncAASObstacle() {
2751         state = false;
2752 }
2753
2754 /*
2755 ===============
2756 idFuncAASObstacle::Save
2757 ===============
2758 */
2759 void idFuncAASObstacle::Save( idSaveGame *savefile ) const {
2760         savefile->WriteBool( state );
2761 }
2762
2763 /*
2764 ===============
2765 idFuncAASObstacle::Restore
2766 ===============
2767 */
2768 void idFuncAASObstacle::Restore( idRestoreGame *savefile ) {
2769         savefile->ReadBool( state );
2770         gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state );
2771 }
2772
2773 /*
2774 ===============
2775 idFuncAASObstacle::Spawn
2776 ===============
2777 */
2778 void idFuncAASObstacle::Spawn( void ) {
2779         state = spawnArgs.GetBool( "start_on" );
2780         gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state );
2781 }
2782
2783 /*
2784 ================
2785 idFuncAASObstacle::Event_Activate
2786 ================
2787 */
2788 void idFuncAASObstacle::Event_Activate( idEntity *activator ) {
2789         state ^= 1;
2790         gameLocal.SetAASAreaState( GetPhysics()->GetAbsBounds(), AREACONTENTS_OBSTACLE, state );
2791 }
2792
2793
2794
2795 /*
2796 ===============================================================================
2797
2798 idFuncRadioChatter
2799
2800 ===============================================================================
2801 */
2802
2803 const idEventDef EV_ResetRadioHud( "<resetradiohud>", "e" );
2804
2805
2806 CLASS_DECLARATION( idEntity, idFuncRadioChatter )
2807 EVENT( EV_Activate,                             idFuncRadioChatter::Event_Activate )
2808 EVENT( EV_ResetRadioHud,                idFuncRadioChatter::Event_ResetRadioHud )
2809 END_CLASS
2810
2811 /*
2812 ===============
2813 idFuncRadioChatter::idFuncRadioChatter
2814 ===============
2815 */
2816 idFuncRadioChatter::idFuncRadioChatter() {
2817         time = 0.0;
2818 }
2819
2820 /*
2821 ===============
2822 idFuncRadioChatter::Save
2823 ===============
2824 */
2825 void idFuncRadioChatter::Save( idSaveGame *savefile ) const {
2826         savefile->WriteFloat( time );
2827 }
2828
2829 /*
2830 ===============
2831 idFuncRadioChatter::Restore
2832 ===============
2833 */
2834 void idFuncRadioChatter::Restore( idRestoreGame *savefile ) {
2835         savefile->ReadFloat( time );
2836 }
2837
2838 /*
2839 ===============
2840 idFuncRadioChatter::Spawn
2841 ===============
2842 */
2843 void idFuncRadioChatter::Spawn( void ) {
2844         time = spawnArgs.GetFloat( "time", "5.0" );
2845 }
2846
2847 /*
2848 ================
2849 idFuncRadioChatter::Event_Activate
2850 ================
2851 */
2852 void idFuncRadioChatter::Event_Activate( idEntity *activator ) {
2853         idPlayer *player;
2854         const char      *sound;
2855         const idSoundShader *shader;
2856         int length;
2857         
2858         if ( activator->IsType( idPlayer::Type ) ) {
2859                 player = static_cast<idPlayer *>( activator );
2860         } else {
2861                 player = gameLocal.GetLocalPlayer();
2862         }
2863
2864         player->hud->HandleNamedEvent( "radioChatterUp" );
2865
2866         sound = spawnArgs.GetString( "snd_radiochatter", "" );
2867         if ( sound && *sound ) {
2868                 shader = declManager->FindSound( sound );
2869                 player->StartSoundShader( shader, SND_CHANNEL_RADIO, SSF_GLOBAL, false, &length );
2870                 time = MS2SEC( length + 150 );
2871         }
2872         // we still put the hud up because this is used with no sound on 
2873         // certain frame commands when the chatter is triggered
2874         PostEventSec( &EV_ResetRadioHud, time, player );
2875
2876 }
2877
2878 /*
2879 ================
2880 idFuncRadioChatter::Event_ResetRadioHud
2881 ================
2882 */
2883 void idFuncRadioChatter::Event_ResetRadioHud( idEntity *activator ) {
2884         idPlayer *player = ( activator->IsType( idPlayer::Type ) ) ? static_cast<idPlayer *>( activator ) : gameLocal.GetLocalPlayer();
2885         player->hud->HandleNamedEvent( "radioChatterDown" );
2886         ActivateTargets( activator );
2887 }
2888
2889
2890 /*
2891 ===============================================================================
2892
2893         idPhantomObjects
2894
2895 ===============================================================================
2896 */
2897
2898 CLASS_DECLARATION( idEntity, idPhantomObjects )
2899         EVENT( EV_Activate,                             idPhantomObjects::Event_Activate )
2900 END_CLASS
2901
2902 /*
2903 ===============
2904 idPhantomObjects::idPhantomObjects
2905 ===============
2906 */
2907 idPhantomObjects::idPhantomObjects() {
2908         target                  = NULL;
2909         end_time                = 0;
2910         throw_time              = 0.0f;
2911         shake_time              = 0.0f;
2912         shake_ang.Zero();
2913         speed                   = 0.0f;
2914         min_wait                = 0;
2915         max_wait                = 0;
2916         fl.neverDormant = false;
2917 }
2918
2919 /*
2920 ===============
2921 idPhantomObjects::Save
2922 ===============
2923 */
2924 void idPhantomObjects::Save( idSaveGame *savefile ) const {
2925         int i;
2926
2927         savefile->WriteInt( end_time );
2928         savefile->WriteFloat( throw_time );
2929         savefile->WriteFloat( shake_time );
2930         savefile->WriteVec3( shake_ang );
2931         savefile->WriteFloat( speed );
2932         savefile->WriteInt( min_wait );
2933         savefile->WriteInt( max_wait );
2934         target.Save( savefile );
2935         savefile->WriteInt( targetTime.Num() );
2936         for( i = 0; i < targetTime.Num(); i++ ) {
2937                 savefile->WriteInt( targetTime[ i ] );
2938         }
2939
2940         for( i = 0; i < lastTargetPos.Num(); i++ ) {
2941                 savefile->WriteVec3( lastTargetPos[ i ] );
2942         }
2943 }
2944
2945 /*
2946 ===============
2947 idPhantomObjects::Restore
2948 ===============
2949 */
2950 void idPhantomObjects::Restore( idRestoreGame *savefile ) {
2951         int num;
2952         int i;
2953
2954         savefile->ReadInt( end_time );
2955         savefile->ReadFloat( throw_time );
2956         savefile->ReadFloat( shake_time );
2957         savefile->ReadVec3( shake_ang );
2958         savefile->ReadFloat( speed );
2959         savefile->ReadInt( min_wait );
2960         savefile->ReadInt( max_wait );
2961         target.Restore( savefile );
2962         
2963         savefile->ReadInt( num );       
2964         targetTime.SetGranularity( 1 );
2965         targetTime.SetNum( num );
2966         lastTargetPos.SetGranularity( 1 );
2967         lastTargetPos.SetNum( num );
2968
2969         for( i = 0; i < num; i++ ) {
2970                 savefile->ReadInt( targetTime[ i ] );
2971         }
2972
2973         if ( savefile->GetBuildNumber() == INITIAL_RELEASE_BUILD_NUMBER ) {
2974                 // these weren't saved out in the first release
2975                 for( i = 0; i < num; i++ ) {
2976                         lastTargetPos[ i ].Zero();
2977                 }
2978         } else {
2979                 for( i = 0; i < num; i++ ) {
2980                         savefile->ReadVec3( lastTargetPos[ i ] );
2981                 }
2982         }
2983 }
2984
2985 /*
2986 ===============
2987 idPhantomObjects::Spawn
2988 ===============
2989 */
2990 void idPhantomObjects::Spawn( void ) {
2991         throw_time = spawnArgs.GetFloat( "time", "5" );
2992         speed = spawnArgs.GetFloat( "speed", "1200" );
2993         shake_time = spawnArgs.GetFloat( "shake_time", "1" );
2994         throw_time -= shake_time;
2995         if ( throw_time < 0.0f ) {
2996                 throw_time = 0.0f;
2997         }
2998         min_wait = SEC2MS( spawnArgs.GetFloat( "min_wait", "1" ) );
2999         max_wait = SEC2MS( spawnArgs.GetFloat( "max_wait", "3" ) );
3000
3001         shake_ang = spawnArgs.GetVector( "shake_ang", "65 65 65" );
3002         Hide();
3003         GetPhysics()->SetContents( 0 );
3004 }
3005
3006 /*
3007 ================
3008 idPhantomObjects::Event_Activate
3009 ================
3010 */
3011 void idPhantomObjects::Event_Activate( idEntity *activator ) {
3012         int i;
3013         float time;
3014         float frac;
3015         float scale;
3016
3017         if ( thinkFlags & TH_THINK ) {
3018                 BecomeInactive( TH_THINK );
3019                 return;
3020         }
3021
3022         RemoveNullTargets();
3023         if ( !targets.Num() ) {
3024                 return;
3025         }
3026
3027         if ( !activator || !activator->IsType( idActor::Type ) ) {
3028                 target = gameLocal.GetLocalPlayer();
3029         } else {
3030                 target = static_cast<idActor *>( activator );
3031         }
3032         
3033         end_time = gameLocal.time + SEC2MS( spawnArgs.GetFloat( "end_time", "0" ) );
3034
3035         targetTime.SetNum( targets.Num() );
3036         lastTargetPos.SetNum( targets.Num() );
3037
3038         const idVec3 &toPos = target.GetEntity()->GetEyePosition();
3039
3040     // calculate the relative times of all the objects
3041         time = 0.0f;
3042         for( i = 0; i < targetTime.Num(); i++ ) {
3043                 targetTime[ i ] = SEC2MS( time );
3044                 lastTargetPos[ i ] = toPos;
3045
3046                 frac = 1.0f - ( float )i / ( float )targetTime.Num();
3047                 time += ( gameLocal.random.RandomFloat() + 1.0f ) * 0.5f * frac + 0.1f;
3048         }
3049
3050         // scale up the times to fit within throw_time
3051         scale = throw_time / time;
3052         for( i = 0; i < targetTime.Num(); i++ ) {
3053                 targetTime[ i ] = gameLocal.time + SEC2MS( shake_time )+ targetTime[ i ] * scale;
3054         }
3055
3056         BecomeActive( TH_THINK );
3057 }
3058
3059 /*
3060 ===============
3061 idPhantomObjects::Think
3062 ================
3063 */
3064 void idPhantomObjects::Think( void ) {
3065         int                     i;
3066         int                     num;
3067         float           time;
3068         idVec3          vel;
3069         idVec3          ang;
3070         idEntity        *ent;
3071         idActor         *targetEnt;
3072         idPhysics       *entPhys;
3073         trace_t         tr;
3074
3075         // if we are completely closed off from the player, don't do anything at all
3076         if ( CheckDormant() ) {
3077                 return;
3078         }
3079
3080         if ( !( thinkFlags & TH_THINK ) ) {
3081                 BecomeInactive( thinkFlags & ~TH_THINK );
3082                 return;
3083         }
3084
3085         targetEnt = target.GetEntity();
3086         if ( !targetEnt || ( targetEnt->health <= 0 ) || ( end_time && ( gameLocal.time > end_time ) ) || gameLocal.inCinematic ) {
3087                 BecomeInactive( TH_THINK );
3088         }
3089
3090         const idVec3 &toPos = targetEnt->GetEyePosition();
3091
3092         num = 0;
3093         for ( i = 0; i < targets.Num(); i++ ) {
3094                 ent = targets[ i ].GetEntity();
3095                 if ( !ent ) {
3096                         continue;
3097                 }
3098                 
3099                 if ( ent->fl.hidden ) {
3100                         // don't throw hidden objects
3101                         continue;
3102                 }
3103
3104                 if ( !targetTime[ i ] ) {
3105                         // already threw this object
3106                         continue;
3107                 }
3108
3109                 num++;
3110
3111                 time = MS2SEC( targetTime[ i ] - gameLocal.time );
3112                 if ( time > shake_time ) {
3113                         continue;
3114                 }
3115
3116                 entPhys = ent->GetPhysics();
3117                 const idVec3 &entOrg = entPhys->GetOrigin();
3118
3119                 gameLocal.clip.TracePoint( tr, entOrg, toPos, MASK_OPAQUE, ent );
3120                 if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == targetEnt ) ) {
3121                         lastTargetPos[ i ] = toPos;
3122                 }
3123
3124                 if ( time < 0.0f ) {
3125                         idAI::PredictTrajectory( entPhys->GetOrigin(), lastTargetPos[ i ], speed, entPhys->GetGravity(), 
3126                                 entPhys->GetClipModel(), entPhys->GetClipMask(), 256.0f, ent, targetEnt, ai_debugTrajectory.GetBool() ? 1 : 0, vel );
3127                         vel *= speed;
3128                         entPhys->SetLinearVelocity( vel );
3129                         if ( !end_time ) {
3130                                 targetTime[ i ] = 0;
3131                         } else {
3132                                 targetTime[ i ] = gameLocal.time + gameLocal.random.RandomInt( max_wait - min_wait ) + min_wait;
3133                         }
3134                         if ( ent->IsType( idMoveable::Type ) ) {
3135                                 idMoveable *ment = static_cast<idMoveable*>( ent );
3136                                 ment->EnableDamage( true, 2.5f );
3137                         }
3138                 } else {
3139                         // this is not the right way to set the angular velocity, but the effect is nice, so I'm keeping it. :)
3140                         ang.Set( gameLocal.random.CRandomFloat() * shake_ang.x, gameLocal.random.CRandomFloat() * shake_ang.y, gameLocal.random.CRandomFloat() * shake_ang.z );
3141                         ang *= ( 1.0f - time / shake_time );
3142                         entPhys->SetAngularVelocity( ang );
3143                 }
3144         }
3145
3146         if ( !num ) {
3147                 BecomeInactive( TH_THINK );
3148         }
3149 }