//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_ai_basenpc.h"
#include "c_te_particlesystem.h"
#include "fx.h"
#include "fx_sparks.h"
#include "c_tracer.h"
#include "clientsideeffects.h"
#include "iefx.h"
#include "dlight.h"
#include "bone_setup.h"
#include "c_rope.h"
#include "fx_line.h"
#include "c_sprite.h"
#include "view.h"
#include "view_scene.h"
#include "materialsystem/imaterialvar.h"
#include "simple_keys.h"
#include "fx_envelope.h"
#include "iclientvehicle.h"
#include "engine/ivdebugoverlay.h"
#include "particles_localspace.h"
#include "dlight.h"
#include "iefx.h"
#include "c_te_effect_dispatch.h"
#include "tier0/vprof.h"
#include "ClientEffectPrecacheSystem.h"
#include <bitbuf.h>
#include "fx_water.h"

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

#define STRIDER_MSG_BIG_SHOT			1
#define STRIDER_MSG_STREAKS				2
#define STRIDER_MSG_DEAD				3

#define STOMP_IK_SLOT					11
const int NUM_STRIDER_IK_TARGETS = 6;

const float	STRIDERFX_BIG_SHOT_TIME = 1.25f;
const float STRIDERFX_END_ALL_TIME = 4.0f;

class C_StriderFX : public C_EnvelopeFX
{
public:
	typedef C_EnvelopeFX	BaseClass;

	C_StriderFX();
	~C_StriderFX()
	{
		EffectShutdown();
	}


	void			Update( C_BaseEntity *pOwner, const Vector &targetPos );

	// Returns the bounds relative to the origin (render bounds)
	virtual void	GetRenderBounds( Vector& mins, Vector& maxs )
	{
		ClearBounds( mins, maxs );
		AddPointToBounds( m_worldPosition, mins, maxs );
		AddPointToBounds( m_targetPosition, mins, maxs );
		mins -= GetRenderOrigin();
		maxs -= GetRenderOrigin();
	}

	virtual void EffectInit( int entityIndex, int attachment )
	{
		m_limitHitTime = 0;
		BaseClass::EffectInit( entityIndex, attachment );
	}
	virtual void EffectShutdown( void )
	{
		m_limitHitTime = 0;
		BaseClass::EffectShutdown();
	}

	virtual int	DrawModel( int flags );
	virtual void LimitTime( float tmax ) 
	{ 
		float dt = tmax - m_t;
		if ( dt < 0 )
		{
			dt = 0;
		}
		m_limitHitTime = gpGlobals->curtime + dt;
		BaseClass::LimitTime( tmax );
	}

	C_BaseEntity			*m_pOwner;
	Vector					m_targetPosition;
	Vector					m_beamEndPosition;
	pixelvis_handle_t		m_queryHandleGun;
	pixelvis_handle_t		m_queryHandleBeamEnd;
	float					m_limitHitTime;
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class C_Strider : public C_AI_BaseNPC
{
	DECLARE_CLASS( C_Strider, C_AI_BaseNPC );
public:
	DECLARE_CLIENTCLASS();
	DECLARE_INTERPOLATION();


					C_Strider();
	virtual			~C_Strider();

	// model specific
	virtual void	ReceiveMessage( int classID, bf_read &msg );
	virtual void	CalculateIKLocks( float currentTime )
	{
		// NOTE: All strider IK is solved on the server, enable this to do it client-side
		//BaseClass::CalculateIKLocks( currentTime );
		if ( m_pIk && m_pIk->m_target.Count() )
		{
			Assert(m_pIk->m_target.Count() > STOMP_IK_SLOT);
			// HACKHACK: Hardcoded 11???  Not a cleaner way to do this
			CIKTarget &target = m_pIk->m_target[STOMP_IK_SLOT];
			target.SetPos( m_vecHitPos );
			// target.latched.pos = m_vecHitPos;

			for ( int i = 0; i < NUM_STRIDER_IK_TARGETS; i++ )
			{
				CIKTarget &target = m_pIk->m_target[i];
				target.SetPos( m_vecIKTarget[i] );
#if 0
				debugoverlay->AddBoxOverlay( m_vecIKTarget[i], Vector( -2, -2, -2 ), Vector( 2, 2, 2), QAngle( 0, 0, 0 ), (int)255*m_pIk->m_target[i].est.latched, 0, 0, 0, 0 );
#endif
			}
		}
	}

	virtual void OnDataChanged( DataUpdateType_t updateType );
	virtual void GetRenderBounds( Vector& theMins, Vector& theMaxs );
	virtual void ClientThink();

private:
	C_Strider( const C_Strider & );
	C_StriderFX	m_cannonFX;
	Vector		m_vecHitPos;
	Vector		m_vecIKTarget[NUM_STRIDER_IK_TARGETS];
	CInterpolatedVar< Vector >		m_iv_vecHitPos;
	CInterpolatedVarArray< Vector, NUM_STRIDER_IK_TARGETS >	m_iv_vecIKTarget;
	Vector		m_vecRenderMins;
	Vector		m_vecRenderMaxs;

	float m_flNextRopeCutTime;
};

IMPLEMENT_CLIENTCLASS_DT(C_Strider, DT_NPC_Strider, CNPC_Strider)
	RecvPropVector(RECVINFO(m_vecHitPos)),
	RecvPropVector(RECVINFO(m_vecIKTarget[0])),
	RecvPropVector(RECVINFO(m_vecIKTarget[1])),
	RecvPropVector(RECVINFO(m_vecIKTarget[2])),
	RecvPropVector(RECVINFO(m_vecIKTarget[3])),
	RecvPropVector(RECVINFO(m_vecIKTarget[4])),
	RecvPropVector(RECVINFO(m_vecIKTarget[5])),
END_RECV_TABLE()

C_StriderFX::C_StriderFX()
{
	m_pOwner = NULL;
	m_active = false;
}

void C_StriderFX::Update( C_BaseEntity *pOwner, const Vector &targetPos )
{
	BaseClass::Update();

	m_pOwner = pOwner;
	
	if ( m_active )
	{
		m_targetPosition = targetPos;
	}
}

// --on gun
// warpy sprite bit
// darkening sprite
// glowy blue flare sprite
// bubble warpy sprite
// after glow sprite

// --on line of sight
// narrow beam
// wide beam

// --on impact point
// sparkly white bits
// sparkly white streaks
// pale blue particle steam

enum 
{
	STRIDERFX_WARP_SCALE = 0,
	STRIDERFX_DARKNESS,
	STRIDERFX_FLARE_COLOR,
	STRIDERFX_FLARE_SIZE,
	STRIDERFX_BUBBLE_SIZE,
	STRIDERFX_BUBBLE_REFRACT,

