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