//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Client's sheild entity
//
// $Workfile:     $
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "C_Shield.h"
#include "tf_shieldshared.h"

enum
{
	NUM_SUBDIVISIONS = 21,
};

#define EMP_WAVE_AMPLITUDE 8.0f

//-----------------------------------------------------------------------------
// Mobile version of the shield 
//-----------------------------------------------------------------------------

class C_ShieldMobile;
class C_ShieldMobileActiveVertList : public IActiveVertList
{
public:
	void			Init( C_ShieldMobile *pShield, unsigned char *pVertList );

// IActiveVertList overrides.
public:

	virtual int		GetActiveVertState( int iVert );
	virtual void	SetActiveVertState( int iVert, int bOn );

private:
	C_ShieldMobile	*m_pShield;
	unsigned char	*m_pVertsActive;
};


class C_ShieldMobile : public C_Shield
{
	DECLARE_CLASS( C_ShieldMobile, C_Shield );
public:
	DECLARE_CLIENTCLASS();

	C_ShieldMobile();
	~C_ShieldMobile();

	void OnDataChanged( DataUpdateType_t updateType );
	virtual void GetBounds( Vector& mins, Vector& maxs );

	virtual void AddEntity( );

	// Return true if the panel is active 
	virtual bool IsPanelActive( int x, int y );

	// Gets at the control point data; who knows how it was made?
	virtual void GetShieldData( Vector const** ppVerts, float* pOpacity, float* pBlend );
	virtual const Vector& GetPoint( int x, int y ) { return m_ShieldEffect.GetPoint( x, y ); }

	virtual void SetThetaPhi( float flTheta, float flPhi ) { m_ShieldEffect.SetThetaPhi(flTheta,flPhi); }

public:
	// networked data
	unsigned char	m_pVertsActive[SHIELD_VERTEX_BYTES];
	unsigned char	m_ShieldState;

private:
	C_ShieldMobile( const C_ShieldMobile& );

	// Is a particular panel an edge?
	bool IsVertexValid( float s, float t ) const;
	void PreRender( );

private:
	CShieldEffect					m_ShieldEffect;
	C_ShieldMobileActiveVertList	m_VertList;
	float m_flTheta;
	float m_flPhi;
};


//-----------------------------------------------------------------------------
// C_ShieldMobileActiveVertList functions
//-----------------------------------------------------------------------------

void C_ShieldMobileActiveVertList::Init( C_ShieldMobile *pShield, unsigned char *pVertList )
{
	m_pShield = pShield;
	m_pVertsActive = pVertList;
}


int C_ShieldMobileActiveVertList::GetActiveVertState( int iVert )
{
	return m_pVertsActive[iVert>>3] & (1 << (iVert & 7));
}


void C_ShieldMobileActiveVertList::SetActiveVertState( int iVert, int bOn )
{
	if ( bOn )
		m_pVertsActive[iVert>>3] |= (1 << (iVert & 7));
	else
		m_pVertsActive[iVert>>3] &= ~(1 << (iVert & 7));
}


//-----------------------------------------------------------------------------
// Data table
//-----------------------------------------------------------------------------

IMPLEMENT_CLIENTCLASS_DT(C_ShieldMobile, DT_Shield_Mobile, CShieldMobile)

	RecvPropInt( RECVINFO(m_ShieldState) ),
	RecvPropArray( 
		RecvPropInt( RECVINFO(m_pVertsActive[0])), 
		m_pVertsActive 
	),
	RecvPropFloat( RECVINFO(m_flTheta) ),
	RecvPropFloat( RECVINFO(m_flPhi) ),

END_RECV_TABLE()


//-----------------------------------------------------------------------------
// Various raycasting routines
//-----------------------------------------------------------------------------


void ShieldTraceLine(const Vector &vecStart, const Vector &vecEnd, 
					 unsigned int mask, int collisionGroup, trace_t *ptr)
{
	UTIL_TraceLine(vecStart, vecEnd, mask, NULL, collisionGroup, ptr );
}

void ShieldTraceHull(const Vector &vecStart, const Vector &vecEnd, 
					 const Vector &hullMin, const Vector &hullMax, 
					 unsigned int mask, int collisionGroup, trace_t *ptr)
{
	CTraceFilterWorldOnly traceFilter;
	enginetrace->TraceHull( vecStart, vecEnd, hullMin, hullMax, mask, &traceFilter, ptr );
}


