//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Dual pistols FTW // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "NPCEvent.h" #include "basehlcombatweapon.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 "gamestats.h" #include "particle_parse.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define Dualpistol_FASTEST_REFIRE_TIME 0.1f #define Dualpistol_FASTEST_DRY_REFIRE_TIME 0.2f #define Dualpistol_ACCURACY_SHOT_PENALTY_TIME 0.5f // Applied amount of time each shot adds to the time we must recover from #define Dualpistol_ACCURACY_MAXIMUM_PENALTY_TIME 3.0f // Maximum penalty to deal out // Light Kill : Define old and new attachments #define DP_old_attachment1 "1" #define DP_old_attachment2 "2" #define DP_new_attachment1 "muzzle" #define DP_new_attachment2 "muzzle2" //ConVar aa_wpn_dualpistol_use_new_accuracy( "aa_wpn_dualpistol_use_new_accuracy", "1" ); ConVar aa_wpn_dualpistol_use_new_attachments( "aa_wpn_dualpistol_use_new_attachments", "0", FCVAR_REPLICATED|FCVAR_ARCHIVE, "Old attachements = (1,2) css model, New one -(muzzle,muzzle2)." ); //----------------------------------------------------------------------------- // CWeaponDualpistol //----------------------------------------------------------------------------- class CWeaponDualpistol : public CBaseHLCombatWeapon { DECLARE_DATADESC(); public: DECLARE_CLASS( CWeaponDualpistol, CBaseHLCombatWeapon ); CWeaponDualpistol(void); DECLARE_SERVERCLASS(); void Precache( void ); void ItemPostFrame( void ); void ItemPreFrame( void ); void ItemBusyFrame( void ); void PrimaryAttack( void ); void Basefire( void ); void AddViewKick( void ); void DryFire( void ); void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); //int ChangeAttachment( void ); //int GetTracerAttachment( void ); void UpdatePenaltyTime( void ); bool Deploy( void ); int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } //Activity GetPrimaryAttackActivity( void ); //Activity GetSecondaryAttackActivity( void ); virtual bool Reload( void ); virtual int GetMinBurst() { return 1; } virtual int GetMaxBurst() { return 3; } /*virtual float GetFireRate( void ) { return 0.5f; }*/ //bool ShouldUseAttachment2ForMuzzleFlashes( void ) { return bFlip; } // alternate between using attachment 1 and attachment 2 for muzzleflashes DECLARE_ACTTABLE(); private: float m_flSoonestPrimaryAttack; float m_flLastAttackTime; float m_flAccuracyPenalty; int m_nNumShotsFired; bool bFlip; }; IMPLEMENT_SERVERCLASS_ST(CWeaponDualpistol, DT_WeaponDualpistol) END_SEND_TABLE() LINK_ENTITY_TO_CLASS( weapon_dualpistol, CWeaponDualpistol ); PRECACHE_WEAPON_REGISTER( weapon_dualpistol ); BEGIN_DATADESC( CWeaponDualpistol ) DEFINE_FIELD( m_flSoonestPrimaryAttack, FIELD_TIME ), DEFINE_FIELD( m_flLastAttackTime, FIELD_TIME ), DEFINE_FIELD( m_flAccuracyPenalty, FIELD_FLOAT ), //NOTENOTE: This is NOT tracking game time DEFINE_FIELD( m_nNumShotsFired, FIELD_INTEGER ), END_DATADESC() acttable_t CWeaponDualpistol::m_acttable[] = { { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, // L1ght 15 : MP animstate { 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_GESTURE_RELOAD_SMG1, false }, //fix { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, //{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, false }, // END { 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_COVER_LOW, ACT_COVER_PISTOL_LOW, true }, { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_PISTOL_LOW, true }, { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_PISTOL,true }, { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, true }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, true }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, true }, // Readiness activities (not aiming) { ACT_IDLE_RELAXED, ACT_IDLE/*_PISTOL*/, false },//never aims { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, { ACT_WALK_RELAXED, ACT_WALK, false },//never aims { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, { ACT_RUN_RELAXED, ACT_RUN, false },//never aims { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, // Readiness activities (aiming) { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims //End readiness activities // Crouch activities { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims // Readiness translations { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, }; IMPLEMENT_ACTTABLE( CWeaponDualpistol ); //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CWeaponDualpistol::CWeaponDualpistol( void ) { m_flSoonestPrimaryAttack = gpGlobals->curtime; m_flAccuracyPenalty = 0.0f; m_fMinRange1 = 24; m_fMaxRange1 = 1500; m_fMinRange2 = 24; m_fMaxRange2 = 200; m_bFiresUnderwater = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::Precache( void ) { PrecacheParticleSystem( "weapon_muzzle_smoke" ); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- void CWeaponDualpistol::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, pOperator->GetAttackSpread(this), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2); pOperator->DoMuzzleFlash(); m_iClip1 = m_iClip1 - 1; } break; default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); break; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::DryFire( void ) { WeaponSound( EMPTY ); SendWeaponAnim( ACT_VM_DRYFIRE ); m_flSoonestPrimaryAttack = gpGlobals->curtime + Dualpistol_FASTEST_DRY_REFIRE_TIME; m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::PrimaryAttack( void ) { if ( ( gpGlobals->curtime - m_flLastAttackTime ) > 0.5f ) { m_nNumShotsFired = 0; } else { m_nNumShotsFired++; } m_flLastAttackTime = gpGlobals->curtime; m_flSoonestPrimaryAttack = gpGlobals->curtime + Dualpistol_FASTEST_REFIRE_TIME; CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, GetOwner() ); 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(); } /*if(m_nNumShotsFired >= 7|9|11) { if (cvar->FindVar("Dualpistol_use_new_attachments")->GetInt() == 1) { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pOwner->GetViewModel(), "muzzle2", true); } else { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pOwner->GetViewModel(), "2", true); } } if(m_nNumShotsFired >= 8,10,12) { if (cvar->FindVar("Dualpistol_use_new_attachments")->GetInt() == 1) { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pOwner->GetViewModel(), "muzzle", true); } else { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pOwner->GetViewModel(), "1", true); } }*/ Basefire(); // Add an accuracy penalty which can move past our maximum penalty time if we're really spastic m_flAccuracyPenalty += Dualpistol_ACCURACY_SHOT_PENALTY_TIME; m_iPrimaryAttacks++; gamestats->Event_WeaponFired( pOwner, true, GetClassname() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::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, Dualpistol_ACCURACY_MAXIMUM_PENALTY_TIME ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::ItemPreFrame( void ) { UpdatePenaltyTime(); BaseClass::ItemPreFrame(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::ItemBusyFrame( void ) { SecondaryAttack(); UpdatePenaltyTime(); BaseClass::ItemBusyFrame(); } //----------------------------------------------------------------------------- // Purpose: Allows firing as fast as button is pressed //----------------------------------------------------------------------------- void CWeaponDualpistol::ItemPostFrame( void ) { BaseClass::ItemPostFrame(); if ( m_bInReload ) return; CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return; //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(); } } //----------------------------------------------------------------------------- // Purpose: // Output : int //----------------------------------------------------------------------------- /*Activity CWeaponDualpistol::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; return ACT_VM_PRIMARYATTACK; }*/ /*Activity CWeaponDualpistol::GetSecondaryAttackActivity( void ) { return ACT_VM_SECONDARYATTACK;; }*/ //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CWeaponDualpistol::Reload( void ) { bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); if ( fRet ) { WeaponSound( RELOAD ); m_flAccuracyPenalty = 0.0f; } return fRet; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponDualpistol::AddViewKick( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( pPlayer == NULL ) return; QAngle viewPunch; viewPunch.x = random->RandomFloat( 0.25f, 0.5f ); viewPunch.y = random->RandomFloat( -.6f, .6f ); viewPunch.z = 0.0f; //Add it to the view punch pPlayer->ViewPunch( viewPunch ); } //----------------------------------------------------------------------------- // Purpose: Dual Pistols Firing //----------------------------------------------------------------------------- /*void CWeaponDualpistol::LeftGunAttack() { // If my clip is empty (and I use clips) start reload if ( UsesClipsForAmmo1() && !m_iClip1 ) { Reload(); return; } // Only the player fires this way so we can cast CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if (!pPlayer) { return; } // MUST call sound before removing a round from the clip of a CMachineGun WeaponSound(SINGLE); pPlayer->DoMuzzleFlash(); SendWeaponAnim( GetSecondaryAttackActivity() ); // player "shoot" animation pPlayer->SetAnimation( PLAYER_ATTACK1 ); FireBulletsInfo_t info; info.m_vecSrc = pPlayer->Weapon_ShootPosition( ); info.m_vecDirShooting = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT ); // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, // especially if the weapon we're firing has a really fast rate of fire. info.m_iShots = 0; float fireRate = GetFireRate(); while ( m_flNextPrimaryAttack <= gpGlobals->curtime ) { m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; info.m_iShots++; if ( !fireRate ) break; } // Make sure we don't fire more than the amount in the clip if ( UsesClipsForAmmo1() ) { info.m_iShots = min( info.m_iShots, m_iClip1 ); m_iClip1 -= info.m_iShots; } else { info.m_iShots = min( info.m_iShots, pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) ); pPlayer->RemoveAmmo( info.m_iShots, m_iPrimaryAmmoType ); } info.m_flDistance = MAX_TRACE_LENGTH; info.m_iAmmoType = m_iPrimaryAmmoType; info.m_iTracerFreq = 2; #if !defined( CLIENT_DLL ) // Fire the bullets info.m_vecSpread = pPlayer->GetAttackSpread( this ); #else //!!!HACKHACK - what does the client want this function for? info.m_vecSpread = GetActiveWeapon()->GetBulletSpread(); #endif // CLIENT_DLL pPlayer->FireBullets( info ); if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { // HEV suit - indicate out of ammo condition pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); } //Add our view kick in AddViewKick(); }*/ /*int CWeaponDualpistol::GetTracerAttachment( void ) { int iAttachment = TRACER_DONT_USE_ATTACHMENT; if ( bFlip ) iAttachment = 2; else iAttachment = 1; return iAttachment; }*/ void CWeaponDualpistol::Basefire( void ) { CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if (!pPlayer) return; // Abort here to handle burst and auto fire modes if ( (UsesClipsForAmmo1() && m_iClip1 == 0) || ( !UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType) ) ) return; // MUST call sound before removing a round from the clip of a CMachineGun WeaponSound(SINGLE); pPlayer->DoMuzzleFlash(); int iBulletsToFire = 0; float fireRate = GetFireRate(); while ( m_flNextPrimaryAttack <= gpGlobals->curtime ) { // MUST call sound before removing a round from the clip of a CHLMachineGun WeaponSound(SINGLE, m_flNextPrimaryAttack); m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; iBulletsToFire++; } // Make sure we don't fire more than the amount in the clip, if this weapon uses clips if ( UsesClipsForAmmo1() ) { if ( iBulletsToFire > m_iClip1 ) iBulletsToFire = m_iClip1; m_iClip1 -= iBulletsToFire; } // Fire the bullets FireBulletsInfo_t info; info.m_iShots = iBulletsToFire; info.m_vecSrc = pPlayer->Weapon_ShootPosition( ); info.m_vecDirShooting = pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); info.m_vecSpread = pPlayer->GetAttackSpread( this ); info.m_flDistance = MAX_TRACE_LENGTH; info.m_iAmmoType = m_iPrimaryAmmoType; info.m_iTracerFreq = 2; FireBullets( info ); //Factor in the view kick AddViewKick(); if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { // HEV suit - indicate out of ammo condition pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); } pPlayer->SetAnimation( PLAYER_ATTACK1 ); //GetTracerAttachment(); //int DP_DoSmokeEffect = random->RandomInt( 1, 70 ); //int DP_DoSmokeEffect2 = random->RandomInt( 1, 70 ); if ( bFlip ) { SendWeaponAnim( GetSecondaryAttackActivity() ); bFlip = false; if (cvar->FindVar("aa_wpn_dualpistol_use_new_attachments")->GetInt() == 1) { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pPlayer->GetViewModel(), DP_new_attachment1, true); } else { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pPlayer->GetViewModel(), DP_old_attachment1, true); } } else { SendWeaponAnim( GetPrimaryAttackActivity() ); bFlip = true; if (cvar->FindVar("aa_wpn_dualpistol_use_new_attachments")->GetInt() == 1) { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pPlayer->GetViewModel(), DP_new_attachment2, true); } else { DispatchParticleEffect( "weapon_muzzle_smoke", PATTACH_POINT_FOLLOW, pPlayer->GetViewModel(), DP_old_attachment2, true); } } //PrepareHitmarker(); } bool CWeaponDualpistol::Deploy( void ) { if (BaseClass::Deploy()) { SendWeaponAnim( ACT_VM_DRAW ); bFlip = false; m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); return true; } return false; }