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.
1034 lines
30 KiB
1034 lines
30 KiB
//========= 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 *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*)(intp)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, msg ); |
|
msg->deleteThis(); |
|
} |
|
} |
|
|
|
|
|
|
|
|