diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_weapon_sniperrifle.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/tf/tf_weapon_sniperrifle.cpp')
| -rw-r--r-- | game/shared/tf/tf_weapon_sniperrifle.cpp | 2271 |
1 files changed, 2271 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_sniperrifle.cpp b/game/shared/tf/tf_weapon_sniperrifle.cpp new file mode 100644 index 0000000..40f095f --- /dev/null +++ b/game/shared/tf/tf_weapon_sniperrifle.cpp @@ -0,0 +1,2271 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: TF Sniper Rifle +// +//=============================================================================// +#include "cbase.h" +#include "tf_fx_shared.h" +#include "tf_weapon_sniperrifle.h" +#include "in_buttons.h" +#include "tf_gamerules.h" + +// Client specific. +#ifdef CLIENT_DLL +#include "view.h" +#include "beamdraw.h" +#include "vgui/ISurface.h" +#include <vgui/ILocalize.h> +#include "vgui_controls/Controls.h" +#include "hud_crosshair.h" +#include "functionproxy.h" +#include "materialsystem/imaterialvar.h" +#include "toolframework_client.h" +#include "input.h" +#include "client_virtualreality.h" +#include "sourcevr/isourcevirtualreality.h" + +// forward declarations +void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); +#else +#include "tf_gamerules.h" +#include "tf_fx.h" +#endif + +#define TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC 50.0 +#define TF_WEAPON_SNIPERRIFLE_UNCHARGE_PER_SEC 75.0 +#define TF_WEAPON_SNIPERRIFLE_DAMAGE_MIN 50 +#define TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX 150 +#define TF_WEAPON_SNIPERRIFLE_RELOAD_TIME 1.5f +#define TF_WEAPON_SNIPERRIFLE_ZOOM_TIME 0.3f + +#define TF_WEAPON_SNIPERRIFLE_NO_CRIT_AFTER_ZOOM_TIME 0.2f + +#define SNIPER_DOT_SPRITE_RED "effects/sniperdot_red.vmt" +#define SNIPER_DOT_SPRITE_BLUE "effects/sniperdot_blue.vmt" +#define SNIPER_CHARGE_BEAM_RED "tfc_sniper_charge_red" +#define SNIPER_CHARGE_BEAM_BLUE "tfc_sniper_charge_blue" + +#ifdef CLIENT_DLL +ConVar tf_sniper_fullcharge_bell( "tf_sniper_fullcharge_bell", "0", FCVAR_ARCHIVE ); +#endif + +//============================================================================= +// +// Weapon Sniper Rifles tables. +// + +IMPLEMENT_NETWORKCLASS_ALIASED( TFSniperRifle, DT_TFSniperRifle ) + +BEGIN_NETWORK_TABLE_NOBASE( CTFSniperRifle, DT_SniperRifleLocalData ) +#if !defined( CLIENT_DLL ) + SendPropFloat( SENDINFO(m_flChargedDamage), 0, SPROP_NOSCALE | SPROP_CHANGES_OFTEN ), +#else + RecvPropFloat( RECVINFO(m_flChargedDamage) ), +#endif +END_NETWORK_TABLE() + +BEGIN_NETWORK_TABLE( CTFSniperRifle, DT_TFSniperRifle ) +#if !defined( CLIENT_DLL ) + SendPropDataTable( "SniperRifleLocalData", 0, &REFERENCE_SEND_TABLE( DT_SniperRifleLocalData ), SendProxy_SendLocalWeaponDataTable ), +#else + RecvPropDataTable( "SniperRifleLocalData", 0, 0, &REFERENCE_RECV_TABLE( DT_SniperRifleLocalData ) ), +#endif +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFSniperRifle ) +#ifdef CLIENT_DLL + DEFINE_PRED_FIELD( m_flUnzoomTime, FIELD_FLOAT, 0 ), + DEFINE_PRED_FIELD( m_flRezoomTime, FIELD_FLOAT, 0 ), + DEFINE_PRED_FIELD( m_bRezoomAfterShot, FIELD_BOOLEAN, 0 ), + DEFINE_PRED_FIELD( m_flChargedDamage, FIELD_FLOAT, 0 ), +#endif +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_sniperrifle, CTFSniperRifle ); +PRECACHE_WEAPON_REGISTER( tf_weapon_sniperrifle ); + +BEGIN_DATADESC( CTFSniperRifle ) +DEFINE_FIELD( m_flUnzoomTime, FIELD_FLOAT ), +DEFINE_FIELD( m_flRezoomTime, FIELD_FLOAT ), +DEFINE_FIELD( m_bRezoomAfterShot, FIELD_BOOLEAN ), +DEFINE_FIELD( m_flChargedDamage, FIELD_FLOAT ), +END_DATADESC() + +//============================================================================= + +IMPLEMENT_NETWORKCLASS_ALIASED( TFSniperRifleDecap, DT_TFSniperRifleDecap ) + +BEGIN_NETWORK_TABLE( CTFSniperRifleDecap, DT_TFSniperRifleDecap ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFSniperRifleDecap ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_sniperrifle_decap, CTFSniperRifleDecap ); +PRECACHE_WEAPON_REGISTER( tf_weapon_sniperrifle_decap ); + +//============================================================================= + +IMPLEMENT_NETWORKCLASS_ALIASED( TFSniperRifleClassic, DT_TFSniperRifleClassic ) + +BEGIN_NETWORK_TABLE( CTFSniperRifleClassic, DT_TFSniperRifleClassic ) +#if !defined( CLIENT_DLL ) + SendPropBool( SENDINFO(m_bCharging) ), +#else + RecvPropBool( RECVINFO(m_bCharging) ), +#endif +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFSniperRifleClassic ) +#ifdef CLIENT_DLL + DEFINE_PRED_FIELD( m_bCharging, FIELD_BOOLEAN, 0 ), +#endif +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_sniperrifle_classic, CTFSniperRifleClassic ); +PRECACHE_WEAPON_REGISTER( tf_weapon_sniperrifle_classic ); +//============================================================================= + +#ifdef STAGING_ONLY + +IMPLEMENT_NETWORKCLASS_ALIASED( TFSniperRifleRevolver, DT_TFSniperRifleRevolver ) + +BEGIN_NETWORK_TABLE( CTFSniperRifleRevolver, DT_TFSniperRifleRevolver ) +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CTFSniperRifleRevolver ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( tf_weapon_sniperrifle_revolver, CTFSniperRifleRevolver ); +PRECACHE_WEAPON_REGISTER( tf_weapon_sniperrifle_revolver ); + +#endif // STAGING_ONLY + +//============================================================================= +// +// Weapon Sniper Rifles functions. +// + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CTFSniperRifle::CTFSniperRifle() +{ +// Server specific. +#ifdef GAME_DLL + m_hSniperDot = NULL; +#else + m_bPlayedBell = false; +#endif + + m_bCurrentShotIsHeadshot = false; + m_flChargedDamage = 0.0f; + m_flChargePerSec = TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC; + + m_bWasAimedAtEnemy = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor. +//----------------------------------------------------------------------------- +CTFSniperRifle::~CTFSniperRifle() +{ +// Server specific. +#ifdef GAME_DLL + DestroySniperDot(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::Spawn() +{ + m_iAltFireHint = HINT_ALTFIRE_SNIPERRIFLE; + BaseClass::Spawn(); + + ResetTimers(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::Precache() +{ + BaseClass::Precache(); + PrecacheModel( SNIPER_DOT_SPRITE_RED ); + PrecacheModel( SNIPER_DOT_SPRITE_BLUE ); + + PrecacheScriptSound( "doomsday.warhead" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ResetTimers( void ) +{ + m_flUnzoomTime = -1; + m_flRezoomTime = -1; + m_bRezoomAfterShot = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::Reload( void ) +{ + // We currently don't reload. + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::CanHolster( void ) const +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( pPlayer ) + { + // 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; + + // don't allow us to holster this weapon if we're in the process of zooming and + // we've just fired the weapon (next primary attack is only 1.5 seconds after firing) + if ( ( pPlayer->GetFOV() < pPlayer->GetDefaultFOV() ) && ( m_flNextPrimaryAttack > gpGlobals->curtime ) ) + return false; + } + + return BaseClass::CanHolster(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ +// Server specific. +#ifdef GAME_DLL + // Destroy the sniper dot. + DestroySniperDot(); +#endif + + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) + { + ZoomOut(); + } + + m_flChargedDamage = 0.0f; +#ifdef CLIENT_DLL + m_bPlayedBell = false; +#endif + ResetTimers(); + + return BaseClass::Holster( pSwitchingTo ); +} + +void CTFSniperRifle::WeaponReset( void ) +{ + BaseClass::WeaponReset(); + + ZoomOut(); + + m_bCurrentShotIsHeadshot = false; + m_flChargePerSec = TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::OwnerCanJump( void ) +{ + return gpGlobals->curtime > m_flUnzoomTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::HandleZooms( void ) +{ + // Get the owning player. + CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); + if ( !pPlayer ) + return; + + // Handle the zoom when taunting. + if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) + { + if ( pPlayer->m_Shared.InCond( TF_COND_AIMING ) ) + { + ToggleZoom(); + } + + //Don't rezoom in the middle of a taunt. + ResetTimers(); + } + + if ( m_flUnzoomTime > 0 && gpGlobals->curtime > m_flUnzoomTime ) + { + if ( m_bRezoomAfterShot ) + { + ZoomOutIn(); + m_bRezoomAfterShot = false; + } + else + { + ZoomOut(); + } + + m_flUnzoomTime = -1; + } + + if ( m_flRezoomTime > 0 ) + { + if ( gpGlobals->curtime > m_flRezoomTime ) + { + ZoomIn(); + m_flRezoomTime = -1; + } + } + + if ( ( pPlayer->m_nButtons & IN_ATTACK2 ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) ) + { + // If we're in the process of rezooming, just cancel it + if ( m_flRezoomTime > 0 || m_flUnzoomTime > 0 ) + { + // Prevent them from rezooming in less time than they would have + m_flNextSecondaryAttack = m_flRezoomTime + TF_WEAPON_SNIPERRIFLE_ZOOM_TIME; + m_flRezoomTime = -1; + } + else + { + Zoom(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ItemPostFrame( void ) +{ + // If we're lowered, we're not allowed to fire + if ( m_bLowered ) + return; + + // Get the owning player. + CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); + if ( !pPlayer ) + return; + + if ( !CanAttack() ) + { + if ( IsZoomed() ) + { + ToggleZoom(); + } + return; + } + + HandleZooms(); + +#ifdef GAME_DLL + // Update the sniper dot position if we have one + if ( m_hSniperDot ) + { + UpdateSniperDot(); + } +#endif + + // Start charging when we're zoomed in, and allowed to fire + if ( pPlayer->m_Shared.IsJumping() ) + { + // Unzoom if we're jumping + if ( IsZoomed() ) + { + ToggleZoom(); + } + + m_flChargedDamage = 0.0f; + m_bRezoomAfterShot = false; + m_flRezoomTime = -1.f; + } + else if ( m_flNextSecondaryAttack <= gpGlobals->curtime ) + { + // Don't start charging in the time just after a shot before we unzoom to play rack anim. + if ( pPlayer->m_Shared.InCond( TF_COND_AIMING ) && !m_bRezoomAfterShot ) + { + float fSniperRifleChargePerSec = m_flChargePerSec; + ApplyChargeSpeedModifications( fSniperRifleChargePerSec ); + fSniperRifleChargePerSec += SniperRifleChargeRateMod(); + + // we don't want sniper charge rate to go too high. + fSniperRifleChargePerSec = clamp( fSniperRifleChargePerSec, 0, 2.f * TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC ); + + m_flChargedDamage = MIN( m_flChargedDamage + gpGlobals->frametime * fSniperRifleChargePerSec, TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX ); + +#ifdef CLIENT_DLL + // play the recharged bell if we're fully charged + if ( IsFullyCharged() && !m_bPlayedBell ) + { + m_bPlayedBell = true; + if ( tf_sniper_fullcharge_bell.GetBool() ) + { + C_TFPlayer::GetLocalTFPlayer()->EmitSound( "TFPlayer.ReCharged" ); + } + } +#endif + } + else + { + m_flChargedDamage = MAX( 0, m_flChargedDamage - gpGlobals->frametime * TF_WEAPON_SNIPERRIFLE_UNCHARGE_PER_SEC ); + } + } + + // Fire. + if ( pPlayer->m_nButtons & IN_ATTACK ) + { + Fire( pPlayer ); + } + + // Idle. + if ( !( ( pPlayer->m_nButtons & IN_ATTACK) || ( pPlayer->m_nButtons & IN_ATTACK2 ) ) ) + { + // No fire buttons down or reloading + if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) ) + { + WeaponIdle(); + } + } + + // Sniper Rage (Hitman's heatmaker) + // Activate on 'R' + // no longer need full charge + if ( (pPlayer->m_nButtons & IN_RELOAD ) && pPlayer->m_Shared.GetRageMeter() > 1.0f ) + { + int iBuffType = 0; + CALL_ATTRIB_HOOK_INT( iBuffType, set_buff_type ); + if ( iBuffType > 0 ) + { + pPlayer->m_Shared.ActivateRageBuff( pPlayer, iBuffType ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::PlayWeaponShootSound( void ) +{ + if ( TFGameRules()->GameModeUsesUpgrades() ) + { + PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" ); + } + + if ( !IsFullyCharged() ) + { + float flDamageBonus = 1.0f; + CALL_ATTRIB_HOOK_FLOAT( flDamageBonus, sniper_full_charge_damage_bonus ); + if ( flDamageBonus > 1.0f ) + { + WeaponSound( SPECIAL3 ); + return; + } + } + + BaseClass::PlayWeaponShootSound(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::Lower( void ) +{ + if ( BaseClass::Lower() ) + { + if ( IsZoomed() ) + { + ToggleZoom(); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Secondary attack. +//----------------------------------------------------------------------------- +void CTFSniperRifle::Zoom( void ) +{ + // Don't allow the player to zoom in while jumping + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( pPlayer && pPlayer->m_Shared.IsJumping() ) + { + if ( pPlayer->GetFOV() >= 75 ) + return; + } + + ToggleZoom(); + + // at least 0.1 seconds from now, but don't stomp a previous value + m_flNextPrimaryAttack = MAX( m_flNextPrimaryAttack, gpGlobals->curtime + 0.1 ); + m_flNextSecondaryAttack = gpGlobals->curtime + TF_WEAPON_SNIPERRIFLE_ZOOM_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ZoomOutIn( void ) +{ + ZoomOut(); + + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( pPlayer && pPlayer->ShouldAutoRezoom() ) + { + float flRezoomDelay = 0.9f; + if ( !UsesClipsForAmmo1() ) + { + // Since sniper rifles don't actually use clips the fast reload hook also affects unzoom and zoom delays + ApplyScopeSpeedModifications( flRezoomDelay ); + } + m_flRezoomTime = gpGlobals->curtime + flRezoomDelay; + } + else + { + m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ZoomIn( void ) +{ + // Start aiming. + CTFPlayer *pPlayer = GetTFPlayerOwner(); + + if ( !pPlayer ) + return; + + if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + return; + + BaseClass::ZoomIn(); + + pPlayer->m_Shared.AddCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + +#ifdef GAME_DLL + // Create the sniper dot. + CreateSniperDot(); + pPlayer->ClearExpression(); +#endif +} + + +//----------------------------------------------------------------------------- +bool CTFSniperRifle::IsZoomed( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + + if ( pPlayer ) + { + return pPlayer->m_Shared.InCond( TF_COND_ZOOMED ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// +// Have we been zoomed in long enough for our shot to do max damage +// +bool CTFSniperRifle::IsFullyCharged( void ) const +{ + return m_flChargedDamage >= TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ZoomOut( void ) +{ + BaseClass::ZoomOut(); + + // Stop aiming + CTFPlayer *pPlayer = GetTFPlayerOwner(); + + if ( !pPlayer ) + return; + + pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + +#ifdef GAME_DLL + // Destroy the sniper dot. + DestroySniperDot(); + pPlayer->ClearExpression(); +#endif + + // if we are thinking about zooming, cancel it + m_flUnzoomTime = -1; + m_flRezoomTime = -1; + m_bRezoomAfterShot = false; + m_flChargedDamage = 0.0f; + +#ifdef CLIENT_DLL + m_bPlayedBell = false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ApplyScopeSpeedModifications( float &flBaseRef ) +{ + CALL_ATTRIB_HOOK_FLOAT( flBaseRef, fast_reload ); + + // Prototype hack + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE || pPlayer->m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) + { + flBaseRef *= 0.5f; + } + else if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KING || pPlayer->m_Shared.InCond( TF_COND_KING_BUFFED ) ) + { + flBaseRef *= 0.75f; + } + + int iMaster = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iMaster, ability_master_sniper ); + if ( iMaster ) + { + flBaseRef *= RemapValClamped( iMaster, 1, 2, 0.6f, 0.3f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ApplyChargeSpeedModifications( float &flBaseRef ) +{ + CALL_ATTRIB_HOOK_FLOAT( flBaseRef, mult_sniper_charge_per_sec ); + + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + Vector vForward; + AngleVectors( pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), &vForward ); + + Vector vShootPos = pPlayer->Weapon_ShootPosition(); + trace_t tr; + UTIL_TraceLine( vShootPos, vShootPos + vForward * m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flRange, MASK_BLOCKLOS_AND_NPCS, pPlayer, COLLISION_GROUP_NONE, &tr ); + + CTFPlayer *pTarget = ToTFPlayer( tr.m_pEnt ); + if ( pTarget && pTarget->IsAlive() && pTarget->GetTeamNumber() != pPlayer->GetTeamNumber() && + !( pTarget->m_Shared.IsStealthed() && !pTarget->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ) ) + { + CALL_ATTRIB_HOOK_FLOAT( flBaseRef, mult_sniper_charge_per_sec_with_enemy_under_crosshair ); + + int nBeep = 0; + CALL_ATTRIB_HOOK_FLOAT( nBeep, sniper_beep_with_enemy_under_crosshair ); + + if ( nBeep > 0 && !m_bWasAimedAtEnemy ) + { + pPlayer->EmitSound( "doomsday.warhead" ); + } + + m_bWasAimedAtEnemy = true; + } + else + { + m_bWasAimedAtEnemy = false; + } + + if ( pPlayer && ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_PRECISION || pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) ) + { + flBaseRef *= 3.0f; + } + else if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KING || pPlayer->m_Shared.InCond( TF_COND_KING_BUFFED ) ) + { + flBaseRef *= 1.5f; + } + + + // Prototype hack + int iMaster = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iMaster, ability_master_sniper ); + if ( iMaster ) + { + flBaseRef *= RemapValClamped( iMaster, 1, 2, 1.5f, 3.f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::MustBeZoomedToFire( void ) +{ + int iModOnlyFireZoomed = 0; + CALL_ATTRIB_HOOK_INT( iModOnlyFireZoomed, sniper_only_fire_zoomed ); + return ( iModOnlyFireZoomed != 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::HandleNoScopeFireDeny( void ) +{ + if ( m_flNextEmptySoundTime < gpGlobals->curtime ) + { + WeaponSound( SPECIAL2 ); + +#ifdef CLIENT_DLL + ParticleProp()->Init( this ); + ParticleProp()->Create( "dxhr_sniper_fizzle", PATTACH_POINT_FOLLOW, "muzzle" ); +#endif + + m_flNextEmptySoundTime = gpGlobals->curtime + 0.5; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ETFDmgCustom CTFSniperRifle::GetPenetrateType() const +{ + if ( IsFullyCharged() ) + { + int iPenetrate = 0; + CALL_ATTRIB_HOOK_INT( iPenetrate, sniper_penetrate_players_when_charged ); + if ( iPenetrate > 0 ) + return TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS; + } + + return BaseClass::GetPenetrateType(); +} + +#ifdef SIXENSE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFSniperRifle::GetRezoomTime() const +{ + return m_flRezoomTime; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::Fire( CTFPlayer *pPlayer ) +{ + // Check the ammo. We don't use clip ammo, check the primary ammo type. + if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + { + HandleFireOnEmpty(); + return; + } + + // Some weapons can only fire while zoomed + if ( MustBeZoomedToFire() ) + { + if ( !IsZoomed() ) + { + HandleNoScopeFireDeny(); + return; + } + } + + if ( m_flNextPrimaryAttack > gpGlobals->curtime ) + return; + + // Fire the sniper shot. + PrimaryAttack(); + + if ( IsZoomed() ) + { + // If we have more bullets, zoom out, play the bolt animation and zoom back in + if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 ) + { + // do not zoom out if we're under rage or about to enter it + if ( !( pPlayer->m_Shared.InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) ) ) + { + float flUnzoomDelay = 0.5f; + if ( !UsesClipsForAmmo1() ) + { + // Since sniper rifles don't actually use clips the fast reload hook also affects unzoom and zoom delays + ApplyScopeSpeedModifications( flUnzoomDelay ); + } + SetRezoom( true, flUnzoomDelay ); // zoom out in 0.5 seconds, then rezoom + } + } + else + { + //just zoom out + SetRezoom( false, 0.5f ); // just zoom out in 0.5 seconds + } + } + else + { + float flZoomDelay = SequenceDuration(); + + // Since sniper rifles don't actually use clips the fast reload hook also affects zoom delays + ApplyScopeSpeedModifications( flZoomDelay ); + + // Prevent primary fire preventing zooms + m_flNextSecondaryAttack = gpGlobals->curtime + flZoomDelay; + } + + m_flChargedDamage = 0.0f; + +#ifdef GAME_DLL + if ( m_hSniperDot ) + { + m_hSniperDot->ResetChargeTime(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::SetRezoom( bool bRezoom, float flDelay ) +{ + m_flUnzoomTime = gpGlobals->curtime + flDelay; + + m_bRezoomAfterShot = bRezoom; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CTFSniperRifle::GetProjectileDamage( void ) +{ + float flDamage = MAX( m_flChargedDamage, TF_WEAPON_SNIPERRIFLE_DAMAGE_MIN ); + + float flDamageMod = 1.f; + CALL_ATTRIB_HOOK_FLOAT( flDamageMod, mult_dmg ); + flDamage *= flDamageMod; + + + if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) + { + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + + if ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) + { + flDamage *= 2.f; + } + } + + if ( IsFullyCharged() ) + { + CALL_ATTRIB_HOOK_FLOAT( flDamage, sniper_full_charge_damage_bonus ); + } + + return flDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFSniperRifle::GetDamageType( void ) const +{ + // Only do hit location damage if we're zoomed + CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); + if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) + return BaseClass::GetDamageType(); + + int nDamageType = BaseClass::GetDamageType() & ~DMG_USE_HITLOCATIONS; + + return nDamageType; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::CreateSniperDot( void ) +{ +// Server specific. +#ifdef GAME_DLL + + // Check to see if we have already been created? + if ( m_hSniperDot ) + return; + + // Get the owning player (make sure we have one). + CBaseCombatCharacter *pPlayer = GetOwner(); + if ( !pPlayer ) + return; + + // Create the sniper dot, but do not make it visible yet. + m_hSniperDot = CSniperDot::Create( GetAbsOrigin(), pPlayer, true ); + m_hSniperDot->ChangeTeam( pPlayer->GetTeamNumber() ); + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::DestroySniperDot( void ) +{ +// Server specific. +#ifdef GAME_DLL + + // Destroy the sniper dot. + if ( m_hSniperDot ) + { + UTIL_Remove( m_hSniperDot ); + m_hSniperDot = NULL; + } + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::UpdateSniperDot( void ) +{ +// Server specific. +#ifdef GAME_DLL + + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if ( !pPlayer ) + return; + + // Get the start and endpoints. + Vector vecMuzzlePos = pPlayer->Weapon_ShootPosition(); + Vector forward; + pPlayer->EyeVectors( &forward ); + Vector vecEndPos = vecMuzzlePos + ( forward * MAX_TRACE_LENGTH ); + + trace_t trace; + UTIL_TraceLine( vecMuzzlePos, vecEndPos, ( MASK_SHOT & ~CONTENTS_WINDOW ), GetOwner(), COLLISION_GROUP_NONE, &trace ); + + // Update the sniper dot. + if ( m_hSniperDot ) + { + CBaseEntity *pEntity = NULL; + if ( trace.DidHitNonWorldEntity() ) + { + pEntity = trace.m_pEnt; + if ( !pEntity || !pEntity->m_takedamage ) + { + pEntity = NULL; + } + } + + m_hSniperDot->Update( pEntity, trace.endpos, trace.plane.normal ); + } + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::CanFireCriticalShot( bool bIsHeadshot ) +{ + m_bCurrentAttackIsCrit = false; + m_bCurrentShotIsHeadshot = false; + + if ( !BaseClass::CanFireCriticalShot( bIsHeadshot ) ) + return false; + + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( pPlayer && pPlayer->m_Shared.IsCritBoosted() ) + { + m_bCurrentShotIsHeadshot = bIsHeadshot; + return true; + } + + // If we don't auto crit on a headshot, use standard criteria to determine other crits. + if ( GetRifleType() == RIFLE_JARATE ) + { + return false; // Never auto crit on headshot. + } + + // can only fire a crit shot if this is a headshot, unless we're critboosted + if ( !bIsHeadshot ) + return false; + + int iFullChargeHeadShotPenalty = 0; + CALL_ATTRIB_HOOK_INT( iFullChargeHeadShotPenalty, sniper_no_headshot_without_full_charge ); + if ( iFullChargeHeadShotPenalty != 0 ) + { + if ( !IsFullyCharged() ) + return false; + } + + int iCanCritNoScope = 0; + CALL_ATTRIB_HOOK_INT( iCanCritNoScope, sniper_crit_no_scope ); + if ( iCanCritNoScope == 0 ) + { + if ( pPlayer ) + { + // no crits if they're not zoomed + if ( pPlayer->GetFOV() >= pPlayer->GetDefaultFOV() ) + { + return false; + } + + // no crits for 0.2 seconds after starting to zoom + if ( ( gpGlobals->curtime - pPlayer->GetFOVTime() ) < TF_WEAPON_SNIPERRIFLE_NO_CRIT_AFTER_ZOOM_TIME ) + { + return false; + } + } + } + + m_bCurrentAttackIsCrit = true; + m_bCurrentShotIsHeadshot = bIsHeadshot; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Our owner was stunned. +//----------------------------------------------------------------------------- +void CTFSniperRifle::OnControlStunned( void ) +{ + BaseClass::OnControlStunned(); + + ZoomOut(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFSniperRifle::GetCustomDamageType() const +{ + if ( IsJarateRifle() ) + { + return TF_DMG_CUSTOM_PENETRATE_NONBURNING_TEAMMATE; + } + + return TF_DMG_CUSTOM_PENETRATE_MY_TEAM; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::Detach( void ) +{ + if ( IsZoomed() ) + { + ToggleZoom(); + } + + BaseClass::Detach(); +} + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::OnPlayerKill( CTFPlayer *pVictim, const CTakeDamageInfo &info ) +{ + BaseClass::OnPlayerKill( pVictim, info ); + + if ( m_iConsecutiveKills == 3 ) + { + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + pPlayer->AwardAchievement( ACHIEVEMENT_TF_SNIPER_RIFLE_NO_MISSING ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::OnBulletFire( int iEnemyPlayersHit ) +{ + BaseClass::OnBulletFire( iEnemyPlayersHit ); + + // Did we completely miss? + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if( iEnemyPlayersHit == 0 && pPlayer && pPlayer->m_Shared.InCond( TF_COND_AIMING ) ) + { + EconEntity_OnOwnerKillEaterEventNoPartner( assert_cast<CEconEntity *>( this ), pPlayer, kKillEaterEvent_NEGATIVE_SniperShotsMissed ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifle::ExplosiveHeadShot( CTFPlayer *pAttacker, CTFPlayer *pVictim ) +{ + if ( !pAttacker ) + return; + + if ( !pVictim ) + return; + + int iExplosiveShot = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER ( pAttacker, iExplosiveShot, explosive_sniper_shot ); + + // Stun the source + float flStunDuration = 1.f + ( ( (float)iExplosiveShot - 1.f ) * 0.5f ); + float flStunAmt = pVictim->IsMiniBoss() ? 0.5f : RemapValClamped( iExplosiveShot, 1, 3, 0.5f, 0.8f ); + pVictim->m_Shared.StunPlayer( flStunDuration, flStunAmt, TF_STUN_MOVEMENT, pAttacker ); + + // Generate an explosion and look for nearby bots + float flDmgRange = 125.f + iExplosiveShot * 25.f; + float flDmg = 130.f + iExplosiveShot * 20.f; + + CBaseEntity *pObjects[ 32 ]; + int nCount = UTIL_EntitiesInSphere( pObjects, ARRAYSIZE( pObjects ), pVictim->GetAbsOrigin(), flDmgRange, FL_CLIENT ); + for ( int i = 0; i < nCount; i++ ) + { + if ( !pObjects[i] ) + continue; + + if ( !pObjects[i]->IsAlive() ) + continue; + + if ( pObjects[i] == pVictim ) + continue; + + if ( pAttacker->InSameTeam( pObjects[i] ) ) + continue; + + if ( !pVictim->FVisible( pObjects[i], MASK_OPAQUE ) ) + continue; + + CTFPlayer *pTFPlayer = static_cast<CTFPlayer *>( pObjects[i] ); + if ( !pTFPlayer ) + continue; + + if ( pTFPlayer->m_Shared.InCond( TF_COND_PHASE ) || pTFPlayer->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) + continue; + + if ( pTFPlayer->m_Shared.IsInvulnerable() ) + continue; + + // Stun + flStunAmt = pTFPlayer->IsMiniBoss() ? 0.5f : RemapValClamped( iExplosiveShot, 1, 3, 0.5f, 0.8f ); + pTFPlayer->m_Shared.StunPlayer( flStunDuration, flStunAmt, TF_STUN_MOVEMENT, pAttacker ); + + // DoT + pTFPlayer->m_Shared.MakeBleed( pAttacker, this, 0.1f, flDmg ); + + // Shoot a beam at them + CPVSFilter filter( pTFPlayer->WorldSpaceCenter() ); + Vector vStart = pVictim->EyePosition(); + Vector vEnd = pTFPlayer->EyePosition(); + te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd }; + TE_TFParticleEffectComplex( filter, 0.0f, "dxhr_arm_muzzleflash", vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTFPlayer, PATTACH_CUSTOMORIGIN ); + + pTFPlayer->EmitSound( "Weapon_Upgrade.ExplosiveHeadshot" ); + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::IsJarateRifle( void ) const +{ + return GetJarateTimeInternal() > 0.f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFSniperRifle::GetJarateTime( void ) const +{ + if ( m_flChargedDamage > 0.f ) + { + return GetJarateTimeInternal(); + } + else + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFSniperRifle::GetJarateTimeInternal( void ) const +{ + float flMaxJarateTime = 0.0f; + CALL_ATTRIB_HOOK_FLOAT( flMaxJarateTime, jarate_duration ); + if ( flMaxJarateTime > 0 ) + { + const float flMinJarateTime = 2.f; + float flDuration = RemapValClamped( m_flChargedDamage, TF_WEAPON_SNIPERRIFLE_DAMAGE_MIN, TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX, flMinJarateTime, flMaxJarateTime ); + return flDuration; + } + + return 0.f; +} + +//----------------------------------------------------------------------------- +// Purpose: UI Progress (same as GetProgress() without the division by 100.0f) +//----------------------------------------------------------------------------- +bool CTFSniperRifle::IsRageFull( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + { + return false; + } + + return ( pPlayer->m_Shared.GetRageMeter() >= 100.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::EffectMeterShouldFlash( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + { + return false; + } + + if ( pPlayer && ( IsRageFull() || pPlayer->m_Shared.IsRageDraining() ) ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: UI Progress +//----------------------------------------------------------------------------- +float CTFSniperRifle::GetProgress( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + { + return 0.f; + } + + return pPlayer->m_Shared.GetRageMeter() / 100.0f; +} + +//============================================================================= +// +// Client specific functions. +// +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifle::ShouldEjectBrass() +{ + if ( GetJarateTimeInternal() > 0.f ) + return false; + else + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFSniperRifle::GetHUDDamagePerc( void ) +{ + return (m_flChargedDamage / TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX); +} + +//----------------------------------------------------------------------------- +// Returns the sniper chargeup from 0 to 1 +//----------------------------------------------------------------------------- +class CProxySniperRifleCharge : public CResultProxy +{ +public: + void OnBind( void *pC_BaseEntity ); +}; + +void CProxySniperRifleCharge::OnBind( void *pC_BaseEntity ) +{ + Assert( m_pResult ); + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( GetSpectatorTarget() != 0 && GetSpectatorMode() == OBS_MODE_IN_EYE ) + { + pPlayer = (C_TFPlayer *)UTIL_PlayerByIndex( GetSpectatorTarget() ); + } + + if ( pPlayer ) + { + CTFSniperRifle *pWeapon = assert_cast<CTFSniperRifle*>(pPlayer->GetActiveTFWeapon()); + if ( pWeapon ) + { + float flChargeValue = ( ( 1.0 - pWeapon->GetHUDDamagePerc() ) * 0.8 ) + 0.6; + + VMatrix mat, temp; + + Vector2D center( 0.5, 0.5 ); + MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f ); + + // scale + { + Vector2D scale( 1.0f, 0.25f ); + MatrixBuildScale( temp, scale.x, scale.y, 1.0f ); + MatrixMultiply( temp, mat, mat ); + } + + MatrixBuildTranslation( temp, center.x, center.y, 0.0f ); + MatrixMultiply( temp, mat, mat ); + + // translation + { + Vector2D translation( 0.0f, flChargeValue ); + MatrixBuildTranslation( temp, translation.x, translation.y, 0.0f ); + MatrixMultiply( temp, mat, mat ); + } + + m_pResult->SetMatrixValue( mat ); + } + } + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CProxySniperRifleCharge, IMaterialProxy, "SniperRifleCharge" IMATERIAL_PROXY_INTERFACE_VERSION ); +#endif + +//============================================================================= +// +// Laser Dot functions. +// + +IMPLEMENT_NETWORKCLASS_ALIASED( SniperDot, DT_SniperDot ) + +BEGIN_NETWORK_TABLE( CSniperDot, DT_SniperDot ) +#ifdef CLIENT_DLL + RecvPropFloat( RECVINFO( m_flChargeStartTime ) ), +#else + SendPropTime( SENDINFO( m_flChargeStartTime ) ), +#endif +END_NETWORK_TABLE() + +LINK_ENTITY_TO_CLASS( env_sniperdot, CSniperDot ); + +BEGIN_DATADESC( CSniperDot ) +DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ), +DEFINE_FIELD( m_hTargetEnt, FIELD_EHANDLE ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CSniperDot::CSniperDot( void ) +{ + m_vecSurfaceNormal.Init(); + m_hTargetEnt = NULL; + +#ifdef CLIENT_DLL + m_hSpriteMaterial = NULL; + m_laserBeamEffect = NULL; +#endif + + + ResetChargeTime(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor. +//----------------------------------------------------------------------------- +CSniperDot::~CSniperDot( void ) +{ +#ifdef CLIENT_DLL + if ( m_laserBeamEffect ) + { + ParticleProp()->StopEmissionAndDestroyImmediately( m_laserBeamEffect ); + + m_laserBeamEffect = NULL; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &origin - +// Output : CSniperDot +//----------------------------------------------------------------------------- +CSniperDot *CSniperDot::Create( const Vector &origin, CBaseEntity *pOwner, bool bVisibleDot ) +{ +// Client specific. +#ifdef CLIENT_DLL + + return NULL; + +// Server specific. +#else + + // Create the sniper dot entity. + CSniperDot *pDot = static_cast<CSniperDot*>( CBaseEntity::Create( "env_sniperdot", origin, QAngle( 0.0f, 0.0f, 0.0f ) ) ); + if ( !pDot ) + return NULL; + + //Create the graphic + pDot->SetMoveType( MOVETYPE_NONE ); + pDot->AddSolidFlags( FSOLID_NOT_SOLID ); + pDot->AddEffects( EF_NOSHADOW ); + UTIL_SetSize( pDot, -Vector( 4.0f, 4.0f, 4.0f ), Vector( 4.0f, 4.0f, 4.0f ) ); + + // Set owner. + pDot->SetOwnerEntity( pOwner ); + + // Force updates even though we don't have a model. + pDot->AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); + + + return pDot; + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSniperDot::Update( CBaseEntity *pTarget, const Vector &vecOrigin, const Vector &vecNormal ) +{ + SetAbsOrigin( vecOrigin ); + m_vecSurfaceNormal = vecNormal; + m_hTargetEnt = pTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CSniperDot::GetChasePosition() +{ + return GetAbsOrigin() - m_vecSurfaceNormal * 10; +} + +//============================================================================= +// +// Client specific functions. +// +#ifdef CLIENT_DLL + +bool CSniperDot::GetRenderingPositions( C_TFPlayer *pPlayer, Vector &vecAttachment, Vector &vecEndPos, float &flSize ) +{ + if ( !pPlayer ) + return false; + + // Get the sprite rendering position. + flSize = 6.0; + bool bScaleSizeByDistance = false; + + const float c_fMaxSizeDistVR = 384.0f; + const float c_flMaxSizeDistUnzoomed = 200.0f; + + if ( !pPlayer->IsDormant() ) + { + Vector vecDir; + QAngle angles; + + float flDist = MAX_TRACE_LENGTH; + + // Always draw the dot in front of our faces when in first-person. + if ( pPlayer->IsLocalPlayer() ) + { + // Take our view position and orientation + vecAttachment = CurrentViewOrigin(); + vecDir = CurrentViewForward(); + + // Clamp the forward distance for the sniper's firstperson + flDist = c_fMaxSizeDistVR; + flSize = 2.0; + + // Make the dot bigger when charging and not zoomed in (The Classic) + if ( pPlayer->m_Shared.InCond( TF_COND_AIMING ) && !pPlayer->m_Shared.InCond( TF_COND_ZOOMED )) + { + flSize = 4.0f; + bScaleSizeByDistance = true; + } + + if ( UseVR() ) + { + // The view direction is not exactly the same as the weapon direction because of stereo, calibration, etc. + g_ClientVirtualReality.OverrideWeaponHudAimVectors ( &vecAttachment, &vecDir ); + + // No clamping, thanks - we need the distance to be correct so that + // vergence works properly, and we'll scale the size up accordingly. + flDist = MAX_TRACE_LENGTH; + bScaleSizeByDistance = true; + } + } + else + { + // Take the owning player eye position and direction. + vecAttachment = pPlayer->EyePosition(); + QAngle anglesEye = pPlayer->EyeAngles(); + AngleVectors( anglesEye, &vecDir ); + } + + trace_t tr; + CTraceFilterIgnoreFriendlyCombatItems filter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() ); + UTIL_TraceLine( vecAttachment, vecAttachment + ( vecDir * flDist ), MASK_SHOT, &filter, &tr ); + + // Backup off the hit plane, towards the source + vecEndPos = tr.endpos + vecDir * -4; + if ( UseVR() ) + { + float fDist = ( vecEndPos - vecAttachment ).Length(); + if ( fDist > c_fMaxSizeDistVR ) + { + // Scale the dot up so it's still visible in first person. + flSize *= ( fDist * ( 1.0f / c_fMaxSizeDistVR ) ); + } + } + else if ( bScaleSizeByDistance ) + { + float fDist = ( vecEndPos - vecAttachment ).Length(); + if ( fDist > c_flMaxSizeDistUnzoomed ) + { + // Scale the dot up so it's still visible in first person. + flSize *= ( fDist * ( 1.0f / c_flMaxSizeDistUnzoomed ) ); + } + } + } + else + { + // Just use our position if we can't predict it otherwise. + vecAttachment = GetAbsOrigin(); + vecEndPos = GetAbsOrigin(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// TFTODO: Make the sniper dot get brighter the more damage it will do. +//----------------------------------------------------------------------------- +int CSniperDot::DrawModel( int flags ) +{ + // Get the owning player. + C_TFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( !pPlayer ) + return -1; + + Vector vecAttachement; + Vector vecEndPos; + float flSize; + + if ( !GetRenderingPositions( pPlayer, vecAttachement, vecEndPos, flSize ) ) + { + return -1; + } + + // Draw our laser dot in space. + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( m_hSpriteMaterial, this ); + + CTFWeaponBase *pBaseWeapon = pPlayer->GetActiveTFWeapon(); + CTFSniperRifle *pWeapon = dynamic_cast< CTFSniperRifle* >( pBaseWeapon ); + + float flLifeTime = gpGlobals->curtime - m_flChargeStartTime; + float flChargePerSec = TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC; + if ( pWeapon ) + { + pWeapon->ApplyChargeSpeedModifications( flChargePerSec ); + } + + // Sniper Rage + if ( pPlayer->m_Shared.InCond( TF_COND_SNIPERCHARGE_RAGE_BUFF ) ) + { + flChargePerSec *= 1.25f; + } + + float flStrength; + + if ( pWeapon ) + { + flStrength = pWeapon->GetHUDDamagePerc(); + + // FIXME: We should find out what's causing this and fix it. + AssertMsg1( flStrength >= ( 0.0f - FLT_EPSILON ) && flStrength <= ( 1.0f + FLT_EPSILON ), "GetHUDDamagePerc returned out of range value: %f", flStrength ); + flStrength = clamp( flStrength, 0.1f, 1.0f ); + } + else + { + flStrength = RemapValClamped( flLifeTime, 0.0, TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX / flChargePerSec, 0.1f, 1.0f ); + } + + color32 innercolor = { 255, 255, 255, 255 }; + color32 outercolor = { 255, 255, 255, 128 }; + + DrawSprite( vecEndPos, flSize, flSize, outercolor ); + DrawSprite( vecEndPos, flSize * flStrength, flSize * flStrength, innercolor ); + + // Successful. + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CSniperDot::ShouldDraw( void ) +{ + if ( IsEffectActive( EF_NODRAW ) ) + return false; + + // Don't draw the sniper dot when in thirdperson. + if ( ::input->CAM_IsThirdPerson() ) + return false; + + return true; +} + +void CSniperDot::ClientThink( void ) +{ + // snipers have laser sights in PvE mode + if ( TFGameRules()->IsPVEModeActive() && GetTeamNumber() == TF_TEAM_PVE_INVADERS ) + { + C_TFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + if ( !m_laserBeamEffect ) + { + m_laserBeamEffect = ParticleProp()->Create( "laser_sight_beam", PATTACH_ABSORIGIN_FOLLOW ); + } + + if ( m_laserBeamEffect ) + { + m_laserBeamEffect->SetSortOrigin( m_laserBeamEffect->GetRenderOrigin() ); + m_laserBeamEffect->SetControlPoint( 2, Vector( 0, 0, 255 ) ); + + Vector vecAttachment; + Vector vecEndPos; + float flSize; + + if ( pPlayer->GetAttachment( "eye_1", vecAttachment ) ) + { + m_laserBeamEffect->SetControlPoint( 1, vecAttachment ); + } + else if ( GetRenderingPositions( pPlayer, vecAttachment, vecEndPos, flSize ) ) + { + m_laserBeamEffect->SetControlPoint( 1, vecAttachment ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSniperDot::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + if ( GetTeamNumber() == TF_TEAM_BLUE ) + { + m_hSpriteMaterial.Init( SNIPER_DOT_SPRITE_BLUE, TEXTURE_GROUP_CLIENT_EFFECTS ); + } + else + { + m_hSpriteMaterial.Init( SNIPER_DOT_SPRITE_RED, TEXTURE_GROUP_CLIENT_EFFECTS ); + } + + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +} + +#endif // CLIENT_DLL + +#ifdef GAME_DLL + +void CTFSniperRifleDecap::OnPlayerKill( CTFPlayer *pVictim, const CTakeDamageInfo &info ) +{ + BaseClass::OnPlayerKill( pVictim, info ); + + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer && IsHeadshot( info.GetDamageCustom() ) ) + { + // If we got a headshot kill, increment our number of decapitations. + int iDecaps = pPlayer->m_Shared.GetDecapitations() + 1; + pPlayer->m_Shared.SetDecapitations( iDecaps ); + } +} + +#endif // GAME_DLL + +static const int MAX_HEAD_BONUS = 6; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CTFSniperRifleDecap::SniperRifleChargeRateMod() +{ + return ( .25f * ( MIN( GetCount(), MAX_HEAD_BONUS ) - 2 ) ) * TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFSniperRifleDecap::GetCount( void ) +{ + CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); + if ( pPlayer ) + { + return pPlayer->m_Shared.GetDecapitations(); + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CTFSniperRifleClassic::CTFSniperRifleClassic() +{ + m_bCharging = false; +#ifdef CLIENT_DLL + m_pChargedEffect = NULL; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor. +//----------------------------------------------------------------------------- +CTFSniperRifleClassic::~CTFSniperRifleClassic() +{ +#ifdef CLIENT_DLL + if ( m_pChargedEffect ) + { + ParticleProp()->StopEmissionAndDestroyImmediately( m_pChargedEffect ); + m_pChargedEffect = NULL; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::Precache() +{ + BaseClass::Precache(); + PrecacheParticleSystem( SNIPER_CHARGE_BEAM_RED ); + PrecacheParticleSystem( SNIPER_CHARGE_BEAM_BLUE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::ZoomOut( void ) +{ + CTFWeaponBaseGun::ZoomOut(); // intentionally skipping CTFSniperRifle::ZoomOut() +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::ZoomIn( void ) +{ + // Start aiming. + CTFPlayer *pPlayer = GetTFPlayerOwner(); + + if ( !pPlayer ) + return; + + if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) + return; + + CTFWeaponBaseGun::ZoomIn(); // intentionally skipping CTFSniperRifle::ZoomIn() +} + +//----------------------------------------------------------------------------- +// Purpose: Secondary attack. +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::Zoom( void ) +{ + ToggleZoom(); + + // at least 0.1 seconds from now, but don't stomp a previous value + m_flNextSecondaryAttack = gpGlobals->curtime + TF_WEAPON_SNIPERRIFLE_ZOOM_TIME; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::HandleZooms( void ) +{ + // Get the owning player. + CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); + if ( !pPlayer ) + return; + + // Handle the zoom when taunting. + if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) + { + if ( IsZoomed() ) + { + ToggleZoom(); + return; + } + } + + if ( ( pPlayer->m_nButtons & IN_ATTACK2 ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) ) + { + Zoom(); + } +} + +#ifdef CLIENT_DLL +void CTFSniperRifleClassic::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + ManageChargeBeam(); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::ItemPostFrame( void ) +{ + // If we're lowered, we're not allowed to fire + if ( m_bLowered ) + return; + + // Get the owning player. + CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); + if ( !pPlayer ) + return; + + if ( !CanAttack() ) + { + if ( IsZoomed() ) + { + ToggleZoom(); + } + WeaponReset(); + return; + } + + HandleZooms(); + +#ifdef GAME_DLL + // Update the sniper dot position if we have one + if ( m_hSniperDot ) + { + UpdateSniperDot(); + } +#endif + + if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) + { + WeaponReset(); + return; + } + + if ( ( pPlayer->m_nButtons & IN_ATTACK ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) ) + { + if ( !m_bCharging ) + { + pPlayer->m_Shared.AddCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + + m_bCharging = true; +#ifdef GAME_DLL + // Create the sniper dot. + CreateSniperDot(); + pPlayer->ClearExpression(); +#endif + } + + float fSniperRifleChargePerSec = m_flChargePerSec; + ApplyChargeSpeedModifications( fSniperRifleChargePerSec ); + fSniperRifleChargePerSec += SniperRifleChargeRateMod(); + + // we don't want sniper charge rate to go too high. + fSniperRifleChargePerSec = clamp( fSniperRifleChargePerSec, 0, 2.f * TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC ); + + m_flChargedDamage = MIN( m_flChargedDamage + gpGlobals->frametime * fSniperRifleChargePerSec, TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX ); + +#ifdef CLIENT_DLL + // play the recharged bell if we're fully charged + if ( IsFullyCharged() && !m_bPlayedBell ) + { + m_bPlayedBell = true; + if ( tf_sniper_fullcharge_bell.GetBool() ) + { + C_TFPlayer::GetLocalTFPlayer()->EmitSound( "TFPlayer.ReCharged" ); + } + } +#endif + } + else if ( m_bCharging ) + { + if ( pPlayer->GetGroundEntity() ) + { + Fire( pPlayer ); + } + else + { + pPlayer->EmitSound( "Player.DenyWeaponSelection" ); + } + + WeaponReset(); + } + else + { + // Idle. + // No fire buttons down or reloading + if ( !ReloadOrSwitchWeapons() && ( m_bInReload == false ) ) + { + WeaponIdle(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFSniperRifleClassic::GetDamageType( void ) const +{ + return CTFWeaponBaseGun::GetDamageType(); // intentionally skipping CTFSniperRifle::GetDamageType() +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifleClassic::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + WeaponReset(); + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifleClassic::Deploy( void ) +{ + WeaponReset(); + + return BaseClass::Deploy(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::WeaponReset( void ) +{ + m_flChargedDamage = 0.0f; + m_bCharging = false; +#ifdef CLIENT_DLL + ManageChargeBeam(); +#endif + + CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); + if ( pPlayer ) + { + pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + } +#ifdef GAME_DLL + // Destroy the sniper dot. + DestroySniperDot(); + if ( pPlayer ) + { + pPlayer->ClearExpression(); + } +#else + m_bPlayedBell = false; +#endif + + m_bCurrentShotIsHeadshot = false; + m_flChargePerSec = TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC; + + CTFWeaponBase::WeaponReset(); // intentionally skipping CTFSniperRifle::WeaponReset() +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSniperRifleClassic::Lower( void ) +{ + if ( BaseClass::Lower() ) + { + WeaponReset(); + return true; + } + + return false; +} + +#ifdef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::ManageChargeBeam( void ) +{ + if ( m_bCharging ) + { + if ( !m_pChargedEffect ) + { + m_pChargedEffect = ParticleProp()->Create( ( GetTeamNumber() == TF_TEAM_RED ) ? SNIPER_CHARGE_BEAM_RED : SNIPER_CHARGE_BEAM_BLUE, PATTACH_POINT_FOLLOW, "laser" ); + } + } + else + { + if ( m_pChargedEffect ) + { + ParticleProp()->StopEmissionAndDestroyImmediately( m_pChargedEffect ); + m_pChargedEffect = NULL; + } + } +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSniperRifleClassic::Detach( void ) +{ + WeaponReset(); + BaseClass::Detach(); +} + +#ifdef STAGING_ONLY + +// ******************************************************************************************************** +// CTFSniperRifleRevolver +// ******************************************************************************************************** +void CTFSniperRifleRevolver::PrimaryAttack() +{ + BaseClass::PrimaryAttack(); +#ifdef GAME_DLL + // Head bob + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( pPlayer /*&& pPlayer->m_Shared.InCond( TF_COND_ZOOMED )*/ ) + { + float impulse = RandomFloat( -2.0f, -1.0f ); + if ( pPlayer->GetFlags() & FL_DUCKING ) + { + impulse = RandomFloat( -0.5f, -0.2f ); + } + pPlayer->ApplyPunchImpulseX( impulse ); + } + + float flCharge = (m_flChargedDamage / TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX); + + if ( flCharge > 0.99 ) + { + // Only at full charge do you get fast attack speed + // reduce the time between attacks + float flCurrTime = gpGlobals->curtime; + //float flTimeBetweenShots = m_flNextPrimaryAttack - flCurrTime; + //float flTime = RemapVal( flCharge, 0.0, 1, flTimeBetweenShots, 0.2 ); + + m_flNextPrimaryAttack = flCurrTime + 0.3; + } +#endif +} +//----------------------------------------------------------------------------- +float CTFSniperRifleRevolver::GetProjectileDamage() +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) + { + float flCharge = ( m_flChargedDamage / TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX ); + if ( flCharge > 0.99 ) + { + return 75.0f; // Full Charge dmg bonus is less then the normal one (150) + } + return 50.0f; + } + return 40.0; +} +//----------------------------------------------------------------------------- +bool CTFSniperRifleRevolver::Reload( void ) +{ + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return false; + + // do not reload if zoomed unless you are empty + if ( m_iClip1 > 0 && pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) + return false; + + bool bReload = CTFWeaponBaseGun::Reload(); // intentionally skipping CTFSniperRifle::Reload(). + if ( bReload && m_iClip1 <= 0 && m_iClip1 != -1 ) + { + if ( pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) + { + ZoomOut(); + m_bRezoomAfterShot = pPlayer->ShouldAutoRezoom(); + } + } + return bReload; +} +//----------------------------------------------------------------------------- +void CTFSniperRifleRevolver::ZoomIn( void ) +{ + // Start aiming. + CTFPlayer *pPlayer = GetTFPlayerOwner(); + + if ( !pPlayer ) + return; + + if ( m_iClip1 <= 0 && m_iClip1 != -1 ) + return; + + pPlayer->m_Shared.AddCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + m_flChargedDamage = 0; + +#ifdef GAME_DLL + // Create the sniper dot. + CreateSniperDot(); + pPlayer->ClearExpression(); +#endif + + CTFWeaponBaseGun::ZoomIn(); // intentionally skipping CTFSniperRifle::ZoomIn() +} +//----------------------------------------------------------------------------- +void CTFSniperRifleRevolver::ZoomOut( void ) +{ + // Start aiming. + CTFPlayer *pPlayer = GetTFPlayerOwner(); + if ( !pPlayer ) + return; + + pPlayer->m_Shared.RemoveCond( TF_COND_AIMING ); + pPlayer->TeamFortress_SetSpeed(); + m_flChargedDamage = 0; + +#ifdef GAME_DLL + // Destroy the sniper dot. + DestroySniperDot(); + pPlayer->ClearExpression(); +#endif + + CTFWeaponBaseGun::ZoomOut(); // intentionally skipping CTFSniperRifle::ZoomOut() +} +//----------------------------------------------------------------------------- +void CTFSniperRifleRevolver::ItemPostFrame( void ) +{ + // If we're lowered, we're not allowed to fire + if ( m_bLowered ) + return; + + // Get the owning player. + CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); + if ( !pPlayer ) + return; + + if ( !CanAttack() ) + { + if ( IsZoomed() ) + { + ToggleZoom(); + } + WeaponReset(); + } + + if ( m_bRezoomAfterShot && m_iClip1 > 0 ) + { + Zoom(); + m_bRezoomAfterShot = false; + } + + HandleZooms(); + +#ifdef GAME_DLL + // Update the sniper dot position if we have one + if ( m_hSniperDot ) + { + UpdateSniperDot(); + } +#endif + + if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) + { + WeaponReset(); + return; + } + + // Handle Charge Meter + if ( m_flNextSecondaryAttack <= gpGlobals->curtime ) + { + // Don't start charging in the time just after a shot before we unzoom to play rack anim. + if ( pPlayer->m_Shared.InCond( TF_COND_AIMING ) && !m_bRezoomAfterShot ) + { + float fSniperRifleChargePerSec = m_flChargePerSec; + ApplyChargeSpeedModifications( fSniperRifleChargePerSec ); + fSniperRifleChargePerSec += SniperRifleChargeRateMod(); + + // we don't want sniper charge rate to go too high. + fSniperRifleChargePerSec = clamp( fSniperRifleChargePerSec, 0, 2.f * TF_WEAPON_SNIPERRIFLE_CHARGE_PER_SEC ); + + m_flChargedDamage = MIN( m_flChargedDamage + gpGlobals->frametime * fSniperRifleChargePerSec, TF_WEAPON_SNIPERRIFLE_DAMAGE_MAX ); + +#ifdef CLIENT_DLL + // play the recharged bell if we're fully charged + if ( IsFullyCharged() && !m_bPlayedBell ) + { + m_bPlayedBell = true; + if ( tf_sniper_fullcharge_bell.GetBool() ) + { + C_TFPlayer::GetLocalTFPlayer()->EmitSound( "TFPlayer.ReCharged" ); + } + } +#endif + } + else + { + m_flChargedDamage = MAX( 0, m_flChargedDamage - gpGlobals->frametime * TF_WEAPON_SNIPERRIFLE_UNCHARGE_PER_SEC ); + } + } + + return CTFWeaponBaseGun::ItemPostFrame(); // intentionally skipping CTFSniperRifle::ItemPostFrame(). This should just fire the gun +} + +//----------------------------------------------------------------------------- +bool CTFSniperRifleRevolver::CanFireCriticalShot( bool bIsHeadshot ) +{ + return CTFSniperRifle::CanFireCriticalShot( bIsHeadshot ); // Skip TFC Sniper Rifle +} + +//----------------------------------------------------------------------------- +// +ConVar tf_sniper_bolt_speed( "tf_sniper_bolt_speed", "3000", FCVAR_REPLICATED, "Dev Convar - Speed of projectile for Revolver Sniper"); +ConVar tf_sniper_bolt_gravity( "tf_sniper_bolt_gravity", "0.1", FCVAR_REPLICATED, "Dev Convar - Gravity of projectile for Revolver Sniper"); +float CTFSniperRifleRevolver::GetProjectileSpeed( void ) +{ + //return 4900.0; + return tf_sniper_bolt_speed.GetFloat(); +} +//----------------------------------------------------------------------------- +float CTFSniperRifleRevolver::GetProjectileGravity( void ) +{ + //return 0.1; + return tf_sniper_bolt_gravity.GetFloat(); +} + + +#endif // STAGING_ONLY |