|
|
//===== Copyright (c) 1996-2005, 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"
#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.
// Color driven by grenade weapon description.
Vector m_MinColor; Vector m_MaxColor;
// 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;
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)), RecvPropVector(RECVINFO(m_MinColor)), RecvPropVector(RECVINFO(m_MaxColor)), 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 void EngineAddSmokeFogOverlayColor( Vector &color ) { g_SmokeFogOverlayColor += color; }
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.
ASSERT_LOCAL_PLAYER_RESOLVABLE(); int nSlot = GET_ACTIVE_SPLITSCREEN_SLOT(); float testDist = (MainViewOrigin(nSlot) - m_SmokeBasePos).Length();
float fadeEnd = m_ExpandRadius;
// The center of the smoke cloud that always gives full fog overlay
float flCoreDistance = fadeEnd * 0.15; if(testDist < fadeEnd) {
float smokeAlpha = 0.0f; if( testDist < flCoreDistance ) { smokeAlpha = m_FadeAlpha; } else { smokeAlpha = (1 - ( testDist - flCoreDistance ) / ( fadeEnd - flCoreDistance ) ) * m_FadeAlpha; }
EngineGetSmokeFogOverlayAlpha() += smokeAlpha; // The fog overlay color is half the intensity of the maximum color value. We multiply it by the smoke's fade alpha and
// later divide the sum by the total alpha value to get the average color weighted by alpha influence.
// This lets us combine multiple colored smoke effects together should the situation arise.
Vector color = m_MaxColor * 0.5f * smokeAlpha; EngineAddSmokeFogOverlayColor(color); } } }
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 = RandomInt( 0, VALVE_RAND_MAX ); int yCountOffset = RandomInt( 0, VALVE_RAND_MAX ); int zCountOffset = RandomInt( 0, VALVE_RAND_MAX );
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, m_SpacingRadius, m_SpacingRadius ); Vector vMaxs = m_SmokeBasePos + Vector( m_SpacingRadius, m_SpacingRadius, m_SpacingRadius ); 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); // 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.7; 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 ); Vector tRenderPos; TransformParticle(ParticleMgr()->GetModelView(), renderPos, tRenderPos); sortKey = tRenderPos.z;
RenderParticle_ColorSizeAngle( pIterator->GetParticleDraw(), tRenderPos, color, alpha * GetAlphaDistanceFade(tRenderPos, 100, 200), // 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 );
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); }
#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 *msg = new KeyValues( "OldParticleSystem_Create" ); msg->SetString( "name", "C_ParticleSmokeGrenade" ); msg->SetInt( "id", nId ); msg->SetFloat( "time", gpGlobals->curtime );
KeyValues *pEmitter = msg->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( clamp( m_MinColor.x * 255.0f, 0, 255 ), clamp( m_MinColor.y * 255.0f, 0, 255 ), clamp( m_MinColor.z * 255.0f, 0, 255 ), 255 ); Color c2( clamp( m_MaxColor.x * 255.0f, 0, 255 ), clamp( m_MaxColor.y * 255.0f, 0, 255 ), clamp( m_MaxColor.z * 255.0f, 0, 255 ), 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, msg ); msg->deleteThis(); } }
|