//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"

#include "iviewrender.h"
#include "view.h"
#include "studio.h"
#include "bone_setup.h"
#include "model_types.h"
#include "beamdraw.h"
#include "engine/ivdebugoverlay.h"
#include "iviewrender_beams.h"
#include "fx.h"
#include "IEffects.h"
#include "c_entitydissolve.h"
#include "movevars_shared.h"
#include "clienteffectprecachesystem.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectBuild )
CLIENTEFFECT_MATERIAL( "effects/tesla_glow_noz" )
CLIENTEFFECT_MATERIAL( "effects/spark" )
CLIENTEFFECT_MATERIAL( "effects/combinemuzzle2" )
CLIENTEFFECT_REGISTER_END()

//-----------------------------------------------------------------------------
// Networking
//-----------------------------------------------------------------------------
IMPLEMENT_CLIENTCLASS_DT( C_EntityDissolve, DT_EntityDissolve, CEntityDissolve )
	RecvPropTime(RECVINFO(m_flStartTime)),
	RecvPropFloat(RECVINFO(m_flFadeOutStart)),
	RecvPropFloat(RECVINFO(m_flFadeOutLength)),
	RecvPropFloat(RECVINFO(m_flFadeOutModelStart)),
	RecvPropFloat(RECVINFO(m_flFadeOutModelLength)),
	RecvPropFloat(RECVINFO(m_flFadeInStart)),
	RecvPropFloat(RECVINFO(m_flFadeInLength)),
	RecvPropInt(RECVINFO(m_nDissolveType)),
	RecvPropVector( RECVINFO( m_vDissolverOrigin) ),
	RecvPropInt( RECVINFO( m_nMagnitude ) ),
END_RECV_TABLE()

extern PMaterialHandle g_Material_Spark;
PMaterialHandle g_Material_AR2Glow = NULL;

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_EntityDissolve::C_EntityDissolve( void )
{
	m_bLinkedToServerEnt = true;
	m_pController = NULL;
	m_bCoreExplode = false;
	m_vEffectColor = Vector( 255, 255, 255 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_EntityDissolve::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
	if ( GetMoveParent() )
	{
		GetMoveParent()->GetRenderBounds( theMins, theMaxs );
	}
	else
	{
		theMins = GetAbsOrigin();
		theMaxs = theMaxs;
	}
}

//-----------------------------------------------------------------------------
// On data changed
//-----------------------------------------------------------------------------
void C_EntityDissolve::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );
	if ( updateType == DATA_UPDATE_CREATED )
	{
		m_flNextSparkTime = m_flStartTime;
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}
}

//-----------------------------------------------------------------------------
// Cleanup
//-----------------------------------------------------------------------------
void C_EntityDissolve::UpdateOnRemove( void )
{
	if ( m_pController )
	{
		physenv->DestroyMotionController( m_pController );
		m_pController = NULL;
	}

	BaseClass::UpdateOnRemove();
}

//------------------------------------------------------------------------------
// Apply the forces to the entity
//------------------------------------------------------------------------------
IMotionEvent::simresult_e C_EntityDissolve::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
	linear.Init();
	angular.Init();

	// Make it zero g
	linear.z -= -1.02 * GetCurrentGravity();

	Vector vel;
	AngularImpulse angVel;
	pObject->GetVelocity( &vel, &angVel );
	vel += linear * deltaTime; // account for gravity scale

	Vector unitVel = vel;
	Vector unitAngVel = angVel;

	float speed = VectorNormalize( unitVel );
//	float angSpeed = VectorNormalize( unitAngVel );

