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.

1043 lines
30 KiB

  1. //===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "c_smoke_trail.h"
  9. #include "smoke_fog_overlay.h"
  10. #include "engine/IEngineTrace.h"
  11. #include "view.h"
  12. #include "dlight.h"
  13. #include "iefx.h"
  14. #include "tier1/keyvalues.h"
  15. #include "toolframework_client.h"
  16. #if CSTRIKE_DLL
  17. #include "c_cs_player.h"
  18. #endif
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. // ------------------------------------------------------------------------- //
  22. // Definitions
  23. // ------------------------------------------------------------------------- //
  24. static Vector s_FadePlaneDirections[] =
  25. {
  26. Vector( 1,0,0),
  27. Vector(-1,0,0),
  28. Vector(0, 1,0),
  29. Vector(0,-1,0),
  30. Vector(0,0, 1),
  31. Vector(0,0,-1)
  32. };
  33. #define NUM_FADE_PLANES (sizeof(s_FadePlaneDirections)/sizeof(s_FadePlaneDirections[0]))
  34. // This is used to randomize the direction it chooses to move a particle in.
  35. int g_OffsetLookup[3] = {-1,0,1};
  36. // ------------------------------------------------------------------------- //
  37. // Classes
  38. // ------------------------------------------------------------------------- //
  39. class C_ParticleSmokeGrenade : public C_BaseParticleEntity, public IPrototypeAppEffect
  40. {
  41. public:
  42. DECLARE_CLASS( C_ParticleSmokeGrenade, C_BaseParticleEntity );
  43. DECLARE_CLIENTCLASS();
  44. C_ParticleSmokeGrenade();
  45. ~C_ParticleSmokeGrenade();
  46. private:
  47. class SmokeGrenadeParticle : public Particle
  48. {
  49. public:
  50. float m_RotationSpeed;
  51. float m_CurRotation;
  52. float m_FadeAlpha; // Set as it moves around.
  53. unsigned char m_ColorInterp; // Amount between min and max colors.
  54. unsigned char m_Color[4];
  55. };
  56. public:
  57. // Optional call. It will use defaults if you don't call this.
  58. void SetParams(
  59. );
  60. // Call this to move the source..
  61. void SetPos(const Vector &pos);
  62. // C_BaseEntity.
  63. public:
  64. virtual void OnDataChanged( DataUpdateType_t updateType );
  65. virtual void CleanupToolRecordingState( KeyValues *msg );
  66. // IPrototypeAppEffect.
  67. public:
  68. virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs);
  69. // IParticleEffect.
  70. public:
  71. virtual void Update(float fTimeDelta);
  72. virtual void RenderParticles( CParticleRenderIterator *pIterator );
  73. virtual void SimulateParticles( CParticleSimulateIterator *pIterator );
  74. virtual void NotifyRemove();
  75. virtual void GetParticlePosition( Particle *pParticle, Vector& worldpos );
  76. virtual void ClientThink();
  77. // Proxies.
  78. public:
  79. static void RecvProxy_CurrentStage( const CRecvProxyData *pData, void *pStruct, void *pOut );
  80. private:
  81. // The SmokeEmitter represents a grid in 3D space.
  82. class SmokeParticleInfo
  83. {
  84. public:
  85. SmokeGrenadeParticle *m_pParticle;
  86. int m_TradeIndex; // -1 if not exchanging yet.
  87. float m_TradeClock; // How long since they started trading.
  88. float m_TradeDuration; // How long the trade will take to finish.
  89. float m_FadeAlpha; // Calculated from nearby world geometry.
  90. unsigned char m_Color[4];
  91. };
  92. void ApplyDynamicLight( const Vector &vParticlePos, Vector &color );
  93. void UpdateDynamicLightList( const Vector &vMins, const Vector &vMaxs );
  94. void UpdateSmokeTrail( float fTimeDelta );
  95. void UpdateParticleAndFindTrade( int iParticle, float fTimeDelta );
  96. void UpdateParticleDuringTrade( int iParticle, float flTimeDelta );
  97. inline int GetSmokeParticleIndex(int x, int y, int z) {return z*m_xCount*m_yCount+y*m_yCount+x;}
  98. inline SmokeParticleInfo* GetSmokeParticleInfo(int x, int y, int z) {return &m_SmokeParticleInfos[GetSmokeParticleIndex(x,y,z)];}
  99. inline void GetParticleInfoXYZ(int index, int &x, int &y, int &z)
  100. {
  101. z = index / (m_xCount*m_yCount);
  102. int zIndex = z*m_xCount*m_yCount;
  103. y = (index - zIndex) / m_yCount;
  104. int yIndex = y*m_yCount;
  105. x = index - zIndex - yIndex;
  106. }
  107. inline bool IsValidXYZCoords(int x, int y, int z)
  108. {
  109. return x >= 0 && y >= 0 && z >= 0 && x < m_xCount && y < m_yCount && z < m_zCount;
  110. }
  111. inline Vector GetSmokeParticlePos(int x, int y, int z)
  112. {
  113. return m_SmokeBasePos +
  114. Vector( ((float)x / (m_xCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius,
  115. ((float)y / (m_yCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius,
  116. ((float)z / (m_zCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius);
  117. }
  118. inline Vector GetSmokeParticlePosIndex(int index)
  119. {
  120. int x, y, z;
  121. GetParticleInfoXYZ(index, x, y, z);
  122. return GetSmokeParticlePos(x, y, z);
  123. }
  124. inline const Vector& GetPos() { return GetAbsOrigin(); }
  125. // Start filling the smoke volume (and stop the smoke trail).
  126. void FillVolume();
  127. // State variables from server.
  128. public:
  129. unsigned char m_CurrentStage;
  130. Vector m_SmokeBasePos;
  131. // What time the effect was initially created
  132. float m_flSpawnTime;
  133. // It will fade out during this time.
  134. float m_FadeStartTime;
  135. float m_FadeEndTime;
  136. float m_FadeAlpha; // Calculated from the fade start/end times each frame.
  137. // Color driven by grenade weapon description.
  138. Vector m_MinColor;
  139. Vector m_MaxColor;
  140. // Used during rendering.. active dlights.
  141. class CActiveLight
  142. {
  143. public:
  144. Vector m_vColor;
  145. Vector m_vOrigin;
  146. float m_flRadiusSqr;
  147. };
  148. CActiveLight m_ActiveLights[MAX_DLIGHTS];
  149. int m_nActiveLights;
  150. private:
  151. C_ParticleSmokeGrenade( const C_ParticleSmokeGrenade & );
  152. bool m_bStarted;
  153. bool m_bVolumeFilled;
  154. PMaterialHandle m_MaterialHandles[NUM_MATERIAL_HANDLES];
  155. SmokeParticleInfo m_SmokeParticleInfos[NUM_PARTICLES_PER_DIMENSION*NUM_PARTICLES_PER_DIMENSION*NUM_PARTICLES_PER_DIMENSION];
  156. int m_xCount, m_yCount, m_zCount;
  157. float m_SpacingRadius;
  158. float m_ExpandTimeCounter; // How long since we started expanding.
  159. float m_ExpandRadius; // How large is our radius.
  160. C_SmokeTrail m_SmokeTrail;
  161. };
  162. // Expose to the particle app.
  163. EXPOSE_PROTOTYPE_EFFECT(SmokeGrenade, C_ParticleSmokeGrenade);
  164. // Datatable..
  165. IMPLEMENT_CLIENTCLASS_DT(C_ParticleSmokeGrenade, DT_ParticleSmokeGrenade, ParticleSmokeGrenade)
  166. RecvPropTime(RECVINFO(m_flSpawnTime)),
  167. RecvPropFloat(RECVINFO(m_FadeStartTime)),
  168. RecvPropFloat(RECVINFO(m_FadeEndTime)),
  169. RecvPropVector(RECVINFO(m_MinColor)),
  170. RecvPropVector(RECVINFO(m_MaxColor)),
  171. RecvPropInt(RECVINFO(m_CurrentStage), 0, &C_ParticleSmokeGrenade::RecvProxy_CurrentStage),
  172. END_RECV_TABLE()
  173. // ------------------------------------------------------------------------- //
  174. // Helpers.
  175. // ------------------------------------------------------------------------- //
  176. static inline void InterpColor(unsigned char dest[4], unsigned char src1[4], unsigned char src2[4], float percent)
  177. {
  178. dest[0] = (unsigned char)(src1[0] + (src2[0] - src1[0]) * percent);
  179. dest[1] = (unsigned char)(src1[1] + (src2[1] - src1[1]) * percent);
  180. dest[2] = (unsigned char)(src1[2] + (src2[2] - src1[2]) * percent);
  181. }
  182. static inline int GetWorldPointContents(const Vector &vPos)
  183. {
  184. #if defined(PARTICLEPROTOTYPE_APP)
  185. return 0;
  186. #else
  187. return enginetrace->GetPointContents( vPos );
  188. #endif
  189. }
  190. static inline void WorldTraceLine( const Vector &start, const Vector &end, int contentsMask, trace_t *trace )
  191. {
  192. #if defined(PARTICLEPROTOTYPE_APP)
  193. trace->fraction = 1;
  194. #else
  195. UTIL_TraceLine(start, end, contentsMask, NULL, COLLISION_GROUP_NONE, trace);
  196. #endif
  197. }
  198. static inline Vector EngineGetLightForPoint(const Vector &vPos)
  199. {
  200. #if defined(PARTICLEPROTOTYPE_APP)
  201. return Vector(1,1,1);
  202. #else
  203. return engine->GetLightForPoint(vPos, true);
  204. #endif
  205. }
  206. static inline const Vector& EngineGetVecRenderOrigin()
  207. {
  208. #if defined(PARTICLEPROTOTYPE_APP)
  209. static Vector dummy(0,0,0);
  210. return dummy;
  211. #else
  212. return CurrentViewOrigin();
  213. #endif
  214. }
  215. static inline float& EngineGetSmokeFogOverlayAlpha()
  216. {
  217. #if defined(PARTICLEPROTOTYPE_APP)
  218. static float dummy;
  219. return dummy;
  220. #else
  221. return g_SmokeFogOverlayAlpha;
  222. #endif
  223. }
  224. static inline void EngineAddSmokeFogOverlayColor( Vector &color )
  225. {
  226. g_SmokeFogOverlayColor += color;
  227. }
  228. static inline C_BaseEntity* ParticleGetEntity(int index)
  229. {
  230. #if defined(PARTICLEPROTOTYPE_APP)
  231. return NULL;
  232. #else
  233. return cl_entitylist->GetEnt(index);
  234. #endif
  235. }
  236. // ------------------------------------------------------------------------- //
  237. // ParticleMovieExplosion
  238. // ------------------------------------------------------------------------- //
  239. C_ParticleSmokeGrenade::C_ParticleSmokeGrenade()
  240. {
  241. memset(m_MaterialHandles, 0, sizeof(m_MaterialHandles));
  242. m_MinColor.Init(0.5, 0.5, 0.5);
  243. m_MaxColor.Init(0.6, 0.6, 0.6 );
  244. m_nActiveLights = 0;
  245. m_ExpandRadius = 0;
  246. m_ExpandTimeCounter = 0;
  247. m_FadeStartTime = 0;
  248. m_FadeEndTime = 0;
  249. m_flSpawnTime = 0;
  250. m_bVolumeFilled = false;
  251. m_CurrentStage = 0;
  252. m_bStarted = false;
  253. }
  254. C_ParticleSmokeGrenade::~C_ParticleSmokeGrenade()
  255. {
  256. ParticleMgr()->RemoveEffect( &m_ParticleEffect );
  257. }
  258. void C_ParticleSmokeGrenade::SetParams(
  259. )
  260. {
  261. }
  262. void C_ParticleSmokeGrenade::OnDataChanged( DataUpdateType_t updateType )
  263. {
  264. C_BaseEntity::OnDataChanged(updateType);
  265. if(updateType == DATA_UPDATE_CREATED )
  266. {
  267. Start(ParticleMgr(), NULL);
  268. }
  269. }
  270. void C_ParticleSmokeGrenade::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs)
  271. {
  272. if(!pParticleMgr->AddEffect( &m_ParticleEffect, this ))
  273. return;
  274. m_SmokeTrail.Start(pParticleMgr, pArgs);
  275. m_SmokeTrail.m_ParticleLifetime = 0.5;
  276. m_SmokeTrail.SetSpawnRate(40);
  277. m_SmokeTrail.m_MinSpeed = 0;
  278. m_SmokeTrail.m_MaxSpeed = 0;
  279. m_SmokeTrail.m_StartSize = 3;
  280. m_SmokeTrail.m_EndSize = 10;
  281. m_SmokeTrail.m_SpawnRadius = 0;
  282. m_SmokeTrail.SetLocalOrigin( GetAbsOrigin() );
  283. for(int i=0; i < NUM_MATERIAL_HANDLES; i++)
  284. {
  285. char str[256];
  286. Q_snprintf(str, sizeof( str ), "particle/particle_smokegrenade%d", i+1);
  287. m_MaterialHandles[i] = m_ParticleEffect.FindOrAddMaterial(str);
  288. }
  289. if( m_CurrentStage == 2 )
  290. {
  291. FillVolume();
  292. }
  293. // Go straight into "fill volume" mode if they want.
  294. if(pArgs)
  295. {
  296. if(pArgs->FindArg("-FillVolume"))
  297. {
  298. FillVolume();
  299. }
  300. }
  301. m_bStarted = true;
  302. SetNextClientThink( CLIENT_THINK_ALWAYS );
  303. #if CSTRIKE_DLL
  304. C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
  305. if ( pPlayer )
  306. {
  307. pPlayer->m_SmokeGrenades.AddToTail( this );
  308. }
  309. #endif
  310. }
  311. void C_ParticleSmokeGrenade::ClientThink()
  312. {
  313. if ( m_CurrentStage == 1 )
  314. {
  315. // Add our influence to the global smoke fog alpha.
  316. ASSERT_LOCAL_PLAYER_RESOLVABLE();
  317. int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT();
  318. float testDist = (MainViewOrigin(nSlot) - m_SmokeBasePos).Length();
  319. float fadeEnd = m_ExpandRadius;
  320. // The center of the smoke cloud that always gives full fog overlay
  321. float flCoreDistance = fadeEnd * 0.15;
  322. if(testDist < fadeEnd)
  323. {
  324. float smokeAlpha = 0.0f;
  325. if( testDist < flCoreDistance )
  326. {
  327. smokeAlpha = m_FadeAlpha;
  328. }
  329. else
  330. {
  331. smokeAlpha = (1 - ( testDist - flCoreDistance ) / ( fadeEnd - flCoreDistance ) ) * m_FadeAlpha;
  332. }
  333. EngineGetSmokeFogOverlayAlpha() += smokeAlpha;
  334. // The fog overlay color is half the intensity of the maximum color value. We multiply it by the smoke's fade alpha and
  335. // later divide the sum by the total alpha value to get the average color weighted by alpha influence.
  336. // This lets us combine multiple colored smoke effects together should the situation arise.
  337. Vector color = m_MaxColor * 0.5f * smokeAlpha;
  338. EngineAddSmokeFogOverlayColor(color);
  339. }
  340. }
  341. }
  342. void C_ParticleSmokeGrenade::UpdateSmokeTrail( float fTimeDelta )
  343. {
  344. C_BaseEntity *pAimEnt = GetFollowedEntity();
  345. if ( pAimEnt )
  346. {
  347. Vector forward, right, up;
  348. // Update the smoke particle color.
  349. if(m_CurrentStage == 0)
  350. {
  351. m_SmokeTrail.m_StartColor = EngineGetLightForPoint(GetAbsOrigin()) * 0.5f;
  352. m_SmokeTrail.m_EndColor = m_SmokeTrail.m_StartColor;
  353. }
  354. // Spin the smoke trail.
  355. AngleVectors(pAimEnt->GetAbsAngles(), &forward, &right, &up);
  356. m_SmokeTrail.m_VelocityOffset = forward * 30 + GetAbsVelocity();
  357. m_SmokeTrail.SetLocalOrigin( GetAbsOrigin() );
  358. m_SmokeTrail.Update(fTimeDelta);
  359. }
  360. }
  361. inline void C_ParticleSmokeGrenade::UpdateParticleDuringTrade( int iParticle, float fTimeDelta )
  362. {
  363. SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[iParticle];
  364. SmokeParticleInfo *pOther = &m_SmokeParticleInfos[pInfo->m_TradeIndex];
  365. Assert(pOther->m_TradeIndex == iParticle);
  366. // This makes sure the trade only gets updated once per frame.
  367. if(pInfo < pOther)
  368. {
  369. // Increment the trade clock..
  370. pInfo->m_TradeClock = (pOther->m_TradeClock += fTimeDelta);
  371. int x, y, z;
  372. GetParticleInfoXYZ(iParticle, x, y, z);
  373. Vector myPos = GetSmokeParticlePos(x, y, z) - m_SmokeBasePos;
  374. int otherX, otherY, otherZ;
  375. GetParticleInfoXYZ(pInfo->m_TradeIndex, otherX, otherY, otherZ);
  376. Vector otherPos = GetSmokeParticlePos(otherX, otherY, otherZ) - m_SmokeBasePos;
  377. // Is the trade finished?
  378. if(pInfo->m_TradeClock >= pInfo->m_TradeDuration)
  379. {
  380. pInfo->m_TradeIndex = pOther->m_TradeIndex = -1;
  381. pInfo->m_pParticle->m_Pos = otherPos;
  382. pOther->m_pParticle->m_Pos = myPos;
  383. SmokeGrenadeParticle *temp = pInfo->m_pParticle;
  384. pInfo->m_pParticle = pOther->m_pParticle;
  385. pOther->m_pParticle = temp;
  386. }
  387. else
  388. {
  389. // Ok, move them closer.
  390. float percent = (float)cos(pInfo->m_TradeClock * 2 * 1.57079632f / pInfo->m_TradeDuration);
  391. percent = percent * 0.5 + 0.5;
  392. pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * (1 - percent);
  393. pOther->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * percent;
  394. InterpColor(pInfo->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, 1-percent);
  395. InterpColor(pOther->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, percent);
  396. pInfo->m_pParticle->m_Pos = myPos + (otherPos - myPos) * (1 - percent);
  397. pOther->m_pParticle->m_Pos = myPos + (otherPos - myPos) * percent;
  398. }
  399. }
  400. }
  401. void C_ParticleSmokeGrenade::UpdateParticleAndFindTrade( int iParticle, float fTimeDelta )
  402. {
  403. SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[iParticle];
  404. pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha;
  405. pInfo->m_pParticle->m_Color[0] = pInfo->m_Color[0];
  406. pInfo->m_pParticle->m_Color[1] = pInfo->m_Color[1];
  407. pInfo->m_pParticle->m_Color[2] = pInfo->m_Color[2];
  408. // Is there an adjacent one that's not trading?
  409. int x, y, z;
  410. GetParticleInfoXYZ(iParticle, x, y, z);
  411. int xCountOffset = RandomInt( 0, VALVE_RAND_MAX );
  412. int yCountOffset = RandomInt( 0, VALVE_RAND_MAX );
  413. int zCountOffset = RandomInt( 0, VALVE_RAND_MAX );
  414. bool bFound = false;
  415. for(int xCount=0; xCount < 3 && !bFound; xCount++)
  416. {
  417. for(int yCount=0; yCount < 3 && !bFound; yCount++)
  418. {
  419. for(int zCount=0; zCount < 3; zCount++)
  420. {
  421. int testX = x + g_OffsetLookup[(xCount+xCountOffset) % 3];
  422. int testY = y + g_OffsetLookup[(yCount+yCountOffset) % 3];
  423. int testZ = z + g_OffsetLookup[(zCount+zCountOffset) % 3];
  424. if(testX == x && testY == y && testZ == z)
  425. continue;
  426. if(IsValidXYZCoords(testX, testY, testZ))
  427. {
  428. SmokeParticleInfo *pOther = GetSmokeParticleInfo(testX, testY, testZ);
  429. if(pOther->m_pParticle && pOther->m_TradeIndex == -1)
  430. {
  431. // Ok, this one is looking to trade also.
  432. pInfo->m_TradeIndex = GetSmokeParticleIndex(testX, testY, testZ);
  433. pOther->m_TradeIndex = iParticle;
  434. pInfo->m_TradeClock = pOther->m_TradeClock = 0;
  435. pInfo->m_TradeDuration = FRand(TRADE_DURATION_MIN, TRADE_DURATION_MAX);
  436. bFound = true;
  437. break;
  438. }
  439. }
  440. }
  441. }
  442. }
  443. }
  444. void C_ParticleSmokeGrenade::Update(float fTimeDelta)
  445. {
  446. float flLifetime = gpGlobals->curtime - m_flSpawnTime;
  447. // Update the smoke trail.
  448. UpdateSmokeTrail( fTimeDelta );
  449. // Update our fade alpha.
  450. if(flLifetime < m_FadeStartTime)
  451. {
  452. m_FadeAlpha = 1;
  453. }
  454. else if(flLifetime < m_FadeEndTime)
  455. {
  456. float fadePercent = (flLifetime - m_FadeStartTime) / (m_FadeEndTime - m_FadeStartTime);
  457. m_FadeAlpha = cos(fadePercent * 3.14159) * 0.5 + 0.5;
  458. }
  459. else
  460. {
  461. m_FadeAlpha = 0;
  462. }
  463. // Scale by the amount the sphere has grown.
  464. m_FadeAlpha *= m_ExpandRadius / (m_SpacingRadius*2);
  465. // Update our bbox.
  466. Vector vMins = m_SmokeBasePos - Vector( m_SpacingRadius, m_SpacingRadius, m_SpacingRadius );
  467. Vector vMaxs = m_SmokeBasePos + Vector( m_SpacingRadius, m_SpacingRadius, m_SpacingRadius );
  468. m_ParticleEffect.SetBBox( vMins, vMaxs );
  469. // Update the current light list.
  470. UpdateDynamicLightList( vMins, vMaxs );
  471. if(m_CurrentStage == 1)
  472. {
  473. // Update the expanding sphere.
  474. m_ExpandTimeCounter = flLifetime;
  475. if(m_ExpandTimeCounter > SMOKESPHERE_EXPAND_TIME)
  476. m_ExpandTimeCounter = SMOKESPHERE_EXPAND_TIME;
  477. m_ExpandRadius = (m_SpacingRadius*2) * (float)sin(m_ExpandTimeCounter * M_PI * 0.5 / SMOKESPHERE_EXPAND_TIME);
  478. // Update all the moving traders and establish new ones.
  479. int nTotal = m_xCount * m_yCount * m_zCount;
  480. for(int i=0; i < nTotal; i++)
  481. {
  482. SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[i];
  483. if(!pInfo->m_pParticle)
  484. continue;
  485. if(pInfo->m_TradeIndex == -1)
  486. {
  487. UpdateParticleAndFindTrade( i, fTimeDelta );
  488. }
  489. else
  490. {
  491. UpdateParticleDuringTrade( i, fTimeDelta );
  492. }
  493. }
  494. }
  495. m_SmokeBasePos = GetPos();
  496. }
  497. void C_ParticleSmokeGrenade::UpdateDynamicLightList( const Vector &vMins, const Vector &vMaxs )
  498. {
  499. dlight_t *lights[MAX_DLIGHTS];
  500. int nLights = effects->CL_GetActiveDLights( lights );
  501. m_nActiveLights = 0;
  502. for ( int i=0; i < nLights; i++ )
  503. {
  504. dlight_t *pIn = lights[i];
  505. if ( pIn->origin.x + pIn->radius <= vMins.x ||
  506. pIn->origin.y + pIn->radius <= vMins.y ||
  507. pIn->origin.z + pIn->radius <= vMins.z ||
  508. pIn->origin.x - pIn->radius >= vMaxs.x ||
  509. pIn->origin.y - pIn->radius >= vMaxs.y ||
  510. pIn->origin.z - pIn->radius >= vMaxs.z )
  511. {
  512. }
  513. else
  514. {
  515. CActiveLight *pOut = &m_ActiveLights[m_nActiveLights];
  516. if ( (pIn->color.r != 0 || pIn->color.g != 0 || pIn->color.b != 0) && pIn->color.exponent != 0 )
  517. {
  518. ColorRGBExp32ToVector( pIn->color, pOut->m_vColor );
  519. pOut->m_vColor /= 255.0f;
  520. pOut->m_flRadiusSqr = (pIn->radius + SMOKEPARTICLE_SIZE) * (pIn->radius + SMOKEPARTICLE_SIZE);
  521. pOut->m_vOrigin = pIn->origin;
  522. ++m_nActiveLights;
  523. }
  524. }
  525. }
  526. }
  527. inline void C_ParticleSmokeGrenade::ApplyDynamicLight( const Vector &vParticlePos, Vector &color )
  528. {
  529. if ( m_nActiveLights )
  530. {
  531. for ( int i=0; i < m_nActiveLights; i++ )
  532. {
  533. CActiveLight *pLight = &m_ActiveLights[i];
  534. float flDistSqr = (vParticlePos - pLight->m_vOrigin).LengthSqr();
  535. if ( flDistSqr < pLight->m_flRadiusSqr )
  536. {
  537. color += pLight->m_vColor * (1 - flDistSqr / pLight->m_flRadiusSqr) * 0.1f;
  538. }
  539. }
  540. // Rescale the color..
  541. float flMax = MAX( color.x, MAX( color.y, color.z ) );
  542. if ( flMax > 1 )
  543. {
  544. color /= flMax;
  545. }
  546. }
  547. }
  548. void C_ParticleSmokeGrenade::RenderParticles( CParticleRenderIterator *pIterator )
  549. {
  550. const SmokeGrenadeParticle *pParticle = (const SmokeGrenadeParticle*)pIterator->GetFirst();
  551. while ( pParticle )
  552. {
  553. Vector vWorldSpacePos = m_SmokeBasePos + pParticle->m_Pos;
  554. float sortKey;
  555. // Draw.
  556. float len = pParticle->m_Pos.Length();
  557. if ( len > m_ExpandRadius )
  558. {
  559. Vector vTemp;
  560. TransformParticle(ParticleMgr()->GetModelView(), vWorldSpacePos, vTemp);
  561. sortKey = vTemp.z;
  562. }
  563. else
  564. {
  565. // This smooths out the growing sphere. Rather than having particles appear in one spot as the sphere
  566. // expands, they stay at the borders.
  567. Vector renderPos;
  568. if(len > m_ExpandRadius * 0.5f)
  569. {
  570. renderPos = m_SmokeBasePos + (pParticle->m_Pos * (m_ExpandRadius * 0.5f)) / len;
  571. }
  572. else
  573. {
  574. renderPos = vWorldSpacePos;
  575. }
  576. // Figure out the alpha based on where it is in the sphere.
  577. float alpha = 1 - len / m_ExpandRadius;
  578. // This changes the ramp to be very solid in the core, then taper off.
  579. static float testCutoff=0.7;
  580. if(alpha > testCutoff)
  581. {
  582. alpha = 1;
  583. }
  584. else
  585. {
  586. // at testCutoff it's 1, at 0, it's 0
  587. alpha = alpha / testCutoff;
  588. }
  589. // Fade out globally.
  590. alpha *= m_FadeAlpha;
  591. // Apply the precalculated fade alpha from world geometry.
  592. alpha *= pParticle->m_FadeAlpha;
  593. // TODO: optimize this whole routine!
  594. Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f);
  595. color.x *= pParticle->m_Color[0] / 255.0f;
  596. color.y *= pParticle->m_Color[1] / 255.0f;
  597. color.z *= pParticle->m_Color[2] / 255.0f;
  598. // Lighting.
  599. ApplyDynamicLight( renderPos, color );
  600. Vector tRenderPos;
  601. TransformParticle(ParticleMgr()->GetModelView(), renderPos, tRenderPos);
  602. sortKey = tRenderPos.z;
  603. RenderParticle_ColorSizeAngle(
  604. pIterator->GetParticleDraw(),
  605. tRenderPos,
  606. color,
  607. alpha * GetAlphaDistanceFade(tRenderPos, 100, 200), // Alpha
  608. SMOKEPARTICLE_SIZE,
  609. pParticle->m_CurRotation
  610. );
  611. }
  612. pParticle = (SmokeGrenadeParticle*)pIterator->GetNext( sortKey );
  613. }
  614. }
  615. void C_ParticleSmokeGrenade::SimulateParticles( CParticleSimulateIterator *pIterator )
  616. {
  617. SmokeGrenadeParticle *pParticle = (SmokeGrenadeParticle*)pIterator->GetFirst();
  618. while ( pParticle )
  619. {
  620. pParticle->m_CurRotation += pParticle->m_RotationSpeed * pIterator->GetTimeDelta();
  621. pParticle = (SmokeGrenadeParticle*)pIterator->GetNext();
  622. }
  623. }
  624. void C_ParticleSmokeGrenade::NotifyRemove()
  625. {
  626. m_xCount = m_yCount = m_zCount = 0;
  627. #if CSTRIKE_DLL
  628. C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
  629. if ( pPlayer )
  630. {
  631. pPlayer->m_SmokeGrenades.FindAndRemove( this );
  632. }
  633. #endif
  634. }
  635. void C_ParticleSmokeGrenade::GetParticlePosition( Particle *pParticle, Vector& worldpos )
  636. {
  637. worldpos = pParticle->m_Pos + m_SmokeBasePos;
  638. }
  639. void C_ParticleSmokeGrenade::RecvProxy_CurrentStage( const CRecvProxyData *pData, void *pStruct, void *pOut )
  640. {
  641. C_ParticleSmokeGrenade *pGrenade = (C_ParticleSmokeGrenade*)pStruct;
  642. Assert( pOut == &pGrenade->m_CurrentStage );
  643. if ( pGrenade && pGrenade->m_CurrentStage == 0 && pData->m_Value.m_Int == 1 )
  644. {
  645. if( pGrenade->m_bStarted )
  646. pGrenade->FillVolume();
  647. else
  648. pGrenade->m_CurrentStage = 2;
  649. }
  650. }
  651. void C_ParticleSmokeGrenade::FillVolume()
  652. {
  653. m_CurrentStage = 1;
  654. m_SmokeBasePos = GetPos();
  655. m_SmokeTrail.SetEmit(false);
  656. m_ExpandTimeCounter = m_ExpandRadius = 0;
  657. m_bVolumeFilled = true;
  658. // Spawn all of our particles.
  659. float overlap = SMOKEPARTICLE_OVERLAP;
  660. m_SpacingRadius = (SMOKEGRENADE_PARTICLERADIUS - overlap) * NUM_PARTICLES_PER_DIMENSION * 0.5f;
  661. m_xCount = m_yCount = m_zCount = NUM_PARTICLES_PER_DIMENSION;
  662. float invNumPerDimX = 1.0f / (m_xCount-1);
  663. float invNumPerDimY = 1.0f / (m_yCount-1);
  664. float invNumPerDimZ = 1.0f / (m_zCount-1);
  665. Vector vPos;
  666. for(int x=0; x < m_xCount; x++)
  667. {
  668. vPos.x = m_SmokeBasePos.x + ((float)x * invNumPerDimX) * m_SpacingRadius * 2 - m_SpacingRadius;
  669. for(int y=0; y < m_yCount; y++)
  670. {
  671. vPos.y = m_SmokeBasePos.y + ((float)y * invNumPerDimY) * m_SpacingRadius * 2 - m_SpacingRadius;
  672. for(int z=0; z < m_zCount; z++)
  673. {
  674. vPos.z = m_SmokeBasePos.z + ((float)z * invNumPerDimZ) * m_SpacingRadius * 2 - m_SpacingRadius;
  675. // Don't spawn and simulate particles that are inside a wall
  676. int contents = enginetrace->GetPointContents( vPos );
  677. if( contents & CONTENTS_SOLID )
  678. {
  679. continue;
  680. }
  681. if(SmokeParticleInfo *pInfo = GetSmokeParticleInfo(x,y,z))
  682. {
  683. // MD 11/10/03: disabled this because we weren't getting coverage near the ground.
  684. // If we want it back in certain cases, we can make it a flag.
  685. /*int contents = GetWorldPointContents(vPos);
  686. if(false && (contents & CONTENTS_SOLID))
  687. {
  688. pInfo->m_pParticle = NULL;
  689. }
  690. else
  691. */
  692. {
  693. SmokeGrenadeParticle *pParticle =
  694. (SmokeGrenadeParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeGrenadeParticle), m_MaterialHandles[rand() % NUM_MATERIAL_HANDLES]);
  695. if(pParticle)
  696. {
  697. pParticle->m_Pos = vPos - m_SmokeBasePos; // store its position in local space
  698. pParticle->m_ColorInterp = (unsigned char)( (rand() * 255) / VALVE_RAND_MAX );
  699. pParticle->m_RotationSpeed = FRand(-ROTATION_SPEED, ROTATION_SPEED); // Rotation speed.
  700. pParticle->m_CurRotation = FRand(-6, 6);
  701. }
  702. #ifdef _DEBUG
  703. int testX, testY, testZ;
  704. int index = GetSmokeParticleIndex(x,y,z);
  705. GetParticleInfoXYZ(index, testX, testY, testZ);
  706. assert(testX == x && testY == y && testZ == z);
  707. #endif
  708. Vector vColor = EngineGetLightForPoint(vPos);
  709. pInfo->m_Color[0] = (unsigned char)(vColor.x * 255.9f);
  710. pInfo->m_Color[1] = (unsigned char)(vColor.y * 255.9f);
  711. pInfo->m_Color[2] = (unsigned char)(vColor.z * 255.9f);
  712. // Cast some rays and if it's too close to anything, fade its alpha down.
  713. pInfo->m_FadeAlpha = 1;
  714. /*for(int i=0; i < NUM_FADE_PLANES; i++)
  715. {
  716. trace_t trace;
  717. WorldTraceLine(vPos, vPos + s_FadePlaneDirections[i] * 100, MASK_SOLID_BRUSHONLY, &trace);
  718. if(trace.fraction < 1.0f)
  719. {
  720. float dist = DotProduct(trace.plane.normal, vPos) - trace.plane.dist;
  721. if(dist < 0)
  722. {
  723. pInfo->m_FadeAlpha = 0;
  724. }
  725. else if(dist < SMOKEPARTICLE_SIZE)
  726. {
  727. float alphaScale = dist / SMOKEPARTICLE_SIZE;
  728. alphaScale *= alphaScale * alphaScale;
  729. pInfo->m_FadeAlpha *= alphaScale;
  730. }
  731. }
  732. }*/
  733. pInfo->m_pParticle = pParticle;
  734. pInfo->m_TradeIndex = -1;
  735. }
  736. }
  737. }
  738. }
  739. }
  740. }
  741. //-----------------------------------------------------------------------------
  742. // This is called after sending this entity's recording state
  743. //-----------------------------------------------------------------------------
  744. void C_ParticleSmokeGrenade::CleanupToolRecordingState( KeyValues *msg )
  745. {
  746. if ( !ToolsEnabled() )
  747. return;
  748. BaseClass::CleanupToolRecordingState( msg );
  749. m_SmokeTrail.CleanupToolRecordingState( msg );
  750. // Generally, this is used to allow the entity to clean up
  751. // allocated state it put into the message, but here we're going
  752. // to use it to send particle system messages because we
  753. // know the grenade has been recorded at this point
  754. if ( !clienttools->IsInRecordingMode() )
  755. return;
  756. // NOTE: Particle system destruction message will be sent by the particle effect itself.
  757. if ( m_bVolumeFilled && GetToolParticleEffectId() == TOOLPARTICLESYSTEMID_INVALID )
  758. {
  759. // Needed for retriggering of the smoke grenade
  760. m_bVolumeFilled = false;
  761. int nId = AllocateToolParticleEffectId();
  762. KeyValues *msg = new KeyValues( "OldParticleSystem_Create" );
  763. msg->SetString( "name", "C_ParticleSmokeGrenade" );
  764. msg->SetInt( "id", nId );
  765. msg->SetFloat( "time", gpGlobals->curtime );
  766. KeyValues *pEmitter = msg->FindKey( "DmeSpriteEmitter", true );
  767. pEmitter->SetInt( "count", NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION );
  768. pEmitter->SetFloat( "duration", 0 );
  769. pEmitter->SetString( "material", "particle/particle_smokegrenade1" );
  770. pEmitter->SetInt( "active", true );
  771. KeyValues *pInitializers = pEmitter->FindKey( "initializers", true );
  772. KeyValues *pPosition = pInitializers->FindKey( "DmeVoxelPositionInitializer", true );
  773. pPosition->SetFloat( "centerx", m_SmokeBasePos.x );
  774. pPosition->SetFloat( "centery", m_SmokeBasePos.y );
  775. pPosition->SetFloat( "centerz", m_SmokeBasePos.z );
  776. pPosition->SetFloat( "particlesPerDimension", m_xCount );
  777. pPosition->SetFloat( "particleSpacing", m_SpacingRadius );
  778. KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true );
  779. pLifetime->SetFloat( "minLifetime", m_FadeEndTime );
  780. pLifetime->SetFloat( "maxLifetime", m_FadeEndTime );
  781. KeyValues *pVelocity = pInitializers->FindKey( "DmeAttachmentVelocityInitializer", true );
  782. pVelocity->SetPtr( "entindex", (void*)entindex() );
  783. pVelocity->SetFloat( "minRandomSpeed", 10 );
  784. pVelocity->SetFloat( "maxRandomSpeed", 20 );
  785. KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true );
  786. pRoll->SetFloat( "minRoll", -6.0f );
  787. pRoll->SetFloat( "maxRoll", 6.0f );
  788. KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true );
  789. pRollSpeed->SetFloat( "minRollSpeed", -ROTATION_SPEED );
  790. pRollSpeed->SetFloat( "maxRollSpeed", ROTATION_SPEED );
  791. KeyValues *pColor = pInitializers->FindKey( "DmeRandomInterpolatedColorInitializer", true );
  792. Color c1(
  793. clamp( m_MinColor.x * 255.0f, 0, 255 ),
  794. clamp( m_MinColor.y * 255.0f, 0, 255 ),
  795. clamp( m_MinColor.z * 255.0f, 0, 255 ), 255 );
  796. Color c2(
  797. clamp( m_MaxColor.x * 255.0f, 0, 255 ),
  798. clamp( m_MaxColor.y * 255.0f, 0, 255 ),
  799. clamp( m_MaxColor.z * 255.0f, 0, 255 ), 255 );
  800. pColor->SetColor( "color1", c1 );
  801. pColor->SetColor( "color2", c2 );
  802. KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true );
  803. pAlpha->SetInt( "minStartAlpha", 255 );
  804. pAlpha->SetInt( "maxStartAlpha", 255 );
  805. pAlpha->SetInt( "minEndAlpha", 0 );
  806. pAlpha->SetInt( "maxEndAlpha", 0 );
  807. KeyValues *pSize = pInitializers->FindKey( "DmeRandomSizeInitializer", true );
  808. pSize->SetFloat( "minStartSize", SMOKEPARTICLE_SIZE );
  809. pSize->SetFloat( "maxStartSize", SMOKEPARTICLE_SIZE );
  810. pSize->SetFloat( "minEndSize", SMOKEPARTICLE_SIZE );
  811. pSize->SetFloat( "maxEndSize", SMOKEPARTICLE_SIZE );
  812. pInitializers->FindKey( "DmeSolidKillInitializer", true );
  813. KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true );
  814. pUpdaters->FindKey( "DmeRollUpdater", true );
  815. pUpdaters->FindKey( "DmeColorUpdater", true );
  816. KeyValues *pAlphaCosineUpdater = pUpdaters->FindKey( "DmeAlphaCosineUpdater", true );
  817. pAlphaCosineUpdater->SetFloat( "duration", m_FadeEndTime - m_FadeStartTime );
  818. pUpdaters->FindKey( "DmeColorDynamicLightUpdater", true );
  819. KeyValues *pSmokeGrenadeUpdater = pUpdaters->FindKey( "DmeSmokeGrenadeUpdater", true );
  820. pSmokeGrenadeUpdater->SetFloat( "centerx", m_SmokeBasePos.x );
  821. pSmokeGrenadeUpdater->SetFloat( "centery", m_SmokeBasePos.y );
  822. pSmokeGrenadeUpdater->SetFloat( "centerz", m_SmokeBasePos.z );
  823. pSmokeGrenadeUpdater->SetFloat( "particlesPerDimension", m_xCount );
  824. pSmokeGrenadeUpdater->SetFloat( "particleSpacing", m_SpacingRadius );
  825. pSmokeGrenadeUpdater->SetFloat( "radiusExpandTime", SMOKESPHERE_EXPAND_TIME );
  826. pSmokeGrenadeUpdater->SetFloat( "cutoffFraction", 0.7f );
  827. ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg );
  828. msg->deleteThis();
  829. }
  830. }