	STRIDERFX_NARROW_BEAM_COLOR,
	STRIDERFX_NARROW_BEAM_SIZE,
	
	STRIDERFX_WIDE_BEAM_COLOR,
	STRIDERFX_WIDE_BEAM_SIZE,

	STRIDERFX_AFTERGLOW_COLOR,

	STRIDERFX_WIDE_BEAM_LENGTH,

	STRIDERFX_SPARK_COUNT,
	STRIDERFX_STREAK_COUNT,
	STRIDERFX_STEAM_COUNT,


	// must be last
	STRIDERFX_PARAMETERS,
};

class CStriderFXEnvelope
{
public:
	CStriderFXEnvelope();

	void AddKey( int parameterIndex, const CSimpleKeyInterp &key )
	{
		Assert( parameterIndex >= 0 && parameterIndex < STRIDERFX_PARAMETERS );

		if ( parameterIndex >= 0 && parameterIndex < STRIDERFX_PARAMETERS )
		{
			m_parameters[parameterIndex].Insert( key );
		}

	}

	CSimpleKeyList		m_parameters[STRIDERFX_PARAMETERS];
};

// NOTE: Beam widths are half-widths or radii, so this is a beam that represents a cylinder with 2" radius
const float NARROW_BEAM_WIDTH = 2;
const float WIDE_BEAM_WIDTH = 16;
const float FLARE_SIZE = 128;
const float	DARK_SIZE = 64;
const float AFTERGLOW_SIZE = 64;

const float WARP_SIZE = 512;
const float WARP_REFRACT = 0.075f;
const float WARP_BUBBLE_SIZE = 256;
const float WARP_BUBBLE_REFRACT = 1.0f;

CStriderFXEnvelope::CStriderFXEnvelope()
{
	AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 1.25, KEY_ACCELERATE, 1 ) );
	AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 1.3, KEY_LINEAR, 0 ) );

	AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 0.5, KEY_SPLINE, 1 ) );
	AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 1.0, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 1.25, KEY_SPLINE, 0 ) );
	AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 2.0, KEY_SPLINE, 0 ) );

	AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 0.5, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 1.25, KEY_ACCELERATE, 1 ) );
	AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 1.5, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 2.0, KEY_SPLINE, 0 ) );

	AddKey( STRIDERFX_FLARE_SIZE, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_FLARE_SIZE, CSimpleKeyInterp( 1.0, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_FLARE_SIZE, CSimpleKeyInterp( 2.0, KEY_LINEAR, 1 ) );

	AddKey( STRIDERFX_BUBBLE_SIZE, CSimpleKeyInterp( 1.3, KEY_LINEAR, 0.5 ) );
	AddKey( STRIDERFX_BUBBLE_SIZE, CSimpleKeyInterp( 2.0, KEY_DECELERATE, 2 ) );

	AddKey( STRIDERFX_BUBBLE_REFRACT, CSimpleKeyInterp( 1.3, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_BUBBLE_REFRACT, CSimpleKeyInterp( 2.0, KEY_LINEAR, 0 ) );

	AddKey( STRIDERFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 1.25, KEY_ACCELERATE, 1.0 ) );
	AddKey( STRIDERFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 1.5, KEY_SPLINE, 0 ) );

	AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 0.5, KEY_ACCELERATE, 1 ) );
	AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 1.5, KEY_DECELERATE, 2 ) );
	
	AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 1.25, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 1.5, KEY_SPLINE, 1 ) );
	AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 1.75, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 2.1, KEY_SPLINE, 0 ) );
	
	AddKey( STRIDERFX_WIDE_BEAM_SIZE, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_WIDE_BEAM_SIZE, CSimpleKeyInterp( 2.1, KEY_LINEAR, 1 ) );

	AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 1.0, KEY_LINEAR, 0 ) );
	AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 1.25, KEY_SPLINE, 1 ) );
	AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 3.0, KEY_LINEAR, 1 ) );
	AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 3.5, KEY_ACCELERATE, 0 ) );

	AddKey( STRIDERFX_WIDE_BEAM_LENGTH, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1.0 ) );
	AddKey( STRIDERFX_WIDE_BEAM_LENGTH, CSimpleKeyInterp( 1.5, KEY_ACCELERATE, 0.0 ) );
	AddKey( STRIDERFX_WIDE_BEAM_LENGTH, CSimpleKeyInterp( 2.1, KEY_LINEAR, 0 ) );

	//AddKey( STRIDERFX_SPARK_COUNT,
	//AddKey( STRIDERFX_STREAK_COUNT,
	//AddKey( STRIDERFX_STEAM_COUNT,
}

