//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:			The Escort's Shield weapon
//
// $Revision: $
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "tf_shield.h"
#include "tf_shareddefs.h"
#include "collisionutils.h"
#include <float.h>
#include "sendproxy.h"
#include "mathlib/mathlib.h"

#define PROBE_EFFECT_TIME		0.15f

ConVar	shield_explosive_damage( "shield_explosive_damage","10", FCVAR_REPLICATED, "Shield power damage from explosions" );

// Percentage of total health that the shield's allowed to go below 0
#define SHIELD_MIN_HEALTH_FACTOR		(-0.5)

//-----------------------------------------------------------------------------
// Stores a list of all active shields
//-----------------------------------------------------------------------------
CUtlVector< CShield* >	CShield::s_Shields;


//-----------------------------------------------------------------------------
// Returns true if the entity is a shield
//-----------------------------------------------------------------------------
bool IsShield( CBaseEntity *pEnt )
{
	// This is a faster way...
	return pEnt && (pEnt->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD);
//	return pEnt && FClassnameIs( pEnt, "shield" )
}


//=============================================================================
// Shield effect
//=============================================================================
EXTERN_SEND_TABLE(DT_BaseEntity)

IMPLEMENT_SERVERCLASS_ST(CShield, DT_Shield)
	SendPropInt(SENDINFO(m_nOwningPlayerIndex), MAX_EDICT_BITS, SPROP_UNSIGNED ),
	SendPropFloat(SENDINFO(m_flPowerLevel), 9, SPROP_ROUNDUP, SHIELD_MIN_HEALTH_FACTOR, 1.0f ),
	SendPropInt(SENDINFO(m_bIsEMPed), 1, SPROP_UNSIGNED ),
END_SEND_TABLE()