//	float speedScale = 0.0;
//	float angSpeedScale = 0.0;

	float flLinearLimit = 50;
	float flLinearLimitDelta = 40;
	if ( speed > flLinearLimit )
	{
		float flDeltaVel = (flLinearLimit - speed) / deltaTime;
		if ( flLinearLimitDelta != 0.0f )
		{
			float flMaxDeltaVel = -flLinearLimitDelta / deltaTime;
			if ( flDeltaVel < flMaxDeltaVel )
			{
				flDeltaVel = flMaxDeltaVel;
			}
		}
		VectorMA( linear, flDeltaVel, unitVel, linear );
	}

	return SIM_GLOBAL_ACCELERATION;
}


//-----------------------------------------------------------------------------
// Tesla effect
//-----------------------------------------------------------------------------
static void FX_BuildTesla( C_BaseEntity *pEntity, Vector &vecOrigin, Vector &vecEnd )
{
	BeamInfo_t beamInfo;
	beamInfo.m_pStartEnt = pEntity;
	beamInfo.m_nStartAttachment = 0;
	beamInfo.m_pEndEnt = NULL;
	beamInfo.m_nEndAttachment = 0;
	beamInfo.m_nType = TE_BEAMTESLA;
	beamInfo.m_vecStart = vecOrigin;
	beamInfo.m_vecEnd = vecEnd;
	beamInfo.m_pszModelName = "sprites/lgtning.vmt";
	beamInfo.m_flHaloScale = 0.0;
	beamInfo.m_flLife = random->RandomFloat( 0.25f, 1.0f );
	beamInfo.m_flWidth = random->RandomFloat( 8.0f, 14.0f );
	beamInfo.m_flEndWidth = 1.0f;
	beamInfo.m_flFadeLength = 0.5f;
	beamInfo.m_flAmplitude = 24;
	beamInfo.m_flBrightness = 255.0;
	beamInfo.m_flSpeed = 150.0f;
	beamInfo.m_nStartFrame = 0.0;
	beamInfo.m_flFrameRate = 30.0;
	beamInfo.m_flRed = 255.0;
	beamInfo.m_flGreen = 255.0;
	beamInfo.m_flBlue = 255.0;
	beamInfo.m_nSegments = 18;
	beamInfo.m_bRenderable = true;
	beamInfo.m_nFlags = 0; //FBEAM_ONLYNOISEONCE;
	
	beams->CreateBeamEntPoint( beamInfo );
}

//-----------------------------------------------------------------------------
// Purpose: Tesla effect
//-----------------------------------------------------------------------------
void C_EntityDissolve::BuildTeslaEffect( mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, bool bRandom, float flYawOffset )
{
	Vector vecOrigin;
	QAngle vecAngles;
	MatrixGetColumn( hitboxToWorld, 3, vecOrigin );
	MatrixAngles( hitboxToWorld, vecAngles.Base() );
	C_BaseEntity *pEntity = GetMoveParent();

	// Make a couple of tries at it
	int iTries = -1;
	Vector vecForward;
	trace_t tr;
	do
	{
		iTries++;

		// Some beams are deliberatly aimed around the point, the rest are random.
		if ( !bRandom )
		{
			QAngle vecTemp = vecAngles;
			vecTemp[YAW] += flYawOffset;
			AngleVectors( vecTemp, &vecForward );

			// Randomly angle it up or down
			vecForward.z = RandomFloat( -1, 1 );
		}
		else
		{
			vecForward = RandomVector( -1, 1 );
		}

		UTIL_TraceLine( vecOrigin, vecOrigin + (vecForward * 192), MASK_SHOT, pEntity, COLLISION_GROUP_NONE, &tr );
	} while ( tr.fraction >= 1.0 && iTries < 3 );

	Vector vecEnd = tr.endpos - (vecForward * 8);

	// Only spark & glow if we hit something
	if ( tr.fraction < 1.0 )
	{
		if ( !EffectOccluded( tr.endpos ) )
		{
			// Move it towards the camera
			Vector vecFlash = tr.endpos;
			Vector vecForward;
			AngleVectors( MainViewAngles(), &vecForward );
			vecFlash -= (vecForward * 8);

			g_pEffects->EnergySplash( vecFlash, -vecForward, false );

			// End glow
			CSmartPtr<CSimpleEmitter> pSimple = CSimpleEmitter::Create( "dust" );
			pSimple->SetSortOrigin( vecFlash );
			SimpleParticle *pParticle;
			pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/tesla_glow_noz" ), vecFlash );
			if ( pParticle != NULL )
			{
				pParticle->m_flLifetime = 0.0f;
				pParticle->m_flDieTime	= RandomFloat( 0.5, 1 );
				pParticle->m_vecVelocity = vec3_origin;
				Vector color( 1,1,1 );
				float  colorRamp = 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	= RandomFloat( 6,13 );
				pParticle->m_uchEndSize		= pParticle->m_uchStartSize - 2;
				pParticle->m_uchStartAlpha	= 255;
				pParticle->m_uchEndAlpha	= 10;
				pParticle->m_flRoll			= RandomFloat( 0,360 );
				pParticle->m_flRollDelta	= 0;
			}
		}
	}

	// Build the tesla
	FX_BuildTesla( pEntity, vecOrigin, tr.endpos );
}

