//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "tier0/vprof.h"

#ifdef CLIENT_DLL
#include "hl1_player_shared.h"
#include "hl1/c_hl1mp_player.h"
//#define CRecipientFilter C_RecipientFilter
#else
#include "hl1mp_player.h"
#endif

// When moving this fast, he plays run anim.
#define ARBITRARY_RUN_SPEED		175.0f

#define MAX_STANDING_RUN_SPEED	320
#define MAX_CROUCHED_RUN_SPEED	110


// ------------------------------------------------------------------------------------------------ //
// CPlayerAnimState declaration.
// ------------------------------------------------------------------------------------------------ //

class CPlayerAnimState : public IHL1MPPlayerAnimState, public CBasePlayerAnimState
{
public:
	
	DECLARE_CLASS( CPlayerAnimState, CBasePlayerAnimState );

	CPlayerAnimState();
	void Init( CHL1MP_Player *pPlayer );

	// This is called by both the client and the server in the same way to trigger events for
	// players firing, jumping, throwing grenades, etc.
	virtual void DoAnimationEvent( PlayerAnimEvent_t event, int nData );
	virtual int CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle );
	virtual float SetOuterBodyYaw( float flValue );
	virtual Activity CalcMainActivity();
	virtual float GetCurrentMaxGroundSpeed();
	virtual void ClearAnimationState();
	virtual bool ShouldUpdateAnimState();
	virtual int SelectWeightedSequence( Activity activity ) ;

	float CalcMovementPlaybackRate( bool *bIsMoving );

	virtual void		ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr );


private:
	
	const char* GetWeaponSuffix();
	bool HandleJumping();
	bool HandleDeath( Activity *deathActivity );


private:
	
	CHL1MP_Player *m_pOuter;
	
	bool m_bJumping;
	bool m_bFirstJumpFrame;
	float m_flJumpStartTime;

	bool m_bFiring;
	float m_flFireStartTime;

	bool m_bDying;
	Activity m_DeathActivity;
};


IHL1MPPlayerAnimState* CreatePlayerAnimState( CHL1MP_Player *pPlayer )
{
	CPlayerAnimState *pRet = new CPlayerAnimState;
	pRet->Init( pPlayer );
	return pRet;
}


// ----------------------------------------------------------------------------- //
// CPlayerAnimState implementation.
// ----------------------------------------------------------------------------- //

CPlayerAnimState::CPlayerAnimState()
{
	m_pOuter = NULL;
	m_bJumping = false;
	m_bFirstJumpFrame = false;
	m_bFiring = false;
}


void CPlayerAnimState::Init( CHL1MP_Player *pPlayer )
{
	m_pOuter = pPlayer;
	
	CModAnimConfig config;
	config.m_flMaxBodyYawDegrees = 90;
	config.m_LegAnimType = LEGANIM_GOLDSRC;
	config.m_bUseAimSequences = true;

	BaseClass::Init( pPlayer, config );
}


const char* CPlayerAnimState::GetWeaponSuffix()
{
	CBaseCombatWeapon *pWeapon = m_pOuter->GetActiveWeapon();
	if ( pWeapon )
		return pWeapon->GetWpnData().szAnimationPrefix;
	else
		return "shotgun";
}


int CPlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle )
{
	const char *pWeaponSuffix = GetWeaponSuffix();
	if ( !pWeaponSuffix )
		return 0;

	if ( strcmp( pWeaponSuffix, "glock" ) == 0 )
		pWeaponSuffix = "onehanded";

	// Are we aiming or firing?
	const char *pAimOrShoot = "aim";
	if ( m_bFiring )
		pAimOrShoot = "shoot";

	// Are we standing or crouching?
	int iSequence = 0;
	const char *pPrefix = "ref";
	if ( m_bDying )
	{
		// While dying, only play the main sequence.. don't layer this one on top.
		*flAimSequenceWeight = 0;	
	}
	else
	{
		switch ( GetCurrentMainSequenceActivity() )
		{
			case ACT_CROUCHIDLE:
			case ACT_RUN_CROUCH:
				pPrefix = "crouch";
				break;
		}
	}

	iSequence = CalcSequenceIndex( "%s_%s_%s", pPrefix, pAimOrShoot, pWeaponSuffix );
	
	// Check if we're done firing.
	if ( m_bFiring )
	{
		float dur = m_pOuter->SequenceDuration( iSequence );
		*flCycle = (gpGlobals->curtime - m_flFireStartTime) / dur;
		if ( *flCycle >= 1 )
		{
			*flCycle = 1;
			m_bFiring = false;
		}
	}	

	return iSequence;
}


