]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/d3xp/Fx.cpp
hello world
[icculus/iodoom3.git] / neo / d3xp / Fx.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 #include "../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "Game_local.h"
33
34 /*
35 ===============================================================================
36
37         idEntityFx
38
39 ===============================================================================
40 */
41
42 const idEventDef EV_Fx_KillFx( "_killfx" );
43 const idEventDef EV_Fx_Action( "_fxAction", "e" );              // implemented by subclasses
44
45 CLASS_DECLARATION( idEntity, idEntityFx )
46 EVENT( EV_Activate,             idEntityFx::Event_Trigger )
47 EVENT( EV_Fx_KillFx,    idEntityFx::Event_ClearFx )
48 END_CLASS
49
50
51 /*
52 ================
53 idEntityFx::Save
54 ================
55 */
56 void idEntityFx::Save( idSaveGame *savefile ) const {
57         int i;
58
59         savefile->WriteInt( started );
60         savefile->WriteInt( nextTriggerTime );
61         savefile->WriteFX( fxEffect );
62         savefile->WriteString( systemName );
63
64         savefile->WriteInt( actions.Num() );
65
66         for ( i = 0; i < actions.Num(); i++ ) {
67
68                 if ( actions[i].lightDefHandle >= 0 ) {
69                         savefile->WriteBool( true );
70                         savefile->WriteRenderLight( actions[i].renderLight );
71                 } else {
72                         savefile->WriteBool( false );
73                 }
74
75                 if ( actions[i].modelDefHandle >= 0 ) {
76                         savefile->WriteBool( true );
77                         savefile->WriteRenderEntity( actions[i].renderEntity );
78                 } else {
79                         savefile->WriteBool( false );
80                 }
81
82                 savefile->WriteFloat( actions[i].delay );
83                 savefile->WriteInt( actions[i].start );
84                 savefile->WriteBool( actions[i].soundStarted );
85                 savefile->WriteBool( actions[i].shakeStarted );
86                 savefile->WriteBool( actions[i].decalDropped );
87                 savefile->WriteBool( actions[i].launched );
88         }
89 }
90
91 /*
92 ================
93 idEntityFx::Restore
94 ================
95 */
96 void idEntityFx::Restore( idRestoreGame *savefile ) {
97         int i;
98         int num;
99         bool hasObject;
100
101         savefile->ReadInt( started );
102         savefile->ReadInt( nextTriggerTime );
103         savefile->ReadFX( fxEffect );
104         savefile->ReadString( systemName );
105
106         savefile->ReadInt( num );
107
108         actions.SetNum( num );
109         for ( i = 0; i < num; i++ ) {
110
111                 savefile->ReadBool( hasObject );
112                 if ( hasObject ) {
113                         savefile->ReadRenderLight( actions[i].renderLight );
114                         actions[i].lightDefHandle = gameRenderWorld->AddLightDef( &actions[i].renderLight );
115                 } else {
116                         memset( &actions[i].renderLight, 0, sizeof( renderLight_t ) );
117                         actions[i].lightDefHandle = -1;
118                 }
119
120                 savefile->ReadBool( hasObject );
121                 if ( hasObject ) {
122                         savefile->ReadRenderEntity( actions[i].renderEntity );
123                         actions[i].modelDefHandle = gameRenderWorld->AddEntityDef( &actions[i].renderEntity );
124                 } else {
125                         memset( &actions[i].renderEntity, 0, sizeof( renderEntity_t ) );
126                         actions[i].modelDefHandle = -1;
127                 }
128
129                 savefile->ReadFloat( actions[i].delay );
130
131                 // let the FX regenerate the particleSystem
132                 actions[i].particleSystem = -1;
133
134                 savefile->ReadInt( actions[i].start );
135                 savefile->ReadBool( actions[i].soundStarted );
136                 savefile->ReadBool( actions[i].shakeStarted );
137                 savefile->ReadBool( actions[i].decalDropped );
138                 savefile->ReadBool( actions[i].launched );
139         }
140 }
141
142 /*
143 ================
144 idEntityFx::Setup
145 ================
146 */
147 void idEntityFx::Setup( const char *fx ) {
148
149         if ( started >= 0 ) {
150                 return;                                 // already started
151         }
152
153         // early during MP Spawn() with no information. wait till we ReadFromSnapshot for more
154         if ( gameLocal.isClient && ( !fx || fx[0] == '\0' ) ) {
155                 return;
156         }
157
158         systemName = fx;
159         started = 0;
160
161         fxEffect = static_cast<const idDeclFX *>( declManager->FindType( DECL_FX, systemName.c_str() ) );
162
163         if ( fxEffect ) {
164                 idFXLocalAction localAction;
165
166                 memset( &localAction, 0, sizeof( idFXLocalAction ) );
167
168                 actions.AssureSize( fxEffect->events.Num(), localAction );
169
170                 for( int i = 0; i<fxEffect->events.Num(); i++ ) {
171                         const idFXSingleAction& fxaction = fxEffect->events[i];
172
173                         idFXLocalAction& laction = actions[i];
174                         if ( fxaction.random1 || fxaction.random2 ) {
175                                 laction.delay = fxaction.random1 + gameLocal.random.RandomFloat() * ( fxaction.random2 - fxaction.random1 );
176                         } else {
177                                 laction.delay = fxaction.delay;
178                         }
179                         laction.start = -1;
180                         laction.lightDefHandle = -1;
181                         laction.modelDefHandle = -1;
182                         laction.particleSystem = -1;
183                         laction.shakeStarted = false;
184                         laction.decalDropped = false;
185                         laction.launched = false;
186                 }
187         }
188 }
189
190 /*
191 ================
192 idEntityFx::EffectName
193 ================
194 */
195 const char *idEntityFx::EffectName( void ) {
196         return fxEffect ? fxEffect->GetName() : NULL;
197 }
198
199 /*
200 ================
201 idEntityFx::Joint
202 ================
203 */
204 const char *idEntityFx::Joint( void ) {
205         return fxEffect ? fxEffect->joint.c_str() : NULL;
206 }
207
208 /*
209 ================
210 idEntityFx::CleanUp
211 ================
212 */
213 void idEntityFx::CleanUp( void ) {
214         if ( !fxEffect ) {
215                 return;
216         }
217         for( int i = 0; i < fxEffect->events.Num(); i++ ) {
218                 const idFXSingleAction& fxaction = fxEffect->events[i];
219                 idFXLocalAction& laction = actions[i];
220                 CleanUpSingleAction( fxaction, laction );               
221         }
222 }
223
224 /*
225 ================
226 idEntityFx::CleanUpSingleAction
227 ================
228 */
229 void idEntityFx::CleanUpSingleAction( const idFXSingleAction& fxaction, idFXLocalAction& laction ) {
230         if ( laction.lightDefHandle != -1 && fxaction.sibling == -1 && fxaction.type != FX_ATTACHLIGHT ) {
231                 gameRenderWorld->FreeLightDef( laction.lightDefHandle );
232                 laction.lightDefHandle = -1;
233         }
234         if ( laction.modelDefHandle != -1 && fxaction.sibling == -1 && fxaction.type != FX_ATTACHENTITY ) {
235                 gameRenderWorld->FreeEntityDef( laction.modelDefHandle );
236                 laction.modelDefHandle = -1;
237         }
238         laction.start = -1;
239 }
240
241 /*
242 ================
243 idEntityFx::Start
244 ================
245 */
246 void idEntityFx::Start( int time ) {
247         if ( !fxEffect ) {
248                 return;
249         }
250         started = time;
251         for( int i = 0; i < fxEffect->events.Num(); i++ ) {
252                 idFXLocalAction& laction = actions[i];
253                 laction.start = time;
254                 laction.soundStarted = false;
255                 laction.shakeStarted = false;
256                 laction.particleSystem = -1;
257                 laction.decalDropped = false;
258                 laction.launched = false;
259         }
260 }
261
262 /*
263 ================
264 idEntityFx::Stop
265 ================
266 */
267 void idEntityFx::Stop( void ) {
268         CleanUp();
269         started = -1;
270 }
271
272 /*
273 ================
274 idEntityFx::Duration
275 ================
276 */
277 const int idEntityFx::Duration( void ) {
278         int max = 0;
279
280         if ( !fxEffect ) {
281                 return max;
282         }
283         for( int i = 0; i < fxEffect->events.Num(); i++ ) {
284                 const idFXSingleAction& fxaction = fxEffect->events[i];
285                 int d = ( fxaction.delay + fxaction.duration ) * 1000.0f;
286                 if ( d > max ) {
287                         max = d;
288                 }
289         }
290
291         return max;
292 }
293
294
295 /*
296 ================
297 idEntityFx::Done
298 ================
299 */
300 const bool idEntityFx::Done() {
301         if (started > 0 && gameLocal.time > started + Duration()) {
302                 return true;
303         }
304         return false;
305 }
306
307 /*
308 ================
309 idEntityFx::ApplyFade
310 ================
311 */
312 void idEntityFx::ApplyFade( const idFXSingleAction& fxaction, idFXLocalAction& laction, const int time, const int actualStart ) {
313         if ( fxaction.fadeInTime || fxaction.fadeOutTime ) {
314                 float fadePct = (float)( time - actualStart ) / ( 1000.0f * ( ( fxaction.fadeInTime != 0 ) ? fxaction.fadeInTime : fxaction.fadeOutTime ) );
315                 if (fadePct > 1.0) {
316                         fadePct = 1.0;
317                 }
318                 if ( laction.modelDefHandle != -1 ) {
319                         laction.renderEntity.shaderParms[SHADERPARM_RED] = (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct;
320                         laction.renderEntity.shaderParms[SHADERPARM_GREEN] = (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct;
321                         laction.renderEntity.shaderParms[SHADERPARM_BLUE] = (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct;
322         
323                         gameRenderWorld->UpdateEntityDef( laction.modelDefHandle, &laction.renderEntity );
324                 }
325                 if ( laction.lightDefHandle != -1 ) {
326                         laction.renderLight.shaderParms[SHADERPARM_RED] = fxaction.lightColor.x * ( (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct );
327                         laction.renderLight.shaderParms[SHADERPARM_GREEN] = fxaction.lightColor.y * ( (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct );
328                         laction.renderLight.shaderParms[SHADERPARM_BLUE] = fxaction.lightColor.z * ( (fxaction.fadeInTime) ? fadePct : 1.0f - fadePct );
329
330                         gameRenderWorld->UpdateLightDef( laction.lightDefHandle, &laction.renderLight );
331                 }
332         }
333 }
334
335 /*
336 ================
337 idEntityFx::Run
338 ================
339 */
340 void idEntityFx::Run( int time ) {
341         int ieff, j;
342         idEntity *ent = NULL;
343         const idDict *projectileDef = NULL;
344         idProjectile *projectile = NULL;
345
346         if ( !fxEffect ) {
347                 return;
348         }
349
350         for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) {
351                 const idFXSingleAction& fxaction = fxEffect->events[ieff];
352                 idFXLocalAction& laction = actions[ieff];
353
354                 //
355                 // if we're currently done with this one
356                 //
357                 if ( laction.start == -1 ) {
358                         continue;
359                 }
360
361                 //
362                 // see if it's delayed
363                 //
364                 if ( laction.delay ) {
365                         if ( laction.start + (time - laction.start) < laction.start + (laction.delay * 1000) ) {
366                                 continue;
367                         }
368                 }
369
370                 //
371                 // each event can have it's own delay and restart
372                 //
373                 int actualStart = laction.delay ? laction.start + (int)( laction.delay * 1000 ) : laction.start;
374                 float pct = (float)( time - actualStart ) / (1000 * fxaction.duration );
375                 if ( pct >= 1.0f ) {
376                         laction.start = -1;
377                         float totalDelay = 0.0f;
378                         if ( fxaction.restart ) {
379                                 if ( fxaction.random1 || fxaction.random2 ) {
380                                         totalDelay = fxaction.random1 + gameLocal.random.RandomFloat() * (fxaction.random2 - fxaction.random1);
381                                 } else {
382                                         totalDelay = fxaction.delay;
383                                 }
384                                 laction.delay = totalDelay;
385                                 laction.start = time;
386                         } 
387                         continue;
388                 }
389
390                 if ( fxaction.fire.Length() ) {
391                         for( j = 0; j < fxEffect->events.Num(); j++ ) {
392                                 if ( fxEffect->events[j].name.Icmp( fxaction.fire ) == 0 ) {
393                                         actions[j].delay = 0;
394                                 }
395                         }
396                 }
397
398                 idFXLocalAction *useAction;
399                 if ( fxaction.sibling == -1 ) {
400                         useAction = &laction;
401                 } else {
402                         useAction = &actions[fxaction.sibling];
403                 }
404                 assert( useAction );
405
406                 switch( fxaction.type ) {
407                         case FX_ATTACHLIGHT:
408                         case FX_LIGHT: {
409                                 if ( useAction->lightDefHandle == -1 ) {
410                                         if ( fxaction.type == FX_LIGHT ) {
411                                                 memset( &useAction->renderLight, 0, sizeof( renderLight_t ) );
412                                                 useAction->renderLight.origin = GetPhysics()->GetOrigin() + fxaction.offset;
413                                                 useAction->renderLight.axis = GetPhysics()->GetAxis();
414                                                 useAction->renderLight.lightRadius[0] = fxaction.lightRadius;
415                                                 useAction->renderLight.lightRadius[1] = fxaction.lightRadius;
416                                                 useAction->renderLight.lightRadius[2] = fxaction.lightRadius;
417                                                 useAction->renderLight.shader = declManager->FindMaterial( fxaction.data, false );
418                                                 useAction->renderLight.shaderParms[ SHADERPARM_RED ]    = fxaction.lightColor.x;
419                                                 useAction->renderLight.shaderParms[ SHADERPARM_GREEN ]  = fxaction.lightColor.y;
420                                                 useAction->renderLight.shaderParms[ SHADERPARM_BLUE ]   = fxaction.lightColor.z;
421                                                 useAction->renderLight.shaderParms[ SHADERPARM_TIMESCALE ]      = 1.0f;
422                                                 useAction->renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time );
423                                                 useAction->renderLight.referenceSound = refSound.referenceSound;
424                                                 useAction->renderLight.pointLight = true;
425                                                 if ( fxaction.noshadows ) {
426                                                         useAction->renderLight.noShadows = true;
427                                                 }
428                                                 useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight );
429                                         }
430                                         if ( fxaction.noshadows ) {
431                                                 for( j = 0; j < fxEffect->events.Num(); j++ ) {
432                                                         idFXLocalAction& laction2 = actions[j];
433                                                         if ( laction2.modelDefHandle != -1 ) {
434                                                                 laction2.renderEntity.noShadow = true;
435                                                         }
436                                                 }
437                                         }
438                                 }
439                                 ApplyFade( fxaction, *useAction, time, actualStart );
440                                 break;
441                         }
442                         case FX_SOUND: {
443                                 if ( !useAction->soundStarted ) {
444                                         useAction->soundStarted = true;
445                                         const idSoundShader *shader = declManager->FindSound(fxaction.data);
446                                         StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL );
447                                         for( j = 0; j < fxEffect->events.Num(); j++ ) {
448                                                 idFXLocalAction& laction2 = actions[j];
449                                                 if ( laction2.lightDefHandle != -1 ) {
450                                                         laction2.renderLight.referenceSound = refSound.referenceSound;
451                                                         gameRenderWorld->UpdateLightDef( laction2.lightDefHandle, &laction2.renderLight );
452                                                 }
453                                         }
454                                 }
455                                 break;
456                         }
457                         case FX_DECAL: {
458                                 if ( !useAction->decalDropped ) {
459                                         useAction->decalDropped = true;
460                                         gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 8.0f, true, fxaction.size, fxaction.data ); 
461                                 }
462                                 break;
463                         }
464                         case FX_SHAKE: {
465                                 if ( !useAction->shakeStarted ) {
466                                         idDict args;
467                                         args.Clear();
468                                         args.SetFloat( "kick_time", fxaction.shakeTime );
469                                         args.SetFloat( "kick_amplitude", fxaction.shakeAmplitude );
470                                         for ( j = 0; j < gameLocal.numClients; j++ ) {
471                                                 idPlayer *player = gameLocal.GetClientByNum( j );
472                                                 if ( player && ( player->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr() < Square( fxaction.shakeDistance ) ) {
473                                                         if ( !gameLocal.isMultiplayer || !fxaction.shakeIgnoreMaster || GetBindMaster() != player ) {
474                                                                 player->playerView.DamageImpulse( fxaction.offset, &args );
475                                                         }
476                                                 }
477                                         }
478                                         if ( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) {
479                                                 idEntity *ignore_ent = NULL;
480                                                 if ( gameLocal.isMultiplayer ) {
481                                                         ignore_ent = this;
482                                                         if ( fxaction.shakeIgnoreMaster ) {
483                                                                 ignore_ent = GetBindMaster();
484                                                         }
485                                                 }
486                                                 // lookup the ent we are bound to?
487                                                 gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true );
488                                         }
489                                         useAction->shakeStarted = true;
490                                 }
491                                 break;
492                         }
493                         case FX_ATTACHENTITY:
494                         case FX_PARTICLE:
495                         case FX_MODEL: {
496                                 if ( useAction->modelDefHandle == -1 ) {
497                                         memset( &useAction->renderEntity, 0, sizeof( renderEntity_t ) );
498                                         useAction->renderEntity.origin = GetPhysics()->GetOrigin() + fxaction.offset;
499                                         useAction->renderEntity.axis = (fxaction.explicitAxis) ? fxaction.axis : GetPhysics()->GetAxis();
500                                         useAction->renderEntity.hModel = renderModelManager->FindModel( fxaction.data );
501                                         useAction->renderEntity.shaderParms[ SHADERPARM_RED ]           = 1.0f;
502                                         useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ]         = 1.0f;
503                                         useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ]          = 1.0f;
504                                         useAction->renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time );
505                                         useAction->renderEntity.shaderParms[3] = 1.0f;
506                                         useAction->renderEntity.shaderParms[5] = 0.0f;
507                                         if ( useAction->renderEntity.hModel ) {
508                                                 useAction->renderEntity.bounds = useAction->renderEntity.hModel->Bounds( &useAction->renderEntity );
509                                         }
510                                         useAction->modelDefHandle = gameRenderWorld->AddEntityDef( &useAction->renderEntity );
511                                 } else if ( fxaction.trackOrigin ) {
512                                         useAction->renderEntity.origin = GetPhysics()->GetOrigin() + fxaction.offset;
513                                         useAction->renderEntity.axis = fxaction.explicitAxis ? fxaction.axis : GetPhysics()->GetAxis();
514 #ifdef _D3XP
515                                         gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity );
516 #endif
517                                 }
518                                 ApplyFade( fxaction, *useAction, time, actualStart );
519                                 break;
520                         }
521                         case FX_LAUNCH: {
522                                 if ( gameLocal.isClient ) {
523                                         // client never spawns entities outside of ClientReadSnapshot
524                                         useAction->launched = true;
525                                         break;
526                                 }
527                                 if ( !useAction->launched ) {
528                                         useAction->launched = true;
529                                         projectile = NULL;
530                                         // FIXME: may need to cache this if it is slow
531                                         projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false );
532                                         if ( !projectileDef ) {
533                                                 gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() );
534                                         } else {
535                                                 gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
536                                                 if ( ent && ent->IsType( idProjectile::Type ) ) {
537                                                         projectile = ( idProjectile * )ent;
538                                                         projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] );
539                                                         projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin );
540                                                 }
541                                         }
542                                 }
543                                 break;
544                         }
545 #ifdef _D3XP
546                         case FX_SHOCKWAVE: {
547                                 if ( gameLocal.isClient ) {
548                                         useAction->shakeStarted = true;
549                                         break;
550                                 }
551                                 if ( !useAction->shakeStarted ) {
552                                         idStr   shockDefName;
553                                         useAction->shakeStarted = true;
554
555                                         shockDefName = fxaction.data;
556                                         if ( !shockDefName.Length() ) {
557                                                 shockDefName = "func_shockwave";
558                                         }
559
560                                         projectileDef = gameLocal.FindEntityDefDict( shockDefName, false );
561                                         if ( !projectileDef ) {
562                                                 gameLocal.Warning( "shockwave \'%s\' not found", shockDefName.c_str() );
563                                         } else {
564                                                 gameLocal.SpawnEntityDef( *projectileDef, &ent );
565                                                 ent->SetOrigin( GetPhysics()->GetOrigin() + fxaction.offset );
566                                                 ent->PostEventMS( &EV_Remove, ent->spawnArgs.GetInt( "duration" ) );
567                                         }
568                                 }
569                                 break;
570                         }
571 #endif
572                 }
573         }
574 }
575
576 /*
577 ================
578 idEntityFx::idEntityFx
579 ================
580 */
581 idEntityFx::idEntityFx() {
582         fxEffect = NULL;
583         started = -1;
584         nextTriggerTime = -1;
585         fl.networkSync = true;
586 }
587
588 /*
589 ================
590 idEntityFx::~idEntityFx
591 ================
592 */
593 idEntityFx::~idEntityFx() {
594         CleanUp();
595         fxEffect = NULL;
596 }
597
598 /*
599 ================
600 idEntityFx::Spawn
601 ================
602 */
603 void idEntityFx::Spawn( void ) {
604
605         if ( g_skipFX.GetBool() ) {
606                 return;
607         }
608
609         const char *fx;
610         nextTriggerTime = 0;
611         fxEffect = NULL;
612         if ( spawnArgs.GetString( "fx", "", &fx ) ) {
613                 systemName = fx;
614         }
615         if ( !spawnArgs.GetBool( "triggered" ) ) {
616                 Setup( fx );
617                 if ( spawnArgs.GetBool( "test" ) || spawnArgs.GetBool( "start" ) || spawnArgs.GetFloat ( "restart" ) ) {
618                         PostEventMS( &EV_Activate, 0, this );
619                 }
620         }
621 }
622
623 /*
624 ================
625 idEntityFx::Think
626
627   Clears any visual fx started when {item,mob,player} was spawned
628 ================
629 */
630 void idEntityFx::Think( void ) {
631         if ( g_skipFX.GetBool() ) {
632                 return;
633         }
634
635         if ( thinkFlags & TH_THINK ) {
636                 Run( gameLocal.time );
637         }
638
639         RunPhysics();
640         Present();
641 }
642
643 /*
644 ================
645 idEntityFx::Event_ClearFx
646
647   Clears any visual fx started when item(mob) was spawned
648 ================
649 */
650 void idEntityFx::Event_ClearFx( void ) {
651
652         if ( g_skipFX.GetBool() ) {
653                 return;
654         }
655
656         Stop();
657         CleanUp();
658         BecomeInactive( TH_THINK );
659
660         if ( spawnArgs.GetBool("test") ) {
661                 PostEventMS( &EV_Activate, 0, this );
662         } else {
663                 if ( spawnArgs.GetFloat( "restart" ) || !spawnArgs.GetBool( "triggered")) {
664                         float rest = spawnArgs.GetFloat( "restart", "0" );
665                         if ( rest == 0.0f ) {
666                                 PostEventSec( &EV_Remove, 0.1f );
667                         } else {
668                                 rest *= gameLocal.random.RandomFloat();
669                                 PostEventSec( &EV_Activate, rest, this );
670                         }
671                 }
672         }
673 }
674
675 /*
676 ================
677 idEntityFx::Event_Trigger
678 ================
679 */
680 void idEntityFx::Event_Trigger( idEntity *activator ) {
681
682         if ( g_skipFX.GetBool() ) {
683                 return;
684         }
685
686         float           fxActionDelay;
687         const char *fx;
688
689         if ( gameLocal.time < nextTriggerTime ) {
690                 return;
691         }
692
693         if ( spawnArgs.GetString( "fx", "", &fx) ) {
694                 Setup( fx );
695                 Start( gameLocal.time );
696                 PostEventMS( &EV_Fx_KillFx, Duration() );
697                 BecomeActive( TH_THINK );
698         }
699
700         fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" );
701         if ( fxActionDelay != 0.0f ) {
702                 nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay );
703         } else {
704                 // prevent multiple triggers on same frame
705                 nextTriggerTime = gameLocal.time + 1;
706         }
707         PostEventSec( &EV_Fx_Action, fxActionDelay, activator );
708 }
709
710
711 /*
712 ================
713 idEntityFx::StartFx
714 ================
715 */
716 idEntityFx *idEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) {
717
718         if ( g_skipFX.GetBool() || !fx || !*fx ) {
719                 return NULL;
720         }
721
722         idDict args;
723         args.SetBool( "start", true );
724         args.Set( "fx", fx );
725         idEntityFx *nfx = static_cast<idEntityFx *>( gameLocal.SpawnEntityType( idEntityFx::Type, &args ) );
726         if ( nfx->Joint() && *nfx->Joint() ) {
727                 nfx->BindToJoint( ent, nfx->Joint(), true );
728                 nfx->SetOrigin( vec3_origin );
729         } else {
730                 nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() );
731                 nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() );
732         }
733
734         if ( bind ) {
735                 // never bind to world spawn
736                 if ( ent != gameLocal.world ) {
737                         nfx->Bind( ent, true );
738                 }
739         }
740         nfx->Show();
741         return nfx;
742 }
743
744 /*
745 =================
746 idEntityFx::WriteToSnapshot
747 =================
748 */
749 void idEntityFx::WriteToSnapshot( idBitMsgDelta &msg ) const {
750         GetPhysics()->WriteToSnapshot( msg );
751         WriteBindToSnapshot( msg );
752         msg.WriteLong( ( fxEffect != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_FX, fxEffect->Index() ) : -1 );
753         msg.WriteLong( started );
754 }
755
756 /*
757 =================
758 idEntityFx::ReadFromSnapshot
759 =================
760 */
761 void idEntityFx::ReadFromSnapshot( const idBitMsgDelta &msg ) {
762         int fx_index, start_time, max_lapse;
763
764         GetPhysics()->ReadFromSnapshot( msg );
765         ReadBindFromSnapshot( msg );
766         fx_index = gameLocal.ClientRemapDecl( DECL_FX, msg.ReadLong() );
767         start_time = msg.ReadLong();
768
769         if ( fx_index != -1 && start_time > 0 && !fxEffect && started < 0 ) {
770                 spawnArgs.GetInt( "effect_lapse", "1000", max_lapse );
771                 if ( gameLocal.time - start_time > max_lapse ) {
772                         // too late, skip the effect completely
773                         started = 0;
774                         return;
775                 }
776                 const idDeclFX *fx = static_cast<const idDeclFX *>( declManager->DeclByIndex( DECL_FX, fx_index ) );
777                 if ( !fx ) {
778                         gameLocal.Error( "FX at index %d not found", fx_index );
779                 }
780                 fxEffect = fx;
781                 Setup( fx->GetName() );
782                 Start( start_time );
783         }
784 }
785
786 /*
787 =================
788 idEntityFx::ClientPredictionThink
789 =================
790 */
791 void idEntityFx::ClientPredictionThink( void ) {
792         if ( gameLocal.isNewFrame ) { 
793                 Run( gameLocal.time ); 
794         }
795         RunPhysics();
796         Present();
797 }
798
799 /*
800 ===============================================================================
801
802   idTeleporter
803         
804 ===============================================================================
805 */
806
807 CLASS_DECLARATION( idEntityFx, idTeleporter )
808         EVENT( EV_Fx_Action,    idTeleporter::Event_DoAction )
809 END_CLASS
810
811 /*
812 ================
813 idTeleporter::Event_DoAction
814 ================
815 */
816 void idTeleporter::Event_DoAction( idEntity *activator ) {
817         float angle;
818
819         angle = spawnArgs.GetFloat( "angle" );
820         idAngles a( 0, spawnArgs.GetFloat( "angle" ), 0 );
821         activator->Teleport( GetPhysics()->GetOrigin(), a, NULL );
822 }