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