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

#include "cbase.h"
#include "c_weapon__stubs.h"
#include "c_basehlcombatweapon.h"
#include "fx.h"
#include "particles_localspace.h"
#include "view.h"
#include "particles_attractor.h"

class C_WeaponPhysCannon: public C_BaseHLCombatWeapon
{
	DECLARE_CLASS( C_WeaponPhysCannon, C_BaseHLCombatWeapon );
public:
	C_WeaponPhysCannon( void );

	DECLARE_CLIENTCLASS();
	DECLARE_PREDICTABLE();

	virtual int DrawModel( int flags );

	// adnan
	// for overriding the mouse input
	void OverrideMouseInput( float *x, float *y );
	// for overriding the mouse -> view angles (but still calc view angles)
	bool OverrideViewAngles( void );
	// end adnan

private:

	bool	SetupEmitter( void );

	bool	m_bIsCurrentlyUpgrading;
	bool	m_bWasUpgraded;

	// adnan
	// check if theyre holding something... we dont need to know what it is though
	bool m_bIsCurrentlyHolding;
	// also check if we're rotating what we're holding
	bool m_bIsCurrentlyRotating;
	// end adnan

	CSmartPtr<CLocalSpaceEmitter>	m_pLocalEmitter;
	CSmartPtr<CSimpleEmitter>		m_pEmitter;
	CSmartPtr<CParticleAttractor>	m_pAttractor;
};

STUB_WEAPON_CLASS_IMPLEMENT( weapon_physcannon, C_WeaponPhysCannon );

IMPLEMENT_CLIENTCLASS_DT( C_WeaponPhysCannon, DT_WeaponPhysCannon, CWeaponPhysCannon )
	RecvPropBool( RECVINFO( m_bIsCurrentlyUpgrading ) ),
	// adnan
	// need to receive if theyre holding something
	RecvPropBool( RECVINFO( m_bIsCurrentlyHolding ) ),
	// also receive if we're rotating what we're holding (by pressing use)
	RecvPropBool( RECVINFO( m_bIsCurrentlyRotating ) ),
	// end adnan
END_RECV_TABLE()

//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
C_WeaponPhysCannon::C_WeaponPhysCannon( void )
{
	m_bWasUpgraded = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool C_WeaponPhysCannon::SetupEmitter( void )
{
	if ( !m_pLocalEmitter.IsValid() )
	{
		m_pLocalEmitter = CLocalSpaceEmitter::Create( "physpowerup", entindex(), LookupAttachment( "core" ) );

		if ( m_pLocalEmitter.IsValid() == false )
			return false;
	}

	if ( !m_pAttractor.IsValid() )
	{
		m_pAttractor = CParticleAttractor::Create( vec3_origin, "physpowerup_att" );

		if ( m_pAttractor.IsValid() == false )
			return false;
	}

	if ( !m_pEmitter.IsValid() )
	{
		m_pEmitter = CSimpleEmitter::Create( "physpowerup_glow" );

		if ( m_pEmitter.IsValid() == false )
			return false;
	}

	return true;
}

// adnan
// this is where we override the mouse input when orbiting
// UPDATE: we shouldnt do this for the rotation...
//  - this will zero out the mouse deltas in the usercmd...
//    - leads to viewangles never being calculated
//  - need to intercept the viewangles AFTER theyve been calculated
//-----------------------------------------------------------------------------
// Purpose: Allow weapons to override mouse input (for binoculars)
//-----------------------------------------------------------------------------
void C_WeaponPhysCannon::OverrideMouseInput( float *x, float *y )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	
	if(!pPlayer)
		return;

	//DevMsg("SHOULD WE OVERRIDE\n");
	
	
	//if(m_grabController.m_bHasRotatedCarryAngles) {
	//if(m_grabController.GetAttached() && (pPlayer->m_nButtons & IN_USE)) {
	/*
	if(m_bIsCurrentlyHolding && m_bIsCurrentlyRotating) {
		DevMsg("HAVE ROTATED ANGLES, NOW ZEROING\n");
		//m_grabController.m_vecRotatedCarryAngles[YAW] += *x;
		//m_grabController.m_vecRotatedCarryAngles[PITCH] += *y;

		*x = 0;
		*y = 0;

		return;
	}
	*/
	
}
// end adnan

