diff options
Diffstat (limited to 'game/client/c_particle_smokegrenade.cpp')
| -rw-r--r-- | game/client/c_particle_smokegrenade.cpp | 1034 |
1 files changed, 1034 insertions, 0 deletions
diff --git a/game/client/c_particle_smokegrenade.cpp b/game/client/c_particle_smokegrenade.cpp new file mode 100644 index 0000000..a4c432a --- /dev/null +++ b/game/client/c_particle_smokegrenade.cpp @@ -0,0 +1,1034 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "cbase.h" +#include "c_smoke_trail.h" +#include "smoke_fog_overlay.h" +#include "engine/IEngineTrace.h" +#include "view.h" +#include "dlight.h" +#include "iefx.h" +#include "tier1/KeyValues.h" +#include "toolframework_client.h" +#include "engine/ivdebugoverlay.h" + +#if CSTRIKE_DLL +#include "c_cs_player.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// ------------------------------------------------------------------------- // +// Definitions +// ------------------------------------------------------------------------- // + +static Vector s_FadePlaneDirections[] = +{ + Vector( 1,0,0), + Vector(-1,0,0), + Vector(0, 1,0), + Vector(0,-1,0), + Vector(0,0, 1), + Vector(0,0,-1) +}; +#define NUM_FADE_PLANES (sizeof(s_FadePlaneDirections)/sizeof(s_FadePlaneDirections[0])) + +// This is used to randomize the direction it chooses to move a particle in. +int g_OffsetLookup[3] = {-1,0,1}; + + +// ------------------------------------------------------------------------- // +// Classes +// ------------------------------------------------------------------------- // +class C_ParticleSmokeGrenade : public C_BaseParticleEntity, public IPrototypeAppEffect +{ +public: + DECLARE_CLASS( C_ParticleSmokeGrenade, C_BaseParticleEntity ); + DECLARE_CLIENTCLASS(); + + C_ParticleSmokeGrenade(); + ~C_ParticleSmokeGrenade(); + +private: + + class SmokeGrenadeParticle : public Particle + { + public: + float m_RotationSpeed; + float m_CurRotation; + float m_FadeAlpha; // Set as it moves around. + unsigned char m_ColorInterp; // Amount between min and max colors. + unsigned char m_Color[4]; + }; + + +public: + + // Optional call. It will use defaults if you don't call this. + void SetParams( + ); + + // Call this to move the source.. + void SetPos(const Vector &pos); + + +// C_BaseEntity. +public: + virtual void OnDataChanged( DataUpdateType_t updateType ); + + virtual void CleanupToolRecordingState( KeyValues *msg ); + +// IPrototypeAppEffect. +public: + virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); + + +// IParticleEffect. +public: + virtual void Update(float fTimeDelta); + virtual void RenderParticles( CParticleRenderIterator *pIterator ); + virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); + virtual void NotifyRemove(); + virtual void GetParticlePosition( Particle *pParticle, Vector& worldpos ); + virtual void ClientThink(); + + +// Proxies. +public: + + static void RecvProxy_CurrentStage( const CRecvProxyData *pData, void *pStruct, void *pOut ); + + +private: + + // The SmokeEmitter represents a grid in 3D space. + class SmokeParticleInfo + { + public: + SmokeGrenadeParticle *m_pParticle; + int m_TradeIndex; // -1 if not exchanging yet. + float m_TradeClock; // How long since they started trading. + float m_TradeDuration; // How long the trade will take to finish. + float m_FadeAlpha; // Calculated from nearby world geometry. + unsigned char m_Color[4]; + }; + + void ApplyDynamicLight( const Vector &vParticlePos, Vector &color ); + void UpdateDynamicLightList( const Vector &vMins, const Vector &vMaxs ); + + void UpdateSmokeTrail( float fTimeDelta ); + + void UpdateParticleAndFindTrade( int iParticle, float fTimeDelta ); + void UpdateParticleDuringTrade( int iParticle, float flTimeDelta ); + + inline int GetSmokeParticleIndex(int x, int y, int z) {return z*m_xCount*m_yCount+y*m_yCount+x;} + inline SmokeParticleInfo* GetSmokeParticleInfo(int x, int y, int z) {return &m_SmokeParticleInfos[GetSmokeParticleIndex(x,y,z)];} + inline void GetParticleInfoXYZ(int index_, int &x, int &y, int &z) + { + z = index_ / (m_xCount*m_yCount); + int zIndex = z*m_xCount*m_yCount; + y = (index_ - zIndex) / m_yCount; + int yIndex = y*m_yCount; + x = index_ - zIndex - yIndex; + } + + inline bool IsValidXYZCoords(int x, int y, int z) + { + return x >= 0 && y >= 0 && z >= 0 && x < m_xCount && y < m_yCount && z < m_zCount; + } + + inline Vector GetSmokeParticlePos(int x, int y, int z) + { + return m_SmokeBasePos + + Vector( ((float)x / (m_xCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius, + ((float)y / (m_yCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius, + ((float)z / (m_zCount-1)) * m_SpacingRadius * 2 - m_SpacingRadius); + } + + inline Vector GetSmokeParticlePosIndex(int index_) + { + int x, y, z; + GetParticleInfoXYZ( index_, x, y, z); + return GetSmokeParticlePos(x, y, z); + } + + inline const Vector& GetPos() { return GetAbsOrigin(); } + + // Start filling the smoke volume (and stop the smoke trail). + void FillVolume(); + + +// State variables from server. +public: + + unsigned char m_CurrentStage; + Vector m_SmokeBasePos; + + // What time the effect was initially created + float m_flSpawnTime; + + // It will fade out during this time. + float m_FadeStartTime; + float m_FadeEndTime; + float m_FadeAlpha; // Calculated from the fade start/end times each frame. + + // Used during rendering.. active dlights. + class CActiveLight + { + public: + Vector m_vColor; + Vector m_vOrigin; + float m_flRadiusSqr; + }; + CActiveLight m_ActiveLights[MAX_DLIGHTS]; + int m_nActiveLights; + + +private: + C_ParticleSmokeGrenade( const C_ParticleSmokeGrenade & ); + + bool m_bStarted; + bool m_bVolumeFilled; + PMaterialHandle m_MaterialHandles[NUM_MATERIAL_HANDLES]; + + SmokeParticleInfo m_SmokeParticleInfos[NUM_PARTICLES_PER_DIMENSION*NUM_PARTICLES_PER_DIMENSION*NUM_PARTICLES_PER_DIMENSION]; + int m_xCount, m_yCount, m_zCount; + float m_SpacingRadius; + + Vector m_MinColor; + Vector m_MaxColor; + + float m_ExpandTimeCounter; // How long since we started expanding. + float m_ExpandRadius; // How large is our radius. + + C_SmokeTrail m_SmokeTrail; +}; + + +// Expose to the particle app. +EXPOSE_PROTOTYPE_EFFECT(SmokeGrenade, C_ParticleSmokeGrenade); + + +// Datatable.. +IMPLEMENT_CLIENTCLASS_DT(C_ParticleSmokeGrenade, DT_ParticleSmokeGrenade, ParticleSmokeGrenade) + RecvPropTime(RECVINFO(m_flSpawnTime)), + RecvPropFloat(RECVINFO(m_FadeStartTime)), + RecvPropFloat(RECVINFO(m_FadeEndTime)), + RecvPropInt(RECVINFO(m_CurrentStage), 0, &C_ParticleSmokeGrenade::RecvProxy_CurrentStage), +END_RECV_TABLE() + + +// ------------------------------------------------------------------------- // +// Helpers. +// ------------------------------------------------------------------------- // + +static inline void InterpColor(unsigned char dest[4], unsigned char src1[4], unsigned char src2[4], float percent) +{ + dest[0] = (unsigned char)(src1[0] + (src2[0] - src1[0]) * percent); + dest[1] = (unsigned char)(src1[1] + (src2[1] - src1[1]) * percent); + dest[2] = (unsigned char)(src1[2] + (src2[2] - src1[2]) * percent); +} + + +static inline int GetWorldPointContents(const Vector &vPos) +{ + #if defined(PARTICLEPROTOTYPE_APP) + return 0; + #else + return enginetrace->GetPointContents( vPos ); + #endif +} + +static inline void WorldTraceLine( const Vector &start, const Vector &end, int contentsMask, trace_t *trace ) +{ + #if defined(PARTICLEPROTOTYPE_APP) + trace->fraction = 1; + #else + UTIL_TraceLine(start, end, contentsMask, NULL, COLLISION_GROUP_NONE, trace); + #endif +} + +static inline Vector EngineGetLightForPoint(const Vector &vPos) +{ + #if defined(PARTICLEPROTOTYPE_APP) + return Vector(1,1,1); + #else + return engine->GetLightForPoint(vPos, true); + #endif +} + +static inline const Vector& EngineGetVecRenderOrigin() +{ + #if defined(PARTICLEPROTOTYPE_APP) + static Vector dummy(0,0,0); + return dummy; + #else + return CurrentViewOrigin(); + #endif +} + +static inline float& EngineGetSmokeFogOverlayAlpha() +{ + #if defined(PARTICLEPROTOTYPE_APP) + static float dummy; + return dummy; + #else + return g_SmokeFogOverlayAlpha; + #endif +} + +static inline C_BaseEntity* ParticleGetEntity(int index) +{ + #if defined(PARTICLEPROTOTYPE_APP) + return NULL; + #else + return cl_entitylist->GetEnt(index); + #endif +} + + + +// ------------------------------------------------------------------------- // +// ParticleMovieExplosion +// ------------------------------------------------------------------------- // +C_ParticleSmokeGrenade::C_ParticleSmokeGrenade() +{ + memset(m_MaterialHandles, 0, sizeof(m_MaterialHandles)); + + m_MinColor.Init(0.5, 0.5, 0.5); + m_MaxColor.Init(0.6, 0.6, 0.6 ); + + m_nActiveLights = 0; + m_ExpandRadius = 0; + m_ExpandTimeCounter = 0; + m_FadeStartTime = 0; + m_FadeEndTime = 0; + m_flSpawnTime = 0; + m_bVolumeFilled = false; + m_CurrentStage = 0; + + m_bStarted = false; +} + + +C_ParticleSmokeGrenade::~C_ParticleSmokeGrenade() +{ + ParticleMgr()->RemoveEffect( &m_ParticleEffect ); +} + + +void C_ParticleSmokeGrenade::SetParams( + ) +{ +} + + +void C_ParticleSmokeGrenade::OnDataChanged( DataUpdateType_t updateType ) +{ + C_BaseEntity::OnDataChanged(updateType); + + if(updateType == DATA_UPDATE_CREATED ) + { + Start(ParticleMgr(), NULL); + } +} + + +void C_ParticleSmokeGrenade::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) +{ + if(!pParticleMgr->AddEffect( &m_ParticleEffect, this )) + return; + + m_SmokeTrail.Start(pParticleMgr, pArgs); + + m_SmokeTrail.m_ParticleLifetime = 0.5; + m_SmokeTrail.SetSpawnRate(40); + m_SmokeTrail.m_MinSpeed = 0; + m_SmokeTrail.m_MaxSpeed = 0; + m_SmokeTrail.m_StartSize = 3; + m_SmokeTrail.m_EndSize = 10; + m_SmokeTrail.m_SpawnRadius = 0; + + m_SmokeTrail.SetLocalOrigin( GetAbsOrigin() ); + + for(int i=0; i < NUM_MATERIAL_HANDLES; i++) + { + char str[256]; + Q_snprintf(str, sizeof( str ), "particle/particle_smokegrenade%d", i+1); + m_MaterialHandles[i] = m_ParticleEffect.FindOrAddMaterial(str); + } + + if( m_CurrentStage == 2 ) + { + FillVolume(); + } + + // Go straight into "fill volume" mode if they want. + if(pArgs) + { + if(pArgs->FindArg("-FillVolume")) + { + FillVolume(); + } + } + + m_bStarted = true; + SetNextClientThink( CLIENT_THINK_ALWAYS ); + +#if CSTRIKE_DLL + C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); + + if ( pPlayer ) + { + pPlayer->m_SmokeGrenades.AddToTail( this ); + } +#endif + +} + +void C_ParticleSmokeGrenade::ClientThink() +{ + if ( m_CurrentStage == 1 ) + { + // Add our influence to the global smoke fog alpha. + + float testDist = (MainViewOrigin() - m_SmokeBasePos ).Length(); + + float fadeEnd = m_ExpandRadius; + + // The center of the smoke cloud that always gives full fog overlay + float flCoreDistance = fadeEnd * 0.3; + + if(testDist < fadeEnd) + { + if( testDist < flCoreDistance ) + { + EngineGetSmokeFogOverlayAlpha() += m_FadeAlpha; + } + else + { + EngineGetSmokeFogOverlayAlpha() += (1 - ( testDist - flCoreDistance ) / ( fadeEnd - flCoreDistance ) ) * m_FadeAlpha; + } + } + } +} + + +void C_ParticleSmokeGrenade::UpdateSmokeTrail( float fTimeDelta ) +{ + C_BaseEntity *pAimEnt = GetFollowedEntity(); + if ( pAimEnt ) + { + Vector forward, right, up; + + // Update the smoke particle color. + if(m_CurrentStage == 0) + { + m_SmokeTrail.m_StartColor = EngineGetLightForPoint(GetAbsOrigin()) * 0.5f; + m_SmokeTrail.m_EndColor = m_SmokeTrail.m_StartColor; + } + + // Spin the smoke trail. + AngleVectors(pAimEnt->GetAbsAngles(), &forward, &right, &up); + m_SmokeTrail.m_VelocityOffset = forward * 30 + GetAbsVelocity(); + + m_SmokeTrail.SetLocalOrigin( GetAbsOrigin() ); + m_SmokeTrail.Update(fTimeDelta); + } +} + + +inline void C_ParticleSmokeGrenade::UpdateParticleDuringTrade( int iParticle, float fTimeDelta ) +{ + SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[iParticle]; + SmokeParticleInfo *pOther = &m_SmokeParticleInfos[pInfo->m_TradeIndex]; + Assert(pOther->m_TradeIndex == iParticle); + + // This makes sure the trade only gets updated once per frame. + if(pInfo < pOther) + { + // Increment the trade clock.. + pInfo->m_TradeClock = (pOther->m_TradeClock += fTimeDelta); + int x, y, z; + GetParticleInfoXYZ(iParticle, x, y, z); + Vector myPos = GetSmokeParticlePos(x, y, z) - m_SmokeBasePos; + + int otherX, otherY, otherZ; + GetParticleInfoXYZ(pInfo->m_TradeIndex, otherX, otherY, otherZ); + Vector otherPos = GetSmokeParticlePos(otherX, otherY, otherZ) - m_SmokeBasePos; + + // Is the trade finished? + if(pInfo->m_TradeClock >= pInfo->m_TradeDuration) + { + pInfo->m_TradeIndex = pOther->m_TradeIndex = -1; + + pInfo->m_pParticle->m_Pos = otherPos; + pOther->m_pParticle->m_Pos = myPos; + + SmokeGrenadeParticle *temp = pInfo->m_pParticle; + pInfo->m_pParticle = pOther->m_pParticle; + pOther->m_pParticle = temp; + } + else + { + // Ok, move them closer. + float percent = (float)cos(pInfo->m_TradeClock * 2 * 1.57079632f / pInfo->m_TradeDuration); + percent = percent * 0.5 + 0.5; + + pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * (1 - percent); + pOther->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha + (pOther->m_FadeAlpha - pInfo->m_FadeAlpha) * percent; + + InterpColor(pInfo->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, 1-percent); + InterpColor(pOther->m_pParticle->m_Color, pInfo->m_Color, pOther->m_Color, percent); + + pInfo->m_pParticle->m_Pos = myPos + (otherPos - myPos) * (1 - percent); + pOther->m_pParticle->m_Pos = myPos + (otherPos - myPos) * percent; + } + } +} + + +void C_ParticleSmokeGrenade::UpdateParticleAndFindTrade( int iParticle, float fTimeDelta ) +{ + SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[iParticle]; + + pInfo->m_pParticle->m_FadeAlpha = pInfo->m_FadeAlpha; + pInfo->m_pParticle->m_Color[0] = pInfo->m_Color[0]; + pInfo->m_pParticle->m_Color[1] = pInfo->m_Color[1]; + pInfo->m_pParticle->m_Color[2] = pInfo->m_Color[2]; + + // Is there an adjacent one that's not trading? + int x, y, z; + GetParticleInfoXYZ(iParticle, x, y, z); + + int xCountOffset = rand(); + int yCountOffset = rand(); + int zCountOffset = rand(); + + bool bFound = false; + for(int xCount=0; xCount < 3 && !bFound; xCount++) + { + for(int yCount=0; yCount < 3 && !bFound; yCount++) + { + for(int zCount=0; zCount < 3; zCount++) + { + int testX = x + g_OffsetLookup[(xCount+xCountOffset) % 3]; + int testY = y + g_OffsetLookup[(yCount+yCountOffset) % 3]; + int testZ = z + g_OffsetLookup[(zCount+zCountOffset) % 3]; + + if(testX == x && testY == y && testZ == z) + continue; + + if(IsValidXYZCoords(testX, testY, testZ)) + { + SmokeParticleInfo *pOther = GetSmokeParticleInfo(testX, testY, testZ); + if(pOther->m_pParticle && pOther->m_TradeIndex == -1) + { + // Ok, this one is looking to trade also. + pInfo->m_TradeIndex = GetSmokeParticleIndex(testX, testY, testZ); + pOther->m_TradeIndex = iParticle; + pInfo->m_TradeClock = pOther->m_TradeClock = 0; + pInfo->m_TradeDuration = FRand(TRADE_DURATION_MIN, TRADE_DURATION_MAX); + + bFound = true; + break; + } + } + } + } + } +} + + +void C_ParticleSmokeGrenade::Update(float fTimeDelta) +{ + float flLifetime = gpGlobals->curtime - m_flSpawnTime; + + // Update the smoke trail. + UpdateSmokeTrail( fTimeDelta ); + + // Update our fade alpha. + if(flLifetime < m_FadeStartTime) + { + m_FadeAlpha = 1; + } + else if(flLifetime < m_FadeEndTime) + { + float fadePercent = (flLifetime - m_FadeStartTime) / (m_FadeEndTime - m_FadeStartTime); + m_FadeAlpha = cos(fadePercent * 3.14159) * 0.5 + 0.5; + } + else + { + m_FadeAlpha = 0; + } + + // Scale by the amount the sphere has grown. + m_FadeAlpha *= m_ExpandRadius / (m_SpacingRadius*2); + + + // Update our bbox. + + Vector vMins = m_SmokeBasePos - Vector( m_SpacingRadius + SMOKEGRENADE_PARTICLERADIUS, m_SpacingRadius + SMOKEGRENADE_PARTICLERADIUS, m_SpacingRadius + SMOKEGRENADE_PARTICLERADIUS ); + Vector vMaxs = m_SmokeBasePos + Vector( m_SpacingRadius + SMOKEGRENADE_PARTICLERADIUS, m_SpacingRadius + SMOKEGRENADE_PARTICLERADIUS, m_SpacingRadius + SMOKEGRENADE_PARTICLERADIUS ); + m_ParticleEffect.SetBBox( vMins, vMaxs ); + + + // Update the current light list. + UpdateDynamicLightList( vMins, vMaxs ); + + + if(m_CurrentStage == 1) + { + // Update the expanding sphere. + m_ExpandTimeCounter = flLifetime; + if(m_ExpandTimeCounter > SMOKESPHERE_EXPAND_TIME) + m_ExpandTimeCounter = SMOKESPHERE_EXPAND_TIME; + + m_ExpandRadius = (m_SpacingRadius*2) * (float)sin(m_ExpandTimeCounter * M_PI * 0.5 / SMOKESPHERE_EXPAND_TIME); + +// debugoverlay->AddBoxOverlay( GetPos(), Vector( -m_ExpandRadius, -m_ExpandRadius, -m_ExpandRadius), Vector( m_ExpandRadius, m_ExpandRadius, m_ExpandRadius), vec3_angle, 0, 255, 0, 1, 1.0f ); + + + // Update all the moving traders and establish new ones. + int nTotal = m_xCount * m_yCount * m_zCount; + for(int i=0; i < nTotal; i++) + { + SmokeParticleInfo *pInfo = &m_SmokeParticleInfos[i]; + + if(!pInfo->m_pParticle) + continue; + + if(pInfo->m_TradeIndex == -1) + { + UpdateParticleAndFindTrade( i, fTimeDelta ); + } + else + { + UpdateParticleDuringTrade( i, fTimeDelta ); + } + } + } + + m_SmokeBasePos = GetPos(); +} + + +void C_ParticleSmokeGrenade::UpdateDynamicLightList( const Vector &vMins, const Vector &vMaxs ) +{ + dlight_t *lights[MAX_DLIGHTS]; + int nLights = effects->CL_GetActiveDLights( lights ); + m_nActiveLights = 0; + for ( int i=0; i < nLights; i++ ) + { + dlight_t *pIn = lights[i]; + if ( pIn->origin.x + pIn->radius <= vMins.x || + pIn->origin.y + pIn->radius <= vMins.y || + pIn->origin.z + pIn->radius <= vMins.z || + pIn->origin.x - pIn->radius >= vMaxs.x || + pIn->origin.y - pIn->radius >= vMaxs.y || + pIn->origin.z - pIn->radius >= vMaxs.z ) + { + } + else + { + CActiveLight *pOut = &m_ActiveLights[m_nActiveLights]; + if ( (pIn->color.r != 0 || pIn->color.g != 0 || pIn->color.b != 0) && pIn->color.exponent != 0 ) + { + ColorRGBExp32ToVector( pIn->color, pOut->m_vColor ); + pOut->m_vColor /= 255.0f; + pOut->m_flRadiusSqr = (pIn->radius + SMOKEPARTICLE_SIZE) * (pIn->radius + SMOKEPARTICLE_SIZE); + pOut->m_vOrigin = pIn->origin; + ++m_nActiveLights; + } + } + } +} + + +inline void C_ParticleSmokeGrenade::ApplyDynamicLight( const Vector &vParticlePos, Vector &color ) +{ + if ( m_nActiveLights ) + { + for ( int i=0; i < m_nActiveLights; i++ ) + { + CActiveLight *pLight = &m_ActiveLights[i]; + + float flDistSqr = (vParticlePos - pLight->m_vOrigin).LengthSqr(); + if ( flDistSqr < pLight->m_flRadiusSqr ) + { + color += pLight->m_vColor * (1 - flDistSqr / pLight->m_flRadiusSqr) * 0.1f; + } + } + + // Rescale the color.. + float flMax = MAX( color.x, MAX( color.y, color.z ) ); + if ( flMax > 1 ) + { + color /= flMax; + } + } +} + + +void C_ParticleSmokeGrenade::RenderParticles( CParticleRenderIterator *pIterator ) +{ + const SmokeGrenadeParticle *pParticle = (const SmokeGrenadeParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + Vector vWorldSpacePos = m_SmokeBasePos + pParticle->m_Pos; + + float sortKey; + + // Draw. + float len = pParticle->m_Pos.Length(); + if ( len > m_ExpandRadius ) + { + Vector vTemp; + TransformParticle(ParticleMgr()->GetModelView(), vWorldSpacePos, vTemp); + sortKey = vTemp.z; + } + else + { + // This smooths out the growing sphere. Rather than having particles appear in one spot as the sphere + // expands, they stay at the borders. + Vector renderPos; + if(len > m_ExpandRadius * 0.5f) + { + renderPos = m_SmokeBasePos + (pParticle->m_Pos * (m_ExpandRadius * 0.5f)) / len; + } + else + { + renderPos = vWorldSpacePos; + } + + // Figure out the alpha based on where it is in the sphere. + float alpha = 1 - len / m_ExpandRadius; + + // This changes the ramp to be very solid in the core, then taper off. + static float testCutoff=0.3; + if(alpha > testCutoff) + { + alpha = 1; + } + else + { + // at testCutoff it's 1, at 0, it's 0 + alpha = alpha / testCutoff; + } + + // Fade out globally. + alpha *= m_FadeAlpha; + + // Apply the precalculated fade alpha from world geometry. + alpha *= pParticle->m_FadeAlpha; + + // TODO: optimize this whole routine! + Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f); + color.x *= pParticle->m_Color[0] / 255.0f; + color.y *= pParticle->m_Color[1] / 255.0f; + color.z *= pParticle->m_Color[2] / 255.0f; + + // Lighting. + ApplyDynamicLight( renderPos, color ); + + color = (color + Vector( 0.5, 0.5, 0.5 )) / 2; //Desaturate + + Vector tRenderPos; + TransformParticle(ParticleMgr()->GetModelView(), renderPos, tRenderPos); + sortKey = tRenderPos.z; + + //debugoverlay->AddBoxOverlay( renderPos, Vector( -2, -2, -2), Vector( 2, 2, 2), vec3_angle, 255, 255, 255, 255, 1.0f ); + + RenderParticle_ColorSizeAngle( + pIterator->GetParticleDraw(), + tRenderPos, + color, + alpha * GetAlphaDistanceFade(tRenderPos, 0, 10), // Alpha + SMOKEPARTICLE_SIZE, + pParticle->m_CurRotation + ); + } + + pParticle = (SmokeGrenadeParticle*)pIterator->GetNext( sortKey ); + } +} + + +void C_ParticleSmokeGrenade::SimulateParticles( CParticleSimulateIterator *pIterator ) +{ + SmokeGrenadeParticle *pParticle = (SmokeGrenadeParticle*)pIterator->GetFirst(); + while ( pParticle ) + { + pParticle->m_CurRotation += pParticle->m_RotationSpeed * pIterator->GetTimeDelta(); + pParticle = (SmokeGrenadeParticle*)pIterator->GetNext(); + } +} + + +void C_ParticleSmokeGrenade::NotifyRemove() +{ + m_xCount = m_yCount = m_zCount = 0; + +#if CSTRIKE_DLL + C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer(); + + if ( pPlayer ) + { + pPlayer->m_SmokeGrenades.FindAndRemove( this ); + } +#endif + +} + + +void C_ParticleSmokeGrenade::GetParticlePosition( Particle *pParticle, Vector& worldpos ) +{ + worldpos = pParticle->m_Pos + m_SmokeBasePos; +} + + +void C_ParticleSmokeGrenade::RecvProxy_CurrentStage( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_ParticleSmokeGrenade *pGrenade = (C_ParticleSmokeGrenade*)pStruct; + Assert( pOut == &pGrenade->m_CurrentStage ); + + if ( pGrenade && pGrenade->m_CurrentStage == 0 && pData->m_Value.m_Int == 1 ) + { + if( pGrenade->m_bStarted ) + pGrenade->FillVolume(); + else + pGrenade->m_CurrentStage = 2; + } +} + +void C_ParticleSmokeGrenade::FillVolume() +{ + m_CurrentStage = 1; + m_SmokeBasePos = GetPos(); + m_SmokeTrail.SetEmit(false); + m_ExpandTimeCounter = m_ExpandRadius = 0; + m_bVolumeFilled = true; + + // Spawn all of our particles. + float overlap = SMOKEPARTICLE_OVERLAP; + + m_SpacingRadius = (SMOKEGRENADE_PARTICLERADIUS - overlap) * NUM_PARTICLES_PER_DIMENSION * 0.5f; + m_xCount = m_yCount = m_zCount = NUM_PARTICLES_PER_DIMENSION; + + float invNumPerDimX = 1.0f / (m_xCount-1); + float invNumPerDimY = 1.0f / (m_yCount-1); + float invNumPerDimZ = 1.0f / (m_zCount-1); + + Vector vPos; + for(int x=0; x < m_xCount; x++) + { + vPos.x = m_SmokeBasePos.x + ((float)x * invNumPerDimX) * m_SpacingRadius * 2 - m_SpacingRadius; + + for(int y=0; y < m_yCount; y++) + { + vPos.y = m_SmokeBasePos.y + ((float)y * invNumPerDimY) * m_SpacingRadius * 2 - m_SpacingRadius; + + for(int z=0; z < m_zCount; z++) + { + vPos.z = m_SmokeBasePos.z + ((float)z * invNumPerDimZ) * m_SpacingRadius * 2 - m_SpacingRadius; + + // Don't spawn and simulate particles that are inside a wall +// int contents = enginetrace->GetPointContents( vPos ); + + // Culling out particles in solid makes smoke not fill up small passageways. + //if( contents & CONTENTS_SOLID ) + //{ + // continue; + //} + + if(SmokeParticleInfo *pInfo = GetSmokeParticleInfo(x,y,z)) + { + // MD 11/10/03: disabled this because we weren't getting coverage near the ground. + // If we want it back in certain cases, we can make it a flag. + /*int contents = GetWorldPointContents(vPos); + if(false && (contents & CONTENTS_SOLID)) + { + pInfo->m_pParticle = NULL; + } + else + */ + { + SmokeGrenadeParticle *pParticle = + (SmokeGrenadeParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeGrenadeParticle), m_MaterialHandles[rand() % NUM_MATERIAL_HANDLES]); + + if(pParticle) + { + pParticle->m_Pos = vPos - m_SmokeBasePos; // store its position in local space + pParticle->m_ColorInterp = (unsigned char)((rand() * 255) / VALVE_RAND_MAX); + pParticle->m_RotationSpeed = FRand(-ROTATION_SPEED, ROTATION_SPEED); // Rotation speed. + pParticle->m_CurRotation = FRand(-6, 6); + + //debugoverlay->AddBoxOverlay( vPos, Vector( -2, -2, -2), Vector( 2, 2, 2), vec3_angle, 255, 0, 0, 255, 5.0f ); + } + + + + #ifdef _DEBUG + int testX, testY, testZ; + int index_ = GetSmokeParticleIndex(x,y,z); + GetParticleInfoXYZ( index_, testX, testY, testZ); + assert(testX == x && testY == y && testZ == z); + #endif + + Vector vColor = EngineGetLightForPoint(vPos); + pInfo->m_Color[0] = (unsigned char)(vColor.x * 255.9f); + pInfo->m_Color[1] = (unsigned char)(vColor.y * 255.9f); + pInfo->m_Color[2] = (unsigned char)(vColor.z * 255.9f); + + // Cast some rays and if it's too close to anything, fade its alpha down. + pInfo->m_FadeAlpha = 1; + + /*for(int i=0; i < NUM_FADE_PLANES; i++) + { + trace_t trace; + WorldTraceLine(vPos, vPos + s_FadePlaneDirections[i] * 100, MASK_SOLID_BRUSHONLY, &trace); + if(trace.fraction < 1.0f) + { + float dist = DotProduct(trace.plane.normal, vPos) - trace.plane.dist; + if(dist < 0) + { + pInfo->m_FadeAlpha = 0; + } + else if(dist < SMOKEPARTICLE_SIZE) + { + float alphaScale = dist / SMOKEPARTICLE_SIZE; + alphaScale *= alphaScale * alphaScale; + pInfo->m_FadeAlpha *= alphaScale; + } + } + }*/ + + pInfo->m_pParticle = pParticle; + pInfo->m_TradeIndex = -1; + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// This is called after sending this entity's recording state +//----------------------------------------------------------------------------- +void C_ParticleSmokeGrenade::CleanupToolRecordingState( KeyValues *msg ) +{ + if ( !ToolsEnabled() ) + return; + + BaseClass::CleanupToolRecordingState( msg ); + m_SmokeTrail.CleanupToolRecordingState( msg ); + + // Generally, this is used to allow the entity to clean up + // allocated state it put into the message, but here we're going + // to use it to send particle system messages because we + // know the grenade has been recorded at this point + if ( !clienttools->IsInRecordingMode() ) + return; + + // NOTE: Particle system destruction message will be sent by the particle effect itself. + if ( m_bVolumeFilled && GetToolParticleEffectId() == TOOLPARTICLESYSTEMID_INVALID ) + { + // Needed for retriggering of the smoke grenade + m_bVolumeFilled = false; + + int nId = AllocateToolParticleEffectId(); + + KeyValues *oldmsg = new KeyValues( "OldParticleSystem_Create" ); + oldmsg->SetString( "name", "C_ParticleSmokeGrenade" ); + oldmsg->SetInt( "id", nId ); + oldmsg->SetFloat( "time", gpGlobals->curtime ); + + KeyValues *pEmitter = oldmsg->FindKey( "DmeSpriteEmitter", true ); + pEmitter->SetInt( "count", NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION ); + pEmitter->SetFloat( "duration", 0 ); + pEmitter->SetString( "material", "particle/particle_smokegrenade1" ); + pEmitter->SetInt( "active", true ); + + KeyValues *pInitializers = pEmitter->FindKey( "initializers", true ); + + KeyValues *pPosition = pInitializers->FindKey( "DmeVoxelPositionInitializer", true ); + pPosition->SetFloat( "centerx", m_SmokeBasePos.x ); + pPosition->SetFloat( "centery", m_SmokeBasePos.y ); + pPosition->SetFloat( "centerz", m_SmokeBasePos.z ); + pPosition->SetFloat( "particlesPerDimension", m_xCount ); + pPosition->SetFloat( "particleSpacing", m_SpacingRadius ); + + KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true ); + pLifetime->SetFloat( "minLifetime", m_FadeEndTime ); + pLifetime->SetFloat( "maxLifetime", m_FadeEndTime ); + + KeyValues *pVelocity = pInitializers->FindKey( "DmeAttachmentVelocityInitializer", true ); + pVelocity->SetPtr( "entindex", (void*)entindex() ); + pVelocity->SetFloat( "minRandomSpeed", 10 ); + pVelocity->SetFloat( "maxRandomSpeed", 20 ); + + KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true ); + pRoll->SetFloat( "minRoll", -6.0f ); + pRoll->SetFloat( "maxRoll", 6.0f ); + + KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true ); + pRollSpeed->SetFloat( "minRollSpeed", -ROTATION_SPEED ); + pRollSpeed->SetFloat( "maxRollSpeed", ROTATION_SPEED ); + + KeyValues *pColor = pInitializers->FindKey( "DmeRandomInterpolatedColorInitializer", true ); + Color c1( + FastFToC( clamp( m_MinColor.x, 0.f, 1.f ) ), + FastFToC( clamp( m_MinColor.y, 0.f, 1.f ) ), + FastFToC( clamp( m_MinColor.z, 0.f, 1.f ) ), 255 ); + Color c2( + FastFToC( clamp( m_MaxColor.x, 0.f, 1.f ) ), + FastFToC( clamp( m_MaxColor.y, 0.f, 1.f ) ), + FastFToC( clamp( m_MaxColor.z, 0.f, 1.f ) ), 255 ); + pColor->SetColor( "color1", c1 ); + pColor->SetColor( "color2", c2 ); + + KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true ); + pAlpha->SetInt( "minStartAlpha", 255 ); + pAlpha->SetInt( "maxStartAlpha", 255 ); + pAlpha->SetInt( "minEndAlpha", 0 ); + pAlpha->SetInt( "maxEndAlpha", 0 ); + + KeyValues *pSize = pInitializers->FindKey( "DmeRandomSizeInitializer", true ); + pSize->SetFloat( "minStartSize", SMOKEPARTICLE_SIZE ); + pSize->SetFloat( "maxStartSize", SMOKEPARTICLE_SIZE ); + pSize->SetFloat( "minEndSize", SMOKEPARTICLE_SIZE ); + pSize->SetFloat( "maxEndSize", SMOKEPARTICLE_SIZE ); + + pInitializers->FindKey( "DmeSolidKillInitializer", true ); + + KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true ); + + pUpdaters->FindKey( "DmeRollUpdater", true ); + pUpdaters->FindKey( "DmeColorUpdater", true ); + + KeyValues *pAlphaCosineUpdater = pUpdaters->FindKey( "DmeAlphaCosineUpdater", true ); + pAlphaCosineUpdater->SetFloat( "duration", m_FadeEndTime - m_FadeStartTime ); + + pUpdaters->FindKey( "DmeColorDynamicLightUpdater", true ); + + KeyValues *pSmokeGrenadeUpdater = pUpdaters->FindKey( "DmeSmokeGrenadeUpdater", true ); + pSmokeGrenadeUpdater->SetFloat( "centerx", m_SmokeBasePos.x ); + pSmokeGrenadeUpdater->SetFloat( "centery", m_SmokeBasePos.y ); + pSmokeGrenadeUpdater->SetFloat( "centerz", m_SmokeBasePos.z ); + pSmokeGrenadeUpdater->SetFloat( "particlesPerDimension", m_xCount ); + pSmokeGrenadeUpdater->SetFloat( "particleSpacing", m_SpacingRadius ); + pSmokeGrenadeUpdater->SetFloat( "radiusExpandTime", SMOKESPHERE_EXPAND_TIME ); + pSmokeGrenadeUpdater->SetFloat( "cutoffFraction", 0.7f ); + + ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, oldmsg ); + oldmsg->deleteThis(); + } +} + + + |