//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose:		Hand grenade
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "NPCEvent.h"
#include "hl1_basecombatweapon_shared.h"
#include "basecombatcharacter.h"
#include "AI_BaseNPC.h"
#include "player.h"
#include "gamerules.h"
#include "in_buttons.h"
#include "soundent.h"
#include "game.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "hl1_basegrenade.h"


#define HANDGRENADE_MODEL "models/w_grenade.mdl"

extern ConVar sk_plr_dmg_grenade;

//-----------------------------------------------------------------------------
// CHandGrenade
//-----------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( grenade_hand, CHandGrenade );

BEGIN_DATADESC( CHandGrenade )
	DEFINE_FUNCTION( CHandGrenade, BounceTouch ),
END_DATADESC()


void CHandGrenade::Spawn( void )
{
	SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
	SetSolid( SOLID_BBOX );

	SetModel( HANDGRENADE_MODEL ); 

	UTIL_SetSize( this, Vector( -1, -4, -4), Vector( 13, 5, 4 ) );

	m_fRegisteredSound = false;

	QAngle angles;
	Vector vel = GetAbsVelocity();

	VectorAngles( vel, angles );
	SetAbsAngles( angles );
	
	SetTouch( BounceTouch );	// Bounce if touched
	
	// Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate
	// will insert a DANGER sound into the world sound list and delay detonation for one second so that 
	// the grenade explodes after the exact amount of time specified in the call to ShootTimed(). 

	SetThink( TumbleThink );
	SetNextThink( gpGlobals->curtime + 0.1 );

	// if the delay is < 0.1 seconds, don't fly anywhere
	if ( (m_flDetonateTime - gpGlobals->curtime) < 0.1 )
	{
		SetNextThink( gpGlobals->curtime );
		SetAbsVelocity( vec3_origin );
	}

	// m_nSequence = random->RandomInt( 3, 6 ); // FIXME: missing tumble animations
	m_flPlaybackRate = 1.0;

	SetGravity( 0.5 );  // Don't change or throw calculations will be off!
	SetFriction( 0.8 );
}


void CHandGrenade::Precache( void )
{
	BaseClass::Precache( );

	engine->PrecacheModel( HANDGRENADE_MODEL );
}


void CHandGrenade ::BounceSound( void )
{
	EmitSound( "Weapon_HandGrenade.GrenadeBounce" );
}


void CHandGrenade::BounceTouch( CBaseEntity *pOther )
{
	if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
		return;

	// don't hit the guy that launched this grenade
	if ( pOther == GetOwner() )
		return;

	// only do damage if we're moving fairly fast
	if ( (pOther->m_takedamage != DAMAGE_NO) && (m_flNextAttack < gpGlobals->curtime && GetAbsVelocity().Length() > 100))
	{
		if ( GetOwner() )
		{
			trace_t tr = CBaseEntity::GetTouchTrace( );
			ClearMultiDamage( );
			Vector forward;
			AngleVectors( GetAbsAngles(), &forward );

			CTakeDamageInfo info( this, GetOwner(), 1, DMG_CLUB );
			CalculateMeleeDamageForce( &info, forward, tr.endpos );
			pOther->DispatchTraceAttack( info, forward, &tr ); 
			ApplyMultiDamage();
		}
		m_flNextAttack = gpGlobals->curtime + 1.0; // debounce
	}

	Vector vecTestVelocity;
	// m_vecAngVelocity = Vector (300, 300, 300);

	// this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical
	// or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. 
	// trimming the Z velocity a bit seems to help quite a bit.
	vecTestVelocity = GetAbsVelocity(); 
	vecTestVelocity.z *= 0.45;

	if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 )
	{
		// grenade is moving really slow. It's probably very close to where it will ultimately stop moving. 
		// emit the danger sound.
		
		// register a radius louder than the explosion, so we make sure everyone gets out of the way
		CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), m_flDamage / 0.4, 0.3 );
		m_fRegisteredSound = TRUE;
	}

	// HACKHACK - On ground isn't always set, so look for ground underneath
	trace_t tr;
	UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,10), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );

	if ( tr.fraction < 1.0 )
	{
		// add a bit of static friction
//		SetAbsVelocity( GetAbsVelocity() * 0.8 );
//		SetSequence( SelectWeightedSequence( ACT_GRENADE_ROLL ) );
	}

	// play bounce sound
	BounceSound();

	m_flPlaybackRate = GetAbsVelocity().Length() / 200.0;
	if (m_flPlaybackRate > 1.0)
		m_flPlaybackRate = 1;
	else if (m_flPlaybackRate < 0.5)
		m_flPlaybackRate = 0;
}


//-----------------------------------------------------------------------------
// CWeaponHandGrenade
//-----------------------------------------------------------------------------

class CWeaponHandGrenade : public CBaseHL1CombatWeapon
{
	DECLARE_CLASS( CWeaponHandGrenade, CBaseHL1CombatWeapon );
public:

	CWeaponHandGrenade( void );

	void	Precache( void );
	void	PrimaryAttack( void );
	void	WeaponIdle( void );
	bool	Deploy( void );
	bool	Holster( CBaseCombatWeapon *pSwitchingTo = NULL );

	DECLARE_SERVERCLASS();
	DECLARE_DATADESC();

private:
	float m_flStartThrow;
	float m_flReleaseThrow;
};

LINK_ENTITY_TO_CLASS( weapon_handgrenade, CWeaponHandGrenade );

PRECACHE_WEAPON_REGISTER( weapon_handgrenade );

IMPLEMENT_SERVERCLASS_ST( CWeaponHandGrenade, DT_WeaponHandGrenade )
END_SEND_TABLE()