CStriderFXEnvelope g_StriderCannonEnvelope;

void ScaleColor( color32 &out, const color32 &in, float scale )
{
	out.r = (byte)(int)((float)in.r * scale);
	out.g = (byte)(int)((float)in.g * scale);
	out.b = (byte)(int)((float)in.b * scale);
	out.a = (byte)(int)((float)in.a * scale);
}

void DrawSpriteTangentSpace( const Vector &vecOrigin, float flWidth, float flHeight, color32 color )
{
	unsigned char pColor[4] = { color.r, color.g, color.b, color.a };

	// Generate half-widths
	flWidth *= 0.5f;
	flHeight *= 0.5f;

	// Compute direction vectors for the sprite
	Vector fwd, right( 1, 0, 0 ), up( 0, 1, 0 );
	VectorSubtract( CurrentViewOrigin(), vecOrigin, fwd );
	float flDist = VectorNormalize( fwd );
	if (flDist >= 1e-3)
	{
		CrossProduct( CurrentViewUp(), fwd, right );
		flDist = VectorNormalize( right );
		if (flDist >= 1e-3)
		{
			CrossProduct( fwd, right, up );
		}
		else
		{
			// In this case, fwd == g_vecVUp, it's right above or 
			// below us in screen space
			CrossProduct( fwd, CurrentViewRight(), up );
			VectorNormalize( up );
			CrossProduct( up, fwd, right );
		}
	}

	Vector left = -right;
	Vector down = -up;
	Vector back = -fwd;

	CMeshBuilder meshBuilder;
	Vector point;
	IMesh* pMesh = materials->GetDynamicMesh( );

	meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 0, 1);
	VectorMA (vecOrigin, -flHeight, up, point);
	VectorMA (point, -flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 0, 0);
	VectorMA (vecOrigin, flHeight, up, point);
	VectorMA (point, -flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 1, 0);
	VectorMA (vecOrigin, flHeight, up, point);
	VectorMA (point, flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 1, 1);
	VectorMA (vecOrigin, -flHeight, up, point);
	VectorMA (point, flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();
	
	meshBuilder.End();
	pMesh->Draw();
}


void Strider_DrawSprite( const Vector &vecOrigin, float size, const color32 &color )
{
	DrawSpriteTangentSpace( vecOrigin, size, size, color );
}


void Strider_DrawLine( const Vector &start, const Vector &end, float width, IMaterial *pMaterial, const color32 &color )
{
	FX_DrawLineFade( start, end, width, pMaterial, color, 8.0f );
}

