summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weaponbase_melee.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/tf/tf_weaponbase_melee.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/tf/tf_weaponbase_melee.cpp')
-rw-r--r--game/shared/tf/tf_weaponbase_melee.cpp1139
1 files changed, 1139 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weaponbase_melee.cpp b/game/shared/tf/tf_weaponbase_melee.cpp
new file mode 100644
index 0000000..98f4bf6
--- /dev/null
+++ b/game/shared/tf/tf_weaponbase_melee.cpp
@@ -0,0 +1,1139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "tf_weaponbase_melee.h"
+#include "effect_dispatch_data.h"
+#include "tf_gamerules.h"
+
+// Server specific.
+#if !defined( CLIENT_DLL )
+#include "tf_player.h"
+#include "tf_gamestats.h"
+#include "ilagcompensationmanager.h"
+#include "tf_passtime_logic.h"
+// Client specific.
+#else
+#include "c_tf_gamestats.h"
+#include "c_tf_player.h"
+// NVNT haptics system interface
+#include "haptics/ihaptics.h"
+#endif
+
+ConVar tf_weapon_criticals_melee( "tf_weapon_criticals_melee", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Controls random crits for melee weapons. 0 - Melee weapons do not randomly crit. 1 - Melee weapons can randomly crit only if tf_weapon_criticals is also enabled. 2 - Melee weapons can always randomly crit regardless of the tf_weapon_criticals setting." );
+
+//=============================================================================
+//
+// TFWeaponBase Melee tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBaseMelee, DT_TFWeaponBaseMelee )
+
+BEGIN_NETWORK_TABLE( CTFWeaponBaseMelee, DT_TFWeaponBaseMelee )
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFWeaponBaseMelee )
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( tf_weaponbase_melee, CTFWeaponBaseMelee );
+
+// Server specific.
+#if !defined( CLIENT_DLL )
+BEGIN_DATADESC( CTFWeaponBaseMelee )
+DEFINE_THINKFUNC( Smack )
+END_DATADESC()
+#endif
+
+#ifndef CLIENT_DLL
+ConVar tf_meleeattackforcescale( "tf_meleeattackforcescale", "80.0", FCVAR_CHEAT | FCVAR_GAMEDLL | FCVAR_DEVELOPMENTONLY );
+#endif
+
+#ifdef _DEBUG
+extern ConVar tf_weapon_criticals_force_random;
+#endif // _DEBUG
+
+//=============================================================================
+//
+// TFWeaponBase Melee functions.
+//
+
+// -----------------------------------------------------------------------------
+// Purpose: Constructor.
+// -----------------------------------------------------------------------------
+CTFWeaponBaseMelee::CTFWeaponBaseMelee()
+{
+ WeaponReset();
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::WeaponReset( void )
+{
+ BaseClass::WeaponReset();
+
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+ m_flSmackTime = -1.0f;
+ m_bConnected = false;
+ m_bMiniCrit = false;
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+bool CTFWeaponBaseMelee::CanHolster( void ) const
+{
+ // For fist users, energy buffs come from steak sandviches which lock us into attacking with melee.
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ) )
+ return false;
+
+ return BaseClass::CanHolster();
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::Precache()
+{
+ BaseClass::Precache();
+
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ char szMeleeSoundStr[128] = "MVM_";
+ const char *shootsound = GetShootSound( MELEE_HIT );
+ if ( shootsound && shootsound[0] )
+ {
+ V_strcat(szMeleeSoundStr, shootsound, sizeof( szMeleeSoundStr ));
+ CBaseEntity::PrecacheScriptSound( szMeleeSoundStr );
+ }
+ }
+ CBaseEntity::PrecacheScriptSound("MVM_Weapon_Default.HitFlesh");
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::Spawn()
+{
+ Precache();
+
+ // Get the weapon information.
+ WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( GetClassname() );
+ Assert( hWpnInfo != GetInvalidWeaponInfoHandle() );
+ CTFWeaponInfo *pWeaponInfo = dynamic_cast< CTFWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
+ Assert( pWeaponInfo && "Failed to get CTFWeaponInfo in melee weapon spawn" );
+ m_pWeaponInfo = pWeaponInfo;
+ Assert( m_pWeaponInfo );
+
+ // No ammo.
+ m_iClip1 = -1;
+
+ BaseClass::Spawn();
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+bool CTFWeaponBaseMelee::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ m_flSmackTime = -1.0f;
+ if ( GetPlayerOwner() )
+ {
+ GetPlayerOwner()->m_flNextAttack = gpGlobals->curtime + 0.5;
+ }
+
+ int iSelfMark = 0;
+ CALL_ATTRIB_HOOK_INT( iSelfMark, self_mark_for_death );
+ if ( iSelfMark )
+ {
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( pPlayer )
+ {
+ pPlayer->m_Shared.AddCond( TF_COND_MARKEDFORDEATH_SILENT, iSelfMark );
+ }
+ }
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+
+int CTFWeaponBaseMelee::GetSwingRange( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( pOwner && pOwner->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
+ {
+ return 128;
+ }
+ else
+ {
+ int iIsSword = 0;
+ CALL_ATTRIB_HOOK_INT( iIsSword, is_a_sword )
+ if ( iIsSword )
+ {
+ return 72; // swords are typically 72
+ }
+ return 48;
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::PrimaryAttack()
+{
+ // Get the current player.
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ if ( !CanAttack() )
+ return;
+
+ // Set the weapon usage mode - primary, secondary.
+ m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
+ m_bConnected = false;
+
+ pPlayer->EndClassSpecialSkill();
+
+ // Swing the weapon.
+ Swing( pPlayer );
+
+ m_bCurrentAttackIsDuringDemoCharge = pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT;
+
+ if ( pPlayer->m_Shared.GetNextMeleeCrit() == MELEE_MINICRIT )
+ {
+ m_bMiniCrit = true;
+ }
+ else
+ {
+ m_bMiniCrit = false;
+ }
+
+#ifdef STAGING_ONLY
+ // Remove Cond if I attack
+ if ( pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) )
+ {
+ pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST );
+ }
+#endif
+
+#if !defined( CLIENT_DLL )
+ pPlayer->SpeakWeaponFire();
+ CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+
+ if ( pPlayer->m_Shared.IsStealthed() && ShouldRemoveInvisibilityOnPrimaryAttack() )
+ {
+ pPlayer->RemoveInvisibility();
+ }
+#endif
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::SecondaryAttack()
+{
+ // semi-auto behaviour
+ if ( m_bInAttack2 )
+ return;
+
+ // Get the current player.
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ pPlayer->DoClassSpecialSkill();
+
+ m_bInAttack2 = true;
+
+#ifdef STAGING_ONLY
+ // Remove Cond if I attack
+ if ( pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) )
+ {
+ pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST );
+ }
+#endif
+
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.5;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+//-----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::Swing( CTFPlayer *pPlayer )
+{
+ CalcIsAttackCritical();
+
+#ifdef GAME_DLL
+ CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+#endif
+#ifdef CLIENT_DLL
+ C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
+#endif
+
+ // Play the melee swing and miss (whoosh) always.
+ SendPlayerAnimEvent( pPlayer );
+
+ DoViewModelAnimation();
+
+ // Set next attack times.
+ float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
+
+ m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
+ m_flNextSecondaryAttack = gpGlobals->curtime + flFireDelay;
+ pPlayer->m_Shared.SetNextStealthTime( m_flNextSecondaryAttack );
+
+ SetWeaponIdleTime( m_flNextPrimaryAttack + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeIdleEmpty );
+
+ if ( IsCurrentAttackACrit() )
+ {
+ WeaponSound( BURST );
+ }
+ else
+ {
+ WeaponSound( MELEE_MISS );
+ }
+
+#ifdef GAME_DLL
+ // Remember if there are potential targets when we start our swing.
+ // If there are, the player is exempt from taking "hurt self on miss" damage
+ // if ALL of these players have died when our swing has finished, and we didn't hit.
+ // This guards against me performing a "good" swing and being punished by a friend
+ // killing my target "out from under me".
+ CUtlVector< CTFPlayer * > enemyVector;
+ CollectPlayers( &enemyVector, GetEnemyTeam( pPlayer->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
+
+ m_potentialVictimVector.RemoveAll();
+ const float looseSwingRange = 1.2f * GetSwingRange();
+
+ for( int i=0; i<enemyVector.Count(); ++i )
+ {
+ Vector toVictim = enemyVector[i]->WorldSpaceCenter() - pPlayer->Weapon_ShootPosition();
+
+ if ( toVictim.IsLengthLessThan( looseSwingRange ) )
+ {
+ m_potentialVictimVector.AddToTail( enemyVector[i] );
+ }
+ }
+#endif
+
+ m_flSmackTime = gpGlobals->curtime + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flSmackDelay;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::DoViewModelAnimation( void )
+{
+ if ( IsCurrentAttackACrit() )
+ {
+ if ( SendWeaponAnim( ACT_VM_SWINGHARD ) )
+ {
+ // check that weapon has the activity
+ return;
+ }
+ }
+
+ Activity act = ( m_iWeaponMode == TF_WEAPON_PRIMARY_MODE ) ? ACT_VM_HITCENTER : ACT_VM_SWINGHARD;
+
+ SendWeaponAnim( act );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow melee weapons to send different anim events
+// Input : -
+//-----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::SendPlayerAnimEvent( CTFPlayer *pPlayer )
+{
+ pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
+}
+
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::ItemPreFrame( void )
+{
+ int iSelfMark = 0;
+ CALL_ATTRIB_HOOK_INT( iSelfMark, self_mark_for_death );
+ if ( iSelfMark )
+ {
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( pPlayer )
+ {
+ pPlayer->m_Shared.AddCond( TF_COND_MARKEDFORDEATH_SILENT, iSelfMark );
+ }
+ }
+
+ return BaseClass::ItemPreFrame();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::ItemPostFrame()
+{
+ // Check for smack.
+ if ( m_flSmackTime > 0.0f && gpGlobals->curtime > m_flSmackTime )
+ {
+ Smack();
+ m_flSmackTime = -1.0f;
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( pPlayer )
+ {
+ pPlayer->m_Shared.SetNextMeleeCrit( MELEE_NOCRIT );
+ }
+ }
+
+ BaseClass::ItemPostFrame();
+}
+
+
+bool CTFWeaponBaseMelee::DoSwingTraceInternal( trace_t &trace, bool bCleave, CUtlVector< trace_t >* pTargetTraceVector )
+{
+ // Setup a volume for the melee weapon to be swung - approx size, so all melee behave the same.
+ static Vector vecSwingMinsBase( -18, -18, -18 );
+ static Vector vecSwingMaxsBase( 18, 18, 18 );
+
+ float fBoundsScale = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( fBoundsScale, melee_bounds_multiplier );
+ Vector vecSwingMins = vecSwingMinsBase * fBoundsScale;
+ Vector vecSwingMaxs = vecSwingMaxsBase * fBoundsScale;
+
+ // Get the current player.
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return false;
+
+ // Setup the swing range.
+ float fSwingRange = GetSwingRange();
+
+ // Scale the range and bounds by the model scale if they're larger
+ // Not scaling down the range for smaller models because midgets need all the help they can get
+ if ( pPlayer->GetModelScale() > 1.0f )
+ {
+ fSwingRange *= pPlayer->GetModelScale();
+ vecSwingMins *= pPlayer->GetModelScale();
+ vecSwingMaxs *= pPlayer->GetModelScale();
+ }
+
+ CALL_ATTRIB_HOOK_FLOAT( fSwingRange, melee_range_multiplier );
+
+ Vector vecForward;
+ AngleVectors( pPlayer->EyeAngles(), &vecForward );
+ Vector vecSwingStart = pPlayer->Weapon_ShootPosition();
+ Vector vecSwingEnd = vecSwingStart + vecForward * fSwingRange;
+
+ // In MvM, melee hits from the robot team wont hit teammates to ensure mobs of melee bots don't
+ // swarm so tightly they hit each other and no-one else
+ bool bDontHitTeammates = pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && TFGameRules()->IsMannVsMachineMode();
+ CTraceFilterIgnoreTeammates ignoreTeammatesFilter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() );
+
+ if ( bCleave )
+ {
+ Ray_t ray;
+ ray.Init( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs );
+ CBaseEntity *pList[256];
+ int nTargetCount = UTIL_EntitiesAlongRay( pList, ARRAYSIZE( pList ), ray, FL_CLIENT|FL_OBJECT );
+
+ int nHitCount = 0;
+ for ( int i=0; i<nTargetCount; ++i )
+ {
+ CBaseEntity *pTarget = pList[i];
+ if ( pTarget == pPlayer )
+ {
+ // don't hit yourself
+ continue;
+ }
+
+ if ( bDontHitTeammates && pTarget->GetTeamNumber() == pPlayer->GetTeamNumber() )
+ {
+ // don't hit teammate
+ continue;
+ }
+
+ if ( pTargetTraceVector )
+ {
+ trace_t tr;
+ UTIL_TraceModel( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, pTarget, COLLISION_GROUP_NONE, &tr );
+ pTargetTraceVector->AddToTail();
+ pTargetTraceVector->Tail() = tr;
+ }
+ nHitCount++;
+ }
+
+ return nHitCount > 0;
+ }
+ else
+ {
+ bool bSapperHit = false;
+
+ // if this weapon can damage sappers, do that trace first
+ int iDmgSappers = 0;
+ CALL_ATTRIB_HOOK_INT( iDmgSappers, set_dmg_apply_to_sapper );
+ if ( iDmgSappers != 0 )
+ {
+ CTraceFilterIgnorePlayers ignorePlayersFilter( NULL, COLLISION_GROUP_NONE );
+ UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &ignorePlayersFilter, &trace );
+ if ( trace.fraction >= 1.0 )
+ {
+ UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &ignorePlayersFilter, &trace );
+ }
+
+ if ( trace.fraction < 1.0f &&
+ trace.m_pEnt &&
+ trace.m_pEnt->IsBaseObject() &&
+ trace.m_pEnt->GetTeamNumber() == pPlayer->GetTeamNumber() )
+ {
+ CBaseObject *pObject = static_cast< CBaseObject* >( trace.m_pEnt );
+ if ( pObject->HasSapper() )
+ {
+ bSapperHit = true;
+ }
+ }
+ }
+
+ if ( !bSapperHit )
+ {
+ // See if we hit anything.
+ if ( bDontHitTeammates )
+ {
+ UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &ignoreTeammatesFilter, &trace );
+ }
+ else
+ {
+ CTraceFilterIgnoreFriendlyCombatItems filter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() );
+ UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &filter, &trace );
+ }
+
+ if ( trace.fraction >= 1.0 )
+ {
+ if ( bDontHitTeammates )
+ {
+ UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &ignoreTeammatesFilter, &trace );
+ }
+ else
+ {
+ CTraceFilterIgnoreFriendlyCombatItems filter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() );
+ UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &filter, &trace );
+ }
+
+ if ( trace.fraction < 1.0 )
+ {
+ // Calculate the point of intersection of the line (or hull) and the object we hit
+ // This is and approximation of the "best" intersection
+ CBaseEntity *pHit = trace.m_pEnt;
+ if ( !pHit || pHit->IsBSPModel() )
+ {
+ // Why duck hull min/max?
+ FindHullIntersection( vecSwingStart, trace, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pPlayer );
+ }
+
+ // This is the point on the actual surface (the hull could have hit space)
+ vecSwingEnd = trace.endpos;
+ }
+ }
+ }
+
+ return ( trace.fraction < 1.0f );
+ }
+}
+
+
+bool CTFWeaponBaseMelee::DoSwingTrace( trace_t &trace )
+{
+ return DoSwingTraceInternal( trace, false, NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+bool CTFWeaponBaseMelee::OnSwingHit( trace_t &trace )
+{
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+
+ // NVNT if this is the client dll and the owner is the local player
+ // Notify the haptics system the local player just hit something.
+#ifdef CLIENT_DLL
+ if(pPlayer==C_TFPlayer::GetLocalTFPlayer() && haptics)
+ haptics->ProcessHapticEvent(2,"Weapons","meleehit");
+#endif
+
+ bool bHitEnemyPlayer = false;
+
+ // Hit sound - immediate.
+ if( trace.m_pEnt->IsPlayer() )
+ {
+ CTFPlayer *pTargetPlayer = ToTFPlayer( trace.m_pEnt );
+
+ bool bPlayMvMHitOnly = false;
+ // handle hitting a robot
+ if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
+ {
+ if ( pTargetPlayer && pTargetPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && !pTargetPlayer->IsPlayer() )
+ {
+ bPlayMvMHitOnly = true;
+
+ CBroadcastRecipientFilter filter;
+ // CSingleUserRecipientFilter filter( ToBasePlayer( GetOwner() ) );
+ // if ( IsPredicted() && CBaseEntity::GetPredictionPlayer() )
+ // {
+ // filter.UsePredictionRules();
+ // }
+
+ char szMeleeSoundStr[128] = "MVM_";
+ const char *shootsound = GetShootSound( MELEE_HIT );
+ if ( shootsound && shootsound[0] )
+ {
+ V_strcat(szMeleeSoundStr, shootsound, sizeof( szMeleeSoundStr ));
+ CSoundParameters params;
+ if ( CBaseEntity::GetParametersForSound( szMeleeSoundStr, params, NULL ) )
+ {
+ EmitSound( filter, GetOwner()->entindex(), szMeleeSoundStr, NULL );
+ }
+ else
+ {
+ EmitSound( filter, GetOwner()->entindex(), "MVM_Weapon_Default.HitFlesh", NULL );
+ }
+ }
+ else
+ {
+ EmitSound( filter, GetOwner()->entindex(), "MVM_Weapon_Default.HitFlesh", NULL );
+ }
+ }
+ }
+ if(! bPlayMvMHitOnly )
+ {
+ WeaponSound( MELEE_HIT );
+ }
+
+#if !defined (CLIENT_DLL)
+
+ if ( pTargetPlayer->m_Shared.HasPasstimeBall() && g_pPasstimeLogic )
+ {
+ // This handles stealing the ball from teammates since there's no damage involved
+ // TODO find a better place for this
+ g_pPasstimeLogic->OnBallCarrierMeleeHit( pTargetPlayer, pPlayer );
+ }
+
+ if ( pPlayer->GetTeamNumber() != pTargetPlayer->GetTeamNumber() )
+ {
+ bHitEnemyPlayer = true;
+
+ if ( TFGameRules()->IsIT( pPlayer ) )
+ {
+ IGameEvent *pEvent = gameeventmanager->CreateEvent( "tagged_player_as_it" );
+ if ( pEvent )
+ {
+ pEvent->SetInt( "player", pPlayer->GetUserID() );
+ gameeventmanager->FireEvent( pEvent, true );
+ }
+
+ // Tag! You're IT!
+ TFGameRules()->SetIT( pTargetPlayer );
+
+ pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_YES );
+
+ UTIL_ClientPrintAll( HUD_PRINTTALK, "#TF_HALLOWEEN_BOSS_ANNOUNCE_TAG", pPlayer->GetPlayerName(), pTargetPlayer->GetPlayerName() );
+
+ CSingleUserReliableRecipientFilter filter( pPlayer );
+ pPlayer->EmitSound( filter, pPlayer->entindex(), "Player.TaggedOtherIT" );
+ }
+ }
+
+ if ( pTargetPlayer->InSameTeam( pPlayer ) || pTargetPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() )
+ {
+ int iSpeedBuffOnHit = 0;
+ CALL_ATTRIB_HOOK_INT( iSpeedBuffOnHit, speed_buff_ally );
+ if ( iSpeedBuffOnHit > 0 && trace.m_pEnt )
+ {
+ pTargetPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, 2.f );
+ pPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, 3.6f ); // give the soldier a bit of additional time to allow them to keep up better with faster classes
+
+ EconEntity_OnOwnerKillEaterEvent( this, pPlayer, pTargetPlayer, kKillEaterEvent_TeammatesWhipped ); // Strange
+ }
+
+ // Give health to teammates on hit
+ int nGiveHealthOnHit = 0;
+ CALL_ATTRIB_HOOK_INT( nGiveHealthOnHit, add_give_health_to_teammate_on_hit );
+ if ( nGiveHealthOnHit != 0 )
+ {
+ // Always keep at least 1 health for ourselves
+ nGiveHealthOnHit = Min( pPlayer->GetHealth() - 1, nGiveHealthOnHit );
+ int nHealthGiven = pTargetPlayer->TakeHealth( nGiveHealthOnHit, DMG_GENERIC );
+
+ if ( nHealthGiven > 0 )
+ {
+ // Subtract health given from my own
+ CTakeDamageInfo info( pPlayer, pPlayer, this, nHealthGiven, DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
+ pPlayer->TakeDamage( info );
+ }
+ }
+ }
+#endif
+ }
+ else
+ {
+ WeaponSound( MELEE_HIT_WORLD );
+ }
+
+ DoMeleeDamage( trace.m_pEnt, trace );
+
+ return bHitEnemyPlayer;
+}
+
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// Note: Think function to delay the impact decal until the animation is finished
+// playing.
+// -----------------------------------------------------------------------------
+void CTFWeaponBaseMelee::Smack( void )
+{
+ trace_t trace;
+
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+#if !defined (CLIENT_DLL)
+ // Move other players back to history positions based on local player's lag
+ lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
+#endif
+
+ bool bHitEnemyPlayer = false;
+
+ int nCleaveAttack = 0;
+ CALL_ATTRIB_HOOK_INT( nCleaveAttack, melee_cleave_attack );
+ bool bCleave = nCleaveAttack > 0;
+
+ // We hit, setup the smack.
+ CUtlVector<trace_t> targetTraceVector;
+ if ( DoSwingTraceInternal( trace, bCleave, &targetTraceVector ) )
+ {
+ if ( bCleave )
+ {
+ for ( int i=0; i<targetTraceVector.Count(); ++i )
+ {
+ bHitEnemyPlayer |= OnSwingHit( targetTraceVector[i] );
+ }
+ }
+ else
+ {
+ bHitEnemyPlayer = OnSwingHit( trace );
+ }
+ }
+ else
+ {
+ // if ALL of my potential targets have been killed by someone else between the
+ // time I started my swing and the time my swing would have landed, don't
+ // punish me for it.
+ bool bIsCleanMiss = true;
+
+#ifdef GAME_DLL
+ for( int i=0; i<m_potentialVictimVector.Count(); ++i )
+ {
+ if ( m_potentialVictimVector[i] != NULL && m_potentialVictimVector[i]->IsAlive() )
+ {
+ bIsCleanMiss = false;
+ break;
+ }
+ }
+#endif
+
+ if ( bIsCleanMiss )
+ {
+ int iHitSelf = 0;
+ CALL_ATTRIB_HOOK_INT( iHitSelf, hit_self_on_miss );
+ if ( iHitSelf == 1 )
+ {
+ DoMeleeDamage( GetTFPlayerOwner(), trace, 0.5f );
+ }
+ }
+ }
+
+#if !defined (CLIENT_DLL)
+
+ // ACHIEVEMENT_TF_MEDIC_BONESAW_NOMISSES
+ if ( GetWeaponID() == TF_WEAPON_BONESAW )
+ {
+ int iCount = pPlayer->GetPerLifeCounterKV( "medic_bonesaw_hits" );
+
+ if ( bHitEnemyPlayer )
+ {
+ if ( ++iCount >= 5 )
+ {
+ pPlayer->AwardAchievement( ACHIEVEMENT_TF_MEDIC_BONESAW_NOMISSES );
+ }
+ }
+ else
+ {
+ iCount = 0;
+ }
+
+ pPlayer->SetPerLifeCounterKV( "medic_bonesaw_hits", iCount );
+ }
+
+ lagcompensation->FinishLagCompensation( pPlayer );
+#endif
+}
+
+void CTFWeaponBaseMelee::DoMeleeDamage( CBaseEntity* ent, trace_t& trace )
+{
+ DoMeleeDamage( ent, trace, 1.f );
+}
+
+void CTFWeaponBaseMelee::DoMeleeDamage( CBaseEntity* ent, trace_t& trace, float flDamageMod )
+{
+ // Get the current player.
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ Vector vecForward;
+ AngleVectors( pPlayer->EyeAngles(), &vecForward );
+ Vector vecSwingStart = pPlayer->Weapon_ShootPosition();
+ Vector vecSwingEnd = vecSwingStart + vecForward * 48;
+
+#ifndef CLIENT_DLL
+ // Do Damage.
+ int iCustomDamage = GetDamageCustom();
+ int iDmgType = DMG_MELEE | DMG_NEVERGIB | DMG_CLUB;
+
+ int iCritFromBehind = 0;
+ CALL_ATTRIB_HOOK_INT( iCritFromBehind, crit_from_behind );
+ if ( iCritFromBehind > 0 )
+ {
+ Vector entForward;
+ AngleVectors( ent->EyeAngles(), &entForward );
+
+ Vector toEnt = ent->GetAbsOrigin() - pPlayer->GetAbsOrigin();
+ toEnt.NormalizeInPlace();
+
+ if ( DotProduct( toEnt, entForward ) > 0.7071f )
+ {
+ iDmgType |= DMG_CRITICAL;
+ }
+ }
+
+ float flDamage = GetMeleeDamage( ent, &iDmgType, &iCustomDamage ) * flDamageMod;
+
+ // Base melee damage increased because we disallow random crits in this mode. Without random crits, melee is underpowered
+ if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
+ {
+ if ( !IsCurrentAttackACrit() ) // Don't multiply base damage if attack is a crit
+ {
+ if ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
+ {
+ flDamage *= 1.9f;
+ }
+ // Strength powerup multiplies damage later and we only want double regular damage. Shields are a source of increased melee damage (charge crit) so they don't need a base boost
+ else if ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() != RUNE_STRENGTH && !pPlayer->m_Shared.IsShieldEquipped() )
+ {
+ flDamage *= 1.3f;
+ }
+ }
+ }
+
+ if ( IsCurrentAttackACrit() )
+ {
+ // TODO: Not removing the old critical path yet, but the new custom damage is marking criticals as well for melee now.
+ iDmgType |= DMG_CRITICAL;
+ }
+ else if ( m_bMiniCrit )
+ {
+ iDmgType |= DMG_RADIUS_MAX; // Unused for melee, indicates this should be a minicrit.
+ }
+
+ CTakeDamageInfo info( pPlayer, pPlayer, this, flDamage, iDmgType, iCustomDamage );
+
+ if ( fabs( flDamage ) >= 1.0f )
+ {
+ CalculateMeleeDamageForce( &info, vecForward, vecSwingEnd, 1.0f / flDamage * GetForceScale() );
+ }
+ else
+ {
+ info.SetDamageForce( vec3_origin );
+ }
+
+ ent->DispatchTraceAttack( info, vecForward, &trace );
+ ApplyMultiDamage();
+
+ OnEntityHit( ent, &info );
+
+ bool bTruce = TFGameRules() && TFGameRules()->IsTruceActive() && pPlayer->IsTruceValidForEnt();
+ if ( !bTruce )
+ {
+ int iCritsForceVictimToLaugh = 0;
+ CALL_ATTRIB_HOOK_INT( iCritsForceVictimToLaugh, crit_forces_victim_to_laugh );
+ if ( iCritsForceVictimToLaugh > 0 && ( IsCurrentAttackACrit() || iDmgType & DMG_CRITICAL ) )
+ {
+ CTFPlayer *pVictimPlayer = ToTFPlayer( ent );
+
+ if ( pVictimPlayer && pVictimPlayer->CanBeForcedToLaugh() && ( pPlayer->GetTeamNumber() != pVictimPlayer->GetTeamNumber() ) )
+ {
+ // force victim to laugh!
+ pVictimPlayer->Taunt( TAUNT_MISC_ITEM, MP_CONCEPT_TAUNT_LAUGH );
+
+ // strange stat tracking
+ EconEntity_OnOwnerKillEaterEvent( this,
+ ToTFPlayer( GetOwner() ),
+ pVictimPlayer,
+ kKillEaterEvent_PlayerTickle );
+ }
+ }
+
+ int iTickleEnemiesWieldingSameWeapon = 0;
+ CALL_ATTRIB_HOOK_INT( iTickleEnemiesWieldingSameWeapon, tickle_enemies_wielding_same_weapon );
+ if ( iTickleEnemiesWieldingSameWeapon > 0 )
+ {
+ CTFPlayer *pVictimPlayer = ToTFPlayer( ent );
+
+ if ( pVictimPlayer && pVictimPlayer->CanBeForcedToLaugh() && ( pPlayer->GetTeamNumber() != pVictimPlayer->GetTeamNumber() ) )
+ {
+ CTFWeaponBase *myWeapon = pPlayer->GetActiveTFWeapon();
+ CTFWeaponBase *theirWeapon = pVictimPlayer->GetActiveTFWeapon();
+
+ if ( myWeapon && theirWeapon )
+ {
+ CEconItemView *myItem = myWeapon->GetAttributeContainer()->GetItem();
+ CEconItemView *theirItem = theirWeapon->GetAttributeContainer()->GetItem();
+
+ if ( myItem && theirItem && myItem->GetItemDefIndex() == theirItem->GetItemDefIndex() )
+ {
+ // force victim to laugh!
+ pVictimPlayer->Taunt( TAUNT_MISC_ITEM, MP_CONCEPT_TAUNT_LAUGH );
+ }
+ }
+ }
+ }
+ }
+ if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
+ {
+ CTFPlayer *pVictimPlayer = ToTFPlayer( ent );
+
+ if ( pVictimPlayer && !pVictimPlayer->InSameTeam( pPlayer ) )
+ {
+ CPASAttenuationFilter filter( pPlayer );
+ Vector origin = pPlayer->GetAbsOrigin();
+ Vector vecDir = pVictimPlayer->GetAbsOrigin() - origin;
+ VectorNormalize( vecDir );
+
+ if ( !pVictimPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_USER_BUFF ) &&
+ !pVictimPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) )
+ {
+ if ( pVictimPlayer->m_Shared.IsCarryingRune() )
+ {
+ pVictimPlayer->DropRune();
+ ClientPrint( pVictimPlayer, HUD_PRINTCENTER, "#TF_Powerup_Knocked_Out" );
+ }
+ else if ( pVictimPlayer->HasTheFlag() )
+ {
+ pVictimPlayer->DropFlag();
+ ClientPrint( pVictimPlayer, HUD_PRINTCENTER, "#TF_CTF_PlayerDrop" );
+ }
+ }
+ EmitSound( filter, entindex(), "Powerup.Knockout_Melee_Hit" );
+ pVictimPlayer->ApplyAirBlastImpulse( vecDir * 400.0f );
+ }
+ }
+
+#endif
+ // Don't impact trace friendly players or objects
+ if ( ent && ent->GetTeamNumber() != pPlayer->GetTeamNumber() )
+ {
+#ifdef CLIENT_DLL
+ UTIL_ImpactTrace( &trace, DMG_CLUB );
+#endif
+ m_bConnected = true;
+ }
+}
+
+#ifndef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CTFWeaponBaseMelee::GetForceScale( void )
+{
+ return tf_meleeattackforcescale.GetFloat();
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CTFWeaponBaseMelee::GetMeleeDamage( CBaseEntity *pTarget, int* piDamageType, int* piCustomDamage )
+{
+ float flDamage = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
+ CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );
+
+ int iCritDoesNoDamage = 0;
+ CALL_ATTRIB_HOOK_INT( iCritDoesNoDamage, crit_does_no_damage );
+ if ( iCritDoesNoDamage > 0 )
+ {
+ if ( IsCurrentAttackACrit() )
+ {
+ return 0.0f;
+ }
+
+ if ( piDamageType && *piDamageType & DMG_CRITICAL )
+ {
+ return 0.0f;
+ }
+ }
+
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( pPlayer )
+ {
+ float flHalfHealth = pPlayer->GetMaxHealth() * 0.5f;
+ if ( pPlayer->GetHealth() < flHalfHealth )
+ {
+ CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg_bonus_while_half_dead );
+ }
+ else
+ {
+ CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg_penalty_while_half_alive );
+ }
+
+ // Some weapons change damage based on player's health
+ float flReducedHealthBonus = 1.0f;
+ CALL_ATTRIB_HOOK_FLOAT( flReducedHealthBonus, mult_dmg_with_reduced_health );
+ if ( flReducedHealthBonus != 1.0f )
+ {
+ float flHealthFraction = clamp( pPlayer->HealthFraction(), 0.0f, 1.0f );
+ flReducedHealthBonus = Lerp( flHealthFraction, flReducedHealthBonus, 1.0f );
+
+ flDamage *= flReducedHealthBonus;
+ }
+ }
+
+ return flDamage;
+}
+
+void CTFWeaponBaseMelee::OnEntityHit( CBaseEntity *pEntity, CTakeDamageInfo *info )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CTFWeaponBaseMelee::CalcIsAttackCriticalHelperNoCrits( void )
+{
+ // This function was called because the tf_weapon_criticals ConVar is off, but if
+ // melee crits are set to be forced on, then call the regular crit helper function.
+ if ( tf_weapon_criticals_melee.GetInt() > 1 )
+ {
+ return CalcIsAttackCriticalHelper();
+ }
+
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return false;
+
+ m_bCurrentAttackIsDuringDemoCharge = pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT;
+
+ if ( pPlayer->m_Shared.GetNextMeleeCrit() == MELEE_CRIT )
+ {
+ return true;
+ }
+ else
+ {
+ return BaseClass::CalcIsAttackCriticalHelperNoCrits();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFWeaponBaseMelee::CalcIsAttackCriticalHelper( void )
+{
+ // If melee crits are off, then check the NoCrits helper.
+ if ( tf_weapon_criticals_melee.GetInt() == 0 )
+ {
+ return CalcIsAttackCriticalHelperNoCrits();
+ }
+
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return false;
+
+ if ( !CanFireCriticalShot() )
+ return false;
+
+ // Crit boosted players fire all crits
+ if ( pPlayer->m_Shared.IsCritBoosted() )
+ return true;
+
+ float flPlayerCritMult = pPlayer->GetCritMult();
+ float flCritChance = TF_DAMAGE_CRIT_CHANCE_MELEE * flPlayerCritMult;
+ CALL_ATTRIB_HOOK_FLOAT( flCritChance, mult_crit_chance );
+
+ // mess with the crit chance seed so it's not based solely on the prediction seed
+ int iMask = ( entindex() << 16 ) | ( pPlayer->entindex() << 8 );
+ int iSeed = CBaseEntity::GetPredictionRandomSeed() ^ iMask;
+ if ( iSeed != m_iCurrentSeed )
+ {
+ m_iCurrentSeed = iSeed;
+ RandomSeed( m_iCurrentSeed );
+ }
+
+ m_bCurrentAttackIsDuringDemoCharge = pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT;
+
+ if ( pPlayer->m_Shared.GetNextMeleeCrit() == MELEE_CRIT )
+ {
+ return true;
+ }
+
+ // Regulate crit frequency to reduce client-side seed hacking
+ float flDamage = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
+ CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );
+ AddToCritBucket( flDamage );
+
+ // Track each request
+ m_nCritChecks++;
+
+ bool bCrit = ( RandomInt( 0, WEAPON_RANDOM_RANGE-1 ) < ( flCritChance ) * WEAPON_RANDOM_RANGE );
+
+#ifdef _DEBUG
+ // Force seed to always say yes
+ if ( tf_weapon_criticals_force_random.GetInt() )
+ {
+ bCrit = true;
+ }
+#endif // _DEBUG
+
+ if ( bCrit )
+ {
+ // Seed says crit. Run it by the manager.
+ bCrit = IsAllowedToWithdrawFromCritBucket( flDamage );
+ }
+
+ return bCrit;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+char const *CTFWeaponBaseMelee::GetShootSound( int iIndex ) const
+{
+ // Custom Melee weapons may override their hit effects
+ if ( iIndex == MELEE_HIT )
+ {
+ const CEconItemView *pItem = GetAttributeContainer()->GetItem();
+ if ( pItem->IsValid() )
+ {
+ const char *pszSound = pItem->GetStaticData()->GetCustomSound( GetTeamNumber(), 1 );
+ if ( pszSound )
+ return pszSound;
+ }
+ }
+
+ return BaseClass::GetShootSound(iIndex);
+}