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