summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_minigun.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_weapon_minigun.cpp')
-rw-r--r--game/shared/tf/tf_weapon_minigun.cpp1496
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