text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  //=============================================================================//
  
  #include "cbase.h"
  #include "npcevent.h"
  #include "in_buttons.h"
  
  #ifdef CLIENT_DLL
  	#include "c_hl2mp_player.h"
  #else
  	#include "hl2mp_player.h"
  #endif
  
  #include "weapon_hl2mpbasehlmpcombatweapon.h"
  
  #define	PISTOL_FASTEST_REFIRE_TIME		0.1f
  #define	PISTOL_FASTEST_DRY_REFIRE_TIME	0.2f
  
  #define	PISTOL_ACCURACY_SHOT_PENALTY_TIME		0.2f	// Applied amount of time each shot adds to the time we must recover from
  #define	PISTOL_ACCURACY_MAXIMUM_PENALTY_TIME	1.5f	// Maximum penalty to deal out
  
  #ifdef CLIENT_DLL
  #define CWeaponPistol C_WeaponPistol
  #endif
  
  //-----------------------------------------------------------------------------
  // CWeaponPistol
  //-----------------------------------------------------------------------------
  
  class CWeaponPistol : public CBaseHL2MPCombatWeapon
  {
  public:
  	DECLARE_CLASS( CWeaponPistol, CBaseHL2MPCombatWeapon );
  
  	CWeaponPistol(void);
  
  	DECLARE_NETWORKCLASS(); 
  	DECLARE_PREDICTABLE();
  
  	void	Precache( void );
  	void	ItemPostFrame( void );
  	void	ItemPreFrame( void );
  	void	ItemBusyFrame( void );
  	void	PrimaryAttack( void );
  	void	AddViewKick( void );
  	void	DryFire( void );
  	void	Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
  
  	void	UpdatePenaltyTime( void );
  
  #ifndef CLIENT_DLL
  	int		CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
  	Activity	GetPrimaryAttackActivity( void );
  #endif
  
  	virtual bool Reload( void );
  
  	virtual const Vector& GetBulletSpread( void )
  	{		
  		// Handle NPCs first
  		static Vector npcCone = VECTOR_CONE_5DEGREES;
  		if ( GetOwner() && GetOwner()->IsNPC() )
  			return npcCone;
  			
  		static Vector cone;
  
  		float ramp = RemapValClamped(	m_flAccuracyPenalty, 
  											0.0f, 
  											PISTOL_ACCURACY_MAXIMUM_PENALTY_TIME, 
  											0.0f, 
  											1.0f ); 
  
  			// We lerp from very accurate to inaccurate over time
  		VectorLerp( VECTOR_CONE_1DEGREES, VECTOR_CONE_6DEGREES, ramp, cone );
  
  		return cone;
  	}
  	
  	virtual int	GetMinBurst() 
  	{ 
  		return 1; 
  	}
  
  	virtual int	GetMaxBurst() 
  	{ 
  		return 3; 
  	}
  
  	virtual float GetFireRate( void ) 
  	{
  		return 0.5f; 
  	}
  	
  #ifndef CLIENT_DLL
  	DECLARE_ACTTABLE();
  #endif
  
  private:
  	CNetworkVar( float,	m_flSoonestPrimaryAttack );
  	CNetworkVar( float,	m_flLastAttackTime );
  	CNetworkVar( float,	m_flAccuracyPenalty );
  	CNetworkVar( int,	m_nNumShotsFired );
  
  private:
  	CWeaponPistol( const CWeaponPistol & );
  };
  
  IMPLEMENT_NETWORKCLASS_ALIASED( WeaponPistol, DT_WeaponPistol )
  
  BEGIN_NETWORK_TABLE( CWeaponPistol, DT_WeaponPistol )
  #ifdef CLIENT_DLL
  	RecvPropTime( RECVINFO( m_flSoonestPrimaryAttack ) ),
  	RecvPropTime( RECVINFO( m_flLastAttackTime ) ),
  	RecvPropFloat( RECVINFO( m_flAccuracyPenalty ) ),
  	RecvPropInt( RECVINFO( m_nNumShotsFired ) ),
  #else
  	SendPropTime( SENDINFO( m_flSoonestPrimaryAttack ) ),
  	SendPropTime( SENDINFO( m_flLastAttackTime ) ),
  	SendPropFloat( SENDINFO( m_flAccuracyPenalty ) ),
  	SendPropInt( SENDINFO( m_nNumShotsFired ) ),
  #endif
  END_NETWORK_TABLE()
  
  #ifdef CLIENT_DLL
  BEGIN_PREDICTION_DATA( CWeaponPistol )
  	DEFINE_PRED_FIELD( m_flSoonestPrimaryAttack, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
  	DEFINE_PRED_FIELD( m_flLastAttackTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
  	DEFINE_PRED_FIELD( m_flAccuracyPenalty, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
  	DEFINE_PRED_FIELD( m_nNumShotsFired, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
  END_PREDICTION_DATA()
  #endif
  
  LINK_ENTITY_TO_CLASS( weapon_pistol, CWeaponPistol );
  PRECACHE_WEAPON_REGISTER( weapon_pistol );
  
  #ifndef CLIENT_DLL
  acttable_t CWeaponPistol::m_acttable[] = 
  {
  	{ ACT_HL2MP_IDLE,					ACT_HL2MP_IDLE_PISTOL,					false },
  	{ ACT_HL2MP_RUN,					ACT_HL2MP_RUN_PISTOL,					false },
  	{ ACT_HL2MP_IDLE_CROUCH,			ACT_HL2MP_IDLE_CROUCH_PISTOL,			false },
  	{ ACT_HL2MP_WALK_CROUCH,			ACT_HL2MP_WALK_CROUCH_PISTOL,			false },
  	{ ACT_HL2MP_GESTURE_RANGE_ATTACK,	ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL,	false },
  	{ ACT_HL2MP_GESTURE_RELOAD,			ACT_HL2MP_GESTURE_RELOAD_PISTOL,		false },
  	{ ACT_HL2MP_JUMP,					ACT_HL2MP_JUMP_PISTOL,					false },
  	{ ACT_RANGE_ATTACK1,				ACT_RANGE_ATTACK_PISTOL,				false },
  
  	{ ACT_IDLE,						ACT_IDLE_PISTOL,				true },
  	{ ACT_IDLE_ANGRY,				ACT_IDLE_ANGRY_PISTOL,			true },
  	{ ACT_RANGE_ATTACK1,			ACT_RANGE_ATTACK_PISTOL,		true },
  	{ ACT_RELOAD,					ACT_RELOAD_PISTOL,				true },
  	{ ACT_WALK_AIM,					ACT_WALK_AIM_PISTOL,			true },
  	{ ACT_RUN_AIM,					ACT_RUN_AIM_PISTOL,				true },
  	{ ACT_GESTURE_RANGE_ATTACK1,	ACT_GESTURE_RANGE_ATTACK_PISTOL,true },
  	{ ACT_RELOAD_LOW,				ACT_RELOAD_PISTOL_LOW,			false },
  	{ ACT_RANGE_ATTACK1_LOW,		ACT_RANGE_ATTACK_PISTOL_LOW,	false },
  	{ ACT_COVER_LOW,				ACT_COVER_PISTOL_LOW,			false },
  	{ ACT_RANGE_AIM_LOW,			ACT_RANGE_AIM_PISTOL_LOW,		false },
  	{ ACT_GESTURE_RELOAD,			ACT_GESTURE_RELOAD_PISTOL,		false },
  	{ ACT_WALK,						ACT_WALK_PISTOL,				false },
  	{ ACT_RUN,						ACT_RUN_PISTOL,					false },
  };
  
  
  IMPLEMENT_ACTTABLE( CWeaponPistol );
  
  #endif
  
  //-----------------------------------------------------------------------------
  // Purpose: Constructor
  //-----------------------------------------------------------------------------
  CWeaponPistol::CWeaponPistol( void )
  {
  	m_flSoonestPrimaryAttack = gpGlobals->curtime;
  	m_flAccuracyPenalty = 0.0f;
  
  	m_fMinRange1		= 24;
  	m_fMaxRange1		= 1500;
  	m_fMinRange2		= 24;
  	m_fMaxRange2		= 200;
  
  	m_bFiresUnderwater	= true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CWeaponPistol::Precache( void )
  {
  	BaseClass::Precache();
  }
  
  #ifndef CLIENT_DLL
  //-----------------------------------------------------------------------------
  // Purpose:
  // Input  :
  // Output :
  //-----------------------------------------------------------------------------
  void CWeaponPistol::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
  {
  	switch( pEvent->event )
  	{
  		case EVENT_WEAPON_PISTOL_FIRE:
  		{
  			Vector vecShootOrigin, vecShootDir;
  			vecShootOrigin = pOperator->Weapon_ShootPosition();
  
  			CAI_BaseNPC *npc = pOperator->MyNPCPointer();
  			ASSERT( npc != NULL );
  
  			vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin );
  
  			CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() );
  
  			WeaponSound( SINGLE_NPC );
  			pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 );
  			pOperator->DoMuzzleFlash();
  			m_iClip1 = m_iClip1 - 1;
  		}
  		break;
  		default:
  			BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
  			break;
  	}
  }
  
  #endif
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CWeaponPistol::DryFire( void )
  {
  	WeaponSound( EMPTY );
  	SendWeaponAnim( ACT_VM_DRYFIRE );
  	
  	m_flSoonestPrimaryAttack	= gpGlobals->curtime + PISTOL_FASTEST_DRY_REFIRE_TIME;
  	m_flNextPrimaryAttack		= gpGlobals->curtime + SequenceDuration();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CWeaponPistol::PrimaryAttack( void )
  {
  	if ( ( gpGlobals->curtime - m_flLastAttackTime ) > 0.5f )
  	{
  		m_nNumShotsFired = 0;
  	}
  	else
  	{
  		m_nNumShotsFired++;
  	}
  
  	m_flLastAttackTime = gpGlobals->curtime;
  	m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME;
  
  #ifndef CLIENT_DLL
  	CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, GetOwner() );
  #endif
  
  	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  
  	if( pOwner )
  	{
  		// Each time the player fires the pistol, reset the view punch. This prevents
  		// the aim from 'drifting off' when the player fires very quickly. This may
  		// not be the ideal way to achieve this, but it's cheap and it works, which is
  		// great for a feature we're evaluating. (sjb)
  		pOwner->ViewPunchReset();
  	}
  
  	BaseClass::PrimaryAttack();
  
  	// Add an accuracy penalty which can move past our maximum penalty time if we're really spastic
  	m_flAccuracyPenalty += PISTOL_ACCURACY_SHOT_PENALTY_TIME;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CWeaponPistol::UpdatePenaltyTime( void )
  {
  	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  
  	if ( pOwner == NULL )
  		return;
  
  	// Check our penalty time decay
  	if ( ( ( pOwner->m_nButtons & IN_ATTACK ) == false ) && ( m_flSoonestPrimaryAttack < gpGlobals->curtime ) )
  	{
  		m_flAccuracyPenalty -= gpGlobals->frametime;
  		m_flAccuracyPenalty = clamp( m_flAccuracyPenalty, 0.0f, PISTOL_ACCURACY_MAXIMUM_PENALTY_TIME );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CWeaponPistol::ItemPreFrame( void )
  {
  	UpdatePenaltyTime();
  
  	BaseClass::ItemPreFrame();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CWeaponPistol::ItemBusyFrame( void )
  {
  	UpdatePenaltyTime();
  
  	BaseClass::ItemBusyFrame();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Allows firing as fast as button is pressed
  //-----------------------------------------------------------------------------
  void CWeaponPistol::ItemPostFrame( void )
  {
  	BaseClass::ItemPostFrame();
  
  	if ( m_bInReload )
  		return;
  	
  	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  
  	if ( pOwner == NULL )
  		return;
  	
  	if ( pOwner->m_nButtons & IN_ATTACK2 )
  	{
  		m_flLastAttackTime = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME;
  		m_flSoonestPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME;
  		m_flNextPrimaryAttack = gpGlobals->curtime + PISTOL_FASTEST_REFIRE_TIME;
  	}
  
  	//Allow a refire as fast as the player can click
  	if ( ( ( pOwner->m_nButtons & IN_ATTACK ) == false ) && ( m_flSoonestPrimaryAttack < gpGlobals->curtime ) )
  	{
  		m_flNextPrimaryAttack = gpGlobals->curtime - 0.1f;
  	}
  	else if ( ( pOwner->m_nButtons & IN_ATTACK ) && ( m_flNextPrimaryAttack < gpGlobals->curtime ) && ( m_iClip1 <= 0 ) )
  	{
  		DryFire();
  	}
  }
  
  #ifndef CLIENT_DLL
  //-----------------------------------------------------------------------------
  // Purpose: 
  // Output : int
  //-----------------------------------------------------------------------------
  Activity CWeaponPistol::GetPrimaryAttackActivity( void )
  {
  	if ( m_nNumShotsFired < 1 )
  		return ACT_VM_PRIMARYATTACK;
  
  	if ( m_nNumShotsFired < 2 )
  		return ACT_VM_RECOIL1;
  
  	if ( m_nNumShotsFired < 3 )
  		return ACT_VM_RECOIL2;
  
  	return ACT_VM_RECOIL3;
  }
  #endif
  
  //-----------------------------------------------------------------------------
  //-----------------------------------------------------------------------------
  bool CWeaponPistol::Reload( void )
  {
  	bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
  	if ( fRet )
  	{
  		WeaponSound( RELOAD );
  		m_flAccuracyPenalty = 0.0f;
  	}
  	return fRet;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CWeaponPistol::AddViewKick( void )
  {
  	CBasePlayer *pPlayer  = ToBasePlayer( GetOwner() );
  	
  	if ( pPlayer == NULL )
  		return;
  
  	QAngle	viewPunch;
  
  	viewPunch.x = SharedRandomFloat( "pistolpax", 0.25f, 0.5f );
  	viewPunch.y = SharedRandomFloat( "pistolpay", -.6f, .6f );
  	viewPunch.z = 0.0f;
  
  	//Add it to the view punch
  	pPlayer->ViewPunch( viewPunch );
  }
  