// adnan
// this is where we say that we dont want ot apply the current calculated view angles
//-----------------------------------------------------------------------------
// Purpose: Allow weapons to override mouse input to viewangles (for orbiting)
//-----------------------------------------------------------------------------
bool C_WeaponPhysCannon::OverrideViewAngles( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	
	if(!pPlayer)
		return false;

	//DevMsg("CALLING IN GRAV GUN HERE\n");

	if(m_bIsCurrentlyHolding && m_bIsCurrentlyRotating) {
		//DevMsg("CALLING IN GRAV GUN HERE 2\n");
	//if(m_grabController.GetAttached() && (pPlayer->m_nButtons & IN_USE)) {
		return true;
	}

	return false;
}
// end adnan

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

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flags - 
// Output : int
//-----------------------------------------------------------------------------
int C_WeaponPhysCannon::DrawModel( int flags )
{
	// If we're not ugrading, don't do anything special
	if ( m_bIsCurrentlyUpgrading == false && m_bWasUpgraded == false )
		return BaseClass::DrawModel( flags );

	if ( gpGlobals->frametime == 0 )
		return BaseClass::DrawModel( flags );

	m_bWasUpgraded = true;

	// Create the particle emitter if it's not already
	if ( SetupEmitter() )
	{
		// Add the power-up particles

		// See if we should draw
		if ( m_bReadyToDraw == false )
			return 0;

		C_BaseAnimating *pAnimating = GetBaseAnimating();
		if (!pAnimating)
			return 0;

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

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

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

		int i;

		float fadePerc = 1.0f;

		if ( m_bIsCurrentlyUpgrading )
		{
			Vector	vecSkew = vec3_origin;

			// Skew the particles in front or in back of their targets
			vecSkew = CurrentViewForward() * 4.0f;

			float spriteScale = 1.0f;
			spriteScale = clamp( spriteScale, 0.75f, 1.0f );

			SimpleParticle *sParticle;

			for ( 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( 4.0f * fadePerc, 1, 3 );

				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), m_pEmitter->GetPMaterial( "effects/combinemuzzle1" ), vecAbsOrigin + offset );

					if ( sParticle == NULL )
						return 1;
					
					sParticle->m_vecVelocity	= vec3_origin;
					sParticle->m_uchStartSize	= 16.0f * spriteScale;
					sParticle->m_flDieTime		= 0.2f;
					sParticle->m_flLifetime		= 0.0f;

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

					float alpha = 40;

					sParticle->m_uchColor[0]	= alpha;
					sParticle->m_uchColor[1]	= alpha;
					sParticle->m_uchColor[2]	= alpha;
					sParticle->m_uchStartAlpha	= alpha;
					sParticle->m_uchEndAlpha	= 0;
					sParticle->m_uchEndSize		= sParticle->m_uchStartSize * 2;
				}
			}
		}
	}

	int		attachment = LookupAttachment( "core" );
	Vector	coreOrigin;
	QAngle	coreAngles;

	GetAttachment( attachment, coreOrigin, coreAngles );

	SimpleParticle *sParticle;

	// Do the core effects
	for ( int i = 0; i < 4; i++ )
	{
		sParticle = (SimpleParticle *) m_pLocalEmitter->AddParticle( sizeof(SimpleParticle), m_pLocalEmitter->GetPMaterial( "effects/strider_muzzle" ), vec3_origin );

		if ( sParticle == NULL )
			return 1;
		
		sParticle->m_vecVelocity	= vec3_origin;
		sParticle->m_flDieTime		= 0.1f;
		sParticle->m_flLifetime		= 0.0f;

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

		float alpha = 255;

		sParticle->m_uchColor[0]	= alpha;
		sParticle->m_uchColor[1]	= alpha;
		sParticle->m_uchColor[2]	= alpha;
		sParticle->m_uchStartAlpha	= alpha;
		sParticle->m_uchEndAlpha	= 0;

		if ( i < 2 )
		{
			sParticle->m_uchStartSize	= random->RandomFloat( 1, 2 ) * (i+1);
			sParticle->m_uchEndSize		= sParticle->m_uchStartSize * 2.0f;
		}
		else
		{
			if ( random->RandomInt( 0, 20 ) == 0 )
			{
				sParticle->m_uchStartSize	= random->RandomFloat( 1, 2 ) * (i+1);
				sParticle->m_uchEndSize		= sParticle->m_uchStartSize * 4.0f;
				sParticle->m_flDieTime		= 0.25f;
			}
			else
			{
				sParticle->m_uchStartSize	= random->RandomFloat( 1, 2 ) * (i+1);
				sParticle->m_uchEndSize		= sParticle->m_uchStartSize * 2.0f;
			}
		}
	}

	if ( m_bWasUpgraded && m_bIsCurrentlyUpgrading )
	{
		// Update our attractor point
		m_pAttractor->SetAttractorOrigin( coreOrigin );

		Vector offset;

		for ( i = 0; i < 4; i++ )
		{
			offset = coreOrigin + RandomVector( -32.0f, 32.0f );

			sParticle = (SimpleParticle *) m_pAttractor->AddParticle( sizeof(SimpleParticle), m_pAttractor->GetPMaterial( "effects/strider_muzzle" ), offset );

			if ( sParticle == NULL )
				return 1;
			
			sParticle->m_vecVelocity	= Vector(0,0,8);
			sParticle->m_flDieTime		= 0.5f;
			sParticle->m_flLifetime		= 0.0f;

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

			float alpha = 255;

			sParticle->m_uchColor[0]	= alpha;
			sParticle->m_uchColor[1]	= alpha;
			sParticle->m_uchColor[2]	= alpha;
			sParticle->m_uchStartAlpha	= alpha;
			sParticle->m_uchEndAlpha	= 0;

			sParticle->m_uchStartSize	= random->RandomFloat( 1, 2 );
			sParticle->m_uchEndSize		= 0;
		}
	}

	return BaseClass::DrawModel( flags );
}

