//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "IViewRender.h"
#include "ClientEffectPrecacheSystem.h"
#include "studio.h"
#include "bone_setup.h"
#include "engine/ivmodelinfo.h"
#include "c_fire_smoke.h"
#include "engine/IEngineSound.h"
#include "iefx.h"
#include "dlight.h"
#include "vstdlib/ICommandLine.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define PARTICLE_FIRE 0
CLIENTEFFECT_REGISTER_BEGIN( SmokeStackMaterials )
CLIENTEFFECT_MATERIAL( "particle/SmokeStack" )
CLIENTEFFECT_MATERIAL( "particle/fire" )
CLIENTEFFECT_MATERIAL( "sprites/flamefromabove" )
CLIENTEFFECT_MATERIAL( "sprites/flamelet1" )
CLIENTEFFECT_MATERIAL( "sprites/flamelet2" )
CLIENTEFFECT_MATERIAL( "sprites/flamelet3" )
CLIENTEFFECT_MATERIAL( "sprites/flamelet4" )
CLIENTEFFECT_MATERIAL( "sprites/flamelet5" )
CLIENTEFFECT_MATERIAL( "sprites/fire1" )
CLIENTEFFECT_REGISTER_END()
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pRecvProp -
// *pStruct -
// *pVarData -
// *pIn -
// objectID -
//-----------------------------------------------------------------------------
void RecvProxy_Scale( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_FireSmoke *pFireSmoke = (C_FireSmoke *) pStruct;
float scale = pData->m_Value.m_Float;
//If changed, update our internal information
if ( ( pFireSmoke->m_flScale != scale ) && ( pFireSmoke->m_flScaleEnd != scale ) )
{
pFireSmoke->m_flScaleStart = pFireSmoke->m_flScaleRegister;
pFireSmoke->m_flScaleEnd = scale;
}
pFireSmoke->m_flScale = scale;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pRecvProp -
// *pStruct -
// *pVarData -
// *pIn -
// objectID -
//-----------------------------------------------------------------------------
void RecvProxy_ScaleTime( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
C_FireSmoke *pFireSmoke = (C_FireSmoke *) pStruct;
float time = pData->m_Value.m_Float;
//If changed, update our internal information
//if ( pFireSmoke->m_flScaleTime != time )
{
if ( time == -1.0f )
{
pFireSmoke->m_flScaleTimeStart = Helper_GetTime()-1.0f;
pFireSmoke->m_flScaleTimeEnd = pFireSmoke->m_flScaleTimeStart;
}
else
{
pFireSmoke->m_flScaleTimeStart = Helper_GetTime();
pFireSmoke->m_flScaleTimeEnd = Helper_GetTime() + time;
}
}
pFireSmoke->m_flScaleTime = time;
}
//Receive datatable
IMPLEMENT_CLIENTCLASS_DT( C_FireSmoke, DT_FireSmoke, CFireSmoke )
RecvPropFloat( RECVINFO( m_flStartScale )),
RecvPropFloat( RECVINFO( m_flScale ), 0, RecvProxy_Scale ),
RecvPropFloat( RECVINFO( m_flScaleTime ), 0, RecvProxy_ScaleTime ),
RecvPropInt( RECVINFO( m_nFlags ) ),
RecvPropInt( RECVINFO( m_nFlameModelIndex ) ),
RecvPropInt( RECVINFO( m_nFlameFromAboveModelIndex ) ),
END_RECV_TABLE()
//==================================================
// C_FireSmoke
//==================================================
C_FireSmoke::C_FireSmoke()
{
//Server-side
m_flStartScale = 0.0f;
m_flScale = 0.0f;
m_flScaleTime = 0.0f;
m_nFlags = bitsFIRESMOKE_NONE;
m_nFlameModelIndex = 0;
m_nFlameFromAboveModelIndex = 0;
//Client-side
m_flScaleRegister = 0.0f;
m_flScaleStart = 0.0f;
m_flScaleEnd = 0.0f;
m_flScaleTimeStart = 0.0f;
m_flScaleTimeEnd = 0.0f;
m_bClipTested = false;
m_flChildFlameSpread = FLAME_CHILD_SPREAD;
//m_pEmitter = NULL;
//Clear all child flames
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
m_entFlames[i].Clear();
m_entFlamesFromAbove[i].Clear();
}
//m_pEmberEmitter = NULL;
m_pSmokeEmitter = NULL;
}
C_FireSmoke::~C_FireSmoke()
{
if ( m_pFireOverlay != NULL )
{
delete m_pFireOverlay;
m_pFireOverlay = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::Simulate( void )
{
if ( ShouldDraw() == false )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
m_entFlames[i].SetRenderColor( 0, 0, 0, 0 );
m_entFlames[i].SetBrightness( 0 );
}
if ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
m_entFlamesFromAbove[i].SetRenderColor( 0, 0, 0, 0 );
m_entFlamesFromAbove[i].SetBrightness( 0 );
}
}
m_nFlags &= ~bitsFIRESMOKE_SMOKE;
}
//Only do this if we're active
if (( m_nFlags & bitsFIRESMOKE_ACTIVE ) == false )
return;
Update();
AddFlames();
}
#define FLAME_ALPHA_START 0.9f
#define FLAME_ALPHA_END 1.0f
#define FLAME_TRANS_START 0.75f
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::AddFlames( void )
{
#if PARTICLE_FIRE
if ( ( gpGlobals->frametime != 0.0f ) && ( m_flScaleRegister > 0.0f ) )
{
Vector offset;
float scalar;
scalar = 32.0f*m_flScaleRegister;
offset[0] = Helper_RandomFloat( -scalar, scalar );
offset[1] = Helper_RandomFloat( -scalar, scalar );
offset[2] = 0.0f;
CSmartPtr<CSimpleEmitter> pEmitter = CSimpleEmitter::Create( "C_FireSmoke" );
pEmitter->SetSortOrigin( GetAbsOrigin()+offset );
SimpleParticle *sParticle;
//for ( int i = 0; i < 1; i++ )
{
scalar = 32.0f*m_flScaleRegister;
offset[0] = Helper_RandomFloat( -scalar, scalar );
offset[1] = Helper_RandomFloat( -scalar, scalar );
offset[2] = 12.0f*m_flScaleRegister;
sParticle = (SimpleParticle *) pEmitter->AddParticle( sizeof(SimpleParticle), pEmitter->GetPMaterial( VarArgs("sprites/flamelet%d", Helper_RandomInt( 1, 5 ) ) ), GetAbsOrigin()+offset );
if ( sParticle )
{
sParticle->m_flLifetime = 0.0f;
sParticle->m_flDieTime = 0.25f;
sParticle->m_flRoll = Helper_RandomInt( 0, 360 );
sParticle->m_flRollDelta = Helper_RandomFloat( -4.0f, 4.0f );
float alpha = Helper_RandomInt( 128, 255 );
sParticle->m_uchColor[0] = alpha;
sParticle->m_uchColor[1] = alpha;
sParticle->m_uchColor[2] = alpha;
sParticle->m_uchStartAlpha = 255;
sParticle->m_uchEndAlpha = 0;
sParticle->m_uchStartSize = 64.0f*m_flScaleRegister;
sParticle->m_uchEndSize = 0;
float speedScale = ((GetAbsOrigin()+offset)-GetAbsOrigin()).Length2D() / (32.0f*m_flScaleRegister);
sParticle->m_vecVelocity = Vector( Helper_RandomFloat( -32.0f, 32.0f ), Helper_RandomFloat( -32.0f, 32.0f ), Helper_RandomFloat( 32.0f, 128.0f )*speedScale );
}
}
pEmitter->Release();
}
#endif
//#if !PARTICLE_FIRE
float alpha = 1.0f;
//Update the child flame alpha
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
if ( m_entFlames[i].GetScale() > 1e-3f )
{
m_entFlames[i].SetRenderColor( ( 255.0f * alpha ), ( 255.0f * alpha ), ( 255.0f * alpha ) );
m_entFlames[i].SetBrightness( 255.0f * alpha );
Assert( m_entFlames[i].GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE );
m_entFlames[i].AddToLeafSystem();
}
}
if ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
if ( m_entFlamesFromAbove[i].GetScale() > 1e-3f )
{
m_entFlamesFromAbove[i].SetRenderColor( ( 255.0f * alpha ), ( 255.0f * alpha ), ( 255.0f * alpha ) );
m_entFlamesFromAbove[i].SetBrightness( 255.0f * alpha );
Assert( m_entFlamesFromAbove[i].GetRenderHandle() != INVALID_CLIENT_RENDER_HANDLE );
m_entFlamesFromAbove[i].AddToLeafSystem();
}
}
}
//#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bnewentity -
//-----------------------------------------------------------------------------
void C_FireSmoke::OnDataChanged( DataUpdateType_t updateType )
{
BaseClass::OnDataChanged( updateType );
UpdateEffects();
if ( updateType == DATA_UPDATE_CREATED )
{
Start();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::UpdateEffects( void )
{
/*
if ( m_pEmberEmitter.IsValid() )
{
m_pEmberEmitter->SetSortOrigin( GetAbsOrigin() );
}
*/
if ( m_pSmokeEmitter.IsValid() )
{
m_pSmokeEmitter->SetSortOrigin( GetAbsOrigin() );
}
if ( m_pFireOverlay != NULL )
{
m_pFireOverlay->m_vPos = GetAbsOrigin();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_FireSmoke::ShouldDraw()
{
if ( GetOwnerEntity() && GetOwnerEntity()->GetRenderColor().a == 0 )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::Start( void )
{
bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL;
// Setup the render handles for stuff we want in the client leaf list.
int i;
for ( i = 0; i < NUM_CHILD_FLAMES; i++ )
{
if ( bTools )
{
ClientEntityList().AddNonNetworkableEntity( &m_entFlames[i] );
}
m_entFlames[i].AddToLeafSystem( RENDER_GROUP_TRANSLUCENT_ENTITY );
}
if ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
if ( bTools )
{
ClientEntityList().AddNonNetworkableEntity( &m_entFlamesFromAbove[i] );
}
m_entFlamesFromAbove[i].AddToLeafSystem( RENDER_GROUP_TRANSLUCENT_ENTITY );
}
}
//Various setup info
m_tParticleSpawn.Init( 2.0f );
QAngle offset;
model_t *pModel;
int maxFrames;
//Setup the child flames
for ( i = 0; i < NUM_CHILD_FLAMES; i++ )
{
//Setup our offset angles
offset[0] = 0.0f;
offset[1] = Helper_RandomFloat( 0, 360 );
offset[2] = 0.0f;
AngleVectors( offset, &m_entFlames[i].m_vecMoveDir );
m_entFlames[i].m_bFadeFromAbove = ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE );
pModel = (model_t *) modelinfo->GetModel( m_nFlameModelIndex );
maxFrames = modelinfo->GetModelFrameCount( pModel );
//Setup all the information for the client entity
m_entFlames[i].SetModelByIndex( m_nFlameModelIndex );
m_entFlames[i].SetLocalOrigin( GetLocalOrigin() );
m_entFlames[i].m_flFrame = Helper_RandomInt( 0.0f, maxFrames - 1 );
m_entFlames[i].m_flSpriteFramerate = Helper_RandomInt( 15, 30 );
m_entFlames[i].SetScale( m_flStartScale );
m_entFlames[i].SetRenderMode( kRenderTransAddFrameBlend );
m_entFlames[i].m_nRenderFX = kRenderFxNone;
m_entFlames[i].SetRenderColor( 255, 255, 255, 255 );
m_entFlames[i].SetBrightness( 255 );
m_entFlames[i].AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
m_entFlames[i].index = -1;
if ( i == 0 )
{
m_entFlameScales[i] = 1.0f;
}
else
{
//Keep a scale offset
m_entFlameScales[i] = random->RandomFloat( 0.5f, 1.0f );
}
}
if ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
pModel = (model_t *) modelinfo->GetModel( m_nFlameFromAboveModelIndex );
maxFrames = modelinfo->GetModelFrameCount( pModel );
//Setup all the information for the client entity
m_entFlamesFromAbove[i].SetModelByIndex( m_nFlameFromAboveModelIndex );
m_entFlamesFromAbove[i].SetLocalOrigin( GetLocalOrigin() );
m_entFlamesFromAbove[i].m_flFrame = Helper_RandomInt( 0.0f, maxFrames - 1 );
m_entFlamesFromAbove[i].m_flSpriteFramerate = Helper_RandomInt( 15, 30 );
m_entFlamesFromAbove[i].SetScale( m_flStartScale );
m_entFlamesFromAbove[i].SetRenderMode( kRenderTransAddFrameBlend );
m_entFlamesFromAbove[i].m_nRenderFX = kRenderFxNone;
m_entFlamesFromAbove[i].SetRenderColor( 255, 255, 255, 255 );
m_entFlamesFromAbove[i].SetBrightness( 255 );
m_entFlamesFromAbove[i].AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
m_entFlamesFromAbove[i].index = -1;
if ( i == 0 )
{
m_entFlameScales[i] = 1.0f;
}
else
{
//Keep a scale offset
m_entFlameScales[i] = random->RandomFloat( 0.5f, 1.0f );
}
}
}
// Start up the smoke
if ( m_nFlags & bitsFIRESMOKE_SMOKE )
{
//m_pEmberEmitter = CEmberEffect::Create( "C_FireSmoke::m_pEmberEmitter" );
m_pSmokeEmitter = CLitSmokeEmitter::Create( "C_FireSmoke::m_pSmokeEmitter" );
m_pSmokeEmitter->Init( "particle/SmokeStack", GetAbsOrigin() );
}
//Only make the glow if we've requested it
if ( m_nFlags & bitsFIRESMOKE_GLOW )
{
#if 0
//Create the fire overlay
if ( m_pFireOverlay = new CFireOverlay( this ) )
{
m_pFireOverlay->m_vPos = GetAbsOrigin();
m_pFireOverlay->m_nSprites = 1;
m_pFireOverlay->m_vBaseColors[0].Init( 0.4f, 0.2f, 0.05f );
m_pFireOverlay->Activate();
}
#endif
}
}
//-----------------------------------------------------------------------------
// Purpose: FIXME: what's the right way to do this?
//-----------------------------------------------------------------------------
void C_FireSmoke::StartClientOnly( void )
{
Start();
ClientEntityList().AddNonNetworkableEntity( this );
CollisionProp()->CreatePartitionHandle();
AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
AddToLeafSystem();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::RemoveClientOnly(void)
{
ClientThinkList()->RemoveThinkable( GetClientHandle() );
// Remove from the client entity list.
ClientEntityList().RemoveEntity( GetClientHandle() );
partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() );
RemoveFromLeafSystem();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::UpdateAnimation( void )
{
int numFrames;
float frametime = Helper_GetFrameTime();
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
m_entFlames[i].m_flFrame += m_entFlames[i].m_flSpriteFramerate * frametime;
numFrames = modelinfo->GetModelFrameCount( m_entFlames[i].GetModel() );
if ( m_entFlames[i].m_flFrame >= numFrames )
{
m_entFlames[i].m_flFrame = m_entFlames[i].m_flFrame - (int)(m_entFlames[i].m_flFrame);
}
}
if ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
m_entFlamesFromAbove[i].m_flFrame += m_entFlamesFromAbove[i].m_flSpriteFramerate * frametime;
numFrames = modelinfo->GetModelFrameCount( m_entFlamesFromAbove[i].GetModel() );
if ( m_entFlamesFromAbove[i].m_flFrame >= numFrames )
{
m_entFlamesFromAbove[i].m_flFrame = m_entFlamesFromAbove[i].m_flFrame - (int)(m_entFlamesFromAbove[i].m_flFrame);
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::UpdateFlames( void )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
float newScale = m_flScaleRegister * m_entFlameScales[i];
Vector dir;
dir[2] = 0.0f;
VectorNormalize( dir );
dir[2] = 0.0f;
Vector offset = GetAbsOrigin();
offset[2] += FLAME_SOURCE_HEIGHT * m_entFlames[i].GetScale();
//NOTENOTE: Sprite renderer assumes a scale of 0.0 means 1.0
if ( m_bFadingOut == false )
{
m_entFlames[i].SetScale( max(0.000001,newScale) );
}
else
{
m_entFlames[i].SetScale( newScale );
}
Assert( !m_entFlames[i].GetMoveParent() );
if ( i != 0 )
{
m_entFlames[i].SetLocalOrigin( offset + ( m_entFlames[i].m_vecMoveDir * (m_entFlames[i].GetScale() * m_flChildFlameSpread) ) );
}
else
{
m_entFlames[i].SetLocalOrigin( offset );
}
}
if ( m_nFlags & bitsFIRESMOKE_VISIBLE_FROM_ABOVE )
{
for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
{
float newScale = m_flScaleRegister * m_entFlameScales[i];
Vector dir;
dir[2] = 0.0f;
VectorNormalize( dir );
dir[2] = 0.0f;
Vector offset = GetAbsOrigin();
offset[2] += FLAME_FROM_ABOVE_SOURCE_HEIGHT * m_entFlamesFromAbove[i].GetScale();
//NOTENOTE: Sprite renderer assumes a scale of 0.0 means 1.0
if ( m_bFadingOut == false )
{
m_entFlamesFromAbove[i].SetScale( max(0.000001,newScale) );
}
else
{
m_entFlamesFromAbove[i].SetScale( newScale );
}
Assert( !m_entFlamesFromAbove[i].GetMoveParent() );
if ( i != 0 )
{
m_entFlamesFromAbove[i].SetLocalOrigin( offset + ( m_entFlames[i].m_vecMoveDir * (m_entFlamesFromAbove[i].GetScale() * m_flChildFlameSpread) ) );
}
else
{
m_entFlamesFromAbove[i].SetLocalOrigin( offset );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::UpdateScale( void )
{
float time = Helper_GetTime();
if ( m_flScaleRegister != m_flScaleEnd )
{
//See if we're done scaling
if ( time > m_flScaleTimeEnd )
{
m_flScaleRegister = m_flStartScale = m_flScaleEnd;
}
else
{
//Lerp the scale and set it
float timeFraction = 1.0f - ( m_flScaleTimeEnd - time ) / ( m_flScaleTimeEnd - m_flScaleTimeStart );
float newScale = 0.0f;
if ( m_bFadingOut == false )
newScale = m_flScaleStart + ( ( m_flScaleEnd - m_flScaleStart ) * timeFraction );
else
newScale = m_flScaleStart - ( ( m_flScaleStart - m_flScaleEnd ) * timeFraction );
m_flScaleRegister = m_flStartScale = newScale;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::Update( void )
{
//If we haven't already, find the clip plane for smoke effects
if ( ( m_nFlags & bitsFIRESMOKE_SMOKE ) && ( m_bClipTested == false ) )
{
FindClipPlane();
}
//Update all our parts
UpdateEffects();
UpdateScale();
UpdateAnimation();
UpdateFlames();
//See if we should emit smoke
if ( m_nFlags & bitsFIRESMOKE_SMOKE )
{
float tempDelta = Helper_GetFrameTime();
while( m_tParticleSpawn.NextEvent( tempDelta ) )
{
SpawnSmoke();
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_FireSmoke::FindClipPlane( void )
{
m_bClipTested = true;
m_flClipPerc = 1.0f;
trace_t tr;
Vector end( 0.0f, 0.0f, SMOKE_RISE_RATE*SMOKE_LIFETIME );
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin()+end, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
//If the ceiling is too close, reject smoke
if ( tr.fraction < 0.5f )
{
m_nFlags &= ~bitsFIRESMOKE_SMOKE;
return;
}
if ( tr.fraction < 1.0f )
{
m_planeClip.Init( tr.plane.normal, tr.plane.dist );
m_nFlags |= bitsFIRESMOKE_SMOKE_COLLISION;
m_flClipPerc = tr.fraction * 0.5f;
}
}
//-----------------------------------------------------------------------------
// Purpose: Spawn smoke (...duh)
//-----------------------------------------------------------------------------
void C_FireSmoke::SpawnSmoke( void )
{
/*
if ( m_pEmberEmitter.IsValid() == false )
return;
*/
if ( m_pSmokeEmitter.IsValid() == false )
return;
float scalar;
Vector offset;
scalar = 32.0f*m_flScaleRegister;
offset[0] = Helper_RandomFloat( -scalar, scalar );
offset[1] = Helper_RandomFloat( -scalar, scalar );
offset[2] = scalar + ( Helper_RandomFloat( -scalar*0.25f, scalar*0.25f ) );
//
// Embers
//
//FIXME: These aren't visible enough to justify their cost, currently -- jdw
/*
SimpleParticle *sParticle;
sParticle = (SimpleParticle *) m_pEmberEmitter->AddParticle( sizeof(SimpleParticle), m_pEmberEmitter->GetPMaterial( "particle/fire"), GetAbsOrigin()+offset );
if( sParticle )
{
sParticle->m_flLifetime = 0.0f;
sParticle->m_flDieTime = EMBER_LIFETIME;
sParticle->m_flRoll = 0;
sParticle->m_flRollDelta = 0;
scalar = Helper_RandomFloat( 0.5f, 2.0f );
sParticle->m_uchColor[0] = min( 255, Helper_RandomFloat( 185.0f, 190.0f ) * scalar );
sParticle->m_uchColor[1] = min( 255, Helper_RandomFloat( 140.0f, 165.0f ) * scalar );
sParticle->m_uchColor[2] = min( 255, 65.0f * scalar );
sParticle->m_uchStartAlpha = 255;
sParticle->m_uchEndAlpha = 0;
sParticle->m_uchStartSize = 2;
sParticle->m_uchEndSize = 0;
sParticle->m_vecVelocity = Vector( Helper_RandomFloat( -16.0f, 16.0f ), Helper_RandomFloat( -16.0f, 16.0f ), Helper_RandomFloat( 92.0f, 128.0f ) );
}
*/
//
// Lit smoke
//
offset[2] += 100.0f;
m_pSmokeEmitter->SetDirectionalLight( GetAbsOrigin(), Vector( 1.0f, 0.5f, 0.2f ), 2500 );
CLitSmokeEmitter::LitSmokeParticle *pParticle;
pParticle = (CLitSmokeEmitter::LitSmokeParticle*) m_pSmokeEmitter->AddParticle(
sizeof(CLitSmokeEmitter::LitSmokeParticle),
m_pSmokeEmitter->GetSmokeMaterial(),
GetAbsOrigin()+offset );
if ( pParticle )
{
float lifeTime = SMOKE_LIFETIME * m_flClipPerc;
pParticle->m_flLifetime = 0;
pParticle->m_flDieTime = random->RandomFloat( lifeTime * 0.75, lifeTime );
pParticle->m_vecVelocity = Vector( random->RandomFloat( -16.0f, 16.0f ), random->RandomFloat( -16.0f, 16.0f ), random->RandomFloat( 2.0f, SMOKE_RISE_RATE ) );
int color = random->RandomInt( 8, 64 );
pParticle->m_uchColor[0] = color;
pParticle->m_uchColor[1] = color;
pParticle->m_uchColor[2] = color;
pParticle->m_uchColor[3] = random->RandomInt( 128, 256 );
pParticle->m_uchStartSize = random->RandomFloat( 32.0f, 48.0f );
pParticle->m_uchEndSize = pParticle->m_uchStartSize * 3.0f;
}
}
IMPLEMENT_CLIENTCLASS_DT( C_EntityFlame, DT_EntityFlame, CEntityFlame )
RecvPropFloat(RECVINFO(m_flSize)),
RecvPropEHandle(RECVINFO(m_hEntAttached)),
RecvPropInt(RECVINFO(m_bUseHitboxes)),
RecvPropTime(RECVINFO(m_flLifetime)),
END_RECV_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
C_EntityFlame::C_EntityFlame( void )
{
m_flSize = 4.0f;
m_pEmitter = NULL;
m_flLifetime = 0;
m_bStartedFading = false;
m_bCreatedClientside = false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
C_EntityFlame::~C_EntityFlame( void )
{
if (m_bAttachedToHitboxes)
{
for (int i = 0; i < NUM_HITBOX_FIRES; i++)
{
delete m_pFireSmoke[i];
}
}
}
RenderGroup_t C_EntityFlame::GetRenderGroup()
{
return RENDER_GROUP_TRANSLUCENT_ENTITY;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EntityFlame::UpdateOnRemove( void )
{
CleanUpRagdollOnRemove();
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EntityFlame::CleanUpRagdollOnRemove( void )
{
if ( !m_hEntAttached )
return;
m_hEntAttached->RemoveFlag( FL_ONFIRE );
m_hEntAttached->SetEffectEntity( NULL );
m_hEntAttached->StopSound( "General.BurningFlesh" );
m_hEntAttached->StopSound( "General.BurningObject" );
m_hEntAttached = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : bnewentity -
//-----------------------------------------------------------------------------
void C_EntityFlame::OnDataChanged( DataUpdateType_t updateType )
{
if ( updateType == DATA_UPDATE_CREATED )
{
C_BaseEntity *pEnt = m_hEntAttached;
if ( !pEnt )
return;
if ( m_bUseHitboxes && pEnt->GetBaseAnimating() != NULL )
{
AttachToHitBoxes();
}
else
{
m_vecLastPosition = GetRenderOrigin();
m_ParticleSpawn.Init( 60 ); //Events per second
m_pEmitter = CEmberEffect::Create("C_EntityFlame::Create");
Assert( m_pEmitter.IsValid() );
if ( m_pEmitter.IsValid() )
{
for ( int i = 1; i < NUM_FLAMELETS+1; i++ )
{
m_MaterialHandle[i-1] = m_pEmitter->GetPMaterial( VarArgs( "sprites/flamelet%d", i ) );
}
m_pEmitter->SetSortOrigin( GetAbsOrigin() );
}
}
}
BaseClass::OnDataChanged( updateType );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EntityFlame::Simulate( void )
{
if ( gpGlobals->frametime <= 0.0f )
return;
#ifdef HL2_EPISODIC
// Server side flames need to shrink and die
if ( !m_bCreatedClientside && !m_bStartedFading )
{
float flTTL = (m_flLifetime - gpGlobals->curtime);
if ( flTTL < 2.0 )
{
for (int i = 0; i < NUM_HITBOX_FIRES; i++)
{
if ( m_pFireSmoke[i] )
{
m_pFireSmoke[i]->m_flScaleStart = m_pFireSmoke[i]->m_flScaleEnd;
m_pFireSmoke[i]->m_flScaleEnd = 0.00001;
m_pFireSmoke[i]->m_flScaleTimeStart = gpGlobals->curtime;
m_pFireSmoke[i]->m_flScaleTimeEnd = m_flLifetime;
m_pFireSmoke[i]->m_flScaleRegister = -1;
}
}
m_bStartedFading = true;
}
}
if ( IsEffectActive(EF_BRIGHTLIGHT) || IsEffectActive(EF_DIMLIGHT) )
{
dlight_t *dl = effects->CL_AllocDlight ( index );
dl->origin = GetAbsOrigin();
dl->origin[2] += 16;
dl->color.r = 254;
dl->color.g = 174;
dl->color.b = 10;
dl->radius = random->RandomFloat(400,431);
dl->die = gpGlobals->curtime + 0.001;
if ( m_pFireSmoke[0] )
{
if ( m_pFireSmoke[0]->m_flScaleRegister == -1 )
{
// We've started shrinking, but UpdateScale() hasn't been
// called since then. We want to use the Start scale instead.
dl->radius *= m_pFireSmoke[0]->m_flScaleStart;
}
else
{
dl->radius *= m_pFireSmoke[0]->m_flScaleRegister;
}
}
}
#endif // HL2_EPISODIC
if ( m_bAttachedToHitboxes )
{
UpdateHitBoxFlames();
}
else if ( !!m_pEmitter )
{
m_pEmitter->SetSortOrigin( GetAbsOrigin() );
SimpleParticle *pParticle;
Vector offset;
Vector moveDiff = GetAbsOrigin() - m_vecLastPosition;
float moveLength = VectorNormalize( moveDiff );
int numPuffs = moveLength / (m_flSize*0.5f);
numPuffs = clamp( numPuffs, 1, 8 );
Vector offsetColor;
float step = moveLength / numPuffs;
//Fill in the gaps
for ( int i = 1; i < numPuffs+1; i++ )
{
offset = m_vecLastPosition + ( moveDiff * step * i );
pParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof(SimpleParticle), m_MaterialHandle[random->RandomInt( 0, NUM_FLAMELETS-1 )], offset );
if ( pParticle )
{
pParticle->m_flDieTime = 0.4f;
pParticle->m_flLifetime = 0.0f;
pParticle->m_flRoll = random->RandomInt( 0, 360 );
pParticle->m_flRollDelta= random->RandomFloat( -2.0f, 2.0f );
pParticle->m_uchStartSize = random->RandomInt( m_flSize*0.25f, m_flSize*0.5f );
pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2.0f;
pParticle->m_uchStartAlpha = 255;
pParticle->m_uchEndAlpha = 0;
pParticle->m_uchColor[0] = pParticle->m_uchColor[1] = pParticle->m_uchColor[2] = 255;
Vector dir;
dir.x = random->RandomFloat( -1.0f, 1.0f );
dir.y = random->RandomFloat( -1.0f, 1.0f );
dir.z = random->RandomFloat( 0.5f, 1.0f );
pParticle->m_vecVelocity = dir * random->RandomInt( 4, 32 );
pParticle->m_vecVelocity[2] = random->RandomInt( 32, 64 );
}
}
}
m_vecLastPosition = GetRenderOrigin();
}
void C_EntityFlame::ClientThink( void )
{
for (int i = 0; i < NUM_HITBOX_FIRES; i++)
{
if ( m_pFireSmoke[i] != NULL )
{
if ( m_pFireSmoke[i]->m_bFadingOut == false )
{
m_pFireSmoke[i]->m_flScaleStart = m_pFireSmoke[i]->m_flScaleEnd;
m_pFireSmoke[i]->m_flScaleEnd = 0.00001;
m_pFireSmoke[i]->m_flScaleTimeStart = Helper_GetTime();
m_pFireSmoke[i]->m_flScaleTimeEnd = Helper_GetTime() + 2.0;
m_pFireSmoke[i]->m_flScaleRegister = -1;
m_pFireSmoke[i]->m_bFadingOut = true;
}
else
{
if ( m_pFireSmoke[i]->m_flScaleTimeEnd <= Helper_GetTime() )
{
if ( m_hEntAttached )
{
CPASAttenuationFilter filter( m_hEntAttached );
m_hEntAttached->EmitSound( filter, m_hEntAttached->GetSoundSourceIndex(), "General.StopBurning" );
}
CleanUpRagdollOnRemove();
Release();
return;
}
}
}
}
SetNextClientThink( gpGlobals->curtime + 0.1f );
}
//-----------------------------------------------------------------------------
// Purpose: Returns the volume of the given box in cubic inches.
//-----------------------------------------------------------------------------
inline float CalcBoxVolume(const Vector &mins, const Vector &maxs)
{
return (maxs.x - mins.x) * (maxs.y - mins.y) * (maxs.z - mins.z);
}
//
// Used for sorting hitboxes by volume.
//
struct HitboxVolume_t
{
int nIndex; // The index of the hitbox in the model.
float flVolume; // The volume of the hitbox in cubic inches.
};
//-----------------------------------------------------------------------------
// Purpose: Callback function to sort hitboxes by decreasing volume.
// To mix up the sort results a little we pick a random result for
// boxes within 50 cubic inches of another.
//-----------------------------------------------------------------------------
int __cdecl SortHitboxVolumes(HitboxVolume_t *elem1, HitboxVolume_t *elem2)
{
if (elem1->flVolume > elem2->flVolume + 50)
{
return -1;
}
if (elem1->flVolume < elem2->flVolume + 50)
{
return 1;
}
if (elem1->flVolume != elem2->flVolume)
{
return random->RandomInt(-1, 1);
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Attaches fire to the hitboxes of an animating character. The fire
// is distributed based on hitbox volumes -- it attaches to the larger
// hitboxes first.
//-----------------------------------------------------------------------------
void C_EntityFlame::AttachToHitBoxes( void )
{
m_pCachedModel = NULL;
C_BaseCombatCharacter *pAnimating = (C_BaseCombatCharacter *)m_hEntAttached.Get();
if (!pAnimating || !pAnimating->GetModel())
{
return;
}
CStudioHdr *pStudioHdr = pAnimating->GetModelPtr();
if (!pStudioHdr)
{
return;
}
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->m_nHitboxSet );
if ( !set )
{
return;
}
if ( !set->numhitboxes )
{
return;
}
m_pCachedModel = pAnimating->GetModel();
CBoneCache *pCache = pAnimating->GetBoneCache( pStudioHdr );
matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() );
//
// Sort the hitboxes by volume.
//
HitboxVolume_t hitboxvolume[MAXSTUDIOBONES];
for ( int i = 0; i < set->numhitboxes; i++ )
{
mstudiobbox_t *pBox = set->pHitbox(i);
hitboxvolume[i].nIndex = i;
hitboxvolume[i].flVolume = CalcBoxVolume(pBox->bbmin, pBox->bbmax);
}
qsort(hitboxvolume, set->numhitboxes, sizeof(hitboxvolume[0]), (int (__cdecl *)(const void *, const void *))SortHitboxVolumes);
//
// Attach fire to the hitboxes.
//
for ( int i = 0; i < NUM_HITBOX_FIRES; i++ )
{
int hitboxindex;
//
// Pick the 5 biggest hitboxes, or random ones if there are less than 5 hitboxes,
// then pick random ones after that.
//
if (( i < 5 ) && ( i < set->numhitboxes ))
{
hitboxindex = i;
}
else
{
hitboxindex = random->RandomInt( 0, set->numhitboxes - 1 );
}
mstudiobbox_t *pBox = set->pHitbox( hitboxvolume[hitboxindex].nIndex );
Assert( hitboxbones[pBox->bone] );
m_nHitbox[i] = hitboxvolume[hitboxindex].nIndex;
m_pFireSmoke[i] = new C_FireSmoke;
//
// Calculate a position within the hitbox to place the fire.
//
m_vecFireOrigin[i] = Vector(random->RandomFloat(pBox->bbmin.x, pBox->bbmax.x), random->RandomFloat(pBox->bbmin.y, pBox->bbmax.y), random->RandomFloat(pBox->bbmin.z, pBox->bbmax.z));
Vector vecAbsOrigin;
VectorTransform( m_vecFireOrigin[i], *hitboxbones[pBox->bone], vecAbsOrigin);
m_pFireSmoke[i]->SetLocalOrigin( vecAbsOrigin );
//
// The first fire emits smoke, the rest do not.
//
m_pFireSmoke[i]->m_nFlags = bitsFIRESMOKE_ACTIVE;
m_pFireSmoke[i]->m_nFlameModelIndex = modelinfo->GetModelIndex("sprites/fire1.vmt");
m_pFireSmoke[i]->m_nFlameFromAboveModelIndex = modelinfo->GetModelIndex("sprites/flamefromabove.vmt");
m_pFireSmoke[i]->m_flScale = 0;
m_pFireSmoke[i]->m_flStartScale = 0;
m_pFireSmoke[i]->m_flScaleTime = 1.5;
m_pFireSmoke[i]->m_flScaleRegister = 0.1;
m_pFireSmoke[i]->m_flChildFlameSpread = 20.0;
m_pFireSmoke[i]->m_flScaleStart = 0;
m_pFireSmoke[i]->SetOwnerEntity( this );
// Do a simple That Looks About Right clamp on the volumes
// so that we don't get flames too large or too tiny.
float flVolume = hitboxvolume[hitboxindex].flVolume;
Assert( IsFinite(flVolume) );
#define FLAME_HITBOX_MIN_VOLUME 1000.0f
#define FLAME_HITBOX_MAX_VOLUME 4000.0f
if( flVolume < FLAME_HITBOX_MIN_VOLUME )
{
flVolume = FLAME_HITBOX_MIN_VOLUME;
}
else if( flVolume > FLAME_HITBOX_MAX_VOLUME )
{
flVolume = FLAME_HITBOX_MAX_VOLUME;
}
m_pFireSmoke[i]->m_flScaleEnd = 0.00012f * flVolume;
m_pFireSmoke[i]->m_flScaleTimeStart = Helper_GetTime();
m_pFireSmoke[i]->m_flScaleTimeEnd = Helper_GetTime() + 2.0;
m_pFireSmoke[i]->StartClientOnly();
}
m_bAttachedToHitboxes = true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EntityFlame::DeleteHitBoxFlames(void)
{
for ( int i = 0; i < NUM_HITBOX_FIRES; i++ )
{
m_pFireSmoke[i]->RemoveClientOnly();
delete m_pFireSmoke[i];
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void C_EntityFlame::UpdateHitBoxFlames( void )
{
C_BaseCombatCharacter *pAnimating = (C_BaseCombatCharacter *)m_hEntAttached.Get();
if (!pAnimating)
{
return;
}
if (pAnimating->GetModel() != m_pCachedModel)
{
if (m_pCachedModel != NULL)
{
// The model changed, we must reattach the flames.
DeleteHitBoxFlames();
AttachToHitBoxes();
}
if (m_pCachedModel == NULL)
{
// We tried to reattach and failed.
return;
}
}
studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() );
if (!pStudioHdr)
{
return;
}
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->m_nHitboxSet );
if ( !set )
{
return;
}
if ( !set->numhitboxes )
{
return;
}
pAnimating->SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
for ( int i = 0; i < NUM_HITBOX_FIRES; i++ )
{
Vector vecAbsOrigin;
mstudiobbox_t *pBox = set->pHitbox(m_nHitbox[i]);
VectorTransform(m_vecFireOrigin[i], pAnimating->GetBoneForWrite( pBox->bone ), vecAbsOrigin);
m_pFireSmoke[i]->SetLocalOrigin(vecAbsOrigin);
}
}
//CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectStriderKill )
//CLIENTEFFECT_MATERIAL( "effects/spark" )
//CLIENTEFFECT_REGISTER_END()
//
//
//class CStriderKillEffect : public CParticleEffect
//{
//public:
//
// CStriderKillEffect( const char *pDebugName ) : CParticleEffect( pDebugName ) {}
//
// static CStriderKillEffect *Create( const char *pDebugName )
// {
// return new CStriderKillEffect( pDebugName );
// }
//
// void Update( float fTimeDelta )
// {
// C_BaseCombatCharacter *pAnimating = (C_BaseCombatCharacter *)m_hEntAttached.Get();
// if (!pAnimating)
// {
// return;
// }
//
// if (pAnimating->model != m_pCachedModel)
// {
// if (m_pCachedModel != NULL)
// {
// // The model changed, we must reattach the sparks.
// DeleteHitBoxFlames();
// AttachToHitBoxes();
// }
//
// if (m_pCachedModel == NULL)
// {
// // We tried to reattach and failed.
// return;
// }
// }
//
// studiohdr_t *pStudioHdr = modelrender->GetStudiomodel( pAnimating->model );
// if (!pStudioHdr)
// {
// return;
// }
//
// mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->m_nHitboxSet );
// if ( !set )
// {
// return;
// }
//
// if ( !set->numhitboxes )
// {
// return;
// }
//
// int boneMask = BONE_USED_BY_HITBOX | BONE_USED_BY_ATTACHMENT;
// studiocache_t *pcache = Studio_GetBoneCache( pStudioHdr, pAnimating->GetSequence(), pAnimating->m_flAnimTime, pAnimating->GetAbsAngles(), pAnimating->GetAbsOrigin(), boneMask );
// if ( !pcache )
// {
// matrix3x4_t bonetoworld[MAXSTUDIOBONES];
//
// pAnimating->SetupBones( bonetoworld, MAXSTUDIOBONES, boneMask, gpGlobals->curtime );
// pcache = Studio_SetBoneCache( pStudioHdr, pAnimating->GetSequence(), pAnimating->m_flAnimTime, pAnimating->GetAbsAngles(), pAnimating->GetAbsOrigin(), boneMask, bonetoworld );
// }
//
// matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
// Studio_LinkHitboxCache( hitboxbones, pcache, pStudioHdr, set );
//
// //for ( int i = 0; i < NUM_HITBOX_SPARKS; i++ )
// //{
// //Vector vecAbsOrigin;
// mstudiobbox_t *pBox = set->pHitbox(m_nHitbox[i]);
// //VectorTransform(m_vecFireOrigin[i], *hitboxbones[pBox->bone], vecAbsOrigin);
// //m_pFireSmoke[i]->SetLocalOrigin(vecAbsOrigin);
// //}
// }
//
// bool SimulateAndRender( Particle *pInParticle, ParticleDraw *pDraw, float &sortKey)
// {
// SimpleParticle *pParticle = (SimpleParticle *) pInParticle;
// float timeDelta = pDraw->GetTimeDelta();
//
// Vector tPos;
// TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, tPos );
// sortKey = (int) tPos.z;
//
// RenderParticle_ColorSizeAngle(
// pDraw,
// tPos,
// UpdateColor( pParticle, timeDelta ),
// UpdateAlpha( pParticle, timeDelta ) * GetAlphaDistanceFade( tPos, m_flNearClipMin, m_flNearClipMax ),
// UpdateScale( pParticle, timeDelta ),
// UpdateRoll( pParticle, timeDelta ) );
//
// //Should this particle die?
// pParticle->m_flLifetime += timeDelta;
//
// if ( pParticle->m_flLifetime >= pParticle->m_flDieTime )
// return false;
//
// return true;
// }
//
//
//private:
//
// EHANDLE m_hEntAttached;
//
// CStriderKillEffect( const CStriderKillEffect & );
//};
//
//
//-----------------------------------------------------------------------------
// Purpose:
// Input : origin -
// normal -
// scale -
//-----------------------------------------------------------------------------
//void FX_StriderKill( CBaseAnimating *pAnimating )
//{
// if ( cl_show_bloodspray.GetBool() == false )
// return;
//
// debugoverlay->AddLineOverlay( origin, origin + normal * 72, 255, 255, 255, true, 10 );
//
// Vector offset;
// float spread = 0.2f;
//
// Vector color = Vector( 0.25f, 0.0f, 0.0f );
// float colorRamp;
//
// int i;
//
// Vector vForward, vRight, vUp;
// Vector offDir;
//
// CSmartPtr<CStriderKillEffect> pSimple = CStriderKillEffect::Create( "striderkill" );
// if ( !pSimple )
// return;
//
// pSimple->SetSortOrigin( pAnimating->GetAbsOrigin() );
//
// PMaterialHandle hMaterial = ParticleMgr()->GetPMaterial( "effects/spark" );
//
// SimpleParticle *pParticle;
//
// for ( i = 0; i < NUM_HITBOX_SPARKS; i++ )
// {
// offset = origin;
// offset[0] += random->RandomFloat( -2.0f, 2.0f ) * scale;
// offset[1] += random->RandomFloat( -2.0f, 2.0f ) * scale;
//
// pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), hMaterial, offset );
//
// if ( pParticle != NULL )
// {
// pParticle->m_flLifetime = 0.0f;
// pParticle->m_flDieTime = 0.75f;
//
// spread = 1.0f;
// pParticle->m_vecVelocity.Random( -spread, spread );
// pParticle->m_vecVelocity += normal;
// VectorNormalize( pParticle->m_vecVelocity );
//
// pParticle->m_flGravity = 0;
//
// colorRamp = random->RandomFloat( 0.75f, 1.25f );
//
// pParticle->m_uchColor[0] = min( 1.0f, color[0] * colorRamp ) * 255.0f;
// pParticle->m_uchColor[1] = min( 1.0f, color[1] * colorRamp ) * 255.0f;
// pParticle->m_uchColor[2] = min( 1.0f, color[2] * colorRamp ) * 255.0f;
//
// pParticle->m_uchStartSize = random->RandomFloat( scale * 0.5, scale * 2 );
// pParticle->m_uchEndSize = pParticle->m_uchStartSize * 2;
//
// pParticle->m_uchStartAlpha = random->RandomInt( 128, 255 );
// pParticle->m_uchEndAlpha = 0;
//
// pParticle->m_flRoll = random->RandomInt( 0, 360 );
// pParticle->m_flRollDelta = random->RandomFloat( -4.0f, 4.0f );
// }
// }
//}