//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "particles_simple.h"
#include "tempent.h"
#include "iefx.h"
#include "decals.h"
#include "IViewRender.h"
#include "engine/ivmodelinfo.h"
#include "view.h"

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

#define	NUM_CHILD_FLAMES	6
#define	CHILD_SPREAD		40

//==================================================
// C_Plasma
//==================================================

//NOTENOTE: Mirrored in dlls/fire_smoke.h
#define	bitsFIRESMOKE_NONE				0x00000000
#define	bitsFIRESMOKE_ACTIVE			0x00000001


class C_PlasmaSprite : public C_Sprite
{
	DECLARE_CLASS( C_PlasmaSprite, C_Sprite );

public:
	Vector	m_vecMoveDir;
};


class C_Plasma : public C_BaseEntity
{
public:
	DECLARE_CLIENTCLASS();
	DECLARE_CLASS( C_Plasma, C_BaseEntity );

	C_Plasma();
	~C_Plasma();

	void	AddEntity( void );

protected:
	void	Update( void );
	void	UpdateAnimation( void );
	void	UpdateScale( void );
	void	UpdateFlames( void );
	void	AddFlames( void );
	void	Start( void );
	
	float	GetFlickerScale( void );

//C_BaseEntity
public:

	virtual void	OnDataChanged( DataUpdateType_t updateType );
	virtual bool	ShouldDraw();

//From the server
public:
	float	m_flStartScale;
	float	m_flScale;
	float	m_flScaleTime;
	int		m_nFlags;
	int		m_nPlasmaModelIndex;
	int		m_nPlasmaModelIndex2;
	int		m_nGlowModelIndex;

//Client-side only
public:
	float	m_flScaleRegister;
	float	m_flScaleStart;
	float	m_flScaleEnd;
	float	m_flScaleTimeStart;
	float	m_flScaleTimeEnd;

	VPlane	m_planeClip;
	bool	m_bClipTested;

protected:
	C_PlasmaSprite		m_entFlames[NUM_CHILD_FLAMES];
	float				m_entFlameScales[NUM_CHILD_FLAMES];

	C_Sprite			m_entGlow;
	float				m_flGlowScale;

