Fx.cpp 23 KB

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