summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_knife.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_weapon_knife.cpp')
-rw-r--r--game/shared/tf/tf_weapon_knife.cpp691
1 files changed, 691 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_knife.cpp b/game/shared/tf/tf_weapon_knife.cpp
new file mode 100644
index 0000000..a1015eb
--- /dev/null
+++ b/game/shared/tf/tf_weapon_knife.cpp
@@ -0,0 +1,691 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Weapon Knife.
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_gamerules.h"
+#include "tf_weapon_knife.h"
+#include "decals.h"
+#include "debugoverlay_shared.h"
+
+// Client specific.
+#ifdef CLIENT_DLL
+#include "c_tf_player.h"
+#include "c_tf_gamestats.h"
+
+// Server specific.
+#else
+#include "tf_player.h"
+#include "tf_gamestats.h"
+#include "ilagcompensationmanager.h"
+#endif
+
+//=============================================================================
+//
+// Weapon Knife tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFKnife, DT_TFWeaponKnife )
+
+BEGIN_NETWORK_TABLE( CTFKnife, DT_TFWeaponKnife )
+#if defined( CLIENT_DLL )
+ RecvPropBool( RECVINFO( m_bReadyToBackstab ) ),
+ RecvPropBool( RECVINFO( m_bKnifeExists ) ),
+ RecvPropFloat( RECVINFO( m_flKnifeRegenerateDuration ) ),
+ RecvPropFloat( RECVINFO( m_flKnifeMeltTimestamp ) ),
+#else
+ SendPropBool( SENDINFO( m_bReadyToBackstab ) ),
+ SendPropBool( SENDINFO( m_bKnifeExists ) ),
+ SendPropFloat( SENDINFO( m_flKnifeRegenerateDuration ), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO( m_flKnifeMeltTimestamp ), 0, SPROP_NOSCALE ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFKnife )
+#ifdef CLIENT_DLL
+ DEFINE_PRED_FIELD( m_bReadyToBackstab, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),
+#endif
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_knife, CTFKnife );
+PRECACHE_WEAPON_REGISTER( tf_weapon_knife );
+
+
+//=============================================================================
+//
+// Weapon Knife functions.
+//
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFKnife::CTFKnife()
+{
+ m_bReadyToBackstab = false;
+ m_flBlockedTime = 0.f;
+ m_bAllowHolsterBecauseForced = false;
+
+ ResetVars();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::ResetVars( void )
+{
+ m_bKnifeExists = true;
+ m_flKnifeRegenerateDuration = 1.0f;
+ m_flKnifeMeltTimestamp = 0.0f;
+ m_bWasTaunting = false;
+ m_bAllowHolsterBecauseForced = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: We are regenerating (ie: resupply cabinet)
+//-----------------------------------------------------------------------------
+void CTFKnife::WeaponRegenerate( void )
+{
+ BaseClass::WeaponRegenerate();
+
+ ResetVars();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::WeaponReset( void )
+{
+ BaseClass::WeaponReset();
+
+ ResetVars();
+}
+
+
+//-----------------------------------------------------------------------------
+bool CTFKnife::DoSwingTrace( trace_t &trace )
+{
+ return BaseClass::DoSwingTrace( trace );
+}
+
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::ApplyOnInjuredAttributes( CTFPlayer *pVictim, CTFPlayer *pAttacker, const CTakeDamageInfo &info )
+{
+ BaseClass::ApplyOnInjuredAttributes( pVictim, pAttacker, info );
+
+ int iMeltsInFire = 0;
+ CALL_ATTRIB_HOOK_INT( iMeltsInFire, melts_in_fire );
+ if ( iMeltsInFire > 0 && info.GetDamageType() & DMG_BURN )
+ {
+ if ( m_bKnifeExists )
+ {
+ // melt it!
+ m_bKnifeExists = false;
+ m_flKnifeRegenerateDuration = iMeltsInFire;
+ m_flKnifeMeltTimestamp = gpGlobals->curtime;
+
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( pPlayer )
+ {
+ pPlayer->EmitSound( "Icicle.Melt" );
+
+ // force switch to sapper
+ // Set flag to allow holstering during this forced switch (holstering might otherwise have been inhibited by being blocked). This addresses
+ // a corner-case where a Spy stabbing a Razorback-equipped sniper with the Spycicle and then immediately burned doesn't switch away and could backstab immediately
+ // even though their knife should have melted.
+ CBaseCombatWeapon *mySapper = pPlayer->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING );
+ m_bAllowHolsterBecauseForced = true;
+ if ( !mySapper )
+ {
+ // this should never happen
+ pPlayer->SwitchToNextBestWeapon( this );
+ }
+ else
+ {
+ pPlayer->Weapon_Switch( mySapper );
+ }
+ m_bAllowHolsterBecauseForced = false;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool CTFKnife::DecreaseRegenerationTime( float value, bool bForce )
+{
+ // didn't do anything
+ if ( m_bKnifeExists )
+ return false;
+
+ float flTime = value * 0.005f * m_flKnifeRegenerateDuration;
+ m_flKnifeMeltTimestamp -= flTime;
+ return true;
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set stealth attack bool
+//-----------------------------------------------------------------------------
+void CTFKnife::PrimaryAttack( void )
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+
+ if ( !CanAttack() )
+ return;
+
+ // Set the weapon usage mode - primary, secondary.
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+
+ m_hBackstabVictim = NULL;
+ int iBackstabVictimHealth = 0;
+
+#if !defined (CLIENT_DLL)
+ // Move other players back to history positions based on local player's lag
+ lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
+#endif
+
+ trace_t trace;
+ if ( DoSwingTrace( trace ) == true )
+ {
+ // we will hit something with the attack
+ if( trace.m_pEnt && trace.m_pEnt->IsPlayer() )
+ {
+ CTFPlayer *pTarget = ToTFPlayer( trace.m_pEnt );
+
+ if ( pTarget && pTarget->GetTeamNumber() != pPlayer->GetTeamNumber() )
+ {
+ // Deal extra damage to players when stabbing them from behind
+ if ( CanPerformBackstabAgainstTarget( pTarget ) )
+ {
+ // store the victim to compare when we do the damage
+ m_hBackstabVictim.Set( pTarget );
+ iBackstabVictimHealth = Max( m_hBackstabVictim->GetHealth(), 75 );
+ }
+ }
+ }
+ }
+
+#ifndef CLIENT_DLL
+ pPlayer->RemoveInvisibility();
+ lagcompensation->FinishLagCompensation( pPlayer );
+#endif
+
+ // Swing the weapon.
+ Swing( pPlayer );
+ Smack();
+ m_flSmackTime = -1.0f;
+
+ m_bReadyToBackstab = false; // Hand is down.
+
+#if !defined( CLIENT_DLL )
+ pPlayer->SpeakWeaponFire();
+ CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+#endif
+#ifdef CLIENT_DLL
+ C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+#endif
+
+ bool bSuccessfulBackstab = IsBackstab() && !m_hBackstabVictim->IsAlive();
+
+ ETFFlagType ignoreTypes[] = { TF_FLAGTYPE_PLAYER_DESTRUCTION };
+ if ( ShouldDisguiseOnBackstab() && bSuccessfulBackstab && !pPlayer->HasTheFlag( ignoreTypes, ARRAYSIZE( ignoreTypes ) ) )
+ {
+ // Different rules in MvM when stabbing bots
+ bool bMvM = TFGameRules() && TFGameRules()->IsMannVsMachineMode() && m_hBackstabVictim->IsBot();
+ if ( bMvM )
+ {
+ // Remove the disguise first, otherwise this attribute is overpowered
+ pPlayer->RemoveDisguise();
+ }
+
+ // We should very quickly disguise as our victim.
+ const float flDelay = bMvM ? 1.5f : 0.2f;
+ SetContextThink( &CTFKnife::DisguiseOnKill, gpGlobals->curtime + flDelay, "DisguiseOnKill" );
+ }
+ else
+ {
+ pPlayer->RemoveDisguise();
+ }
+
+
+#ifdef GAME_DLL
+ int iSanguisuge = 0;
+ CALL_ATTRIB_HOOK_INT( iSanguisuge, sanguisuge );
+ if ( bSuccessfulBackstab && iSanguisuge > 0 )
+ {
+ // Our health cap is 3x our default maximum health cap. This is so high to make up for
+ // the fact that our default is lowered by equipping the weapon.
+ int iBaseMaxHealth = pPlayer->GetMaxHealth() * 3,
+ iNewHealth = MIN( pPlayer->GetHealth() + iBackstabVictimHealth, iBaseMaxHealth ),
+ iDeltaHealth = iNewHealth - pPlayer->GetHealth();
+
+ if ( iDeltaHealth > 0 )
+ {
+ pPlayer->TakeHealth( iDeltaHealth, DMG_IGNORE_MAXHEALTH );
+ pPlayer->m_Shared.HealthKitPickupEffects( iDeltaHealth );
+ }
+ }
+#endif // GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::DisguiseOnKill()
+{
+#ifdef GAME_DLL
+ if ( !m_hBackstabVictim.Get() )
+ return;
+
+ int nTeam = m_hBackstabVictim->GetTeamNumber();
+ int nClass = m_hBackstabVictim->GetPlayerClass()->GetClassIndex();
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( pPlayer )
+ {
+ pPlayer->m_Shared.Disguise( nTeam, nClass, m_hBackstabVictim.Get(), true );
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFKnife::ShouldDisguiseOnBackstab()
+{
+ int iDisguiseAsVictim = 0;
+ CALL_ATTRIB_HOOK_INT( iDisguiseAsVictim, set_disguise_on_backstab );
+ if ( iDisguiseAsVictim == 1 )
+ return true;
+ else
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Do backstab damage
+//-----------------------------------------------------------------------------
+float CTFKnife::GetMeleeDamage( CBaseEntity *pTarget, int* piDamageType, int* piCustomDamage )
+{
+ float flBaseDamage = BaseClass::GetMeleeDamage( pTarget, piDamageType, piCustomDamage );
+ CTFPlayer *pTFOwner = ToTFPlayer( GetPlayerOwner() );
+ if ( !pTFOwner )
+ return false;
+
+ if ( pTarget->IsPlayer() )
+ {
+ if ( IsBackstab() )
+ {
+ CTFPlayer *pTFTarget = ToTFPlayer( pTarget );
+ // Special rules in modes where player power grows significantly
+ if ( !pTFOwner->IsBot() && pTFTarget && pTFTarget->IsMiniBoss() )
+ {
+ // MvM: Cap damage against bots and check for a damage upgrade
+ float flBonusDmg = 1.f;
+ CALL_ATTRIB_HOOK_FLOAT( flBonusDmg, mult_dmg );
+ flBaseDamage = 250.f * flBonusDmg;
+
+ // Minibosses: Adjust damage when backstabbing based on level of armor piercing
+ // Base amount is 25% of normal damage. Each level adds 25% to a cap of 125%.
+ float flArmorPiercing = 25.f;
+ CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pTFOwner, flArmorPiercing, armor_piercing );
+ flBaseDamage *= clamp( flArmorPiercing / 100.0f, 0.25f, 1.25f );
+ }
+ else // Regular game mode, or the attacker is a bot
+ {
+ // Do twice the target's health so that random modification will still kill him.
+ flBaseDamage = pTarget->GetHealth() * 2;
+ }
+
+ // Declare a backstab.
+ *piCustomDamage = TF_DMG_CUSTOM_BACKSTAB;
+ }
+ else if (pTFOwner->m_Shared.IsCritBoosted())
+ {
+ m_bCurrentAttackIsCrit = true;
+ }
+ else
+ {
+ m_bCurrentAttackIsCrit = false; // don't do a crit if we failed the above checks.
+ }
+ }
+
+ return flBaseDamage;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Are we in a backstab position?
+//-----------------------------------------------------------------------------
+bool CTFKnife::CanPerformBackstabAgainstTarget( CTFPlayer *pTarget )
+{
+ if ( !pTarget )
+ return false;
+
+ // Immune?
+ int iNoBackstab = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pTarget, iNoBackstab, cannot_be_backstabbed );
+ if ( iNoBackstab )
+ return false;
+
+ // Behind and facing target's back?
+ if ( IsBehindAndFacingTarget( pTarget ) )
+ return true;
+
+ // Is target (bot) disabled via a sapper?
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pTarget->GetTeamNumber() == TF_TEAM_PVE_INVADERS )
+ {
+ if ( pTarget->m_Shared.InCond( TF_COND_MVM_BOT_STUN_RADIOWAVE ) )
+ return true;
+
+ if ( pTarget->m_Shared.InCond( TF_COND_SAPPED ) && !pTarget->IsMiniBoss() )
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine if we are reasonably facing our target.
+//-----------------------------------------------------------------------------
+bool CTFKnife::IsBehindAndFacingTarget( CTFPlayer *pTarget )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() );
+ if ( !pOwner )
+ return false;
+
+ // Get a vector from owner origin to target origin
+ Vector vecToTarget;
+ vecToTarget = pTarget->WorldSpaceCenter() - pOwner->WorldSpaceCenter();
+ vecToTarget.z = 0.0f;
+ vecToTarget.NormalizeInPlace();
+
+ // Get owner forward view vector
+ Vector vecOwnerForward;
+ AngleVectors( pOwner->EyeAngles(), &vecOwnerForward, NULL, NULL );
+ vecOwnerForward.z = 0.0f;
+ vecOwnerForward.NormalizeInPlace();
+
+ // Get target forward view vector
+ Vector vecTargetForward;
+ AngleVectors( pTarget->EyeAngles(), &vecTargetForward, NULL, NULL );
+ vecTargetForward.z = 0.0f;
+ vecTargetForward.NormalizeInPlace();
+
+ // Make sure owner is behind, facing and aiming at target's back
+ float flPosVsTargetViewDot = DotProduct( vecToTarget, vecTargetForward ); // Behind?
+ float flPosVsOwnerViewDot = DotProduct( vecToTarget, vecOwnerForward ); // Facing?
+ float flViewAnglesDot = DotProduct( vecTargetForward, vecOwnerForward ); // Facestab?
+
+ // Debug
+ // NDebugOverlay::HorzArrow( pTarget->WorldSpaceCenter(), pTarget->WorldSpaceCenter() + 50.0f * vecTargetForward, 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ // NDebugOverlay::HorzArrow( pOwner->WorldSpaceCenter(), pOwner->WorldSpaceCenter() + 50.0f * vecOwnerForward, 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
+ // DevMsg( "PosDot: %3.2f FacingDot: %3.2f AnglesDot: %3.2f\n", flPosVsTargetViewDot, flPosVsOwnerViewDot, flViewAnglesDot );
+
+ return ( flPosVsTargetViewDot > 0.f && flPosVsOwnerViewDot > 0.5 && flViewAnglesDot > -0.3f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFKnife::CalcIsAttackCriticalHelper( void )
+{
+ // Always crit from behind, never from front
+ return IsBackstab();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFKnife::CalcIsAttackCriticalHelperNoCrits( void )
+{
+ // Always crit from behind, never from front
+ return IsBackstab();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow melee weapons to send different anim events
+// Input : -
+//-----------------------------------------------------------------------------
+void CTFKnife::SendPlayerAnimEvent( CTFPlayer *pPlayer )
+{
+ if ( IsBackstab() )
+ {
+ pPlayer->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, ACT_MP_ATTACK_STAND_SECONDARYFIRE );
+ }
+ else
+ {
+ pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFKnife::CanDeploy( void )
+{
+ m_bKnifeExists = ( gpGlobals->curtime - m_flKnifeMeltTimestamp > m_flKnifeRegenerateDuration );
+
+ if ( m_bKnifeExists == false )
+ {
+ // melted icicle has not yet regenerated
+ return false;
+ }
+
+ return BaseClass::CanDeploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFKnife::Deploy( void )
+{
+ bool bDeployed = BaseClass::Deploy();
+
+ m_bReadyToBackstab = false;
+
+ m_bKnifeExists = true;
+
+ return bDeployed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::ItemPreFrame( void )
+{
+ BaseClass::ItemPreFrame();
+ ProcessDisguiseImpulse();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::ItemPostFrame( void )
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( pPlayer )
+ {
+ if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) )
+ {
+ m_bWasTaunting = true;
+ }
+ else if ( m_bWasTaunting )
+ {
+ // we were taunting and now we're not
+ m_bWasTaunting = false;
+
+ // force switch away from knife if we can't deploy it right now
+ if ( !CanDeploy() )
+ {
+ // force switch to sapper
+ CBaseCombatWeapon *mySapper = pPlayer->Weapon_GetWeaponByType( TF_WPN_TYPE_BUILDING );
+ if ( !mySapper )
+ {
+ // this should never happen
+ pPlayer->SwitchToNextBestWeapon( this );
+ }
+ else
+ {
+ pPlayer->Weapon_Switch( mySapper );
+ }
+ }
+ }
+ }
+
+ BackstabVMThink();
+ BaseClass::ItemPostFrame();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::ItemBusyFrame( void )
+{
+ BaseClass::ItemBusyFrame();
+ ProcessDisguiseImpulse();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::ItemHolsterFrame( void )
+{
+ BaseClass::ItemHolsterFrame();
+ ProcessDisguiseImpulse();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Special case handling of 'Your Eternal Reward' input polling
+//-----------------------------------------------------------------------------
+void CTFKnife::ProcessDisguiseImpulse( void )
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ // Only care about this with the right weapon
+ if ( GetKnifeType() != KNIFE_DISGUISE_ONKILL )
+ return;
+
+ // If we're not already disguised, ignore
+ if ( !pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
+ return;
+
+ pPlayer->m_Shared.ProcessDisguiseImpulse( pPlayer );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFKnife::BackstabVMThink( void )
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ if ( pPlayer->GetActiveWeapon() != this )
+ return;
+
+ // Don't do this if we are doing something other than idling.
+// int iIdealActivity = GetIdealActivity();
+// if ( (iIdealActivity != ACT_VM_IDLE) && (iIdealActivity != ACT_BACKSTAB_VM_IDLE) )
+// return;
+ int iActivity = GetActivity();
+ if ( (iActivity != ACT_VM_IDLE) && (iActivity != ACT_BACKSTAB_VM_IDLE) && (iActivity != ACT_MELEE_VM_IDLE) &&
+ (iActivity != ACT_ITEM1_VM_IDLE) && (iActivity != ACT_ITEM1_BACKSTAB_VM_IDLE) &&
+ (iActivity != ACT_ITEM2_VM_IDLE) && (iActivity != ACT_ITEM2_BACKSTAB_VM_IDLE) )
+ return;
+
+
+ // Are we in backstab range and not cloaked?
+ trace_t trace;
+ if ( DoSwingTrace( trace ) == true && CanAttack() )
+ {
+ // We will hit something if we attack.
+ if( trace.m_pEnt && trace.m_pEnt->IsPlayer() )
+ {
+ CTFPlayer *pTarget = ToTFPlayer( trace.m_pEnt );
+
+ if ( pTarget && pTarget->GetTeamNumber() != pPlayer->GetTeamNumber() )
+ {
+ if ( CanPerformBackstabAgainstTarget( pTarget ) )
+ {
+ if ( !m_bReadyToBackstab )
+ {
+ SendWeaponAnim( ACT_BACKSTAB_VM_UP );
+
+ m_bReadyToBackstab = true;
+ }
+ }
+ else if ( m_bReadyToBackstab )
+ {
+
+ SendWeaponAnim( ACT_BACKSTAB_VM_DOWN );
+
+ m_bReadyToBackstab = false;
+ }
+ }
+ }
+ }
+ else if ( m_bReadyToBackstab )
+ {
+ SendWeaponAnim( ACT_BACKSTAB_VM_DOWN );
+ m_bReadyToBackstab = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play animation appropriate to ball status.
+//-----------------------------------------------------------------------------
+bool CTFKnife::SendWeaponAnim( int iActivity )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return BaseClass::SendWeaponAnim( iActivity );
+
+ if ( m_bReadyToBackstab )
+ {
+ switch ( iActivity )
+ {
+ case ACT_VM_IDLE:
+ case ACT_ITEM1_VM_IDLE:
+ case ACT_ITEM2_VM_IDLE:
+ iActivity = ACT_BACKSTAB_VM_IDLE;
+ break;
+ }
+ }
+
+ return BaseClass::SendWeaponAnim( iActivity );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The spy's backstab was blocked by a player item.
+//-----------------------------------------------------------------------------
+void CTFKnife::BackstabBlocked( void )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ // Delay the spy's next attack for a time.
+ pPlayer->SetNextAttack( gpGlobals->curtime + 2.f );
+ m_flBlockedTime = gpGlobals->curtime;
+
+ SendWeaponAnim( ACT_MELEE_VM_STUN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spy can't change weapons if knife is hot.
+//-----------------------------------------------------------------------------
+bool CTFKnife::CanHolster( void ) const
+{
+ if ( !m_bAllowHolsterBecauseForced && gpGlobals->curtime - m_flBlockedTime < 2.f )
+ return false;
+ else
+ return BaseClass::CanHolster();
+} \ No newline at end of file