	TimedEvent			m_tParticleSpawn;
	TimedEvent			m_tDecalSpawn;

private:
	C_Plasma( const C_Plasma & );
};

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pRecvProp - 
//			*pStruct - 
//			*pVarData - 
//			*pIn - 
//			objectID - 
//-----------------------------------------------------------------------------
void RecvProxy_PlasmaScale( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	C_Plasma	*pPlasmaSmoke	= (C_Plasma	*) pStruct;
	float scale				= pData->m_Value.m_Float;

	//If changed, update our internal information
	if ( pPlasmaSmoke->m_flScale != scale )
	{
		pPlasmaSmoke->m_flScaleStart	= pPlasmaSmoke->m_flScaleRegister;
		pPlasmaSmoke->m_flScaleEnd		= scale;			

		pPlasmaSmoke->m_flScale = scale;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pRecvProp - 
//			*pStruct - 
//			*pVarData - 
//			*pIn - 
//			objectID - 
//-----------------------------------------------------------------------------
void RecvProxy_PlasmaScaleTime( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	C_Plasma	*pPlasmaSmoke	= (C_Plasma	*) pStruct;
	float time				= pData->m_Value.m_Float;

	//If changed, update our internal information
	if ( pPlasmaSmoke->m_flScaleTime != time )
	{
		if ( time == -1.0f )
		{
			pPlasmaSmoke->m_flScaleTimeStart	= gpGlobals->curtime-1.0f;
			pPlasmaSmoke->m_flScaleTimeEnd	= pPlasmaSmoke->m_flScaleTimeStart;
		}
		else
		{
			pPlasmaSmoke->m_flScaleTimeStart	= gpGlobals->curtime;
			pPlasmaSmoke->m_flScaleTimeEnd	= gpGlobals->curtime + time;
		}

		pPlasmaSmoke->m_flScaleTime = time;
	}

	
}

//Receive datatable
IMPLEMENT_CLIENTCLASS_DT( C_Plasma, DT_Plasma, CPlasma )
	RecvPropFloat( RECVINFO( m_flStartScale )),
	RecvPropFloat( RECVINFO( m_flScale ), 0, RecvProxy_PlasmaScale ),
	RecvPropFloat( RECVINFO( m_flScaleTime ), 0, RecvProxy_PlasmaScaleTime ),
	RecvPropInt( RECVINFO( m_nFlags ) ),
	RecvPropInt( RECVINFO( m_nPlasmaModelIndex ) ),
	RecvPropInt( RECVINFO( m_nPlasmaModelIndex2 ) ),
	RecvPropInt( RECVINFO( m_nGlowModelIndex ) ),
END_RECV_TABLE()

//==================================================
// C_Plasma
//==================================================

C_Plasma::C_Plasma()
{
	//Server-side
	m_flStartScale		= 0.0f;
	m_flScale			= 0.0f;
	m_flScaleTime		= 0.0f;
	m_nFlags			= bitsFIRESMOKE_NONE;
	m_nPlasmaModelIndex	= 0;
	m_nPlasmaModelIndex2	= 0;
	m_nGlowModelIndex	= 0;

	//Client-side
	m_flScaleRegister	= 0.0f;
	m_flScaleStart		= 0.0f;
	m_flScaleEnd		= 0.0f;
	m_flScaleTimeStart	= 0.0f;
	m_flScaleTimeEnd	= 0.0f;
	m_flGlowScale		= 0.0f;
	m_bClipTested		= false;
	
	m_entGlow.Clear();
	
	//Clear all child flames
	for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
	{
		m_entFlames[i].Clear();
	}
}

C_Plasma::~C_Plasma()
{
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float C_Plasma::GetFlickerScale( void )
{
	float	result = 0.0f;

	result = sin( gpGlobals->curtime * 10000.0f );
	result += 0.5f * sin( gpGlobals->curtime * 2000.0f );
	result -= 0.5f * cos( gpGlobals->curtime * 8000.0f );
	
	return result * 0.1f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_Plasma::AddEntity( void )
{
	//Only do this if we're active
	if ( ( m_nFlags & bitsFIRESMOKE_ACTIVE ) == false )
		return;

	Update();
	AddFlames();

	float	dScale = m_flScaleRegister - m_flGlowScale;
	m_flGlowScale = m_flScaleRegister;

	// Note: Sprite renderer assumes scale of 0.0 is 1.0
	m_entGlow.SetScale( max( 0.0000001f, (m_flScaleRegister*1.5f) + GetFlickerScale() ) );
	m_entGlow.SetLocalOriginDim( Z_INDEX, m_entGlow.GetLocalOriginDim( Z_INDEX ) + ( dScale * 32.0f ) );
}

#define	FLAME_ALPHA_START	0.8f
#define FLAME_ALPHA_END		1.0f

#define	FLAME_TRANS_START	0.75f

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_Plasma::AddFlames( void )
{
	Vector	viewDir = GetAbsOrigin() - CurrentViewOrigin();
	VectorNormalize(viewDir);
	float	dot		= viewDir.Dot( Vector( 0, 0, 1 ) );	//NOTENOTE: Flames always point up
	float	alpha	= 1.0f;

	dot = fabs( dot );

	if ( dot < FLAME_ALPHA_START )
	{
		alpha = 1.0f;
	}

	for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
	{
		if ( m_entFlames[i].GetScale() > 0.0f )
		{
			m_entFlames[i].SetRenderColor( ( 255.0f * alpha ), ( 255.0f * alpha ), ( 255.0f * alpha ) );
			m_entFlames[i].SetBrightness( 255.0f * alpha );
		}

		m_entFlames[i].AddToLeafSystem();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bnewentity - 
//-----------------------------------------------------------------------------
void C_Plasma::OnDataChanged( DataUpdateType_t updateType )
{
	C_BaseEntity::OnDataChanged( updateType );

	if ( updateType == DATA_UPDATE_CREATED )
	{
		Start();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_Plasma::ShouldDraw()
{
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_Plasma::Start( void )
{
	//Various setup info
	m_tParticleSpawn.Init( 10.0f );
	m_tDecalSpawn.Init( 20.0f);

	QAngle	offset;
	int		maxFrames;

	// Setup the child flames
	int i;
	for ( i = 0; i < NUM_CHILD_FLAMES; i++ )
	{
		//Setup our offset angles
		offset[0] = 0.0f;
		offset[1] = random->RandomFloat( 0, 360 );
		offset[2] = 0.0f;
	
  		AngleVectors( offset, &m_entFlames[i].m_vecMoveDir );
		
		int	nModelIndex = ( i % 2 ) ? m_nPlasmaModelIndex : m_nPlasmaModelIndex2;

		model_t *pModel	= (model_t *) modelinfo->GetModel( nModelIndex );
		maxFrames	= modelinfo->GetModelFrameCount( pModel );

		// Setup all the information for the client entity
		m_entFlames[i].SetModelByIndex( nModelIndex );
		m_entFlames[i].SetLocalOrigin( GetLocalOrigin() );
		m_entFlames[i].m_flFrame			= random->RandomInt( 0.0f, maxFrames );
		m_entFlames[i].m_flSpriteFramerate	= (float) random->RandomInt( 15, 20 );
		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].index				= -1;
		
		if ( i == 0 )
		{
			m_entFlameScales[i] = 1.0f;
		}
		else
		{
			//Keep a scale offset
			m_entFlameScales[i] = 1.0f - ( ( (float) i / (float) NUM_CHILD_FLAMES ) );
		}
	}

	// Setup the glow
	m_entGlow.SetModelByIndex( m_nGlowModelIndex );
	m_entGlow.SetLocalOrigin( GetLocalOrigin() );
	m_entGlow.SetScale( m_flStartScale );
	m_entGlow.SetRenderMode( kRenderTransAdd );
	m_entGlow.m_nRenderFX		= kRenderFxNone;
	m_entGlow.SetRenderColor( 255, 255, 255, 255 );
	m_entGlow.SetBrightness( 255 );
	m_entGlow.index				= -1;
	
	m_flGlowScale				= m_flStartScale;

	m_entGlow.AddToLeafSystem( RENDER_GROUP_TRANSLUCENT_ENTITY );

	for( i=0; i < NUM_CHILD_FLAMES; i++ )
		m_entFlames[i].AddToLeafSystem( RENDER_GROUP_TRANSLUCENT_ENTITY );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_Plasma::UpdateAnimation( void )
{
	int		numFrames;
	float	frametime	= gpGlobals->frametime;

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


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_Plasma::UpdateFlames( void )
{
	for ( int i = 0; i < NUM_CHILD_FLAMES; i++ )
	{
		float	newScale = m_flScaleRegister * m_entFlameScales[i];
		float	dScale = newScale - m_entFlames[i].GetScale();

		Vector dir;

		dir[2] = 0.0f;
		VectorNormalize( dir );
		dir[2] = 0.0f;

		Vector	offset = GetAbsOrigin();
		offset[2] = m_entFlames[i].GetAbsOrigin()[2];

		// Note: Sprite render assumes 0 scale means 1.0
		m_entFlames[i].SetScale ( max(0.000001,newScale) );
		
		if ( i != 0 )
		{
			m_entFlames[i].SetLocalOrigin( offset + ( m_entFlames[i].m_vecMoveDir * ((m_entFlames[i].GetScale())*CHILD_SPREAD) ) );
		}

		Assert( !m_entFlames[i].GetMoveParent() );
		m_entFlames[i].SetLocalOriginDim( Z_INDEX, m_entFlames[i].GetLocalOriginDim( Z_INDEX ) + ( dScale * 64.0f ) );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_Plasma::UpdateScale( void )
{
	float	time = gpGlobals->curtime;

	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 = m_flScaleStart + ( ( m_flScaleEnd - m_flScaleStart ) * timeFraction );

			m_flScaleRegister = m_flStartScale = newScale;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : fTimeDelta - 
//-----------------------------------------------------------------------------
void C_Plasma::Update( void )
{
	//Update all our parts
	UpdateScale();
	UpdateAnimation();
	UpdateFlames();

	if (m_flScaleRegister > 0.1)
	{
		float tempDelta = gpGlobals->frametime;
		while( m_tDecalSpawn.NextEvent( tempDelta ) )
		{
			// Add decal to floor
			C_BaseEntity *ent = cl_entitylist->GetEnt( 0 );
			if ( ent )
			{
				int index = decalsystem->GetDecalIndexForName( "PlasmaGlowFade" );
				if ( index >= 0 )
				{
					effects->DecalShoot( index, 0, ent->GetModel(), ent->GetAbsOrigin(), ent->GetAbsAngles(), GetAbsOrigin(), 0, 0 );
				}
			}
		}
	}
}