int	C_StriderFX::DrawModel( int )
{
	static color32 white = {255,255,255,255};
	Vector params[STRIDERFX_PARAMETERS];
	bool hasParam[STRIDERFX_PARAMETERS];

	if ( !m_active )
		return 1;

	C_BaseEntity *ent = cl_entitylist->GetEnt( m_entityIndex );
	if ( ent )
	{
		QAngle angles;
		ent->GetAttachment( m_attachment, m_worldPosition, angles );
	}

	// This forces time to drive from the main clock instead of being integrated per-draw below
	// that way the effect moves on even when culled for visibility
	if ( m_limitHitTime > 0 && m_tMax > 0 )
	{
		float dt = m_limitHitTime - gpGlobals->curtime;
		if ( dt < 0 )
		{
			dt = 0;
		}
		// if the clock needs to move, update it.
		if ( m_tMax - dt > m_t )
		{
			m_t = m_tMax - dt;
			m_beamEndPosition = m_worldPosition;
		}
	}
	else
	{
		// don't have enough info to derive the time, integrate current frame time
		m_t += gpGlobals->frametime;
		if ( m_tMax > 0 )
		{
			m_t = clamp( m_t, 0, m_tMax );
			m_beamEndPosition = m_worldPosition;
		}
	}
	float t = m_t;

	bool hasAny = false;
	memset( hasParam, 0, sizeof(hasParam) );
	for ( int i = 0; i < STRIDERFX_PARAMETERS; i++ )
	{
		hasParam[i] = g_StriderCannonEnvelope.m_parameters[i].Interp( params[i], t );
		hasAny = hasAny || hasParam[i];
	}

	pixelvis_queryparams_t gunParams;
	gunParams.Init(m_worldPosition, 4.0f);
	float gunFractionVisible = PixelVisibility_FractionVisible( gunParams, &m_queryHandleGun );
	bool gunVisible = gunFractionVisible > 0.0f ? true : false;

	// draw the narrow beam
	if ( hasParam[STRIDERFX_NARROW_BEAM_COLOR] && hasParam[STRIDERFX_NARROW_BEAM_SIZE] )
	{
		IMaterial *pMat = materials->FindMaterial( "sprites/bluelaser1", TEXTURE_GROUP_CLIENT_EFFECTS );
		float width = NARROW_BEAM_WIDTH * params[STRIDERFX_NARROW_BEAM_SIZE].x;
		color32 color;
		float bright = params[STRIDERFX_NARROW_BEAM_COLOR].x;
		ScaleColor( color, white, bright );

		Strider_DrawLine( m_beamEndPosition, m_targetPosition, width, pMat, color );
	}

	// draw the wide beam
	if ( hasParam[STRIDERFX_WIDE_BEAM_COLOR] && hasParam[STRIDERFX_WIDE_BEAM_SIZE] )
	{
		IMaterial *pMat = materials->FindMaterial( "effects/blueblacklargebeam", TEXTURE_GROUP_CLIENT_EFFECTS );
		float width = WIDE_BEAM_WIDTH * params[STRIDERFX_WIDE_BEAM_SIZE].x;
		color32 color;
		float bright = params[STRIDERFX_WIDE_BEAM_COLOR].x;
		ScaleColor( color, white, bright );
		Vector wideBeamEnd = m_beamEndPosition;
		if ( hasParam[STRIDERFX_WIDE_BEAM_LENGTH] )
		{
			float amt = params[STRIDERFX_WIDE_BEAM_LENGTH].x;
			wideBeamEnd = m_beamEndPosition * amt + m_targetPosition * (1-amt);
		}

		Strider_DrawLine( wideBeamEnd, m_targetPosition, width, pMat, color );
	}

// after glow sprite
	bool updated = false;
// warpy sprite bit
	if ( hasParam[STRIDERFX_WARP_SCALE] && !hasParam[STRIDERFX_BUBBLE_SIZE] && gunVisible )
	{
		if ( !updated )
		{
			updated = true;
			materials->Flush();
			UpdateRefractTexture();
		}

		IMaterial *pMat = materials->FindMaterial( "effects/strider_pinch_dudv", TEXTURE_GROUP_CLIENT_EFFECTS );
		float size = WARP_SIZE;
		float refract = params[STRIDERFX_WARP_SCALE].x * WARP_REFRACT * gunFractionVisible;

		materials->Bind( pMat, (IClientRenderable*)this );
		IMaterialVar *pVar = pMat->FindVar( "$refractamount", NULL );
		pVar->SetFloatValue( refract );
		Strider_DrawSprite( m_worldPosition, size, white );
	}
// darkening sprite
// glowy blue flare sprite
	if ( hasParam[STRIDERFX_FLARE_COLOR] && hasParam[STRIDERFX_FLARE_SIZE] && hasParam[STRIDERFX_DARKNESS] && gunVisible )
	{
		IMaterial *pMat = materials->FindMaterial( "effects/blueblackflash", TEXTURE_GROUP_CLIENT_EFFECTS );
		float size = FLARE_SIZE * params[STRIDERFX_FLARE_SIZE].x;
		color32 color;
		float bright = params[STRIDERFX_FLARE_COLOR].x * gunFractionVisible;
		ScaleColor( color, white, bright );
		color.a = (int)(255 * params[STRIDERFX_DARKNESS].x);
		materials->Bind( pMat, (IClientRenderable*)this );
		Strider_DrawSprite( m_worldPosition, size, color );
	}
// bubble warpy sprite
	if ( hasParam[STRIDERFX_BUBBLE_SIZE] )
	{
		Vector wideBeamEnd = m_beamEndPosition;
		if ( hasParam[STRIDERFX_WIDE_BEAM_LENGTH] )
		{
			float amt = params[STRIDERFX_WIDE_BEAM_LENGTH].x;
			wideBeamEnd = m_beamEndPosition * amt + m_targetPosition * (1-amt);
		}
		pixelvis_queryparams_t endParams;
		endParams.Init(wideBeamEnd, 4.0f, 0.001f);
		float endFractionVisible = PixelVisibility_FractionVisible( endParams, &m_queryHandleBeamEnd );
		bool endVisible = endFractionVisible > 0.0f ? true : false;

		if ( endVisible )
		{
			if ( !updated )
			{
				updated = true;
				materials->Flush();
				UpdateRefractTexture();
			}
			IMaterial *pMat = materials->FindMaterial( "effects/strider_bulge_dudv", TEXTURE_GROUP_CLIENT_EFFECTS );
			float refract = endFractionVisible * WARP_BUBBLE_REFRACT * params[STRIDERFX_BUBBLE_REFRACT].x;
			float size = WARP_BUBBLE_SIZE * params[STRIDERFX_BUBBLE_SIZE].x;
			IMaterialVar *pVar = pMat->FindVar( "$refractamount", NULL );
			pVar->SetFloatValue( refract );

			materials->Bind( pMat, (IClientRenderable*)this );
			Strider_DrawSprite( wideBeamEnd, size, white );
		}
	}
	else
	{
		// call this to have the check ready on the first frame
		pixelvis_queryparams_t endParams;
		endParams.Init(m_beamEndPosition, 4.0f, 0.001f);
		PixelVisibility_FractionVisible( endParams, &m_queryHandleBeamEnd );
	}
	if ( hasParam[STRIDERFX_AFTERGLOW_COLOR] && gunVisible )
	{
		IMaterial *pMat = materials->FindMaterial( "effects/blueblackflash", TEXTURE_GROUP_CLIENT_EFFECTS );
		float size = AFTERGLOW_SIZE;// * params[STRIDERFX_FLARE_SIZE].x;
		color32 color;
		float bright = params[STRIDERFX_AFTERGLOW_COLOR].x * gunFractionVisible;
		ScaleColor( color, white, bright );

		materials->Bind( pMat, (IClientRenderable*)this );
		Strider_DrawSprite( m_worldPosition, size, color );

		dlight_t *dl = effects->CL_AllocDlight( m_entityIndex );
		dl->origin = m_worldPosition;
		dl->color.r = 40;
		dl->color.g = 60;
		dl->color.b = 255;
		dl->color.exponent = 5;
		dl->radius = bright * 128;
		dl->die = gpGlobals->curtime + 0.001;
	}

	if ( m_t >= STRIDERFX_END_ALL_TIME && !hasAny )
	{
		EffectShutdown();
	}
	return 1;
}




