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 }