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.
2271 lines
61 KiB
2271 lines
61 KiB
//========= 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
|
|
|