//-----------------------------------------------------------------------------
// Purpose: Strider class implementation
//-----------------------------------------------------------------------------
C_Strider::C_Strider()
{
	AddVar( &m_vecHitPos, &m_iv_vecHitPos, LATCH_ANIMATION_VAR );

	memset(m_vecIKTarget, 0, sizeof(m_vecIKTarget));
	AddVar( &m_vecIKTarget, &m_iv_vecIKTarget, LATCH_ANIMATION_VAR );

	m_flNextRopeCutTime = 0;
}

C_Strider::~C_Strider()
{
}

void C_Strider::ReceiveMessage( int classID, bf_read &msg )
{
	if ( classID != GetClientClass()->m_ClassID )
	{
		// message is for subclass
		BaseClass::ReceiveMessage( classID, msg );
		return;
	}

	int messageType = msg.ReadByte();
	switch( messageType )
	{
	case STRIDER_MSG_STREAKS:
		{
			Vector	pos;
			msg.ReadBitVec3Coord( pos );
			m_cannonFX.SetRenderOrigin( pos );
			m_cannonFX.EffectInit( entindex(), LookupAttachment( "BigGun" ) );
			m_cannonFX.LimitTime( STRIDERFX_BIG_SHOT_TIME );
		}
		break;

	case STRIDER_MSG_BIG_SHOT:
		{
			Vector tmp;
			msg.ReadBitVec3Coord( tmp );
			m_cannonFX.SetTime( STRIDERFX_BIG_SHOT_TIME );
			m_cannonFX.LimitTime( STRIDERFX_END_ALL_TIME );
		}
		break;

	case STRIDER_MSG_DEAD:
		{
			m_cannonFX.EffectShutdown();
		}
		break;
	}
}