//-----------------------------------------------------------------------------
// Sorts the components of a vector
//-----------------------------------------------------------------------------
static inline void SortAbsVectorComponents( const Vector& src, int* pVecIdx )
{
	Vector absVec( fabs(src[0]), fabs(src[1]), fabs(src[2]) );

	int maxIdx = (absVec[0] > absVec[1]) ? 0 : 1;
	if (absVec[2] > absVec[maxIdx])
	{
		maxIdx = 2;
	}

	// always choose something right-handed....
	switch(	maxIdx )
	{
	case 0:
		pVecIdx[0] = 1;
		pVecIdx[1] = 2;
		pVecIdx[2] = 0;
		break;
	case 1:
		pVecIdx[0] = 2;
		pVecIdx[1] = 0;
		pVecIdx[2] = 1;
		break;
	case 2:
		pVecIdx[0] = 0;
		pVecIdx[1] = 1;
		pVecIdx[2] = 2;
		break;
	}
}

//-----------------------------------------------------------------------------
// Compute the bounding box's center, size, and basis
//-----------------------------------------------------------------------------
void C_EntityDissolve::ComputeRenderInfo( mstudiobbox_t *pHitBox, const matrix3x4_t &hitboxToWorld, 
										 Vector *pVecAbsOrigin, Vector *pXVec, Vector *pYVec )
{
	// Compute the center of the hitbox in worldspace
	Vector vecHitboxCenter;
	VectorAdd( pHitBox->bbmin, pHitBox->bbmax, vecHitboxCenter );
	vecHitboxCenter *= 0.5f;
	VectorTransform( vecHitboxCenter, hitboxToWorld, *pVecAbsOrigin );

	// Get the object's basis
	Vector vec[3];
	MatrixGetColumn( hitboxToWorld, 0, vec[0] );
	MatrixGetColumn( hitboxToWorld, 1, vec[1] );
	MatrixGetColumn( hitboxToWorld, 2, vec[2] );
//	vec[1] *= -1.0f;

	Vector vecViewDir;
	VectorSubtract( CurrentViewOrigin(), *pVecAbsOrigin, vecViewDir );
	VectorNormalize( vecViewDir );

	// Project the shadow casting direction into the space of the hitbox
	Vector localViewDir;
	localViewDir[0] = DotProduct( vec[0], vecViewDir );
	localViewDir[1] = DotProduct( vec[1], vecViewDir );
	localViewDir[2] = DotProduct( vec[2], vecViewDir );

	// Figure out which vector has the largest component perpendicular
	// to the view direction...
	// Sort by how perpendicular it is
	int vecIdx[3];
	SortAbsVectorComponents( localViewDir, vecIdx );

	// Here's our hitbox basis vectors; namely the ones that are
	// most perpendicular to the view direction
	*pXVec = vec[vecIdx[0]];
	*pYVec = vec[vecIdx[1]];

	// Project them into a plane perpendicular to the view direction
	*pXVec -= vecViewDir * DotProduct( vecViewDir, *pXVec );
	*pYVec -= vecViewDir * DotProduct( vecViewDir, *pYVec );
	VectorNormalize( *pXVec );
	VectorNormalize( *pYVec );

	// Compute the hitbox size
	Vector boxSize;
	VectorSubtract( pHitBox->bbmax, pHitBox->bbmin, boxSize );

	// We project the two longest sides into the vectors perpendicular
	// to the projection direction, then add in the projection of the perp direction
	Vector2D size( boxSize[vecIdx[0]], boxSize[vecIdx[1]] );
	size.x *= fabs( DotProduct( vec[vecIdx[0]], *pXVec ) );
	size.y *= fabs( DotProduct( vec[vecIdx[1]], *pYVec ) );

	// Add the third component into x and y
	size.x += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], *pXVec ) );
	size.y += boxSize[vecIdx[2]] * fabs( DotProduct( vec[vecIdx[2]], *pYVec ) );

	// Bloat a bit, since the shadow wants to extend outside the model a bit
	size *= 2.0f;

	// Clamp the minimum size
	Vector2DMax( size, Vector2D(10.0f, 10.0f), size );

	// Factor the size into the xvec + yvec
	(*pXVec) *= size.x * 0.5f;
	(*pYVec) *= size.y * 0.5f;
}


