Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1496 lines
40 KiB

//========= 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