void C_Strider::OnDataChanged( DataUpdateType_t updateType )
{
	if ( updateType == DATA_UPDATE_CREATED )
	{
		// We need to have our render bounds defined or shadow creation won't work correctly
		ClientThink();
		ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS );
	}

	BaseClass::OnDataChanged( updateType );

	m_cannonFX.Update( this, m_vecHitPos );
}

//-----------------------------------------------------------------------------
// Purpose: Recompute my rendering box
//-----------------------------------------------------------------------------
void C_Strider::ClientThink()
{
	int i;
	Vector vecMins, vecMaxs;
	Vector vecAbsMins, vecAbsMaxs;
	matrix3x4_t worldToStrider, hitboxToStrider;
	Vector vecBoxMins, vecBoxMaxs;
	Vector vecBoxAbsMins, vecBoxAbsMaxs;
	mstudiohitboxset_t *set;
	CBoneCache *pCache = NULL;

	// The reason why this is here, as opposed to in SetObjectCollisionBox,
	// is because of IK. The code below recomputes bones so as to get at the hitboxes,
	// which causes IK to trigger, which causes raycasts against the other entities to occur,
	// which is illegal to do while in the Relink phase.

	studiohdr_t *pStudioHdr = GetModel()?modelinfo->GetStudiomodel( GetModel() ):NULL;
	if (!pStudioHdr)
		goto doneWithComputation;

	set = pStudioHdr->pHitboxSet( m_nHitboxSet );
	if ( !set || !set->numhitboxes )
		goto doneWithComputation;

	pCache = GetBoneCache( );

	matrix3x4_t *hitboxbones[MAXSTUDIOBONES];
	pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones );

	// Compute a box in world space that surrounds this entity
	m_vecRenderMins.Init( FLT_MAX, FLT_MAX, FLT_MAX );
	m_vecRenderMaxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );

	MatrixInvert( EntityToWorldTransform(), worldToStrider );

	for ( i = 0; i < set->numhitboxes; i++ )
	{
		mstudiobbox_t *pbox = set->pHitbox(i);
		ConcatTransforms( worldToStrider, *hitboxbones[pbox->bone], hitboxToStrider );

		TransformAABB( hitboxToStrider, pbox->bbmin, pbox->bbmax, vecBoxMins, vecBoxMaxs );
		VectorMin( m_vecRenderMins, vecBoxMins, m_vecRenderMins );
		VectorMax( m_vecRenderMaxs, vecBoxMaxs, m_vecRenderMaxs );
	}

	// UNDONE: Disabled this until we can get closer to a final map and tune
#if 0
	// Cut ropes.
	if ( gpGlobals->curtime >= m_flNextRopeCutTime )
	{
		// Blow the bbox out a little.
		Vector vExtendedMins = vecMins - Vector( 50, 50, 50 );
		Vector vExtendedMaxs = vecMaxs + Vector( 50, 50, 50 );

		C_RopeKeyframe *ropes[512];
		int nRopes = C_RopeKeyframe::GetRopesIntersectingAABB( ropes, ARRAYSIZE( ropes ), GetAbsOrigin() + vExtendedMins, GetAbsOrigin() + vExtendedMaxs );
		for ( int i=0; i < nRopes; i++ )
		{
			C_RopeKeyframe *pRope = ropes[i];

			if ( pRope->GetEndEntity() )
			{
				Vector vPos;
				if ( pRope->GetEndPointPos( 1, vPos ) )
				{
					// Detach the endpoint.
					pRope->SetEndEntity( NULL );
					
					// Make some spark effect here..
					g_pEffects->Sparks( vPos );
				}				
			}
		}

		m_flNextRopeCutTime = gpGlobals->curtime + 0.5;
	}
#endif

doneWithComputation:	
	// True argument because the origin may have stayed the same, but the size is expected to always change
	g_pClientShadowMgr->AddToDirtyShadowList( this, true );
}


//-----------------------------------------------------------------------------
// Purpose: Recompute my rendering box
//-----------------------------------------------------------------------------
void C_Strider::GetRenderBounds( Vector& theMins, Vector& theMaxs )
{
	theMins = m_vecRenderMins;
	theMaxs = m_vecRenderMaxs;
}


