source-engine/game/client/c_particle_smokegrenade.cpp

1035 lines
30 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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();
}
}