Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

632 lines
16 KiB

  1. // ClientInferno.cpp
  2. // Render client-side Inferno effects
  3. // Author: Michael Booth, February 2005
  4. // Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
  5. #include "cbase.h"
  6. #include "igamesystem.h"
  7. #include "hud_macros.h"
  8. #include "view.h"
  9. #include "enginesprite.h"
  10. #include "precache_register.h"
  11. #include "iefx.h"
  12. #include "dlight.h"
  13. #include "tier0/vprof.h"
  14. #include "debugoverlay_shared.h"
  15. #include "basecsgrenade_projectile.h"
  16. #include "clientinferno.h"
  17. // NOTE: This has to be the last file included!
  18. #include "tier0/memdbgon.h"
  19. PRECACHE_REGISTER_BEGIN( GLOBAL, InfernoMaterials )
  20. PRECACHE( MATERIAL, "sprites/white" )
  21. PRECACHE_REGISTER_END()
  22. ConVar InfernoDlightSpacing( "inferno_dlight_spacing", "200", FCVAR_CHEAT, "Inferno dlights are at least this far apart" );
  23. ConVar InfernoDlights( "inferno_dlights", "30", 0, "Min FPS at which molotov dlights will be created" );
  24. //ConVar InfernoParticles( "inferno_particles", "molotov_groundfire", FCVAR_REPLICATED | FCVAR_CHEAT );
  25. ConVar InfernoFire( "inferno_fire", "2" );
  26. enum FireMaskType
  27. {
  28. OLD_FIRE_MASK = 1,
  29. NEW_FIRE_MASK = 2
  30. };
  31. IMPLEMENT_CLIENTCLASS_DT( C_Inferno, DT_Inferno, CInferno )
  32. RecvPropArray3( RECVINFO_ARRAY( m_fireXDelta ), RecvPropInt( RECVINFO(m_fireXDelta[0] ) ) ),
  33. RecvPropArray3( RECVINFO_ARRAY( m_fireYDelta ), RecvPropInt( RECVINFO(m_fireYDelta[0] ) ) ),
  34. RecvPropArray3( RECVINFO_ARRAY( m_fireZDelta ), RecvPropInt( RECVINFO(m_fireZDelta[0] ) ) ),
  35. RecvPropArray3( RECVINFO_ARRAY( m_bFireIsBurning ), RecvPropBool( RECVINFO(m_bFireIsBurning[0] ) ) ),
  36. //RecvPropArray3( RECVINFO_ARRAY( m_BurnNormal ), RecvPropVector( RECVINFO(m_BurnNormal[0] ) ) ),
  37. RecvPropInt( RECVINFO( m_fireCount ) ),
  38. END_RECV_TABLE()
  39. //-----------------------------------------------------------------------------------------------
  40. C_Inferno::C_Inferno()
  41. {
  42. m_maxFireHalfWidth = 30.0f;
  43. m_maxFireHeight = 80.0f;
  44. m_burnParticleEffect = NULL;
  45. }
  46. //-----------------------------------------------------------------------------------------------
  47. C_Inferno::~C_Inferno()
  48. {
  49. if ( m_burnParticleEffect.IsValid() )
  50. {
  51. m_burnParticleEffect->StopEmission();
  52. }
  53. }
  54. //-----------------------------------------------------------------------------------------------
  55. void C_Inferno::Spawn( void )
  56. {
  57. BaseClass::Spawn();
  58. m_fireCount = 0;
  59. m_lastFireCount = 0;
  60. m_drawableCount = 0;
  61. m_burnParticleEffect = NULL;
  62. m_minBounds = Vector( 0, 0, 0 );
  63. m_maxBounds = Vector( 0, 0, 0 );
  64. SetNextClientThink( CLIENT_THINK_ALWAYS );
  65. }
  66. //-----------------------------------------------------------------------------------------------
  67. /**
  68. * Monitor changes and recompute render bounds
  69. */
  70. void C_Inferno::ClientThink()
  71. {
  72. VPROF_BUDGET( "C_Inferno::ClientThink", "Magic" );
  73. bool bIsAttachedToMovingObject = (GetMoveParent() != NULL) ? true : false;
  74. if (true || m_lastFireCount != m_fireCount || bIsAttachedToMovingObject )
  75. {
  76. SynchronizeDrawables();
  77. m_lastFireCount = m_fireCount;
  78. }
  79. bool bDidRecomputeBounds = false;
  80. // update Drawables
  81. for( int i=0; i<m_drawableCount; ++i )
  82. {
  83. Drawable *draw = &m_drawable[i];
  84. switch( draw->m_state )
  85. {
  86. case STARTING:
  87. {
  88. float growRate = draw->m_maxSize/2.0f;
  89. draw->m_size = growRate * (gpGlobals->realtime - draw->m_stateTimestamp);
  90. if (draw->m_size > draw->m_maxSize)
  91. {
  92. draw->m_size = draw->m_maxSize;
  93. draw->SetState( BURNING );
  94. }
  95. break;
  96. }
  97. case GOING_OUT:
  98. {
  99. float dieRate = draw->m_maxSize/2.0f;
  100. draw->m_size = draw->m_maxSize - dieRate * (gpGlobals->realtime - draw->m_stateTimestamp);
  101. if (draw->m_size <= 0.0f)
  102. {
  103. draw->SetState( FIRE_OUT );
  104. // render bounds changed
  105. RecomputeBounds();
  106. bDidRecomputeBounds = true;
  107. if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE )
  108. {
  109. ClientLeafSystem()->RenderableChanged( GetRenderHandle() );
  110. }
  111. }
  112. break;
  113. }
  114. }
  115. }
  116. if( bIsAttachedToMovingObject && !bDidRecomputeBounds )
  117. {
  118. RecomputeBounds();
  119. }
  120. UpdateParticles();
  121. }
  122. //--------------------------------------------------------------------------------------------------------
  123. void C_Inferno::OnNewParticleEffect( const char *pszParticleName, CNewParticleEffect *pNewParticleEffect )
  124. {
  125. if ( FStrEq( pszParticleName, GetParticleEffectName() ) )
  126. {
  127. m_burnParticleEffect = pNewParticleEffect;
  128. }
  129. }
  130. //--------------------------------------------------------------------------------------------------------
  131. void C_Inferno::OnParticleEffectDeleted( CNewParticleEffect *pParticleEffect )
  132. {
  133. if ( m_burnParticleEffect == pParticleEffect )
  134. {
  135. m_burnParticleEffect = NULL;
  136. }
  137. }
  138. //--------------------------------------------------------------------------------------------------------
  139. void C_Inferno::UpdateParticles( void )
  140. {
  141. if ( m_drawableCount > 0 && (InfernoFire.GetInt() & NEW_FIRE_MASK) != 0 )
  142. {
  143. if ( !m_burnParticleEffect.IsValid() )
  144. {
  145. MDLCACHE_CRITICAL_SECTION();
  146. m_burnParticleEffect = ParticleProp()->Create( GetParticleEffectName(), PATTACH_ABSORIGIN_FOLLOW );
  147. /*
  148. DevMsg( "inferno @ %f %f %f / %f %f %f\n",
  149. GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z,
  150. GetAbsAngles()[PITCH], GetAbsAngles()[YAW], GetAbsAngles()[ROLL] );
  151. NDebugOverlay::Cross3D( GetAbsOrigin(), 5, 255, 0, 0, false, 30.0f );
  152. NDebugOverlay::Cross3D( GetAbsOrigin(), 5, 128, 0, 0, true, 30.0f );
  153. */
  154. }
  155. else
  156. {
  157. for( int i=0; i<m_drawableCount && i<MAX_PARTICLE_CONTROL_POINTS; ++i )
  158. {
  159. Drawable *draw = &m_drawable[i];
  160. // TODO: need a way to disable a control point!!!
  161. if ( draw->m_state >= FIRE_OUT )
  162. {
  163. Vector vecCenter = draw->m_pos;
  164. //VectorLerp( m_minBounds, m_maxBounds, 0.5f, vecCenter );
  165. draw->m_pos = vecCenter + Vector( 0, 0, -9999 );
  166. draw->m_size = 0;
  167. // this sucks
  168. if ( i != 0 )
  169. m_burnParticleEffect->SetControlPoint( i, vec3_invalid );
  170. //engine->Con_NPrintf( i + 10, "0 0 0" );
  171. //NDebugOverlay::Cross3D( draw->m_pos, 5, 0, 0, 255, false, 5.1f );
  172. }
  173. else
  174. {
  175. //NDebugOverlay::Cross3D( draw->m_pos, 5, 0, 0, 255, false, 0.1f );
  176. //NDebugOverlay::Line( GetAbsOrigin(), draw->m_pos, 0, 255, 0, true, 0.1f );
  177. m_burnParticleEffect->SetControlPointEntity( i, NULL );
  178. m_burnParticleEffect->SetControlPoint( i, draw->m_pos );
  179. // FIXME - Set orientation to burn normal once we have per particle normals.
  180. //m_burnParticleEffect->SetControlPointOrientation( i, Orientation );
  181. if ( i % 2 == 0 )
  182. {
  183. //Elight, for perf reasons only for every other fire
  184. dlight_t *el = effects->CL_AllocElight( draw->m_dlightIndex );
  185. el->origin = draw->m_pos;
  186. el->origin[2] += 64;
  187. el->color.r = 254;
  188. el->color.g = 100;
  189. el->color.b = 10;
  190. el->radius = random->RandomFloat(60, 120);
  191. el->die = gpGlobals->curtime + random->RandomFloat( 0.01, 0.025 );
  192. el->color.exponent = 5;
  193. }
  194. }
  195. }
  196. SetNextClientThink( 0.1f );
  197. m_burnParticleEffect->SetNeedsBBoxUpdate( true );
  198. Vector vecCenter = GetRenderOrigin();
  199. m_burnParticleEffect->SetSortOrigin( vecCenter );
  200. //NDebugOverlay::Cross3D( vecCenter, 5, 255, 0, 255, false, 0.5f );
  201. Vector vecMin, vecMax;
  202. GetRenderBounds( vecMin, vecMax );
  203. //NDebugOverlay::Box( vecCenter, vecMin, vecMax, 255, 0, 255, 255, 0.5 );
  204. }
  205. }
  206. else
  207. {
  208. if ( m_burnParticleEffect.IsValid() )
  209. {
  210. m_burnParticleEffect->StopEmission();
  211. // m_burnParticleEffect->SetRemoveFlag();
  212. }
  213. }
  214. }
  215. //-----------------------------------------------------------------------------------------------
  216. void C_Inferno::GetRenderBounds( Vector& mins, Vector& maxs )
  217. {
  218. if (m_drawableCount)
  219. {
  220. mins = m_minBounds - GetRenderOrigin();
  221. maxs = m_maxBounds - GetRenderOrigin();
  222. }
  223. else
  224. {
  225. mins = Vector( 0, 0, 0 );
  226. maxs = Vector( 0, 0, 0 );
  227. }
  228. }
  229. //-----------------------------------------------------------------------------------------------
  230. /**
  231. * Returns the bounds as an AABB in worldspace
  232. */
  233. void C_Inferno::GetRenderBoundsWorldspace( Vector& mins, Vector& maxs )
  234. {
  235. if (m_drawableCount)
  236. {
  237. mins = m_minBounds;
  238. maxs = m_maxBounds;
  239. }
  240. else
  241. {
  242. mins = Vector( 0, 0, 0 );
  243. maxs = Vector( 0, 0, 0 );
  244. }
  245. }
  246. //-----------------------------------------------------------------------------------------------
  247. void C_Inferno::RecomputeBounds( void )
  248. {
  249. m_minBounds = GetAbsOrigin() + Vector( 64.9f, 64.9f, 64.9f );
  250. m_maxBounds = GetAbsOrigin() + Vector( -64.9f, -64.9f, -64.9f );
  251. for( int i=0; i<m_drawableCount; ++i )
  252. {
  253. Drawable *draw = &m_drawable[i];
  254. if (draw->m_state == FIRE_OUT)
  255. continue;
  256. if (draw->m_pos.x - m_maxFireHalfWidth < m_minBounds.x)
  257. m_minBounds.x = draw->m_pos.x - m_maxFireHalfWidth;
  258. if (draw->m_pos.x + m_maxFireHalfWidth > m_maxBounds.x)
  259. m_maxBounds.x = draw->m_pos.x + m_maxFireHalfWidth;
  260. if (draw->m_pos.y - m_maxFireHalfWidth < m_minBounds.y)
  261. m_minBounds.y = draw->m_pos.y - m_maxFireHalfWidth;
  262. if (draw->m_pos.y + m_maxFireHalfWidth > m_maxBounds.y)
  263. m_maxBounds.y = draw->m_pos.y + m_maxFireHalfWidth;
  264. if (draw->m_pos.z < m_minBounds.z)
  265. m_minBounds.z = draw->m_pos.z;
  266. if (draw->m_pos.z + m_maxFireHeight > m_maxBounds.z)
  267. m_maxBounds.z = draw->m_pos.z + m_maxFireHeight;
  268. }
  269. }
  270. //-----------------------------------------------------------------------------------------------
  271. /**
  272. * Given a position, return the fire there
  273. */
  274. C_Inferno::Drawable *C_Inferno::GetDrawable( const Vector &pos )
  275. {
  276. for( int i=0; i<m_drawableCount; ++i )
  277. {
  278. const float equalTolerance = 12.0f;
  279. if (VectorsAreEqual( m_drawable[i].m_pos, pos, equalTolerance ))
  280. {
  281. m_drawable[ i ].m_pos = pos;
  282. return &m_drawable[i];
  283. }
  284. }
  285. return NULL;
  286. }
  287. //-----------------------------------------------------------------------------------------------
  288. /**
  289. * Compare m_fireX etc to m_drawable and update states
  290. */
  291. void C_Inferno::SynchronizeDrawables( void )
  292. {
  293. VPROF_BUDGET( "C_Inferno::SynchronizeDrawables", "Magic" );
  294. int i;
  295. // mark all fires that are "burning" as "unknown" - active ones will be reset
  296. for( i=0; i<m_drawableCount; ++i )
  297. {
  298. if (m_drawable[i].m_state == BURNING)
  299. {
  300. m_drawable[i].m_state = UNKNOWN;
  301. }
  302. }
  303. Vector vInfernoOrigin = GetAbsOrigin();
  304. for( i=0; i<m_fireCount; ++i )
  305. {
  306. Vector firePos = vInfernoOrigin;
  307. Vector vecFlamePos = Vector( m_fireXDelta[ i ], m_fireYDelta[ i ], m_fireZDelta[ i ] );
  308. firePos.x += vecFlamePos.x;
  309. firePos.y += vecFlamePos.y;
  310. firePos.z += vecFlamePos.z;
  311. Vector fireNormal = m_BurnNormal[i];
  312. Drawable *info = GetDrawable( firePos );
  313. if ( m_bFireIsBurning[i] == false )
  314. {
  315. if ( info && info->m_state != FIRE_OUT )
  316. {
  317. info->m_state = FIRE_OUT;
  318. // render bounds changed
  319. RecomputeBounds();
  320. if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE )
  321. {
  322. ClientLeafSystem()->RenderableChanged( GetRenderHandle() );
  323. }
  324. }
  325. continue;
  326. }
  327. else if ( info )
  328. {
  329. // existing fire continues to burn
  330. if (info->m_state == UNKNOWN)
  331. {
  332. info->m_state = BURNING;
  333. }
  334. }
  335. else if (m_drawableCount < MAX_INFERNO_FIRES)
  336. {
  337. // new fire
  338. info = &m_drawable[ m_drawableCount ];
  339. info->SetState( STARTING );
  340. info->m_pos = firePos;
  341. info->m_normal = fireNormal;
  342. info->m_frame = 0;
  343. info->m_framerate = random->RandomFloat( 0.04f, 0.06f );
  344. info->m_mirror = (random->RandomInt( 0, 100 ) < 50);
  345. info->m_size = 0.0f;
  346. info->m_maxSize = random->RandomFloat( 70.0f, 90.0f );
  347. bool closeDlight = false;
  348. for ( int i=0; i<m_drawableCount; ++i )
  349. {
  350. if ( m_drawable[i].m_state != FIRE_OUT )
  351. {
  352. if ( m_drawable[i].m_dlightIndex > 0 )
  353. {
  354. if ( m_drawable[i].m_pos.DistToSqr( firePos ) < InfernoDlightSpacing.GetFloat() * InfernoDlightSpacing.GetFloat() )
  355. {
  356. closeDlight = true;
  357. break;
  358. }
  359. }
  360. }
  361. }
  362. if ( closeDlight )
  363. {
  364. info->m_dlightIndex = 0;
  365. }
  366. else
  367. {
  368. info->m_dlightIndex = LIGHT_INDEX_TE_DYNAMIC + index + m_drawableCount;
  369. }
  370. // render bounds changed
  371. RecomputeBounds();
  372. if ( GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE )
  373. {
  374. ClientLeafSystem()->RenderableChanged( GetRenderHandle() );
  375. }
  376. ++m_drawableCount;
  377. }
  378. }
  379. // any fires still in the UNKNOWN state are now GOING_OUT
  380. for( i=0; i<m_drawableCount; ++i )
  381. {
  382. if (m_drawable[i].m_state == UNKNOWN)
  383. {
  384. m_drawable[i].SetState( GOING_OUT );
  385. }
  386. }
  387. }
  388. //-----------------------------------------------------------------------------------------------
  389. int C_Inferno::DrawModel( int flags, const RenderableInstance_t &instance )
  390. {
  391. VPROF_BUDGET( "C_Inferno::DrawModel", "DeadRun" );
  392. IMaterial *material;
  393. CEngineSprite *sprite;
  394. const model_t *model;
  395. if ( (InfernoFire.GetInt() & OLD_FIRE_MASK) == 0 )
  396. return 0;
  397. model = modelinfo->GetModel( modelinfo->GetModelIndex( "sprites/fire1.vmt" ) );
  398. if (model == NULL)
  399. return 0;
  400. sprite = (CEngineSprite *)modelinfo->GetModelExtraData( model );
  401. if (sprite == NULL)
  402. return 0;
  403. material = sprite->GetMaterial( kRenderTransAdd );
  404. if (material == NULL)
  405. return 0;
  406. CMatRenderContextPtr pRenderContext( materials );
  407. pRenderContext->Bind( material );
  408. IMesh *pMesh = pRenderContext->GetDynamicMesh();
  409. if ( pMesh )
  410. {
  411. // draw the actual flames
  412. for( int i=0; i<m_drawableCount; ++i )
  413. {
  414. int frame = (int)(gpGlobals->realtime/m_drawable[i].m_framerate) % sprite->GetNumFrames();
  415. sprite->SetFrame( kRenderTransAdd, frame );
  416. DrawFire( &m_drawable[i], pMesh );
  417. }
  418. }
  419. return 0;
  420. }
  421. //-----------------------------------------------------------------------------------------------
  422. /**
  423. * Render an individual fire sprite
  424. */
  425. void C_Inferno::DrawFire( C_Inferno::Drawable *fire, IMesh *mesh )
  426. {
  427. const float halfWidth = fire->m_size/3.0f;
  428. //unsigned char color[4] = { 255,255,255,255 };
  429. unsigned char color[4] = { 150,150,150,255 };
  430. CMeshBuilder meshBuilder;
  431. meshBuilder.Begin( mesh, MATERIAL_QUADS, 1 );
  432. const Vector &right = (fire->m_mirror) ? -CurrentViewRight() : CurrentViewRight();
  433. Vector up( 0.0f, 0.0f, 1.0f );
  434. Vector top = fire->m_pos + up * fire->m_size;
  435. const Vector &bottom = fire->m_pos;
  436. Vector pos = top + right * halfWidth;
  437. meshBuilder.Position3fv( pos.Base() );
  438. meshBuilder.Color4ubv( color );
  439. meshBuilder.TexCoord2f( 0, 1, 0 );
  440. meshBuilder.AdvanceVertex();
  441. pos = bottom + right * halfWidth;
  442. meshBuilder.Position3fv( pos.Base() );
  443. meshBuilder.Color4ubv( color );
  444. meshBuilder.TexCoord2f( 0, 1, 1 );
  445. meshBuilder.AdvanceVertex();
  446. pos = bottom - right * halfWidth;
  447. meshBuilder.Position3fv( pos.Base() );
  448. meshBuilder.Color4ubv( color );
  449. meshBuilder.TexCoord2f( 0, 0, 1 );
  450. meshBuilder.AdvanceVertex();
  451. pos = top - right * halfWidth;
  452. meshBuilder.Position3fv( pos.Base() );
  453. meshBuilder.Color4ubv( color );
  454. meshBuilder.TexCoord2f( 0, 0, 0 );
  455. meshBuilder.AdvanceVertex();
  456. meshBuilder.End();
  457. mesh->Draw();
  458. if ( fire->m_dlightIndex > 0 && InfernoDlights.GetFloat() >= 1 )
  459. {
  460. static float lastRealTime = -1.0f;
  461. float realFrameTime = gpGlobals->realtime - lastRealTime;
  462. if ( realFrameTime > 2 )
  463. {
  464. realFrameTime = -1.0f;
  465. }
  466. if ( realFrameTime > 0 )
  467. {
  468. static float AverageFPS = -1;
  469. static int high = -1;
  470. static int low = -1;
  471. int nFps = -1;
  472. const float NewWeight = 0.1f;
  473. float NewFrame = 1.0f / realFrameTime;
  474. if ( AverageFPS < 0.0f )
  475. {
  476. AverageFPS = NewFrame;
  477. high = (int)AverageFPS;
  478. low = (int)AverageFPS;
  479. }
  480. else
  481. {
  482. AverageFPS *= ( 1.0f - NewWeight ) ;
  483. AverageFPS += ( ( NewFrame ) * NewWeight );
  484. }
  485. int NewFrameInt = (int)NewFrame;
  486. if( NewFrameInt < low ) low = NewFrameInt;
  487. if( NewFrameInt > high ) high = NewFrameInt;
  488. nFps = static_cast<int>( AverageFPS );
  489. if ( nFps < InfernoDlights.GetFloat() )
  490. {
  491. fire->m_dlightIndex = 0;
  492. lastRealTime = gpGlobals->realtime;
  493. return;
  494. }
  495. }
  496. lastRealTime = gpGlobals->realtime;
  497. // These are the dlight params from the Ep1 fire glows, with a slightly larger flicker
  498. // (radius delta is larger, starting from 250 instead of 400).
  499. float scale = fire->m_size / fire->m_maxSize * 1.5f;
  500. dlight_t *el = effects->CL_AllocElight( fire->m_dlightIndex );
  501. el->origin = bottom;
  502. el->origin[2] += 16.0f * scale;
  503. el->color.r = 254;
  504. el->color.g = 100;
  505. el->color.b = 10;
  506. el->radius = random->RandomFloat(50, 131) * scale;
  507. el->die = gpGlobals->curtime + 0.1f;
  508. el->color.exponent = 5;
  509. /*
  510. dlight_t *dl = effects->CL_AllocDlight ( fire->m_dlightIndex );
  511. dl->origin = bottom;
  512. dl->origin[2] += 16.0f * scale;
  513. dl->color.r = 254;
  514. dl->color.g = 174;
  515. dl->color.b = 10;
  516. dl->radius = random->RandomFloat(350,431) * scale;
  517. dl->die = gpGlobals->curtime + 0.1f;
  518. */
  519. }
  520. }
  521. IMPLEMENT_CLIENTCLASS_DT( C_FireCrackerBlast, DT_FireCrackerBlast, CFireCrackerBlast )
  522. END_RECV_TABLE()