//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------

C_ShieldMobile::C_ShieldMobile() : m_ShieldEffect(ShieldTraceLine, ShieldTraceHull)
{
	m_VertList.Init( this, m_pVertsActive );
	m_ShieldEffect.SetActiveVertexList( &m_VertList );
	m_ShieldEffect.Spawn(vec3_origin, vec3_angle);
	InitShield( SHIELD_NUM_HORIZONTAL_POINTS, SHIELD_NUM_VERTICAL_POINTS, NUM_SUBDIVISIONS );
}

C_ShieldMobile::~C_ShieldMobile()
{
}

//-----------------------------------------------------------------------------
// Get this after the data changes
//-----------------------------------------------------------------------------

void C_ShieldMobile::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );

	m_ShieldEffect.SetCurrentPosition( GetAbsOrigin() );
	m_ShieldEffect.SetCurrentAngles( GetAbsAngles() );
	m_ShieldEffect.SetThetaPhi( m_flTheta, m_flPhi );

	// No need to simulate, just compute active panels from network data
	m_ShieldEffect.ComputeControlPoints();
	m_ShieldEffect.ComputePanelActivity();
}

//-----------------------------------------------------------------------------
// A little pre-render processing
//-----------------------------------------------------------------------------

void C_ShieldMobile::PreRender( )
{
	if (m_ShieldState & SHIELD_MOBILE_EMP)
	{
		// Decay fade if we've been EMPed or if we're inactive
		if (m_FadeValue > 0.0f)
		{
			m_FadeValue -= gpGlobals->frametime / SHIELD_EMP_FADE_TIME;
			if (m_FadeValue < 0.0f)
			{
				m_FadeValue = 0.0f;

				// Reset the shield to un-wobbled state
				m_ShieldEffect.ComputeControlPoints();
			}
			else
			{
				Vector dir;
				AngleVectors( m_ShieldEffect.GetCurrentAngles(), & dir );

				// Futz with the control points if we've been EMPed
				for (int i = 0; i < SHIELD_NUM_CONTROL_POINTS; ++i)
				{
					// Get the direction for the point
					float factor = -EMP_WAVE_AMPLITUDE * sin( i * M_PI * 0.5f + gpGlobals->curtime * M_PI / SHIELD_EMP_WOBBLE_TIME );
					m_ShieldEffect.GetPoint(i) += dir * factor;
				}
			}
		}
	}
	else
	{
		// Fade back in, no longer EMPed
		if (m_FadeValue < 1.0f)
		{
			m_FadeValue += gpGlobals->frametime / SHIELD_EMP_FADE_TIME;
			if (m_FadeValue >= 1.0f)
			{
				m_FadeValue = 1.0f;
			}
		}
	}
}

void C_ShieldMobile::AddEntity( )
{
	BaseClass::AddEntity( );
	PreRender();
}

//-----------------------------------------------------------------------------
// Bounds computation
//-----------------------------------------------------------------------------

void C_ShieldMobile::GetBounds( Vector& mins, Vector& maxs )
{
	m_ShieldEffect.ComputeBounds( mins, maxs );
}

//-----------------------------------------------------------------------------
// Return true if the panel is active 
//-----------------------------------------------------------------------------

bool C_ShieldMobile::IsPanelActive( int x, int y )
{
	return m_ShieldEffect.IsPanelActive(x, y);
}

//-----------------------------------------------------------------------------
// Gets at the control point data; who knows how it was made?
//-----------------------------------------------------------------------------

void C_ShieldMobile::GetShieldData( Vector const** ppVerts, float* pOpacity, float* pBlend )
{
	for ( int i = 0; i < SHIELD_NUM_CONTROL_POINTS; ++i )
	{
		ppVerts[i] = &m_ShieldEffect.GetControlPoint(i);

		if ( m_pVertsActive[i >> 3] & (1 << (i & 0x7)) )
		{
			pOpacity[i] = m_ShieldEffect.ComputeOpacity( *ppVerts[i], GetAbsOrigin() );
			pBlend[i] = 1.0f;
		}
		else
		{
			pOpacity[i] = 192.0f;
			pBlend[i] = 0.0f;
		}
	}
}