//-----------------------------------------------------------------------------
// Sparks!
//-----------------------------------------------------------------------------
void C_EntityDissolve::DoSparks( mstudiohitboxset_t *set, matrix3x4_t *hitboxbones[MAXSTUDIOBONES] )
{
	if ( m_flNextSparkTime > gpGlobals->curtime )
		return;

	float dt = m_flStartTime + m_flFadeOutStart - gpGlobals->curtime;
	dt = clamp( dt, 0.0f, m_flFadeOutStart );
	
	float flNextTime;
	if (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL)
	{
		flNextTime = SimpleSplineRemapVal( dt, 0.0f, m_flFadeOutStart, 2.0f * TICK_INTERVAL, 0.4f );
	}
	else
	{
		// m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT);
		flNextTime = SimpleSplineRemapVal( dt, 0.0f, m_flFadeOutStart, 0.3f, 1.0f );
	}

	m_flNextSparkTime = gpGlobals->curtime + flNextTime;

	// Send out beams around us
	int iNumBeamsAround = 2;
	int iNumRandomBeams = 1;
	int iTotalBeams = iNumBeamsAround + iNumRandomBeams;
	float flYawOffset = RandomFloat(0,360);
	for ( int i = 0; i < iTotalBeams; i++ )
	{
		int nHitbox = random->RandomInt( 0, set->numhitboxes - 1 );
		mstudiobbox_t *pBox = set->pHitbox(nHitbox);

		float flActualYawOffset = 0;
		bool bRandom = ( i >= iNumBeamsAround );
		if ( !bRandom )
		{
			flActualYawOffset = anglemod( flYawOffset + ((360 / iTotalBeams) * i) );
		}

		BuildTeslaEffect( pBox, *hitboxbones[pBox->bone], bRandom, flActualYawOffset );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_EntityDissolve::SetupEmitter( void )
{
	if ( !m_pEmitter )
	{
		m_pEmitter = CSimpleEmitter::Create( "C_EntityDissolve" );
		m_pEmitter->SetSortOrigin( GetAbsOrigin() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float C_EntityDissolve::GetFadeInPercentage( void )
{
	float dt = gpGlobals->curtime - m_flStartTime;
	
	if ( dt > m_flFadeOutStart )
		return 1.0f;

	if ( dt < m_flFadeInStart )
		return 0.0f;

	if ( (dt > m_flFadeInStart) && (dt < m_flFadeInStart + m_flFadeInLength) )
	{
		dt -= m_flFadeInStart;
		
		return ( dt / m_flFadeInLength );
	}

	return 1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float C_EntityDissolve::GetFadeOutPercentage( void )
{
	float dt = gpGlobals->curtime - m_flStartTime;
	
	if ( dt < m_flFadeInStart )
		return 1.0f;

	if ( dt > m_flFadeOutStart )
	{
		dt -= m_flFadeOutStart;
		
		if ( dt > m_flFadeOutLength )
			return 0.0f;
		
		return 1.0f - ( dt / m_flFadeOutLength );
	}
	
	return 1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float C_EntityDissolve::GetModelFadeOutPercentage( void )
{
	float dt = gpGlobals->curtime - m_flStartTime;
	
	if ( dt < m_flFadeOutModelStart )
		return 1.0f;

	if ( dt > m_flFadeOutModelStart )
	{
		dt -= m_flFadeOutModelStart;
		
		if ( dt > m_flFadeOutModelLength )
			return 0.0f;
		
		return 1.0f - ( dt / m_flFadeOutModelLength );
	}
	
	return 1.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_EntityDissolve::ClientThink( void )
{
	C_BaseEntity *pEnt = GetMoveParent();
	if ( !pEnt )
		return;

	bool bIsRagdoll;
#ifdef TF_CLIENT_DLL
	bIsRagdoll = true;
#else
	C_BaseAnimating *pAnimating = GetMoveParent() ? GetMoveParent()->GetBaseAnimating() : NULL;
	if (!pAnimating)
		return;
	bIsRagdoll = pAnimating->IsRagdoll();
#endif

	// NOTE: IsRagdoll means *client-side* ragdoll. We shouldn't be trying to fight
	// the server ragdoll (or any server physics) on the client
	if (( !m_pController ) && ( m_nDissolveType == ENTITY_DISSOLVE_NORMAL ) && bIsRagdoll )
	{
		IPhysicsObject *ppList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
		int nCount = pEnt->VPhysicsGetObjectList( ppList, ARRAYSIZE(ppList) );
		if ( nCount > 0 )
		{
			m_pController = physenv->CreateMotionController( this );
			for ( int i = 0; i < nCount; ++i )
			{
				m_pController->AttachObject( ppList[i], true );
			}
		}
	}

	color32 color;

	color.r = ( 1.0f - GetFadeInPercentage() ) * m_vEffectColor.x;
	color.g = ( 1.0f - GetFadeInPercentage() ) * m_vEffectColor.y;
	color.b = ( 1.0f - GetFadeInPercentage() ) * m_vEffectColor.z;
	color.a = GetModelFadeOutPercentage() * 255.0f;

	// Setup the entity fade
	pEnt->SetRenderMode( kRenderTransColor );
	pEnt->SetRenderColor( color.r, color.g, color.b, color.a );

	if ( GetModelFadeOutPercentage() <= 0.2f )
	{
		m_bCoreExplode = true;
	}

	// If we're dead, fade out
	if ( GetFadeOutPercentage() <= 0.0f )
	{
		// Do NOT remove from the client entity list. It'll confuse the local network backdoor, and the entity will never get destroyed
		// because when the server says to destroy it, the client won't be able to find it.
		// ClientEntityList().RemoveEntity( GetClientHandle() );

		partition->Remove( PARTITION_CLIENT_SOLID_EDICTS | PARTITION_CLIENT_RESPONSIVE_EDICTS | PARTITION_CLIENT_NON_STATIC_EDICTS, CollisionProp()->GetPartitionHandle() );

		RemoveFromLeafSystem();

		//FIXME: Ick!
		//Adrian: I'll assume we don't need the ragdoll either so I'll remove that too.
		if ( m_bLinkedToServerEnt == false )
		{
			Release();

			C_ClientRagdoll *pRagdoll = dynamic_cast <C_ClientRagdoll *> ( pEnt );

			if ( pRagdoll )
			{
				pRagdoll->ReleaseRagdoll();
			}
#ifdef TF_CLIENT_DLL
			else
			{
				pEnt->Release();
			}
#endif
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flags - 
// Output : int
//-----------------------------------------------------------------------------
int C_EntityDissolve::DrawModel( int flags )
{
	// See if we should draw
	if ( gpGlobals->frametime == 0 || m_bReadyToDraw == false )
		return 0;

	C_BaseAnimating *pAnimating = GetMoveParent() ? GetMoveParent()->GetBaseAnimating() : NULL;
	if ( pAnimating == NULL )
		return 0;

	matrix3x4_t	*hitboxbones[MAXSTUDIOBONES];
	if ( pAnimating->HitboxToWorldTransforms( hitboxbones ) == false )
		return 0;

	studiohdr_t *pStudioHdr = modelinfo->GetStudiomodel( pAnimating->GetModel() );
	if ( pStudioHdr == NULL )
		return false;

	mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->GetHitboxSet() );
	if ( set == NULL )
		return false;

	// Make sure the emitter is setup properly
	SetupEmitter();
	
	// Get fade percentages for the effect
	float fadeInPerc = GetFadeInPercentage();
	float fadeOutPerc = GetFadeOutPercentage();

	float fadePerc = ( fadeInPerc >= 1.0f ) ? fadeOutPerc : fadeInPerc;

	Vector vecSkew = vec3_origin;

	// Do extra effects under certain circumstances
	if ( ( fadePerc < 0.99f ) && ( (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) ) )
	{
		DoSparks( set, hitboxbones );
	}

	// Skew the particles in front or in back of their targets
	vecSkew = CurrentViewForward() * ( 8.0f - ( ( 1.0f - fadePerc ) * 32.0f ) );

	float spriteScale = ( ( gpGlobals->curtime - m_flStartTime ) / m_flFadeOutLength );
	spriteScale = clamp( spriteScale, 0.75f, 1.0f );

	// Cache off this material reference
	if ( g_Material_Spark == NULL )
	{
		g_Material_Spark = ParticleMgr()->GetPMaterial( "effects/spark" );
	}

	if ( g_Material_AR2Glow == NULL )
	{
		g_Material_AR2Glow = ParticleMgr()->GetPMaterial( "effects/combinemuzzle2" );
	}

	SimpleParticle *sParticle;

	for ( int i = 0; i < set->numhitboxes; ++i )
	{
		Vector vecAbsOrigin, xvec, yvec;
		mstudiobbox_t *pBox = set->pHitbox(i);
		ComputeRenderInfo( pBox, *hitboxbones[pBox->bone], &vecAbsOrigin, &xvec, &yvec );

		Vector offset;
		Vector	xDir, yDir;

		xDir = xvec;
		float xScale = VectorNormalize( xDir ) * 0.75f;

		yDir = yvec;
		float yScale = VectorNormalize( yDir ) * 0.75f;

		int numParticles = clamp( 3.0f * fadePerc, 0.f, 3.f );

		int iTempParts = 2;

		if ( m_nDissolveType == ENTITY_DISSOLVE_CORE )
		{
			if ( m_bCoreExplode == true )
			{
				numParticles = 15;
				iTempParts = 20;
			}
		}

		for ( int j = 0; j < iTempParts; j++ )
		{
			// Skew the origin
			offset = xDir * Helper_RandomFloat( -xScale*0.5f, xScale*0.5f ) + yDir * Helper_RandomFloat( -yScale*0.5f, yScale*0.5f );
			offset += vecSkew;

			if ( random->RandomInt( 0, 2 ) != 0 )
				continue;

			sParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof(SimpleParticle), g_Material_Spark, vecAbsOrigin + offset );
			
			if ( sParticle == NULL )
				return 1;

			sParticle->m_vecVelocity	= Vector( Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( 16.0f, 64.0f ) );
			
			if ( m_nDissolveType == ENTITY_DISSOLVE_CORE )
			{
				if ( m_bCoreExplode == true )
				{
					Vector vDirection = (vecAbsOrigin + offset) - m_vDissolverOrigin;
					VectorNormalize( vDirection );
					sParticle->m_vecVelocity = vDirection * m_nMagnitude;
				}
			}

			if ( sParticle->m_vecVelocity.z > 0 )
			{
				sParticle->m_uchStartSize	= random->RandomFloat( 4, 6 ) * spriteScale;
			}
			else
			{
				sParticle->m_uchStartSize	= 2 * spriteScale;
			}

			sParticle->m_flDieTime = random->RandomFloat( 0.4f, 0.5f );
			
			// If we're the last particles, last longer
			if ( numParticles == 0 )
			{
				sParticle->m_flDieTime *= 2.0f;
				sParticle->m_uchStartSize = 2 * spriteScale;
				sParticle->m_flRollDelta	= Helper_RandomFloat( -4.0f, 4.0f );

				if ( m_nDissolveType == ENTITY_DISSOLVE_CORE )
				{
					if ( m_bCoreExplode == true )
					{
						sParticle->m_flDieTime *= 2.0f;
						sParticle->m_flRollDelta	= Helper_RandomFloat( -1.0f, 1.0f );
					}
				}
			}
			else
			{
				sParticle->m_flRollDelta	= Helper_RandomFloat( -8.0f, 8.0f );
			}
			
			sParticle->m_flLifetime		= 0.0f;

			sParticle->m_flRoll			= Helper_RandomInt( 0, 360 );

			float alpha = 255;

			sParticle->m_uchColor[0]	= m_vEffectColor.x;
			sParticle->m_uchColor[1]	= m_vEffectColor.y;
			sParticle->m_uchColor[2]	= m_vEffectColor.z;
			sParticle->m_uchStartAlpha	= alpha;
			sParticle->m_uchEndAlpha	= 0;
			sParticle->m_uchEndSize		= 0;
		}
			
		for ( int j = 0; j < numParticles; j++ )
		{
			offset = xDir * Helper_RandomFloat( -xScale*0.5f, xScale*0.5f ) + yDir * Helper_RandomFloat( -yScale*0.5f, yScale*0.5f );
			offset += vecSkew;

			sParticle = (SimpleParticle *) m_pEmitter->AddParticle( sizeof(SimpleParticle), g_Material_AR2Glow, vecAbsOrigin + offset );

			if ( sParticle == NULL )
				return 1;
			
			sParticle->m_vecVelocity	= Vector( Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -4.0f, 4.0f ), Helper_RandomFloat( -64.0f, 128.0f ) );
			sParticle->m_uchStartSize	= random->RandomFloat( 8, 12 ) * spriteScale;
			sParticle->m_flDieTime		= 0.1f;
			sParticle->m_flLifetime		= 0.0f;

			sParticle->m_flRoll			= Helper_RandomInt( 0, 360 );
			sParticle->m_flRollDelta	= Helper_RandomFloat( -2.0f, 2.0f );

			float alpha = 255;

			sParticle->m_uchColor[0]	= m_vEffectColor.x;
			sParticle->m_uchColor[1]	= m_vEffectColor.y;
			sParticle->m_uchColor[2]	= m_vEffectColor.z;
			sParticle->m_uchStartAlpha	= alpha;
			sParticle->m_uchEndAlpha	= 0;
			sParticle->m_uchEndSize		= 0;

			if ( m_nDissolveType == ENTITY_DISSOLVE_CORE )
			{
				if ( m_bCoreExplode == true )
				{
					Vector vDirection = (vecAbsOrigin + offset) - m_vDissolverOrigin;

					VectorNormalize( vDirection );

					sParticle->m_vecVelocity = vDirection * m_nMagnitude;

					sParticle->m_flDieTime		= 0.5f;
				}
			}
		}
	}

	return 1;
}