BEGIN_DATADESC( CWeaponHandGrenade )
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWeaponHandGrenade::CWeaponHandGrenade( void )
{
	m_bReloadsSingly	= false;
	m_bFiresUnderwater	= true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponHandGrenade::Precache( void )
{
	UTIL_PrecacheOther( "grenade_hand" );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponHandGrenade::PrimaryAttack( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( !pPlayer )
	{
		return;
	}

	if ( ( m_flStartThrow <= 0 ) && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 )
	{
		m_flStartThrow		= gpGlobals->curtime;
		m_flReleaseThrow	= 0;

		SendWeaponAnim( ACT_VM_PRIMARYATTACK );
		SetWeaponIdleTime( gpGlobals->curtime + 0.5 );
	}
}


void CWeaponHandGrenade::WeaponIdle( void )
{
	if ( m_flReleaseThrow == 0 && m_flStartThrow )
		 m_flReleaseThrow = gpGlobals->curtime;

	if ( !HasWeaponIdleTimeElapsed() )
		return;

	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( !pPlayer )
	{
		return;
	}

	if ( m_flStartThrow )
	{
		Vector vecAiming = pPlayer->GetAutoaimVector( 0 );

		QAngle angThrow;
		VectorAngles( vecAiming, angThrow );

		Vector vecUp;
		Vector vecRight;
		AngleVectors( angThrow, NULL, &vecRight, &vecUp );

		if ( angThrow.x > 180 )	// player is pitching up
			angThrow.x = -15 - ( 360 - angThrow.x ) * ( ( 90 - 10 ) / 90.0 );
		else					// player is pitching down
			angThrow.x = -15 + angThrow.x * ( ( 90 + 10 ) / 90.0 );

		float flVel = ( 90 - angThrow.x ) * 4;
		if ( flVel > 500 )
			flVel = 500;

		Vector vecFwd;
		AngleVectors( angThrow, &vecFwd );

		Vector vecSrc	= pPlayer->EyePosition() + vecFwd * 16;
		Vector vecThrow	= vecFwd * flVel + pPlayer->GetAbsVelocity();

		CHandGrenade *pGrenade = (CHandGrenade*)CreateNoSpawn( "grenade_hand", vecSrc, vec3_angle, this );

		// alway explode 3 seconds after the pin was pulled
		pGrenade->m_flDetonateTime = m_flStartThrow + 3.0;
		pGrenade->Spawn( );
		pGrenade->SetOwner( pPlayer );
		pGrenade->SetOwnerEntity( pPlayer );
		pGrenade->SetGravity( 0.5 );
		pGrenade->SetFriction( 0.8 );
		pGrenade->SetAbsOrigin( vecSrc );
		pGrenade->SetAbsVelocity( vecThrow );
		VectorAngles( vecThrow, angThrow );
		pGrenade->SetAbsAngles( angThrow );
		pGrenade->SetDamage( sk_plr_dmg_grenade.GetFloat() );
		pGrenade->SetDamageRadius( pGrenade->GetDamage() * 2.5 );
//		pGrenade->ResetSequence( pGrenade->SelectWeightedSequence( ACT_GRENADE_TOSS ) );

		if ( flVel < 500 )
		{
			SendWeaponAnim( ACT_HANDGRENADE_THROW1 );
		}
		else if ( flVel < 1000 )
		{
			SendWeaponAnim( ACT_HANDGRENADE_THROW2 );
		}
		else
		{
			SendWeaponAnim( ACT_HANDGRENADE_THROW3 );
		}

		// player "shoot" animation
		pPlayer->SetAnimation( PLAYER_ATTACK1 );

		m_flReleaseThrow	= 0;
		m_flStartThrow		= 0;

		SetWeaponIdleTime( gpGlobals->curtime + 0.5 );
		m_flNextPrimaryAttack	= gpGlobals->curtime + 0.5;
		m_flNextSecondaryAttack	= gpGlobals->curtime + 0.5;

		pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType );

		return;
	}
	else if ( m_flReleaseThrow > 0 )
	{
		// we've finished the throw, restart.
		m_flStartThrow = 0;

		if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 )
		{
			SendWeaponAnim( ACT_VM_DRAW );
		}
		else
		{
//			RetireWeapon();
			return;
		}

		SetWeaponIdleTime( gpGlobals->curtime + random->RandomFloat( 10, 15 ) );
		m_flReleaseThrow = -1;
		return;
	}

	if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 )
	{
		float flRand = random->RandomFloat( 0, 1 );
		if ( flRand <= 0.75 )
		{
			SendWeaponAnim( ACT_VM_IDLE );
			SetWeaponIdleTime( gpGlobals->curtime + random->RandomFloat( 10, 15 ) );// how long till we do this again.
		}
		else 
		{
			SendWeaponAnim( ACT_VM_FIDGET );
		}
	}
}

bool CWeaponHandGrenade::Deploy( void )
{
	m_flReleaseThrow = -1;

	return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_VM_DRAW, (char*)GetAnimPrefix() );
}

bool CWeaponHandGrenade::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( !pPlayer )
	{
		return false;
	}

	if ( m_flStartThrow > 0 )
	{
		return false;
	}

	if ( !BaseClass::Holster( pSwitchingTo ) )
	{
		return false;
	}

	if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
	{
		SetThink( &CWeaponHandGrenade::DestroyItem );
		SetNextThink( gpGlobals->curtime + 0.1 );
	}

	pPlayer->SetNextAttack( gpGlobals->curtime + 0.5 );

	return true;
}