//-----------------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------------
CShield::CShield()
{
	s_Shields.AddToTail(this);
	AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
	SetupRecharge( 0,0,0,0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CShield::~CShield()
{
	int i = s_Shields.Find(this);
	if (i >= 0)
		s_Shields.FastRemove(i);
}

//-----------------------------------------------------------------------------
// Precache !!
//-----------------------------------------------------------------------------
void CShield::Precache()
{
	SetClassname( "shield" );
}

//-----------------------------------------------------------------------------
// Spawn !!
//-----------------------------------------------------------------------------
void CShield::Spawn( void )
{
	m_bIsEMPed = 0;
	SetCollisionGroup( TFCOLLISION_GROUP_SHIELD );

	// Make it translucent
	m_nRenderFX = kRenderTransAlpha;
	SetRenderColorA( 255 );

	m_flNextRechargeTime = gpGlobals->curtime;

	CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE );
	SetSolid( SOLID_CUSTOM );

	// Stuff can't come to a rest on shields!
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	UTIL_SetSize( this, vec3_origin, vec3_origin );
}


//-----------------------------------------------------------------------------
// Owner
//-----------------------------------------------------------------------------
void CShield::SetOwnerEntity( CBaseEntity *pOwner )
{
	BaseClass::SetOwnerEntity( pOwner );

	if (pOwner->IsPlayer())
	{
		m_nOwningPlayerIndex = pOwner->entindex();
	}
	else
	{
		m_nOwningPlayerIndex = 0;
	}
	ChangeTeam( pOwner->GetTeamNumber() );
}



//-----------------------------------------------------------------------------
// Does this shield protect from a particular damage type?
//-----------------------------------------------------------------------------
float CShield::ProtectionAmount( int damageType ) const
{
	// As a test, we're trying to make shields impervious to everything
	return 1.0f;
}
		   

//-----------------------------------------------------------------------------
// Activates/deactivates a shield for collision purposes
//-----------------------------------------------------------------------------
void CShield::ActivateCollisions( bool activate )
{
	if ( activate )
	{
		RemoveSolidFlags( FSOLID_NOT_SOLID );
	}
	else
	{
		AddSolidFlags( FSOLID_NOT_SOLID );
	}
}

//-----------------------------------------------------------------------------
// Activates all shields 
//-----------------------------------------------------------------------------
void CShield::ActivateShields( bool activate, int team )
{
	for (int i = s_Shields.Count(); --i >= 0; )
	{
		// Activate all shields on the same team
		if ( (team == -1) || (team == s_Shields[i]->GetTeamNumber()) )
		{
			s_Shields[i]->ActivateCollisions( activate );
		}
	}
}


//-----------------------------------------------------------------------------
// Checks a ray against shields only
//-----------------------------------------------------------------------------
bool CShield::IsBlockedByShields( const Vector& src, const Vector& end )
{
	trace_t tr;
	Ray_t ray;
	ray.Init( src, end );

	for (int i = s_Shields.Count(); --i >= 0; )
	{
		if (!s_Shields[i]->ShouldCollide( TFCOLLISION_GROUP_WEAPON, MASK_ALL ))
			continue;

		// Coarse bbox test first
		Vector vecAbsMins, vecAbsMaxs;
		s_Shields[i]->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); 
		if (!IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, ray.m_Start, ray.m_Delta ))
			continue;

		if (s_Shields[i]->TestCollision( ray, MASK_ALL, tr ))
			return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Called when we hit something that we deflect...
//-----------------------------------------------------------------------------
void CShield::RegisterDeflection(const Vector& vecDir, int bitsDamageType, trace_t *ptr)
{
	// Probes don't hurt shields
	if (bitsDamageType & DMG_PROBE)
		return;

	Vector normalDir;
	VectorCopy( vecDir, normalDir );
	VectorNormalize( normalDir );

	// Uncomment this line when the client predicts the shield deflections
	//filter.UsePredictionRules();
	EntityMessageBegin( this );
		WRITE_LONG( ptr->hitgroup );
		WRITE_VEC3NORMAL( normalDir );
		WRITE_BYTE( false );	// This was not a partial block
	MessageEnd();

	// If this is a buckshot round, don't pay the power cost to stop it once we've gone over our max buckshot hits this frame
	if ( bitsDamageType & DMG_BUCKSHOT )
	{
		m_iBuckshotHitsThisFrame++;
		if ( m_iBuckshotHitsThisFrame > 4 )
			return;
	}

	// If this is an explosion, it counts for extra
	if ( bitsDamageType & DMG_BLAST )
	{
		SetPower( m_flPower - shield_explosive_damage.GetFloat() );
	}
	else
	{
		// Reduce our power level by a hit
		SetPower( m_flPower - 1 );
	}

	// If we've lost power fully, don't recharge for a bit
	if ( m_flPower <= 0 )
	{
		m_flNextRechargeTime = gpGlobals->curtime + 1.0;
	}
}

//-----------------------------------------------------------------------------
// Called when we hit something that we let through...
//-----------------------------------------------------------------------------
void CShield::RegisterPassThru(const Vector& vecDir, int bitsDamageType, trace_t *ptr)
{
	// Probes don't hurt shields
	if (bitsDamageType & DMG_PROBE)
		return;

	Vector normalDir;
	VectorCopy( vecDir, normalDir );
	VectorNormalize( normalDir );

	EntityMessageBegin( this );
		WRITE_LONG( ptr->hitgroup );
		WRITE_VEC3NORMAL( normalDir );
		WRITE_BYTE( true );	// This was a partial block
	MessageEnd();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CShield::SetPower( float flPower )
{
	m_flPower = MAX( (SHIELD_MIN_HEALTH_FACTOR * m_flMaxPower), flPower );
	if ( m_flPower > m_flMaxPower )
	{
		m_flPower = m_flMaxPower;
	}
	m_flPowerLevel = ( m_flPower / m_flMaxPower );
}

//-----------------------------------------------------------------------------
// Purpose: Setup shield recharging parameters
//-----------------------------------------------------------------------------
void CShield::SetupRecharge( float flPower, float flDelay, float flAmount, float flTickTime )
{ 
	m_flMaxPower = flPower; 
	SetPower( flPower );
	m_flPowerLevel = 1.0;
	m_flRechargeDelay = flDelay;
	m_flRechargeAmount = flAmount;
	m_flRechargeTime = flTickTime;
}

//-----------------------------------------------------------------------------
// Purpose: Recharge the shield if we haven't taken damage for a while
//-----------------------------------------------------------------------------
void CShield::Think( void )
{
	// Clear out buckshot count
	m_iBuckshotHitsThisFrame = 0;

	if ( m_flNextRechargeTime < gpGlobals->curtime )
	{
		if ( m_flPower < m_flMaxPower )
		{
			SetPower( m_flPower + m_flRechargeAmount );
		}

		m_flNextRechargeTime = gpGlobals->curtime + m_flRechargeTime;
	}

	// Let derived objects think if they want to
	if ( m_pfnThink )
	{
		(this->*m_pfnThink)();
	}
}

//-----------------------------------------------------------------------------
// Indicates the shield has been EMPed (or not)
//-----------------------------------------------------------------------------
void CShield::SetEMPed( bool isEmped )
{
	m_bIsEMPed = isEmped;
}

//-----------------------------------------------------------------------------
// Helper method for collision testing
//-----------------------------------------------------------------------------
#pragma warning ( disable : 4701 )

bool CShield::TestCollision( const Ray_t& ray, unsigned int mask, trace_t& trace )
{
	// Can't block anything if we're EMPed, or we've got no power left to block
	if ( IsEMPed() )
		return false;
	if ( m_flPower <= 0 )
		return false;

	// Here, we're gonna test for collision.
	// If we don't stop this kind of bullet, we'll generate an effect here
	// but we won't change the trace to indicate a collision.

	// It's just polygon soup...
	int hitgroup;
	bool firstTri;
	int v1[2], v2[2], v3[2];
	float ihit, jhit;
	float mint = FLT_MAX;
	float t;

	int h = Height();
	int w = Width();

	for (int i = 0; i < h - 1; ++i)
	{
		for (int j = 0; j < w - 1; ++j)
		{
			// Don't test if this panel ain't active...
			if (!IsPanelActive( j, i ))
				continue;

			// NOTE: Structure order of points so that our barycentric
			// axes for each triangle are along the (u,v) directions of the mesh
			// The barycentric coords we'll need below 

			// Two triangles per quad...
			t = IntersectRayWithTriangle( ray,
				GetPoint( j, i + 1 ),
				GetPoint( j + 1, i + 1 ),
				GetPoint( j, i ), true );
			if ((t >= 0.0f) && (t < mint))
			{
				mint = t;
				v1[0] = j;		v1[1] = i + 1;
				v2[0] = j + 1;	v2[1] = i + 1;
				v3[0] = j;		v3[1] = i;
				ihit = i; jhit = j;
				firstTri = true;
			}
			
			t = IntersectRayWithTriangle( ray,
				GetPoint( j + 1, i ),
				GetPoint( j, i ),
				GetPoint( j + 1, i + 1 ), true );
			if ((t >= 0.0f) && (t < mint))
			{
				mint = t;
				v1[0] = j + 1;	v1[1] = i;
				v2[0] = j;		v2[1] = i;
				v3[0] = j + 1;	v3[1] = i + 1;
				ihit = i; jhit = j;
				firstTri = false;
			}
		}
	}

	if (mint == FLT_MAX)
		return false;

	// Stuff the barycentric coordinates of the triangle hit into the hit group 
	// For the first triangle, the first edge goes along u, the second edge goes
	// along -v. For the second triangle, the first edge goes along -u,
	// the second edge goes along v.
	const Vector& v1vec = GetPoint(v1[0], v1[1]);
	const Vector& v2vec = GetPoint(v2[0], v2[1]);
	const Vector& v3vec = GetPoint(v3[0], v3[1]);
	float u, v;
	bool ok = ComputeIntersectionBarycentricCoordinates( ray, 
		v1vec, v2vec, v3vec, u, v );
	Assert( ok );
	if ( !ok )
	{
		return false;
	}

	if (firstTri)
		v = 1.0 - v;
	else
		u = 1.0 - u;
	v += ihit; u += jhit;
	v /= (h - 1);
	u /= (w - 1);

	// Compress (u,v) into 1 dot 15, v in top bits
	hitgroup = (((int)(v * (1 << 15))) << 16) + (int)(u * (1 << 15));

	Vector normal;
	float intercept;
	ComputeTrianglePlane( v1vec, v2vec, v3vec, normal, intercept ); 

	UTIL_SetTrace( trace, ray, edict(), mint, hitgroup, CONTENTS_SOLID, normal, intercept );
	VectorAdd( trace.startpos, ray.m_StartOffset, trace.startpos );
	VectorAdd( trace.endpos, ray.m_StartOffset, trace.endpos );

	return true;
}

#pragma warning ( default : 4701 )