void CPlayerAnimState::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
{
	if ( event == PLAYERANIMEVENT_JUMP )
	{
		// Main animation goes to ACT_HOP.
		m_bJumping = true;
		m_bFirstJumpFrame = true;
		m_flJumpStartTime = gpGlobals->curtime;
	}
	else if ( event == PLAYERANIMEVENT_FIRE_GUN )
	{
		// The middle part of the aim layer sequence becomes "shoot" until that animation is complete.
		m_bFiring = true;
		m_flFireStartTime = gpGlobals->curtime;
	}
}


float CPlayerAnimState::SetOuterBodyYaw( float flValue )
{
//	m_pOuter->SetBoneController( 0, flValue );

	float fAcc = flValue / 4;

	for ( int i = 0; i < 4; i++ )
	{
		m_pOuter->SetBoneController( i, fAcc );
	}

	return flValue;
}


bool CPlayerAnimState::HandleJumping()
{
	if ( m_bJumping )
	{
		if ( m_bFirstJumpFrame )
		{
			m_bFirstJumpFrame = false;
			RestartMainSequence();	// Reset the animation.
		}

		// Don't check if he's on the ground for a sec.. sometimes the client still has the
		// on-ground flag set right when the message comes in.
		if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f )
		{
			if ( m_pOuter->GetFlags() & FL_ONGROUND )
			{
				m_bJumping = false;
				RestartMainSequence();	// Reset the animation.
			}
		}
	}

	// Are we still jumping? If so, keep playing the jump animation.
	return m_bJumping;
}

int CPlayerAnimState::SelectWeightedSequence( Activity activity ) 
{
	return m_pOuter->m_iRealSequence;
}

bool CPlayerAnimState::HandleDeath( Activity *deathActivity )
{
	if ( m_bDying )
	{
		if ( m_pOuter->IsAlive() )
		{
			m_bDying = false;
		}
		else
		{
			*deathActivity = m_DeathActivity;
		}
	}
	return m_bDying;
}


Activity CPlayerAnimState::CalcMainActivity()
{
	Activity deathActivity = ACT_IDLE;
	if ( HandleDeath( &deathActivity ) )
	{
		return deathActivity;
	}
	else if ( HandleJumping() )
	{
		return ACT_HOP;
	}
	else
	{
		Activity idealActivity = ACT_IDLE;
		float flOuterSpeed = GetOuterXYSpeed();

		if ( m_pOuter->GetFlags() & FL_DUCKING )
		{
			if ( flOuterSpeed > 0.1f )
				idealActivity = ACT_RUN_CROUCH;
			else
				idealActivity = ACT_CROUCHIDLE;
		}
		else
		{
			if ( flOuterSpeed > 0.1f )
			{
				if ( flOuterSpeed > ARBITRARY_RUN_SPEED )
					idealActivity = ACT_RUN;
				else
					idealActivity = ACT_WALK;
			}
			else
			{
				idealActivity = ACT_IDLE;
			}
		}

		return idealActivity	;
	}
}


float CPlayerAnimState::GetCurrentMaxGroundSpeed()
{
	Activity act = GetCurrentMainSequenceActivity();
	if ( act == ACT_CROUCHIDLE || act == ACT_RUN_CROUCH )
		return MAX_CROUCHED_RUN_SPEED;
	else
		return MAX_STANDING_RUN_SPEED;
}


void CPlayerAnimState::ClearAnimationState()
{
	m_bJumping = false;
	m_bFiring = false;
	m_bDying = false;

	BaseClass::ClearAnimationState();
}


bool CPlayerAnimState::ShouldUpdateAnimState()
{
	return true;
}

float CPlayerAnimState::CalcMovementPlaybackRate( bool *bIsMoving )
{
	// Determine ideal playback rate
	Vector vel;
	GetOuterAbsVelocity( vel );

	float flReturnValue = BaseClass::CalcMovementPlaybackRate( bIsMoving );

	Activity eActivity = GetOuter()->GetSequenceActivity( GetOuter()->GetSequence() ) ;

	if ( eActivity == ACT_RUN || eActivity == ACT_WALK || eActivity == ACT_CROUCH )
	{
		VectorNormalize( vel );

		Vector vForward;
		AngleVectors( GetOuter()->EyeAngles(), &vForward );

		float flDot = DotProduct( vel, vForward );

		if ( flDot < 0 )
		{
			flReturnValue *= -1;
		}
	}

	return flReturnValue;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr )
{
	VPROF( "CBasePlayerAnimState::ComputePoseParam_BodyPitch" );

	// Get pitch from v_angle
	float flPitch = -m_flEyePitch;
	if ( flPitch > 180.0f )
	{
		flPitch -= 360.0f;
	}
	flPitch = clamp( flPitch, -50, 45 );

	// See if we have a blender for pitch
	int pitch = GetOuter()->LookupPoseParameter( pStudioHdr, "XR" );
	if ( pitch < 0 )
		return;

	GetOuter()->SetPoseParameter( pStudioHdr, pitch, flPitch );
	g_flLastBodyPitch = flPitch;
}