2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Game_local.h"
35 ===============================================================================
39 ===============================================================================
42 const idEventDef EV_Fx_KillFx( "_killfx" );
43 const idEventDef EV_Fx_Action( "_fxAction", "e" ); // implemented by subclasses
45 CLASS_DECLARATION( idEntity, idEntityFx )
46 EVENT( EV_Activate, idEntityFx::Event_Trigger )
47 EVENT( EV_Fx_KillFx, idEntityFx::Event_ClearFx )
56 void idEntityFx::Save( idSaveGame *savefile ) const {
59 savefile->WriteInt( started );
60 savefile->WriteInt( nextTriggerTime );
61 savefile->WriteFX( fxEffect );
62 savefile->WriteString( systemName );
64 savefile->WriteInt( actions.Num() );
66 for ( i = 0; i < actions.Num(); i++ ) {
68 if ( actions[i].lightDefHandle >= 0 ) {
69 savefile->WriteBool( true );
70 savefile->WriteRenderLight( actions[i].renderLight );
72 savefile->WriteBool( false );
75 if ( actions[i].modelDefHandle >= 0 ) {
76 savefile->WriteBool( true );
77 savefile->WriteRenderEntity( actions[i].renderEntity );
79 savefile->WriteBool( false );
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 );
96 void idEntityFx::Restore( idRestoreGame *savefile ) {
101 savefile->ReadInt( started );
102 savefile->ReadInt( nextTriggerTime );
103 savefile->ReadFX( fxEffect );
104 savefile->ReadString( systemName );
106 savefile->ReadInt( num );
108 actions.SetNum( num );
109 for ( i = 0; i < num; i++ ) {
111 savefile->ReadBool( hasObject );
113 savefile->ReadRenderLight( actions[i].renderLight );
114 actions[i].lightDefHandle = gameRenderWorld->AddLightDef( &actions[i].renderLight );
116 memset( &actions[i].renderLight, 0, sizeof( renderLight_t ) );
117 actions[i].lightDefHandle = -1;
120 savefile->ReadBool( hasObject );
122 savefile->ReadRenderEntity( actions[i].renderEntity );
123 actions[i].modelDefHandle = gameRenderWorld->AddEntityDef( &actions[i].renderEntity );
125 memset( &actions[i].renderEntity, 0, sizeof( renderEntity_t ) );
126 actions[i].modelDefHandle = -1;
129 savefile->ReadFloat( actions[i].delay );
131 // let the FX regenerate the particleSystem
132 actions[i].particleSystem = -1;
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 );
147 void idEntityFx::Setup( const char *fx ) {
149 if ( started >= 0 ) {
150 return; // already started
153 // early during MP Spawn() with no information. wait till we ReadFromSnapshot for more
154 if ( gameLocal.isClient && ( !fx || fx[0] == '\0' ) ) {
161 fxEffect = static_cast<const idDeclFX *>( declManager->FindType( DECL_FX, systemName.c_str() ) );
164 idFXLocalAction localAction;
166 memset( &localAction, 0, sizeof( idFXLocalAction ) );
168 actions.AssureSize( fxEffect->events.Num(), localAction );
170 for( int i = 0; i<fxEffect->events.Num(); i++ ) {
171 const idFXSingleAction& fxaction = fxEffect->events[i];
173 idFXLocalAction& laction = actions[i];
174 if ( fxaction.random1 || fxaction.random2 ) {
175 laction.delay = fxaction.random1 + gameLocal.random.RandomFloat() * ( fxaction.random2 - fxaction.random1 );
177 laction.delay = fxaction.delay;
180 laction.lightDefHandle = -1;
181 laction.modelDefHandle = -1;
182 laction.particleSystem = -1;
183 laction.shakeStarted = false;
184 laction.decalDropped = false;
185 laction.launched = false;
192 idEntityFx::EffectName
195 const char *idEntityFx::EffectName( void ) {
196 return fxEffect ? fxEffect->GetName() : NULL;
204 const char *idEntityFx::Joint( void ) {
205 return fxEffect ? fxEffect->joint.c_str() : NULL;
213 void idEntityFx::CleanUp( void ) {
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 );
226 idEntityFx::CleanUpSingleAction
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;
234 if ( laction.modelDefHandle != -1 && fxaction.sibling == -1 && fxaction.type != FX_ATTACHENTITY ) {
235 gameRenderWorld->FreeEntityDef( laction.modelDefHandle );
236 laction.modelDefHandle = -1;
246 void idEntityFx::Start( int 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;
267 void idEntityFx::Stop( void ) {
277 const int idEntityFx::Duration( void ) {
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;
300 const bool idEntityFx::Done() {
301 if (started > 0 && gameLocal.time > started + Duration()) {
309 idEntityFx::ApplyFade
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 ) );
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;
323 gameRenderWorld->UpdateEntityDef( laction.modelDefHandle, &laction.renderEntity );
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 );
330 gameRenderWorld->UpdateLightDef( laction.lightDefHandle, &laction.renderLight );
340 void idEntityFx::Run( int time ) {
342 idEntity *ent = NULL;
343 const idDict *projectileDef = NULL;
344 idProjectile *projectile = NULL;
350 for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) {
351 const idFXSingleAction& fxaction = fxEffect->events[ieff];
352 idFXLocalAction& laction = actions[ieff];
355 // if we're currently done with this one
357 if ( laction.start == -1 ) {
362 // see if it's delayed
364 if ( laction.delay ) {
365 if ( laction.start + (time - laction.start) < laction.start + (laction.delay * 1000) ) {
371 // each event can have it's own delay and restart
373 int actualStart = laction.delay ? laction.start + (int)( laction.delay * 1000 ) : laction.start;
374 float pct = (float)( time - actualStart ) / (1000 * fxaction.duration );
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);
382 totalDelay = fxaction.delay;
384 laction.delay = totalDelay;
385 laction.start = time;
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;
398 idFXLocalAction *useAction;
399 if ( fxaction.sibling == -1 ) {
400 useAction = &laction;
402 useAction = &actions[fxaction.sibling];
406 switch( fxaction.type ) {
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;
428 useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight );
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;
439 ApplyFade( fxaction, *useAction, time, actualStart );
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 );
458 if ( !useAction->decalDropped ) {
459 useAction->decalDropped = true;
460 gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 8.0f, true, fxaction.size, fxaction.data );
465 if ( !useAction->shakeStarted ) {
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 );
478 if ( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) {
479 idEntity *ignore_ent = NULL;
480 if ( gameLocal.isMultiplayer ) {
482 if ( fxaction.shakeIgnoreMaster ) {
483 ignore_ent = GetBindMaster();
486 // lookup the ent we are bound to?
487 gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true );
489 useAction->shakeStarted = true;
493 case FX_ATTACHENTITY:
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 );
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();
515 gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity );
518 ApplyFade( fxaction, *useAction, time, actualStart );
522 if ( gameLocal.isClient ) {
523 // client never spawns entities outside of ClientReadSnapshot
524 useAction->launched = true;
527 if ( !useAction->launched ) {
528 useAction->launched = true;
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() );
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 );
547 if ( gameLocal.isClient ) {
548 useAction->shakeStarted = true;
551 if ( !useAction->shakeStarted ) {
553 useAction->shakeStarted = true;
555 shockDefName = fxaction.data;
556 if ( !shockDefName.Length() ) {
557 shockDefName = "func_shockwave";
560 projectileDef = gameLocal.FindEntityDefDict( shockDefName, false );
561 if ( !projectileDef ) {
562 gameLocal.Warning( "shockwave \'%s\' not found", shockDefName.c_str() );
564 gameLocal.SpawnEntityDef( *projectileDef, &ent );
565 ent->SetOrigin( GetPhysics()->GetOrigin() + fxaction.offset );
566 ent->PostEventMS( &EV_Remove, ent->spawnArgs.GetInt( "duration" ) );
578 idEntityFx::idEntityFx
581 idEntityFx::idEntityFx() {
584 nextTriggerTime = -1;
585 fl.networkSync = true;
590 idEntityFx::~idEntityFx
593 idEntityFx::~idEntityFx() {
603 void idEntityFx::Spawn( void ) {
605 if ( g_skipFX.GetBool() ) {
612 if ( spawnArgs.GetString( "fx", "", &fx ) ) {
615 if ( !spawnArgs.GetBool( "triggered" ) ) {
617 if ( spawnArgs.GetBool( "test" ) || spawnArgs.GetBool( "start" ) || spawnArgs.GetFloat ( "restart" ) ) {
618 PostEventMS( &EV_Activate, 0, this );
627 Clears any visual fx started when {item,mob,player} was spawned
630 void idEntityFx::Think( void ) {
631 if ( g_skipFX.GetBool() ) {
635 if ( thinkFlags & TH_THINK ) {
636 Run( gameLocal.time );
645 idEntityFx::Event_ClearFx
647 Clears any visual fx started when item(mob) was spawned
650 void idEntityFx::Event_ClearFx( void ) {
652 if ( g_skipFX.GetBool() ) {
658 BecomeInactive( TH_THINK );
660 if ( spawnArgs.GetBool("test") ) {
661 PostEventMS( &EV_Activate, 0, this );
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 );
668 rest *= gameLocal.random.RandomFloat();
669 PostEventSec( &EV_Activate, rest, this );
677 idEntityFx::Event_Trigger
680 void idEntityFx::Event_Trigger( idEntity *activator ) {
682 if ( g_skipFX.GetBool() ) {
689 if ( gameLocal.time < nextTriggerTime ) {
693 if ( spawnArgs.GetString( "fx", "", &fx) ) {
695 Start( gameLocal.time );
696 PostEventMS( &EV_Fx_KillFx, Duration() );
697 BecomeActive( TH_THINK );
700 fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" );
701 if ( fxActionDelay != 0.0f ) {
702 nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay );
704 // prevent multiple triggers on same frame
705 nextTriggerTime = gameLocal.time + 1;
707 PostEventSec( &EV_Fx_Action, fxActionDelay, activator );
716 idEntityFx *idEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) {
718 if ( g_skipFX.GetBool() || !fx || !*fx ) {
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 );
730 nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() );
731 nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() );
735 // never bind to world spawn
736 if ( ent != gameLocal.world ) {
737 nfx->Bind( ent, true );
746 idEntityFx::WriteToSnapshot
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 );
758 idEntityFx::ReadFromSnapshot
761 void idEntityFx::ReadFromSnapshot( const idBitMsgDelta &msg ) {
762 int fx_index, start_time, max_lapse;
764 GetPhysics()->ReadFromSnapshot( msg );
765 ReadBindFromSnapshot( msg );
766 fx_index = gameLocal.ClientRemapDecl( DECL_FX, msg.ReadLong() );
767 start_time = msg.ReadLong();
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
776 const idDeclFX *fx = static_cast<const idDeclFX *>( declManager->DeclByIndex( DECL_FX, fx_index ) );
778 gameLocal.Error( "FX at index %d not found", fx_index );
781 Setup( fx->GetName() );
788 idEntityFx::ClientPredictionThink
791 void idEntityFx::ClientPredictionThink( void ) {
792 if ( gameLocal.isNewFrame ) {
793 Run( gameLocal.time );
800 ===============================================================================
804 ===============================================================================
807 CLASS_DECLARATION( idEntityFx, idTeleporter )
808 EVENT( EV_Fx_Action, idTeleporter::Event_DoAction )
813 idTeleporter::Event_DoAction
816 void idTeleporter::Event_DoAction( idEntity *activator ) {
819 angle = spawnArgs.GetFloat( "angle" );
820 idAngles a( 0, spawnArgs.GetFloat( "angle" ), 0 );
821 activator->Teleport( GetPhysics()->GetOrigin(), a, NULL );