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::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs)
{
	if(!pParticleMgr->AddEffect( &m_ParticleEffect, this ))
		return;

	m_LifetimeCounter = 0;
	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;
}
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);
	}	
}
//-----------------------------------------------------------------------------
// This is called after sending this entity's recording state
//-----------------------------------------------------------------------------
void C_ParticleSmokeGrenade::CleanupToolRecordingState( KeyValues *msg )
{
	if ( !ToolsEnabled() )
		return;

	BaseClass::CleanupToolRecordingState( msg );
	m_SmokeTrail.CleanupToolRecordingState( msg );

	// Generally, this is used to allow the entity to clean up
	// allocated state it put into the message, but here we're going
	// to use it to send particle system messages because we
	// know the grenade has been recorded at this point
	if ( !clienttools->IsInRecordingMode() )
		return;
	
	// NOTE: Particle system destruction message will be sent by the particle effect itself.
	if ( m_bVolumeFilled && GetToolParticleEffectId() == TOOLPARTICLESYSTEMID_INVALID )
	{
		// Needed for retriggering of the smoke grenade
		m_bVolumeFilled = false;

		int nId = AllocateToolParticleEffectId();

		KeyValues *msg = new KeyValues( "OldParticleSystem_Create" );
		msg->SetString( "name", "C_ParticleSmokeGrenade" );
		msg->SetInt( "id", nId );
		msg->SetFloat( "time", gpGlobals->curtime );

		KeyValues *pEmitter = msg->FindKey( "DmeSpriteEmitter", true );
		pEmitter->SetInt( "count", NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION * NUM_PARTICLES_PER_DIMENSION );
		pEmitter->SetFloat( "duration", 0 );
		pEmitter->SetString( "material", "particle/particle_smokegrenade1" );
		pEmitter->SetInt( "active", true );

		KeyValues *pInitializers = pEmitter->FindKey( "initializers", true );

		KeyValues *pPosition = pInitializers->FindKey( "DmeVoxelPositionInitializer", true );
		pPosition->SetFloat( "centerx", m_SmokeBasePos.x );
		pPosition->SetFloat( "centery", m_SmokeBasePos.y );
		pPosition->SetFloat( "centerz", m_SmokeBasePos.z );
		pPosition->SetFloat( "particlesPerDimension", m_xCount );
		pPosition->SetFloat( "particleSpacing", m_SpacingRadius );

		KeyValues *pLifetime = pInitializers->FindKey( "DmeRandomLifetimeInitializer", true );
		pLifetime->SetFloat( "minLifetime", m_FadeEndTime );
 		pLifetime->SetFloat( "maxLifetime", m_FadeEndTime );

		KeyValues *pVelocity = pInitializers->FindKey( "DmeAttachmentVelocityInitializer", true );
		pVelocity->SetPtr( "entindex", (void*)entindex() );
 		pVelocity->SetFloat( "minRandomSpeed", 10 );
 		pVelocity->SetFloat( "maxRandomSpeed", 20 );

		KeyValues *pRoll = pInitializers->FindKey( "DmeRandomRollInitializer", true );
		pRoll->SetFloat( "minRoll", -6.0f );
 		pRoll->SetFloat( "maxRoll", 6.0f );

		KeyValues *pRollSpeed = pInitializers->FindKey( "DmeRandomRollSpeedInitializer", true );
		pRollSpeed->SetFloat( "minRollSpeed", -ROTATION_SPEED );
 		pRollSpeed->SetFloat( "maxRollSpeed", ROTATION_SPEED );

		KeyValues *pColor = pInitializers->FindKey( "DmeRandomInterpolatedColorInitializer", true );
		Color c1( 
			clamp( m_MinColor.x * 255.0f, 0, 255 ),
			clamp( m_MinColor.y * 255.0f, 0, 255 ),
			clamp( m_MinColor.z * 255.0f, 0, 255 ), 255 );
		Color c2( 
			clamp( m_MaxColor.x * 255.0f, 0, 255 ),
			clamp( m_MaxColor.y * 255.0f, 0, 255 ),
			clamp( m_MaxColor.z * 255.0f, 0, 255 ), 255 );
		pColor->SetColor( "color1", c1 );
		pColor->SetColor( "color2", c2 );

		KeyValues *pAlpha = pInitializers->FindKey( "DmeRandomAlphaInitializer", true );
		pAlpha->SetInt( "minStartAlpha", 255 );
		pAlpha->SetInt( "maxStartAlpha", 255 );
		pAlpha->SetInt( "minEndAlpha", 0 );
		pAlpha->SetInt( "maxEndAlpha", 0 );

		KeyValues *pSize = pInitializers->FindKey( "DmeRandomSizeInitializer", true );
		pSize->SetFloat( "minStartSize", SMOKEPARTICLE_SIZE );
		pSize->SetFloat( "maxStartSize", SMOKEPARTICLE_SIZE );
		pSize->SetFloat( "minEndSize", SMOKEPARTICLE_SIZE );
		pSize->SetFloat( "maxEndSize", SMOKEPARTICLE_SIZE );

		pInitializers->FindKey( "DmeSolidKillInitializer", true );

		KeyValues *pUpdaters = pEmitter->FindKey( "updaters", true );

		pUpdaters->FindKey( "DmeRollUpdater", true );
		pUpdaters->FindKey( "DmeColorUpdater", true );

		KeyValues *pAlphaCosineUpdater = pUpdaters->FindKey( "DmeAlphaCosineUpdater", true );
		pAlphaCosineUpdater->SetFloat( "duration", m_FadeEndTime - m_FadeStartTime );
		
		pUpdaters->FindKey( "DmeColorDynamicLightUpdater", true );

		KeyValues *pSmokeGrenadeUpdater = pUpdaters->FindKey( "DmeSmokeGrenadeUpdater", true );
 		pSmokeGrenadeUpdater->SetFloat( "centerx", m_SmokeBasePos.x );
		pSmokeGrenadeUpdater->SetFloat( "centery", m_SmokeBasePos.y );
		pSmokeGrenadeUpdater->SetFloat( "centerz", m_SmokeBasePos.z );
		pSmokeGrenadeUpdater->SetFloat( "particlesPerDimension", m_xCount );
		pSmokeGrenadeUpdater->SetFloat( "particleSpacing", m_SpacingRadius );
		pSmokeGrenadeUpdater->SetFloat( "radiusExpandTime", SMOKESPHERE_EXPAND_TIME );
		pSmokeGrenadeUpdater->SetFloat( "cutoffFraction", 0.7f );

		ToolFramework_PostToolMessage( HTOOLHANDLE_INVALID, msg );
		msg->deleteThis();
	}
}
void C_ParticleSmokeGrenade::FillVolume()
{
	m_CurrentStage = 1;
	m_SmokeBasePos = GetPos();
	m_SmokeTrail.SetEmit(false);
	m_ExpandTimeCounter = m_ExpandRadius = 0;
	m_bVolumeFilled = true;

	// Spawn all of our particles.
	float overlap = SMOKEPARTICLE_OVERLAP;

	m_SpacingRadius = (SMOKEGRENADE_PARTICLERADIUS - overlap) * NUM_PARTICLES_PER_DIMENSION * 0.5f;
	m_xCount = m_yCount = m_zCount = NUM_PARTICLES_PER_DIMENSION;

	float invNumPerDimX = 1.0f / (m_xCount-1);
	float invNumPerDimY = 1.0f / (m_yCount-1);
	float invNumPerDimZ = 1.0f / (m_zCount-1);

	Vector vPos;
	for(int x=0; x < m_xCount; x++)
	{
		vPos.x = m_SmokeBasePos.x + ((float)x * invNumPerDimX) * m_SpacingRadius * 2 - m_SpacingRadius;

		for(int y=0; y < m_yCount; y++)
		{
			vPos.y = m_SmokeBasePos.y + ((float)y * invNumPerDimY) * m_SpacingRadius * 2 - m_SpacingRadius;
							  
			for(int z=0; z < m_zCount; z++)
			{
				vPos.z = m_SmokeBasePos.z + ((float)z * invNumPerDimZ) * m_SpacingRadius * 2 - m_SpacingRadius;

				// Don't spawn and simulate particles that are inside a wall
				int contents = enginetrace->GetPointContents( vPos );

				if( contents & CONTENTS_SOLID )
				{
					continue;
				}

				if(SmokeParticleInfo *pInfo = GetSmokeParticleInfo(x,y,z))
				{
					// MD 11/10/03: disabled this because we weren't getting coverage near the ground.
					// If we want it back in certain cases, we can make it a flag.
					/*int contents = GetWorldPointContents(vPos);
					if(false && (contents & CONTENTS_SOLID))
					{
						pInfo->m_pParticle = NULL;
					}
					else
					*/
					{
						SmokeGrenadeParticle *pParticle = 
							(SmokeGrenadeParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeGrenadeParticle), m_MaterialHandles[rand() % NUM_MATERIAL_HANDLES]);

						if(pParticle)
						{
							pParticle->m_Pos = vPos - m_SmokeBasePos; // store its position in local space
							pParticle->m_ColorInterp = (unsigned char)((rand() * 255) / RAND_MAX);
							pParticle->m_RotationSpeed = FRand(-ROTATION_SPEED, ROTATION_SPEED); // Rotation speed.
							pParticle->m_CurRotation = FRand(-6, 6);
						}

						#ifdef _DEBUG
							int testX, testY, testZ;
							int index = GetSmokeParticleIndex(x,y,z);
							GetParticleInfoXYZ(index, testX, testY, testZ);
							assert(testX == x && testY == y && testZ == z);
						#endif

						Vector vColor = EngineGetLightForPoint(vPos);
						pInfo->m_Color[0] = (unsigned char)(vColor.x * 255.9f);
						pInfo->m_Color[1] = (unsigned char)(vColor.y * 255.9f);
						pInfo->m_Color[2] = (unsigned char)(vColor.z * 255.9f);

						// Cast some rays and if it's too close to anything, fade its alpha down.
						pInfo->m_FadeAlpha = 1;

						/*for(int i=0; i < NUM_FADE_PLANES; i++)
						{
							trace_t trace;
							WorldTraceLine(vPos, vPos + s_FadePlaneDirections[i] * 100, MASK_SOLID_BRUSHONLY, &trace);
							if(trace.fraction < 1.0f)
							{
								float dist = DotProduct(trace.plane.normal, vPos) - trace.plane.dist;
								if(dist < 0)
								{
									pInfo->m_FadeAlpha = 0;
								}
								else if(dist < SMOKEPARTICLE_SIZE)
								{
									float alphaScale = dist / SMOKEPARTICLE_SIZE;
									alphaScale *= alphaScale * alphaScale;
									pInfo->m_FadeAlpha *= alphaScale;
								}
							}
						}*/

						pInfo->m_pParticle = pParticle;
						pInfo->m_TradeIndex = -1;
					}
				}
			}
		}
	}
}
void C_ParticleSmokeGrenade::Update(float fTimeDelta)
{
	m_LifetimeCounter += fTimeDelta;
	
	// Update the smoke trail.
	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);
	}	
	
	
	// Update our fade alpha.
	if(m_LifetimeCounter < m_FadeStartTime)
	{
		m_FadeAlpha = 1;
	}
	else if(m_LifetimeCounter < m_FadeEndTime)
	{
		float fadePercent = (m_LifetimeCounter - 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 / SMOKESPHERE_MAX_RADIUS;

	if(m_CurrentStage == 1)
	{
		// Update the expanding sphere.
		m_ExpandTimeCounter += fTimeDelta;
		if(m_ExpandTimeCounter > SMOKESPHERE_EXPAND_TIME)
			m_ExpandTimeCounter = SMOKESPHERE_EXPAND_TIME;

		m_ExpandRadius = SMOKESPHERE_MAX_RADIUS * (float)sin(m_ExpandTimeCounter * 3.14159265358 * 0.5 / SMOKESPHERE_EXPAND_TIME);

		// Add our influence to the global smoke fog alpha.
		float testDist = (EngineGetVecRenderOrigin() - m_SmokeBasePos).Length();
		float fadeEnd = m_ExpandRadius * 0.75;
		if(testDist < fadeEnd)
		{
			EngineGetSmokeFogOverlayAlpha() += 1 - testDist / fadeEnd;
		}	


		// This is used to randomize the direction it chooses to move a particle in.
		int offsetLookup[3] = {-1,0,1};

		// 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)
			{
				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(i, 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 + offsetLookup[(xCount+xCountOffset) % 3];
							int testY = y + offsetLookup[(yCount+yCountOffset) % 3];
							int testZ = z + 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 = i;
									pInfo->m_TradeClock = pOther->m_TradeClock = 0;
									pInfo->m_TradeDuration = FRand(TRADE_DURATION_MIN, TRADE_DURATION_MAX);
									
									bFound = true;
									break;
								}
							}
						}
					}
				}
			}
			else
			{
				SmokeParticleInfo *pOther = &m_SmokeParticleInfos[pInfo->m_TradeIndex];
				assert(pOther->m_TradeIndex == i);
				
				// 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(i, x, y, z);
					Vector myPos = GetSmokeParticlePos(x, y, z);
					
					int otherX, otherY, otherZ;
					GetParticleInfoXYZ(pInfo->m_TradeIndex, otherX, otherY, otherZ);
					Vector otherPos = GetSmokeParticlePos(otherX, otherY, otherZ);

					// 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;
					}
				}
			}
		}
	}
}