//-----------------------------------------------------------------------------
// Strider muzzle flashes
//-----------------------------------------------------------------------------
void MuzzleFlash_Strider( int entityIndex, int attachmentIndex )
{
	VPROF_BUDGET( "MuzzleFlash_Strider", VPROF_BUDGETGROUP_PARTICLE_RENDERING );

	matrix3x4_t	matAttachment;
	// If the client hasn't seen this entity yet, bail.
	if ( !FX_GetAttachmentTransform( entityIndex, attachmentIndex, matAttachment ) )
		return;

	CSmartPtr<CLocalSpaceEmitter> pSimple = CLocalSpaceEmitter::Create( "MuzzleFlash_Strider", entityIndex, attachmentIndex );

	SimpleParticle *pParticle;
	Vector			forward(1,0,0), offset; //NOTENOTE: All coords are in local space

	float flScale = random->RandomFloat( 3.0f, 4.0f );

	float burstSpeed = random->RandomFloat( 400.0f, 600.0f );

#define	FRONT_LENGTH 12

	// Front flash
	for ( int i = 1; i < FRONT_LENGTH; i++ )
	{
		offset = (forward * (i*2.0f*flScale));

		pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset );
			
		if ( pParticle == NULL )
			return;

		pParticle->m_flLifetime		= 0.0f;
		pParticle->m_flDieTime		= 0.1f;

		pParticle->m_vecVelocity = forward * burstSpeed;

		pParticle->m_uchColor[0]	= 255;
		pParticle->m_uchColor[1]	= 255;
		pParticle->m_uchColor[2]	= 255;

		pParticle->m_uchStartAlpha	= 255.0f;
		pParticle->m_uchEndAlpha	= 0;

		pParticle->m_uchStartSize	= ( (random->RandomFloat( 6.0f, 8.0f ) * (FRONT_LENGTH-(i))/(FRONT_LENGTH*0.75f)) * flScale );
		pParticle->m_uchEndSize		= pParticle->m_uchStartSize;
		pParticle->m_flRoll			= random->RandomInt( 0, 360 );
		pParticle->m_flRollDelta	= 0.0f;
	}
	
	Vector right(0,1,0), up(0,0,1);
	Vector dir = right - up;

#define	SIDE_LENGTH	8

	burstSpeed = random->RandomFloat( 400.0f, 600.0f );

	// Diagonal flash
	for ( i = 1; i < SIDE_LENGTH; i++ )
	{
		offset = (dir * (i*flScale));

		pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset );
			
		if ( pParticle == NULL )
			return;

		pParticle->m_flLifetime		= 0.0f;
		pParticle->m_flDieTime		= 0.2f;

		pParticle->m_vecVelocity = dir * burstSpeed * 0.25f;

		pParticle->m_uchColor[0]	= 255;
		pParticle->m_uchColor[1]	= 255;
		pParticle->m_uchColor[2]	= 255;

		pParticle->m_uchStartAlpha	= 255;
		pParticle->m_uchEndAlpha	= 0;

		pParticle->m_uchStartSize	= ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale );
		pParticle->m_uchEndSize		= pParticle->m_uchStartSize;
		pParticle->m_flRoll			= random->RandomInt( 0, 360 );
		pParticle->m_flRollDelta	= 0.0f;
	}

	dir = right + up;
	burstSpeed = random->RandomFloat( 400.0f, 600.0f );

	// Diagonal flash
	for ( i = 1; i < SIDE_LENGTH; i++ )
	{
		offset = (-dir * (i*flScale));

		pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset );
			
		if ( pParticle == NULL )
			return;

		pParticle->m_flLifetime		= 0.0f;
		pParticle->m_flDieTime		= 0.2f;

		pParticle->m_vecVelocity = dir * -burstSpeed * 0.25f;

		pParticle->m_uchColor[0]	= 255;
		pParticle->m_uchColor[1]	= 255;
		pParticle->m_uchColor[2]	= 255;

		pParticle->m_uchStartAlpha	= 255;
		pParticle->m_uchEndAlpha	= 0;

		pParticle->m_uchStartSize	= ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale );
		pParticle->m_uchEndSize		= pParticle->m_uchStartSize;
		pParticle->m_flRoll			= random->RandomInt( 0, 360 );
		pParticle->m_flRollDelta	= 0.0f;
	}

	dir = up;
	burstSpeed = random->RandomFloat( 400.0f, 600.0f );

	// Top flash
	for ( i = 1; i < SIDE_LENGTH; i++ )
	{
		offset = (dir * (i*flScale));

		pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset );
			
		if ( pParticle == NULL )
			return;

		pParticle->m_flLifetime		= 0.0f;
		pParticle->m_flDieTime		= 0.2f;

		pParticle->m_vecVelocity = dir * burstSpeed * 0.25f;

		pParticle->m_uchColor[0]	= 255;
		pParticle->m_uchColor[1]	= 255;
		pParticle->m_uchColor[2]	= 255;

		pParticle->m_uchStartAlpha	= 255;
		pParticle->m_uchEndAlpha	= 0;

		pParticle->m_uchStartSize	= ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale );
		pParticle->m_uchEndSize		= pParticle->m_uchStartSize;
		pParticle->m_flRoll			= random->RandomInt( 0, 360 );
		pParticle->m_flRollDelta	= 0.0f;
	}

	pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/strider_muzzle" ), vec3_origin );
		
	if ( pParticle == NULL )
		return;

	pParticle->m_flLifetime		= 0.0f;
	pParticle->m_flDieTime		= random->RandomFloat( 0.3f, 0.4f );

	pParticle->m_vecVelocity.Init();

	pParticle->m_uchColor[0]	= 255;
	pParticle->m_uchColor[1]	= 255;
	pParticle->m_uchColor[2]	= 255;

	pParticle->m_uchStartAlpha	= 255;
	pParticle->m_uchEndAlpha	= 0;

	pParticle->m_uchStartSize	= flScale * random->RandomFloat( 12.0f, 16.0f );
	pParticle->m_uchEndSize		= 0.0f;
	pParticle->m_flRoll			= random->RandomInt( 0, 360 );
	pParticle->m_flRollDelta	= 0.0f;

	Vector		origin;
	MatrixGetColumn( matAttachment, 3, &origin );

	dlight_t *el = effects->CL_AllocElight( LIGHT_INDEX_MUZZLEFLASH + entityIndex );

	el->origin	= origin;

	el->color.r = 64;
	el->color.g = 128;
	el->color.b = 255;
	el->color.exponent = 5;

	el->radius	= random->RandomInt( 100, 150 );
	el->decay	= el->radius / 0.05f;
	el->die		= gpGlobals->curtime + 0.1f;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &data - 
