DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

Fx.cpp (23528B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Game_local.h"
     33 
     34 /*
     35 ===============================================================================
     36 
     37 	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 ( common->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() {
    196 	return fxEffect ? fxEffect->GetName() : NULL;
    197 }
    198 
    199 /*
    200 ================
    201 idEntityFx::Joint
    202 ================
    203 */
    204 const char *idEntityFx::Joint() {
    205 	return fxEffect ? fxEffect->joint.c_str() : NULL;
    206 }
    207 
    208 /*
    209 ================
    210 idEntityFx::CleanUp
    211 ================
    212 */
    213 void idEntityFx::CleanUp() {
    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() {
    268 	CleanUp();
    269 	started = -1;
    270 }
    271 
    272 /*
    273 ================
    274 idEntityFx::Duration
    275 ================
    276 */
    277 const int idEntityFx::Duration() {
    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 ( !common->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 ( common->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 					gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity );
    515 				}
    516 				ApplyFade( fxaction, *useAction, time, actualStart );
    517 				break;
    518 			}
    519 			case FX_LAUNCH: {
    520 				if ( common->IsClient() ) {
    521 					// client never spawns entities outside of ClientReadSnapshot
    522 					useAction->launched = true;
    523 					break;
    524 				}
    525 				if ( !useAction->launched ) {
    526 					useAction->launched = true;
    527 					projectile = NULL;
    528 					// FIXME: may need to cache this if it is slow
    529 					projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false );
    530 					if ( !projectileDef ) {
    531 						gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() );
    532 					} else {
    533 						gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
    534 						if ( ent && ent->IsType( idProjectile::Type ) ) {
    535 							projectile = ( idProjectile * )ent;
    536 							projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] );
    537 							projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin );
    538 						}
    539 					}
    540 				}
    541 				break;
    542 			}
    543 			case FX_SHOCKWAVE: {
    544 				if ( common->IsClient() ) {
    545 					useAction->shakeStarted = true;
    546 					break;
    547 				}
    548 				if ( !useAction->shakeStarted ) {
    549 					idStr	shockDefName;
    550 					useAction->shakeStarted = true;
    551 
    552 					shockDefName = fxaction.data;
    553 					if ( !shockDefName.Length() ) {
    554 						shockDefName = "func_shockwave";
    555 					}
    556 
    557 					projectileDef = gameLocal.FindEntityDefDict( shockDefName, false );
    558 					if ( !projectileDef ) {
    559 						gameLocal.Warning( "shockwave \'%s\' not found", shockDefName.c_str() );
    560 					} else {
    561 						gameLocal.SpawnEntityDef( *projectileDef, &ent );
    562 						ent->SetOrigin( GetPhysics()->GetOrigin() + fxaction.offset );
    563 						ent->PostEventMS( &EV_Remove, ent->spawnArgs.GetInt( "duration" ) );
    564 					}
    565 				}
    566 				break;
    567 			}
    568 		}
    569 	}
    570 }
    571 
    572 /*
    573 ================
    574 idEntityFx::idEntityFx
    575 ================
    576 */
    577 idEntityFx::idEntityFx() {
    578 	fxEffect = NULL;
    579 	started = -1;
    580 	nextTriggerTime = -1;
    581 	fl.networkSync = true;
    582 }
    583 
    584 /*
    585 ================
    586 idEntityFx::~idEntityFx
    587 ================
    588 */
    589 idEntityFx::~idEntityFx() {
    590 	CleanUp();
    591 	fxEffect = NULL;
    592 }
    593 
    594 /*
    595 ================
    596 idEntityFx::Spawn
    597 ================
    598 */
    599 void idEntityFx::Spawn() {
    600 
    601 	if ( g_skipFX.GetBool() ) {
    602 		return;
    603 	}
    604 
    605 	const char *fx;
    606 	nextTriggerTime = 0;
    607 	fxEffect = NULL;
    608 	if ( spawnArgs.GetString( "fx", "", &fx ) ) {
    609 		systemName = fx;
    610 	}
    611 	if ( !spawnArgs.GetBool( "triggered" ) ) {
    612 		Setup( fx );
    613 		if ( spawnArgs.GetBool( "test" ) || spawnArgs.GetBool( "start" ) || spawnArgs.GetFloat ( "restart" ) ) {
    614 			PostEventMS( &EV_Activate, 0, this );
    615 		}
    616 	}
    617 }
    618 
    619 /*
    620 ================
    621 idEntityFx::Think
    622 
    623   Clears any visual fx started when {item,mob,player} was spawned
    624 ================
    625 */
    626 void idEntityFx::Think() {
    627 	if ( g_skipFX.GetBool() ) {
    628 		return;
    629 	}
    630 
    631 	if ( thinkFlags & TH_THINK ) {
    632 		Run( gameLocal.time );
    633 	}
    634 
    635 	RunPhysics();
    636 	Present();
    637 }
    638 
    639 /*
    640 ================
    641 idEntityFx::Event_ClearFx
    642 
    643   Clears any visual fx started when item(mob) was spawned
    644 ================
    645 */
    646 void idEntityFx::Event_ClearFx() {
    647 
    648 	if ( g_skipFX.GetBool() ) {
    649 		return;
    650 	}
    651 
    652 	Stop();
    653 	CleanUp();
    654 	BecomeInactive( TH_THINK );
    655 
    656 	if ( spawnArgs.GetBool("test") ) {
    657 		PostEventMS( &EV_Activate, 0, this );
    658 	} else {
    659 		if ( spawnArgs.GetFloat( "restart" ) || !spawnArgs.GetBool( "triggered")) {
    660 			float rest = spawnArgs.GetFloat( "restart", "0" );
    661 			if ( rest == 0.0f ) {
    662 				PostEventSec( &EV_Remove, 0.1f );
    663 			} else {
    664 				rest *= gameLocal.random.RandomFloat();
    665 				PostEventSec( &EV_Activate, rest, this );
    666 			}
    667 		}
    668 	}
    669 }
    670 
    671 /*
    672 ================
    673 idEntityFx::Event_Trigger
    674 ================
    675 */
    676 void idEntityFx::Event_Trigger( idEntity *activator ) {
    677 
    678 	if ( g_skipFX.GetBool() ) {
    679 		return;
    680 	}
    681 
    682 	float		fxActionDelay;
    683 	const char *fx;
    684 
    685 	if ( gameLocal.time < nextTriggerTime ) {
    686 		return;
    687 	}
    688 
    689 	if ( spawnArgs.GetString( "fx", "", &fx) ) {
    690 		Setup( fx );
    691 		Start( gameLocal.time );
    692 		PostEventMS( &EV_Fx_KillFx, Duration() );
    693 		BecomeActive( TH_THINK );
    694 	}
    695 
    696 	fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" );
    697 	if ( fxActionDelay != 0.0f ) {
    698 		nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay );
    699 	} else {
    700 		// prevent multiple triggers on same frame
    701 		nextTriggerTime = gameLocal.time + 1;
    702 	}
    703 	PostEventSec( &EV_Fx_Action, fxActionDelay, activator );
    704 }
    705 
    706 
    707 /*
    708 ================
    709 idEntityFx::StartFx
    710 ================
    711 */
    712 idEntityFx *idEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) {
    713 
    714 	if ( g_skipFX.GetBool() || !fx || !*fx ) {
    715 		return NULL;
    716 	}
    717 
    718 	idDict args;
    719 	args.SetBool( "start", true );
    720 	args.Set( "fx", fx );
    721 	idEntityFx *nfx = static_cast<idEntityFx *>( gameLocal.SpawnEntityType( idEntityFx::Type, &args ) );
    722 	if ( nfx->Joint() && *nfx->Joint() ) {
    723 		nfx->BindToJoint( ent, nfx->Joint(), true );
    724 		nfx->SetOrigin( vec3_origin );
    725 	} else {
    726 		nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() );
    727 		nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() );
    728 	}
    729 
    730 	if ( bind ) {
    731 		// never bind to world spawn
    732 		if ( ent != gameLocal.world ) {
    733 			nfx->Bind( ent, true );
    734 		}
    735 	}
    736 	nfx->Show();
    737 	return nfx;
    738 }
    739 
    740 /*
    741 =================
    742 idEntityFx::WriteToSnapshot
    743 =================
    744 */
    745 void idEntityFx::WriteToSnapshot( idBitMsg &msg ) const {
    746 	GetPhysics()->WriteToSnapshot( msg );
    747 	WriteBindToSnapshot( msg );
    748 	msg.WriteLong( ( fxEffect != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_FX, fxEffect->Index() ) : -1 );
    749 	msg.WriteLong( started );
    750 }
    751 
    752 /*
    753 =================
    754 idEntityFx::ReadFromSnapshot
    755 =================
    756 */
    757 void idEntityFx::ReadFromSnapshot( const idBitMsg &msg ) {
    758 	int fx_index, start_time, max_lapse;
    759 
    760 	GetPhysics()->ReadFromSnapshot( msg );
    761 	ReadBindFromSnapshot( msg );
    762 	fx_index = gameLocal.ClientRemapDecl( DECL_FX, msg.ReadLong() );
    763 	start_time = msg.ReadLong();
    764 
    765 	if ( fx_index != -1 && start_time > 0 && !fxEffect && started < 0 ) {
    766 		spawnArgs.GetInt( "effect_lapse", "1000", max_lapse );
    767 		if ( gameLocal.time - start_time > max_lapse ) {
    768 			// too late, skip the effect completely
    769 			started = 0;
    770 			return;
    771 		}
    772 		const idDeclFX *fx = static_cast<const idDeclFX *>( declManager->DeclByIndex( DECL_FX, fx_index ) );
    773 		if ( !fx ) {
    774 			gameLocal.Error( "FX at index %d not found", fx_index );
    775 		}
    776 		fxEffect = fx;
    777 		Setup( fx->GetName() );
    778 		Start( start_time );
    779 	}
    780 }
    781 
    782 /*
    783 =================
    784 idEntityFx::ClientThink
    785 =================
    786 */
    787 void idEntityFx::ClientThink( const int curTime, const float fraction, const bool predict ) {
    788 
    789 	if ( gameLocal.isNewFrame ) { 
    790 		Run( gameLocal.serverTime ); 
    791 	}
    792 
    793 	InterpolatePhysics( fraction );
    794 	Present();
    795 }
    796 
    797 /*
    798 =================
    799 idEntityFx::ClientPredictionThink
    800 =================
    801 */
    802 void idEntityFx::ClientPredictionThink() {
    803 	if ( gameLocal.isNewFrame ) { 
    804 		Run( gameLocal.time ); 
    805 	}
    806 	RunPhysics();
    807 	Present();
    808 }
    809 
    810 /*
    811 ===============================================================================
    812 
    813   idTeleporter
    814 	
    815 ===============================================================================
    816 */
    817 
    818 CLASS_DECLARATION( idEntityFx, idTeleporter )
    819 	EVENT( EV_Fx_Action,	idTeleporter::Event_DoAction )
    820 END_CLASS
    821 
    822 /*
    823 ================
    824 idTeleporter::Event_DoAction
    825 ================
    826 */
    827 void idTeleporter::Event_DoAction( idEntity *activator ) {
    828 	float angle;
    829 
    830 	angle = spawnArgs.GetFloat( "angle" );
    831 	idAngles a( 0, spawnArgs.GetFloat( "angle" ), 0 );
    832 	activator->Teleport( GetPhysics()->GetOrigin(), a, NULL );
    833 }