diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_weapon_minigun.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/tf/tf_weapon_minigun.cpp')
| -rw-r--r-- | game/shared/tf/tf_weapon_minigun.cpp | 1496 |
1 files changed, 1496 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_minigun.cpp b/game/shared/tf/tf_weapon_minigun.cpp new file mode 100644 index 0000000..1f5a4fd --- /dev/null +++ b/game/shared/tf/tf_weapon_minigun.cpp @@ -0,0 +1,1496 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "cbase.h" +#include "tf_weapon_minigun.h" +#include "decals.h" +#include "in_buttons.h" +#include "tf_fx_shared.h" +#include "debugoverlay_shared.h" +#include "tf_gamerules.h" + +// Client specific. +#ifdef CLIENT_DLL +#include "c_tf_player.h" +#include "soundenvelope.h" +#include "achievementmgr.h" +#include "baseachievement.h" +#include "achievements_tf.h" +#include "prediction.h" +#include "clientmode_tf.h" +#include "bone_setup.h" +// NVNT haptics system interface +#include "haptics/ihaptics.h" +// Server specific. +#else +#include "tf_player.h" +#include "particle_parse.h" +#include "tf_gamestats.h" +#include "baseprojectile.h" +#endif + +#define MAX_BARREL_SPIN_VELOCITY 20 +#define TF_MINIGUN_SPINUP_TIME 0.75f +#define TF_MINIGUN_PENALTY_PERIOD 1.f + +//============================================================================= +// +// Weapon Minigun tables. +// +IMPLEMENT_NETWORKCLASS_ALIASED( TFMinigun, DT_WeaponMinigun ) + +BEGIN_NETWORK_TABLE( CTFMinigun, DT_WeaponMinigun ) +// Client specific. +#ifdef CLIENT_DLL + RecvPropInt( RECVINFO( m_iWeaponState ) ), + RecvPropBool( RECVINFO( m_bCritShot ) ) +// Server specific. +#else + SendPropInt( SENDINFO( m_iWeaponState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ), + SendPropBool( SENDINFO( m_bCritShot ) ) +#endif +END_NETWORK_TABLE() + +#ifdef CLIENT_DLL +BEGIN_PREDICTION_DATA( CTFMinigun ) + DEFINE_FIELD( m_iWeaponState, FIELD_INTEGER ), +END_PREDICTION_DATA() +#endif + +LINK_ENTITY_TO_CLASS( tf_weapon_minigun, CTFMinigun ); +PRECACHE_WEAPON_REGISTER( tf_weapon_minigun ); + + +// Server specific. +#ifndef CLIENT_DLL +BEGIN_DATADESC( CTFMinigun ) +END_DATADESC() +#endif + +//============================================================================= +// +// Weapon Minigun functions. +// + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CTFMinigun::CTFMinigun() +{ +#ifdef CLIENT_DLL + m_pSoundCur = NULL; + + m_hEjectBrassWeapon = NULL; + m_pEjectBrassEffect = NULL; + m_iEjectBrassAttachment = -1; + + m_hMuzzleEffectWeapon = NULL; + m_pMuzzleEffect = NULL; + m_iMuzzleAttachment = -1; + + m_nShotsFired = 0; + + ListenForGameEvent( "teamplay_round_active" ); + ListenForGameEvent( "localplayer_respawn" ); + + m_bRageDraining = false; + m_bPrevRageDraining = false; +#endif + m_bAttack3Down = false; + + WeaponReset(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor. +//----------------------------------------------------------------------------- +CTFMinigun::~CTFMinigun() +{ + WeaponReset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::WeaponReset( void ) +{ + BaseClass::WeaponReset(); + + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( pPlayer ) + { + pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + +#ifdef GAME_DLL + pPlayer->ClearWeaponFireScene(); + m_flAegisCheckTime = 0.0f; +#endif + + m_flNextRingOfFireAttackTime = 0.0f; + m_flLastAmmoDrainTime = gpGlobals->curtime; + m_flAccumulatedAmmoDrain = 0.0f; + } + + SetWeaponState( AC_STATE_IDLE ); + m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; + m_bCritShot = false; + m_flStartedFiringAt = -1.0f; + m_flStartedWindUpAt = -1.f; + m_flNextFiringSpeech = 0.0f; + + m_flBarrelAngle = 0.0f; + + m_flBarrelCurrentVelocity = 0.0f; + m_flBarrelTargetVelocity = 0.0f; + +#ifdef CLIENT_DLL + if ( m_pSoundCur ) + { + CSoundEnvelopeController::GetController().SoundDestroy( m_pSoundCur ); + m_pSoundCur = NULL; + } + + m_iMinigunSoundCur = -1; + m_flMinigunSoundCurrentPitch = 1.0f; + + StopMuzzleEffect(); + StopBrassEffect(); +#endif +} + +#ifdef GAME_DLL +int CTFMinigun::UpdateTransmitState( void ) +{ + // ALWAYS transmit to all clients. + return SetTransmitState( FL_EDICT_ALWAYS ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::Precache( void ) +{ + PrecacheScriptSound( "Halloween.HeadlessBossAxeHitWorld" ); + + // FIXME: Do we still need these?? + PrecacheScriptSound( "MVM.GiantHeavyGunWindUp" ); + PrecacheScriptSound( "MVM.GiantHeavyGunWindDown" ); + PrecacheScriptSound( "MVM.GiantHeavyGunFire" ); + PrecacheScriptSound( "MVM.GiantHeavyGunSpin" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::ItemPostFrame( void ) +{ + // Prevent base code from ever playing empty sounds, minigun handles them manually. + m_flNextEmptySoundTime = gpGlobals->curtime + 1.0; + +#ifdef GAME_DLL + CBasePlayer *pOwner = GetPlayerOwner(); + if ( pOwner ) + { + if ( ( pOwner->m_nButtons & IN_ATTACK3 ) && !m_bAttack3Down ) + { + ActivatePushBackAttackMode(); + m_bAttack3Down = true; + } + else if ( !( pOwner->m_nButtons & IN_ATTACK3 ) && m_bAttack3Down ) + { + m_bAttack3Down = false; + } + } +#endif // GAME_DLL + + BaseClass::ItemPostFrame(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::PrimaryAttack() +{ + SharedAttack(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::SharedAttack() +{ + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( !pPlayer ) + return; + + if ( !CanAttack() ) + { + WeaponIdle(); + return; + } + +#ifdef CLIENT_DLL + m_bRageDraining = pPlayer->m_Shared.IsRageDraining(); +#endif // CLIENT_DLL + + if ( pPlayer->m_nButtons & IN_ATTACK ) + { + m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; + } + else if ( pPlayer->m_nButtons & IN_ATTACK2 ) + { + m_iWeaponMode = TF_WEAPON_SECONDARY_MODE; + } + + switch ( m_iWeaponState ) + { + default: + case AC_STATE_IDLE: + { + // Removed the need for cells to powerup the AC + WindUp(); + + float flSpinUpTime = TF_MINIGUN_SPINUP_TIME; + CALL_ATTRIB_HOOK_FLOAT( flSpinUpTime, mult_minigun_spinup_time ); + + float flSpinTimeMultiplier = Max( flSpinUpTime, 0.00001f ); + if ( pPlayer->GetViewModel( 0 ) ) + { + pPlayer->GetViewModel( 0 )->SetPlaybackRate( TF_MINIGUN_SPINUP_TIME / flSpinTimeMultiplier ); + } + if ( pPlayer->GetViewModel( 1 ) ) + { + pPlayer->GetViewModel( 1 )->SetPlaybackRate( TF_MINIGUN_SPINUP_TIME / flSpinTimeMultiplier ); + } + + m_flNextPrimaryAttack = gpGlobals->curtime + flSpinUpTime; + m_flNextSecondaryAttack = gpGlobals->curtime + flSpinUpTime; + m_flTimeWeaponIdle = gpGlobals->curtime + flSpinUpTime; + m_flStartedFiringAt = -1.f; + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRE ); + break; + } + case AC_STATE_STARTFIRING: + { + // Start playing the looping fire sound + if ( m_flNextPrimaryAttack <= gpGlobals->curtime ) + { + if ( m_iWeaponMode == TF_WEAPON_SECONDARY_MODE ) + { + SetWeaponState( AC_STATE_SPINNING ); + } + else + { + SetWeaponState( AC_STATE_FIRING ); + } + +#ifdef GAME_DLL + if ( m_iWeaponState == AC_STATE_SPINNING ) + { + pPlayer->SpeakWeaponFire( MP_CONCEPT_WINDMINIGUN ); + } + else + { + pPlayer->SpeakWeaponFire( MP_CONCEPT_FIREMINIGUN ); + } +#endif + + m_flNextSecondaryAttack = m_flNextPrimaryAttack = m_flTimeWeaponIdle = gpGlobals->curtime + 0.1; + } + break; + } + case AC_STATE_FIRING: + { + if ( m_iWeaponMode == TF_WEAPON_SECONDARY_MODE ) + { + SetWeaponState( AC_STATE_SPINNING ); + } + + if ( m_iWeaponState == AC_STATE_SPINNING ) + { +#ifdef GAME_DLL + pPlayer->ClearWeaponFireScene(); + pPlayer->SpeakWeaponFire( MP_CONCEPT_WINDMINIGUN ); +#endif + m_flNextSecondaryAttack = m_flNextPrimaryAttack = m_flTimeWeaponIdle = gpGlobals->curtime + 0.1; + + } + else if ( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) + { + SetWeaponState( AC_STATE_DRYFIRE ); + } + else + { + if ( m_flStartedFiringAt < 0 ) + { + m_flStartedFiringAt = gpGlobals->curtime; + } + +#ifdef GAME_DLL + if ( m_flNextFiringSpeech < gpGlobals->curtime ) + { + m_flNextFiringSpeech = gpGlobals->curtime + 5.0; + pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_MINIGUN_FIREWEAPON ); + } +#endif + +#ifdef CLIENT_DLL + int nAmmo = 0; + if ( prediction->IsFirstTimePredicted() && + C_BasePlayer::GetLocalPlayer() == pPlayer ) + { + nAmmo = pPlayer->GetAmmoCount( m_iPrimaryAmmoType ); + } +#endif + + // Only fire if we're actually shooting + BaseClass::PrimaryAttack(); // fire and do timers + +#ifdef CLIENT_DLL + if ( prediction->IsFirstTimePredicted() && + C_BasePlayer::GetLocalPlayer() == pPlayer && + nAmmo != pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) ) // did PrimaryAttack() fire a shot? (checking our ammo to find out) + { + m_nShotsFired++; + if ( m_nShotsFired == 1000 ) // == and not >= so we don't keep awarding this every shot after it's achieved + { + g_AchievementMgrTF.OnAchievementEvent( ACHIEVEMENT_TF_HEAVY_FIRE_LOTS ); + } + // NVNT the local player fired a shot. notify the haptics system. + if ( haptics ) + haptics->ProcessHapticEvent(2,"Weapons","minigun_fire"); + } +#endif + CalcIsAttackCritical(); + m_bCritShot = IsCurrentAttackACrit(); + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); + +#ifdef GAME_DLL + + int iAttackProjectiles = 0; + CALL_ATTRIB_HOOK_INT( iAttackProjectiles, attack_projectiles ); + +#ifdef TF_RAID_MODE + if ( TFGameRules()->IsBossBattleMode() ) + { + iAttackProjectiles = 1; + } +#endif // TF_RAID_MODE + + if ( iAttackProjectiles ) + { + AttackEnemyProjectiles(); + } + +#endif // GAME_DLL + + m_flTimeWeaponIdle = gpGlobals->curtime + 0.2; + } + break; + } + case AC_STATE_DRYFIRE: + { + m_flStartedFiringAt = -1.f; + m_flStartedWindUpAt = -1.f; + + if ( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) > 0 ) + { + SetWeaponState( AC_STATE_FIRING ); + } + else if ( m_iWeaponMode == TF_WEAPON_SECONDARY_MODE ) + { + SetWeaponState( AC_STATE_SPINNING ); + } + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + break; + } + case AC_STATE_SPINNING: + { + m_flStartedFiringAt = -1.f; + + if ( m_iWeaponMode == TF_WEAPON_PRIMARY_MODE ) + { + if ( pPlayer->GetAmmoCount(m_iPrimaryAmmoType) > 0 ) + { +#ifdef GAME_DLL + pPlayer->ClearWeaponFireScene(); + pPlayer->SpeakWeaponFire( MP_CONCEPT_FIREMINIGUN ); +#endif + SetWeaponState( AC_STATE_FIRING ); + } + else + { + SetWeaponState( AC_STATE_DRYFIRE ); + } + } + + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + break; + } + } + + if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 ) + { + if ( m_iWeaponState > AC_STATE_STARTFIRING ) + { + int nRingOfFireWhileAiming = 0; + CALL_ATTRIB_HOOK_INT( nRingOfFireWhileAiming, ring_of_fire_while_aiming ); + if ( nRingOfFireWhileAiming != 0 ) + { + RingOfFireAttack( nRingOfFireWhileAiming ); + } + } + + if ( m_iWeaponState == AC_STATE_SPINNING || m_iWeaponState == AC_STATE_FIRING ) + { + int nUsesAmmoWhileAiming = 0; + CALL_ATTRIB_HOOK_INT( nUsesAmmoWhileAiming, uses_ammo_while_aiming ); + if ( nUsesAmmoWhileAiming > 0 ) + { + m_flAccumulatedAmmoDrain += nUsesAmmoWhileAiming * ( gpGlobals->curtime - m_flLastAmmoDrainTime ); + m_flLastAmmoDrainTime = gpGlobals->curtime; + + if ( m_flAccumulatedAmmoDrain > 1.0f ) + { + int nAmmoRemoved = m_flAccumulatedAmmoDrain; + pPlayer->RemoveAmmo( nAmmoRemoved, m_iPrimaryAmmoType ); + + m_flAccumulatedAmmoDrain -= nAmmoRemoved; + } + } + } + } +} + +void CTFMinigun::SetWeaponState( MinigunState_t nState ) +{ + if ( m_iWeaponState != nState ) + { + if ( m_iWeaponState == AC_STATE_IDLE || m_iWeaponState == AC_STATE_STARTFIRING || m_iWeaponState == AC_STATE_DRYFIRE ) + { + // Transitioning from non firing or non fully spinning states resets when our drain start point and when the ring of fire can start + m_flLastAmmoDrainTime = gpGlobals->curtime; + m_flNextRingOfFireAttackTime = gpGlobals->curtime + 0.5f; + } + + m_iWeaponState = nState; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fall through to Primary Attack +//----------------------------------------------------------------------------- +void CTFMinigun::SecondaryAttack( void ) +{ + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( !pPlayer ) + return; + + SharedAttack(); +} + +void CTFMinigun::RingOfFireAttack( int nDamage ) +{ + if ( m_flNextRingOfFireAttackTime == 0.0f || m_flNextRingOfFireAttackTime > gpGlobals->curtime ) + return; + + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( !pPlayer ) + return; + +#ifdef GAME_DLL + + Vector vOrigin = pPlayer->GetAbsOrigin(); + const float flFireRadius = 135.0f; + const float flFireRadiusSqr = flFireRadius * flFireRadius; + + CBaseEntity *pEntity = NULL; + for ( CEntitySphereQuery sphere( vOrigin, flFireRadius ); (pEntity = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() ) + { + // Skip players on the same team or who are invuln + CTFPlayer *pVictim = ToTFPlayer( pEntity ); + if ( !pVictim || InSameTeam( pVictim ) || pVictim->m_Shared.InCond( TF_COND_INVULNERABLE ) ) + continue; + + // Make sure their bounding box is near our ground plane + Vector vMins = pVictim->GetPlayerMins(); + Vector vMaxs = pVictim->GetPlayerMaxs(); + if ( !( vOrigin.z > pVictim->GetAbsOrigin().z + vMins.z - 32.0f && vOrigin.z < pVictim->GetAbsOrigin().z + vMaxs.z ) ) + { + continue; + } + + // CEntitySphereQuery actually does a box test. So we need to make sure the distance is less than the radius first. + Vector vecPos; + pEntity->CollisionProp()->CalcNearestPoint( vOrigin, &vecPos ); + if ( ( vOrigin - vecPos ).LengthSqr() > flFireRadiusSqr ) + continue; + + // Finally LOS test + trace_t tr; + Vector vecSrc = WorldSpaceCenter(); + Vector vecSpot = pEntity->WorldSpaceCenter(); + CTraceFilterSimple filter( this, COLLISION_GROUP_PROJECTILE ); + UTIL_TraceLine( vecSrc, vecSpot, MASK_SOLID_BRUSHONLY, &filter, &tr ); + + // If we don't trace the whole way to the target, and we didn't hit the target entity, we're blocked + if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity ) + continue; + + pVictim->TakeDamage( CTakeDamageInfo( pPlayer, pPlayer, this, vec3_origin, vOrigin, nDamage, DMG_PLASMA, 0, &vOrigin ) ); + } + + DispatchParticleEffect( "heavy_ring_of_fire", pPlayer->GetAbsOrigin(), vec3_angle ); + +#else + + DispatchParticleEffect( "heavy_ring_of_fire_fp", pPlayer->GetAbsOrigin(), vec3_angle ); + +#endif // #ifdef GAME_DLL + + m_flNextRingOfFireAttackTime = gpGlobals->curtime + 0.5f; +} + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Purpose: Scans along a line for rockets and grenades to destroy +//----------------------------------------------------------------------------- +void CTFMinigun::AttackEnemyProjectiles( void ) +{ + if ( gpGlobals->curtime < m_flAegisCheckTime ) + return; + + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( !pPlayer ) + return; + + // Parameters + const int nSweepDist = 300; // How far out + const int nHitDist = ( pPlayer->IsMiniBoss() ) ? 56 : 38; // How far from the center line (radial) + float flRechargeTime = 0.1f; + + // Pos + const Vector &vecGunPos = ( pPlayer->IsMiniBoss() ) ? pPlayer->Weapon_ShootPosition() : pPlayer->EyePosition(); + Vector vecForward; + AngleVectors( GetAbsAngles(), &vecForward ); + Vector vecGunAimEnd = vecGunPos + vecForward * (float)nSweepDist; + + bool bDebug = false; + if ( bDebug ) + { + // NDebugOverlay::Sphere( vecGunPos + vecForward * nSweepDist, nSweepDist, 0, 255, 0, 40, 5 ); + NDebugOverlay::Box( vecGunPos, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 255, 0, 0, 40, 5 ); + NDebugOverlay::Box( vecGunAimEnd, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 255, 0, 0, 40, 5 ); + NDebugOverlay::Line( vecGunPos, vecGunAimEnd, 255, 255, 255, true, 5 ); + } + + // Iterate through each grenade/rocket in the sphere + const int nMaxEnts = 32; + CBaseEntity *pObjects[ nMaxEnts ]; + int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, vecGunPos, nSweepDist, FL_GRENADE ); + for ( int i = 0; i < nCount; i++ ) + { + if ( InSameTeam( pObjects[i] ) ) + continue; + + // Hit? + const Vector &vecGrenadePos = pObjects[i]->GetAbsOrigin(); + float flDistToLine = CalcDistanceToLineSegment( vecGrenadePos, vecGunPos, vecGunAimEnd ); + if ( flDistToLine <= nHitDist ) + { + if ( pPlayer->FVisible( pObjects[i], MASK_SOLID ) == false ) + continue; + + if ( ( pObjects[i]->GetFlags() & FL_ONGROUND ) ) + continue; + + if ( !pObjects[i]->IsDeflectable() ) + continue; + + CBaseProjectile *pProjectile = dynamic_cast< CBaseProjectile* >( pObjects[i] ); + if ( pProjectile && pProjectile->IsDestroyable() ) + { + pProjectile->IncrementDestroyableHitCount(); + + if ( bDebug ) + { + NDebugOverlay::Box( vecGrenadePos, -Vector( 5, 5, 5 ), Vector( 5, 5, 5 ), 255, 0, 255, 40, 5 ); + } + + // Did we destroy it? + int iAttackProjectiles = 0; + CALL_ATTRIB_HOOK_INT( iAttackProjectiles, attack_projectiles ); + int nHitsRequired = m_bCritShot ? 1 : (int)RemapValClamped( iAttackProjectiles, 1, 2, 2, 1 ); + if ( pProjectile->GetDestroyableHitCount() >= nHitsRequired ) + { + pProjectile->Destroy( false, true ); + + EmitSound( "Halloween.HeadlessBossAxeHitWorld" ); + + CTF_GameStats.Event_PlayerAwardBonusPoints( pPlayer, NULL, 2 ); + + // Weaker version has a longer cooldown + if ( iAttackProjectiles < 2 ) + { + flRechargeTime = 0.3f; + } + } + else + { + // Nicked it + pObjects[i]->EmitSound( "FX_RicochetSound.Ricochet" ); + } + } + } + } + + m_flAegisCheckTime = gpGlobals->curtime + flRechargeTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Reduces damage and adds extra knockback. +//----------------------------------------------------------------------------- +void CTFMinigun::ActivatePushBackAttackMode( void ) +{ + CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); + if ( !pOwner ) + return; + + int iRage = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iRage, generate_rage_on_dmg ); + if ( !iRage ) + return; + + if ( pOwner->m_Shared.IsRageDraining() ) + return; + + if ( pOwner->m_Shared.GetRageMeter() < 100.f ) + { + pOwner->EmitSound( "Player.DenyWeaponSelection" ); + return; + } + + pOwner->m_Shared.StartRageDrain(); + EmitSound( "Heavy.Battlecry03" ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: UI Progress (same as GetProgress() without the division by 100.0f) +//----------------------------------------------------------------------------- +bool CTFMinigun::IsRageFull( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return false; + + return ( pPlayer->m_Shared.GetRageMeter() >= 100.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMinigun::EffectMeterShouldFlash( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return false; + + if ( pPlayer && ( IsRageFull() || pPlayer->m_Shared.IsRageDraining() ) ) + return true; + else + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMinigun::CanInspect() const +{ + return BaseClass::CanInspect() && CanHolster(); +} + +//----------------------------------------------------------------------------- +// Purpose: UI Progress +//----------------------------------------------------------------------------- +float CTFMinigun::GetProgress( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return 0.f; + + return pPlayer->m_Shared.GetRageMeter() / 100.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::WindUp( void ) +{ + // Get the player owning the weapon. + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( !pPlayer ) + return; + + // Play wind-up animation and sound (SPECIAL1). + SendWeaponAnim( ACT_MP_ATTACK_STAND_PREFIRE ); + + // Set the appropriate firing state. + SetWeaponState( AC_STATE_STARTFIRING ); + pPlayer->m_Shared.AddCond( TF_COND_AIMING ); + +#ifndef CLIENT_DLL + pPlayer->StopRandomExpressions(); +#endif + +#ifdef CLIENT_DLL + WeaponSoundUpdate(); +#endif + + // Update player's speed + pPlayer->TeamFortress_SetSpeed(); + + if ( m_flStartedWindUpAt == -1.f ) + { + m_flStartedWindUpAt = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMinigun::CanHolster( void ) const +{ + bool bCanHolster = CanHolsterWhileSpinning(); + + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if( pPlayer ) + { + // PASSTIME need to be able to immediately holster when you catch the ball + if ( pPlayer->m_Shared.HasPasstimeBall() ) + return true; + + // TF_COND_MELEE_ONLY need to be able to immediately holster and switch to melee weapon + if ( pPlayer->m_Shared.InCond( TF_COND_MELEE_ONLY ) ) + return true; + } + +#ifdef STAGING_ONLY + // Agility powerup allows holstering while spinning + bCanHolster |= ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ); +#endif //STAGING_ONLY + + if ( bCanHolster ) + { + if ( m_iWeaponState == AC_STATE_STARTFIRING || m_iWeaponState == AC_STATE_FIRING ) + return false; + } + else + { + if ( m_iWeaponState > AC_STATE_IDLE ) + return false; + + if ( GetActivity() == ACT_MP_ATTACK_STAND_POSTFIRE || GetActivity() == ACT_PRIMARY_ATTACK_STAND_POSTFIRE ) + { + if ( !IsViewModelSequenceFinished() ) + return false; + } + } + + return BaseClass::CanHolster(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMinigun::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + if ( m_iWeaponState > AC_STATE_IDLE ) + { + WindDown(); + } + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMinigun::Lower( void ) +{ + if ( m_iWeaponState > AC_STATE_IDLE ) + { + WindDown(); + } + + return BaseClass::Lower(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::WindDown( void ) +{ + // Get the player owning the weapon. + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( !pPlayer ) + return; + + SendWeaponAnim( ACT_MP_ATTACK_STAND_POSTFIRE ); + +#ifdef CLIENT_DLL + if ( !HasSpinSounds() && m_iWeaponState == AC_STATE_FIRING ) + { + PlayStopFiringSound(); + } +#endif + + // Set the appropriate firing state. + SetWeaponState( AC_STATE_IDLE ); + pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); +#ifdef CLIENT_DLL + WeaponSoundUpdate(); +#else + pPlayer->ClearWeaponFireScene(); +#endif + + // Time to weapon idle. + m_flTimeWeaponIdle = gpGlobals->curtime + 2.0; + + // Update player's speed + pPlayer->TeamFortress_SetSpeed(); + +#ifdef CLIENT_DLL + m_flBarrelTargetVelocity = 0; + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && GetOwner() == pLocalPlayer ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "localplayer_winddown" ); + if ( event ) + { + gameeventmanager->FireEventClientSide( event ); + } + } +#endif + + m_flStartedWindUpAt = -1.f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::WeaponIdle() +{ + if ( gpGlobals->curtime < m_flTimeWeaponIdle ) + return; + + // Always wind down if we've hit here, because it only happens when the player has stopped firing/spinning + if ( m_iWeaponState != AC_STATE_IDLE ) + { + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( pPlayer ) + { + pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_POST ); + } + + WindDown(); + return; + } + + BaseClass::WeaponIdle(); + + m_flTimeWeaponIdle = gpGlobals->curtime + 12.5;// how long till we do this again. +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::FireGameEvent( IGameEvent * event ) +{ +#ifdef CLIENT_DLL + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pLocalPlayer && GetOwner() == pLocalPlayer ) + { + if ( FStrEq( event->GetName(), "teamplay_round_active" ) || + FStrEq( event->GetName(), "localplayer_respawn" ) ) + { + m_nShotsFired = 0; + } + } + + BaseClass::FireGameEvent( event ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFMinigun::SendWeaponAnim( int iActivity ) +{ +#ifdef CLIENT_DLL + // Client procedurally animates the barrel bone + if ( iActivity == ACT_MP_ATTACK_STAND_PRIMARYFIRE || iActivity == ACT_MP_ATTACK_STAND_PREFIRE ) + { + m_flBarrelTargetVelocity = MAX_BARREL_SPIN_VELOCITY; + } + else if ( iActivity == ACT_MP_ATTACK_STAND_POSTFIRE ) + { + m_flBarrelTargetVelocity = 0; + } + +#endif + + + // When we start firing, play the startup firing anim first + if ( iActivity == ACT_VM_PRIMARYATTACK ) + { + // If we're already playing the fire anim, let it continue. It loops. + if ( GetActivity() == ACT_VM_PRIMARYATTACK ) + return true; + + // Otherwise, play the start it + return BaseClass::SendWeaponAnim( ACT_VM_PRIMARYATTACK ); + } + + return BaseClass::SendWeaponAnim( iActivity ); +} + +//----------------------------------------------------------------------------- +// Purpose: This will force the minigun to turn off the firing sound and play the spinning sound +//----------------------------------------------------------------------------- +void CTFMinigun::HandleFireOnEmpty( void ) +{ + if ( m_iWeaponState == AC_STATE_FIRING || m_iWeaponState == AC_STATE_SPINNING ) + { + SetWeaponState( AC_STATE_DRYFIRE ); + + SendWeaponAnim( ACT_VM_SECONDARYATTACK ); + + if ( m_iWeaponMode == TF_WEAPON_SECONDARY_MODE ) + { + SetWeaponState ( AC_STATE_SPINNING ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFMinigun::GetProjectileDamage( void ) +{ + float flDamage = BaseClass::GetProjectileDamage(); + + if ( GetFiringDuration() < TF_MINIGUN_PENALTY_PERIOD ) + { + float flMod = 1.f; + flMod = RemapValClamped( GetFiringDuration(), 0.2f, TF_MINIGUN_PENALTY_PERIOD, 0.5f, 1.f ); + flDamage *= flMod; + } + + return flDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFMinigun::GetWeaponSpread( void ) +{ + float flSpread = BaseClass::GetWeaponSpread(); + + // How long have we been spun up - sans the min period required to fire + float flPreFireWindUp = GetWindUpDuration() - TF_MINIGUN_SPINUP_TIME; + // DevMsg( "PreFireTime: %.2f\n", flPreFireWindUp ); + + if ( GetFiringDuration() < TF_MINIGUN_PENALTY_PERIOD && flPreFireWindUp < 1.f ) + { + // If we've spun up - prior to pressing fire - reduce accuracy penalty + float flSpinTime = Max( flPreFireWindUp, GetFiringDuration() ); + const float flMaxSpread = 1.5f; + float flMod = RemapValClamped( flSpinTime, 0.f, TF_MINIGUN_PENALTY_PERIOD, flMaxSpread, 1.f ); + // DevMsg( "SpreadMod: %.2f\n", flMod ); + + flSpread *= flMod; + } + + return flSpread; +} + + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CStudioHdr *CTFMinigun::OnNewModel( void ) +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + + m_iBarrelBone = LookupBone( "barrel" ); + + // skip resetting this while recording in the tool + // we change the weapon to the worldmodel and back to the viewmodel when recording + // which causes the minigun to not spin while recording + if ( !IsToolRecording() ) + { + m_flBarrelAngle = 0; + + m_flBarrelCurrentVelocity = 0; + m_flBarrelTargetVelocity = 0; + } + + return hdr; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::StandardBlendingRules( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + BaseClass::StandardBlendingRules( hdr, pos, q, currentTime, boneMask ); + + if (m_iBarrelBone != -1) + { + UpdateBarrelMovement(); + + // Weapon happens to be aligned to (0,0,0) + // If that changes, use this code block instead to + // modify the angles + + /* + RadianEuler a; + QuaternionAngles( q[iBarrelBone], a ); + + a.x = m_flBarrelAngle; + + AngleQuaternion( a, q[iBarrelBone] ); + */ + + AngleQuaternion( RadianEuler( 0, 0, m_flBarrelAngle ), q[m_iBarrelBone] ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Updates the velocity and position of the rotating barrel +//----------------------------------------------------------------------------- +void CTFMinigun::UpdateBarrelMovement() +{ + if ( m_flBarrelCurrentVelocity != m_flBarrelTargetVelocity ) + { + float flBarrelAcceleration = CanHolsterWhileSpinning() ? 0.5f : 0.1f; + + // update barrel velocity to bring it up to speed or to rest + m_flBarrelCurrentVelocity = Approach( m_flBarrelTargetVelocity, m_flBarrelCurrentVelocity, flBarrelAcceleration ); + + if ( 0 == m_flBarrelCurrentVelocity ) + { + // if we've stopped rotating, turn off the wind-down sound + WeaponSoundUpdate(); + } + } + + // update the barrel rotation based on current velocity + m_flBarrelAngle += m_flBarrelCurrentVelocity * gpGlobals->frametime; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::OnDataChanged( DataUpdateType_t updateType ) +{ + // Brass ejection and muzzle flash. + HandleBrassEffect(); + HandleMuzzleEffect(); + + BaseClass::OnDataChanged( updateType ); + + WeaponSoundUpdate(); + + // Turn off the firing sound here for the Tomislav + if( m_iPrevMinigunState == AC_STATE_FIRING && + ( m_iWeaponState == AC_STATE_SPINNING || m_iWeaponState == AC_STATE_IDLE ) ) + { + if ( !HasSpinSounds() ) + { + PlayStopFiringSound(); + } + } + + m_iPrevMinigunState = m_iWeaponState; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::UpdateOnRemove( void ) +{ + if ( m_pSoundCur ) + { + CSoundEnvelopeController::GetController().SoundDestroy( m_pSoundCur ); + m_pSoundCur = NULL; + } + + // Force the particle system off. + StopMuzzleEffect(); + StopBrassEffect(); + + BaseClass::UpdateOnRemove(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::SetDormant( bool bDormant ) +{ + // If I'm going from active to dormant and I'm carried by another player, stop our firing sound. + if ( !IsCarriedByLocalPlayer() ) + { + // Am I firing? Stop the firing sound. + if ( !IsDormant() && bDormant && m_iWeaponState >= AC_STATE_FIRING ) + { + WeaponSoundUpdate(); + } + + // If firing and going dormant - stop the brass effect. + if ( !IsDormant() && bDormant && m_iWeaponState != AC_STATE_IDLE ) + { + StopMuzzleEffect(); + StopBrassEffect(); + } + } + + // Deliberately skip base combat weapon + C_BaseEntity::SetDormant( bDormant ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// won't be called for w_ version of the model, so this isn't getting updated twice +//----------------------------------------------------------------------------- +void CTFMinigun::ItemPreFrame( void ) +{ + UpdateBarrelMovement(); + BaseClass::ItemPreFrame(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::StartBrassEffect() +{ + StopBrassEffect(); + + m_hEjectBrassWeapon = GetWeaponForEffect(); + if ( !m_hEjectBrassWeapon ) + return; + + if ( UsingViewModel() && !g_pClientMode->ShouldDrawViewModel() ) + { + // Prevent effects when the ViewModel is hidden with r_drawviewmodel=0 + return; + } + + // Try and setup the attachment point if it doesn't already exist. + // This caching will mess up if we go third person from first - we only do this in taunts and don't fire so we should + // be okay for now. + if ( m_iEjectBrassAttachment == -1 ) + { + m_iEjectBrassAttachment = m_hEjectBrassWeapon->LookupAttachment( "eject_brass" ); + } + + // Start the brass ejection, if a system hasn't already been started. + if ( m_iEjectBrassAttachment > 0 && m_pEjectBrassEffect == NULL ) + { + m_pEjectBrassEffect = m_hEjectBrassWeapon->ParticleProp()->Create( "eject_minigunbrass", PATTACH_POINT_FOLLOW, m_iEjectBrassAttachment ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::StartMuzzleEffect() +{ + StopMuzzleEffect(); + + m_hMuzzleEffectWeapon = GetWeaponForEffect(); + if ( !m_hMuzzleEffectWeapon ) + return; + + if ( UsingViewModel() && !g_pClientMode->ShouldDrawViewModel() ) + { + // Prevent effects when the ViewModel is hidden with r_drawviewmodel=0 + return; + } + + // Try and setup the attachment point if it doesn't already exist. + // This caching will mess up if we go third person from first - we only do this in taunts and don't fire so we should + // be okay for now. + if ( m_iMuzzleAttachment <= 0 ) + { + m_iMuzzleAttachment = m_hMuzzleEffectWeapon->LookupAttachment( "muzzle" ); + } + + // Start the muzzle flash, if a system hasn't already been started. + if ( m_iMuzzleAttachment > 0 && m_pMuzzleEffect == NULL ) + { + m_pMuzzleEffect = m_hMuzzleEffectWeapon->ParticleProp()->Create( "muzzle_minigun_constant", PATTACH_POINT_FOLLOW, m_iMuzzleAttachment ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::StopBrassEffect() +{ + if ( !m_hEjectBrassWeapon ) + return; + + // Stop the brass ejection. + if ( m_pEjectBrassEffect ) + { + m_hEjectBrassWeapon->ParticleProp()->StopEmission( m_pEjectBrassEffect ); + m_hEjectBrassWeapon = NULL; + m_pEjectBrassEffect = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::StopMuzzleEffect() +{ + if ( !m_hMuzzleEffectWeapon ) + return; + + // Stop the muzzle flash. + if ( m_pMuzzleEffect ) + { + m_hMuzzleEffectWeapon->ParticleProp()->StopEmission( m_pMuzzleEffect ); + m_hMuzzleEffectWeapon = NULL; + m_pMuzzleEffect = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::HandleBrassEffect() +{ + if ( m_iWeaponState == AC_STATE_FIRING && m_pEjectBrassEffect == NULL ) + { + StartBrassEffect(); + } + else if ( m_iWeaponState != AC_STATE_FIRING && m_pEjectBrassEffect ) + { + StopBrassEffect(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::HandleMuzzleEffect() +{ + if ( m_iWeaponState == AC_STATE_FIRING && m_pMuzzleEffect == NULL ) + { + StartMuzzleEffect(); + } + else if ( m_iWeaponState != AC_STATE_FIRING && m_pMuzzleEffect ) + { + StopMuzzleEffect(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: View model barrel rotation angle. Calculated here, implemented in +// tf_viewmodel.cpp +//----------------------------------------------------------------------------- +float CTFMinigun::GetBarrelRotation( void ) +{ + return m_flBarrelAngle; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::ViewModelAttachmentBlending( CStudioHdr *hdr, Vector pos[], Quaternion q[], float currentTime, int boneMask ) +{ + int iBarrelBone = Studio_BoneIndexByName( hdr, "barrel" ); + + // Assert( iBarrelBone != -1 ); + + if ( iBarrelBone != -1 ) + { + if ( hdr->boneFlags( iBarrelBone ) & boneMask ) + { + RadianEuler a; + QuaternionAngles( q[iBarrelBone], a ); + + a.z = GetBarrelRotation(); + + AngleQuaternion( a, q[iBarrelBone] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFMinigun::CreateMove( float flInputSampleTime, CUserCmd *pCmd, const QAngle &vecOldViewAngles ) +{ + // Prevent jumping while firing + if ( m_iWeaponState != AC_STATE_IDLE ) + { + pCmd->buttons &= ~IN_JUMP; + } + + BaseClass::CreateMove( flInputSampleTime, pCmd, vecOldViewAngles ); +} + +//----------------------------------------------------------------------------- +// Purpose: Ensures the correct sound (including silence) is playing for +// current weapon state. +//----------------------------------------------------------------------------- +void CTFMinigun::WeaponSoundUpdate() +{ + // determine the desired sound for our current state + int iSound = -1; + switch ( m_iWeaponState ) + { + case AC_STATE_IDLE: + if ( !HasSpinSounds() && m_iMinigunSoundCur == SPECIAL2 ) + { + // Don't turn off SPECIAL2 (stop firing sound) for non spinning miniguns. + // We don't have a wind-down sound. + return; + } + else if ( HasSpinSounds() && m_flBarrelCurrentVelocity > 0 ) + { + iSound = SPECIAL2; // wind down sound + + if ( m_flBarrelTargetVelocity > 0 ) + { + m_flBarrelTargetVelocity = 0; + } + } + else + { + iSound = -1; + } + break; + case AC_STATE_STARTFIRING: + iSound = SPECIAL1; // wind up sound + break; + case AC_STATE_FIRING: + { + if ( m_bCritShot == true ) + { + iSound = BURST; // Crit sound + } + else + { + iSound = WPN_DOUBLE; // firing sound + } + } + break; + case AC_STATE_SPINNING: + if ( HasSpinSounds() ) + iSound = SPECIAL3; // spinning sound + else + return; + break; + case AC_STATE_DRYFIRE: + iSound = EMPTY; // out of ammo, still trying to fire + break; + default: + Assert( false ); + break; + } + + // Get the pitch we should play at + float flPitch = 1.0f; + + float flSpeed = ApplyFireDelay( 1.0f ); + if ( flSpeed != 1.0f ) + { + flPitch = RemapValClamped( flSpeed, 1.5f, 0.5f, 80.f, 120.f ); + } + + if ( m_bRageDraining ) + { + flPitch /= 1.65; + } + + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + // if we're already playing the desired sound, nothing to do + if ( m_iMinigunSoundCur == iSound && m_bPrevRageDraining == m_bRageDraining ) + { + // If the pitch is different we need to modify it + if ( m_flMinigunSoundCurrentPitch != flPitch ) + { + m_flMinigunSoundCurrentPitch = flPitch; + + if ( m_pSoundCur ) + { + controller.SoundChangePitch( m_pSoundCur, m_flMinigunSoundCurrentPitch, 0.3f ); + } + } + return; + } + + // if we're playing some other sound, stop it + if ( m_pSoundCur ) + { + // Stop the previous sound immediately + CSoundEnvelopeController::GetController().SoundDestroy( m_pSoundCur ); + m_pSoundCur = NULL; + } + m_iMinigunSoundCur = iSound; + // if there's no sound to play for current state, we're done + if ( -1 == iSound ) + return; + + m_flMinigunSoundCurrentPitch = flPitch; + + // play the appropriate sound + const char *shootsound = GetShootSound( iSound ); + CLocalPlayerFilter filter; + m_pSoundCur = controller.SoundCreate( filter, entindex(), shootsound ); + controller.Play( m_pSoundCur, 1.0, 100 ); + controller.SoundChangeVolume( m_pSoundCur, 1.0, 0.1 ); + + if ( m_flMinigunSoundCurrentPitch != 1.0f ) + { + controller.SoundChangePitch( m_pSoundCur, m_flMinigunSoundCurrentPitch, 0.0 ); + } + + m_bPrevRageDraining = m_bRageDraining; +} + +void CTFMinigun::PlayStopFiringSound() +{ + if ( m_pSoundCur ) + { + CSoundEnvelopeController::GetController().SoundDestroy( m_pSoundCur ); + m_pSoundCur = NULL; + } + + m_iMinigunSoundCur = SPECIAL2; + + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + const char *shootsound = GetShootSound( SPECIAL2 ); + CLocalPlayerFilter filter; + m_pSoundCur = controller.SoundCreate( filter, entindex(), shootsound ); + controller.Play( m_pSoundCur, 1.0, 100 ); + controller.SoundChangeVolume( m_pSoundCur, 1.0, 0.1 ); +} + +#endif |