//-----------------------------------------------------------------------------
void StriderMuzzleFlashCallback( const CEffectData &data )
{
	MuzzleFlash_Strider( data.m_nEntIndex, data.m_nAttachmentIndex );
}

DECLARE_CLIENT_EFFECT( "StriderMuzzleFlash", StriderMuzzleFlashCallback );

#define	BLOOD_MIN_SPEED	64.0f*4.0f
#define BLOOD_MAX_SPEED 256.0f*4.0f

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &origin - 
//			&normal - 
//			scale - 
//-----------------------------------------------------------------------------
void StriderBlood( const Vector &origin, const Vector &normal, float scale )
{
	VPROF_BUDGET( "StriderBlood", VPROF_BUDGETGROUP_PARTICLE_RENDERING );
	
	Vector	offset;
	
	CSmartPtr<CSplashParticle> pSimple = CSplashParticle::Create( "splish" );
	pSimple->SetSortOrigin( origin );

	Vector	color[3];
	color[0] = color[1] = color[2] = Vector( 1, 1, 1 );

	float	colorRamp;

	int i;
	float	flScale = scale / 8.0f;

	PMaterialHandle	hMaterial = g_ParticleMgr.GetPMaterial( "effects/slime1" );

	float	length = 0.1f;
	Vector	vForward, vRight, vUp;
	Vector	offDir;

	TrailParticle	*tParticle;

	CSmartPtr<CTrailParticles> sparkEmitter = CTrailParticles::Create( "splash" );

	if ( !sparkEmitter )
		return;

	sparkEmitter->SetSortOrigin( origin );
	sparkEmitter->m_ParticleCollision.SetGravity( 800.0f );
	sparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN );
	sparkEmitter->SetVelocityDampen( 2.0f );

	//Dump out drops
	for ( i = 0; i < 64; i++ )
	{
		offset = origin;
		offset[0] += random->RandomFloat( -16.0f, 16.0f ) * flScale;
		offset[1] += random->RandomFloat( -16.0f, 16.0f ) * flScale;

		tParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset );

		if ( tParticle == NULL )
			break;

		tParticle->m_flLifetime	= 0.0f;
		tParticle->m_flDieTime	= 1.0f;

		offDir = normal + RandomVector( -1.0f, 1.0f );

		tParticle->m_vecVelocity = offDir * random->RandomFloat( BLOOD_MIN_SPEED * flScale * 2.0f, BLOOD_MAX_SPEED * flScale * 2.0f );
		tParticle->m_vecVelocity[2] += random->RandomFloat( 8.0f, 32.0f ) * flScale;

		tParticle->m_flWidth		= random->RandomFloat( 12.0f, 16.0f ) * flScale;
		tParticle->m_flLength		= random->RandomFloat( length*0.5f, length ) * flScale;

		colorRamp = random->RandomFloat( 0.5f, 2.0f );

		int randomColor = random->RandomInt( 0, 2 );

		tParticle->m_color.r = min( 1.0f, color[randomColor].x * colorRamp ) * 255;
		tParticle->m_color.g = min( 1.0f, color[randomColor].y * colorRamp ) * 255;
		tParticle->m_color.b = min( 1.0f, color[randomColor].z * colorRamp ) * 255;
		tParticle->m_color.a = 255;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &data - 
//-----------------------------------------------------------------------------
void StriderBloodCallback( const CEffectData &data )
{
	StriderBlood( data.m_vOrigin, data.m_vNormal, data.m_flScale );
}

DECLARE_CLIENT_EFFECT( "StriderBlood", StriderBloodCallback );

