Fx.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  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 ( common->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() {
  153. return fxEffect ? fxEffect->GetName() : NULL;
  154. }
  155. /*
  156. ================
  157. idEntityFx::Joint
  158. ================
  159. */
  160. const char *idEntityFx::Joint() {
  161. return fxEffect ? fxEffect->joint.c_str() : NULL;
  162. }
  163. /*
  164. ================
  165. idEntityFx::CleanUp
  166. ================
  167. */
  168. void idEntityFx::CleanUp() {
  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() {
  220. CleanUp();
  221. started = -1;
  222. }
  223. /*
  224. ================
  225. idEntityFx::Duration
  226. ================
  227. */
  228. const int idEntityFx::Duration() {
  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 ( !common->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 ( common->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. gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity );
  450. }
  451. ApplyFade( fxaction, *useAction, time, actualStart );
  452. break;
  453. }
  454. case FX_LAUNCH: {
  455. if ( common->IsClient() ) {
  456. // client never spawns entities outside of ClientReadSnapshot
  457. useAction->launched = true;
  458. break;
  459. }
  460. if ( !useAction->launched ) {
  461. useAction->launched = true;
  462. projectile = NULL;
  463. // FIXME: may need to cache this if it is slow
  464. projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false );
  465. if ( !projectileDef ) {
  466. gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() );
  467. } else {
  468. gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
  469. if ( ent && ent->IsType( idProjectile::Type ) ) {
  470. projectile = ( idProjectile * )ent;
  471. projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] );
  472. projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin );
  473. }
  474. }
  475. }
  476. break;
  477. }
  478. case FX_SHOCKWAVE: {
  479. if ( common->IsClient() ) {
  480. useAction->shakeStarted = true;
  481. break;
  482. }
  483. if ( !useAction->shakeStarted ) {
  484. idStr shockDefName;
  485. useAction->shakeStarted = true;
  486. shockDefName = fxaction.data;
  487. if ( !shockDefName.Length() ) {
  488. shockDefName = "func_shockwave";
  489. }
  490. projectileDef = gameLocal.FindEntityDefDict( shockDefName, false );
  491. if ( !projectileDef ) {
  492. gameLocal.Warning( "shockwave \'%s\' not found", shockDefName.c_str() );
  493. } else {
  494. gameLocal.SpawnEntityDef( *projectileDef, &ent );
  495. ent->SetOrigin( GetPhysics()->GetOrigin() + fxaction.offset );
  496. ent->PostEventMS( &EV_Remove, ent->spawnArgs.GetInt( "duration" ) );
  497. }
  498. }
  499. break;
  500. }
  501. }
  502. }
  503. }
  504. /*
  505. ================
  506. idEntityFx::idEntityFx
  507. ================
  508. */
  509. idEntityFx::idEntityFx() {
  510. fxEffect = NULL;
  511. started = -1;
  512. nextTriggerTime = -1;
  513. fl.networkSync = true;
  514. }
  515. /*
  516. ================
  517. idEntityFx::~idEntityFx
  518. ================
  519. */
  520. idEntityFx::~idEntityFx() {
  521. CleanUp();
  522. fxEffect = NULL;
  523. }
  524. /*
  525. ================
  526. idEntityFx::Spawn
  527. ================
  528. */
  529. void idEntityFx::Spawn() {
  530. if ( g_skipFX.GetBool() ) {
  531. return;
  532. }
  533. const char *fx;
  534. nextTriggerTime = 0;
  535. fxEffect = NULL;
  536. if ( spawnArgs.GetString( "fx", "", &fx ) ) {
  537. systemName = fx;
  538. }
  539. if ( !spawnArgs.GetBool( "triggered" ) ) {
  540. Setup( fx );
  541. if ( spawnArgs.GetBool( "test" ) || spawnArgs.GetBool( "start" ) || spawnArgs.GetFloat ( "restart" ) ) {
  542. PostEventMS( &EV_Activate, 0, this );
  543. }
  544. }
  545. }
  546. /*
  547. ================
  548. idEntityFx::Think
  549. Clears any visual fx started when {item,mob,player} was spawned
  550. ================
  551. */
  552. void idEntityFx::Think() {
  553. if ( g_skipFX.GetBool() ) {
  554. return;
  555. }
  556. if ( thinkFlags & TH_THINK ) {
  557. Run( gameLocal.time );
  558. }
  559. RunPhysics();
  560. Present();
  561. }
  562. /*
  563. ================
  564. idEntityFx::Event_ClearFx
  565. Clears any visual fx started when item(mob) was spawned
  566. ================
  567. */
  568. void idEntityFx::Event_ClearFx() {
  569. if ( g_skipFX.GetBool() ) {
  570. return;
  571. }
  572. Stop();
  573. CleanUp();
  574. BecomeInactive( TH_THINK );
  575. if ( spawnArgs.GetBool("test") ) {
  576. PostEventMS( &EV_Activate, 0, this );
  577. } else {
  578. if ( spawnArgs.GetFloat( "restart" ) || !spawnArgs.GetBool( "triggered")) {
  579. float rest = spawnArgs.GetFloat( "restart", "0" );
  580. if ( rest == 0.0f ) {
  581. PostEventSec( &EV_Remove, 0.1f );
  582. } else {
  583. rest *= gameLocal.random.RandomFloat();
  584. PostEventSec( &EV_Activate, rest, this );
  585. }
  586. }
  587. }
  588. }
  589. /*
  590. ================
  591. idEntityFx::Event_Trigger
  592. ================
  593. */
  594. void idEntityFx::Event_Trigger( idEntity *activator ) {
  595. if ( g_skipFX.GetBool() ) {
  596. return;
  597. }
  598. float fxActionDelay;
  599. const char *fx;
  600. if ( gameLocal.time < nextTriggerTime ) {
  601. return;
  602. }
  603. if ( spawnArgs.GetString( "fx", "", &fx) ) {
  604. Setup( fx );
  605. Start( gameLocal.time );
  606. PostEventMS( &EV_Fx_KillFx, Duration() );
  607. BecomeActive( TH_THINK );
  608. }
  609. fxActionDelay = spawnArgs.GetFloat( "fxActionDelay" );
  610. if ( fxActionDelay != 0.0f ) {
  611. nextTriggerTime = gameLocal.time + SEC2MS( fxActionDelay );
  612. } else {
  613. // prevent multiple triggers on same frame
  614. nextTriggerTime = gameLocal.time + 1;
  615. }
  616. PostEventSec( &EV_Fx_Action, fxActionDelay, activator );
  617. }
  618. /*
  619. ================
  620. idEntityFx::StartFx
  621. ================
  622. */
  623. idEntityFx *idEntityFx::StartFx( const char *fx, const idVec3 *useOrigin, const idMat3 *useAxis, idEntity *ent, bool bind ) {
  624. if ( g_skipFX.GetBool() || !fx || !*fx ) {
  625. return NULL;
  626. }
  627. idDict args;
  628. args.SetBool( "start", true );
  629. args.Set( "fx", fx );
  630. idEntityFx *nfx = static_cast<idEntityFx *>( gameLocal.SpawnEntityType( idEntityFx::Type, &args ) );
  631. if ( nfx->Joint() && *nfx->Joint() ) {
  632. nfx->BindToJoint( ent, nfx->Joint(), true );
  633. nfx->SetOrigin( vec3_origin );
  634. } else {
  635. nfx->SetOrigin( (useOrigin) ? *useOrigin : ent->GetPhysics()->GetOrigin() );
  636. nfx->SetAxis( (useAxis) ? *useAxis : ent->GetPhysics()->GetAxis() );
  637. }
  638. if ( bind ) {
  639. // never bind to world spawn
  640. if ( ent != gameLocal.world ) {
  641. nfx->Bind( ent, true );
  642. }
  643. }
  644. nfx->Show();
  645. return nfx;
  646. }
  647. /*
  648. =================
  649. idEntityFx::WriteToSnapshot
  650. =================
  651. */
  652. void idEntityFx::WriteToSnapshot( idBitMsg &msg ) const {
  653. GetPhysics()->WriteToSnapshot( msg );
  654. WriteBindToSnapshot( msg );
  655. msg.WriteLong( ( fxEffect != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_FX, fxEffect->Index() ) : -1 );
  656. msg.WriteLong( started );
  657. }
  658. /*
  659. =================
  660. idEntityFx::ReadFromSnapshot
  661. =================
  662. */
  663. void idEntityFx::ReadFromSnapshot( const idBitMsg &msg ) {
  664. int fx_index, start_time, max_lapse;
  665. GetPhysics()->ReadFromSnapshot( msg );
  666. ReadBindFromSnapshot( msg );
  667. fx_index = gameLocal.ClientRemapDecl( DECL_FX, msg.ReadLong() );
  668. start_time = msg.ReadLong();
  669. if ( fx_index != -1 && start_time > 0 && !fxEffect && started < 0 ) {
  670. spawnArgs.GetInt( "effect_lapse", "1000", max_lapse );
  671. if ( gameLocal.time - start_time > max_lapse ) {
  672. // too late, skip the effect completely
  673. started = 0;
  674. return;
  675. }
  676. const idDeclFX *fx = static_cast<const idDeclFX *>( declManager->DeclByIndex( DECL_FX, fx_index ) );
  677. if ( !fx ) {
  678. gameLocal.Error( "FX at index %d not found", fx_index );
  679. }
  680. fxEffect = fx;
  681. Setup( fx->GetName() );
  682. Start( start_time );
  683. }
  684. }
  685. /*
  686. =================
  687. idEntityFx::ClientThink
  688. =================
  689. */
  690. void idEntityFx::ClientThink( const int curTime, const float fraction, const bool predict ) {
  691. if ( gameLocal.isNewFrame ) {
  692. Run( gameLocal.serverTime );
  693. }
  694. InterpolatePhysics( fraction );
  695. Present();
  696. }
  697. /*
  698. =================
  699. idEntityFx::ClientPredictionThink
  700. =================
  701. */
  702. void idEntityFx::ClientPredictionThink() {
  703. if ( gameLocal.isNewFrame ) {
  704. Run( gameLocal.time );
  705. }
  706. RunPhysics();
  707. Present();
  708. }
  709. /*
  710. ===============================================================================
  711. idTeleporter
  712. ===============================================================================
  713. */
  714. CLASS_DECLARATION( idEntityFx, idTeleporter )
  715. EVENT( EV_Fx_Action, idTeleporter::Event_DoAction )
  716. END_CLASS
  717. /*
  718. ================
  719. idTeleporter::Event_DoAction
  720. ================
  721. */
  722. void idTeleporter::Event_DoAction( idEntity *activator ) {
  723. float angle;
  724. angle = spawnArgs.GetFloat( "angle" );
  725. idAngles a( 0, spawnArgs.GetFloat( "angle" ), 0 );
  726. activator->Teleport( GetPhysics()->GetOrigin(), a, NULL );
  727. }