Team Fortress 2 Source Code as on 22/4/2020
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.

1139 lines
32 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "tf_weaponbase_melee.h"
  8. #include "effect_dispatch_data.h"
  9. #include "tf_gamerules.h"
  10. // Server specific.
  11. #if !defined( CLIENT_DLL )
  12. #include "tf_player.h"
  13. #include "tf_gamestats.h"
  14. #include "ilagcompensationmanager.h"
  15. #include "tf_passtime_logic.h"
  16. // Client specific.
  17. #else
  18. #include "c_tf_gamestats.h"
  19. #include "c_tf_player.h"
  20. // NVNT haptics system interface
  21. #include "haptics/ihaptics.h"
  22. #endif
  23. 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." );
  24. //=============================================================================
  25. //
  26. // TFWeaponBase Melee tables.
  27. //
  28. IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBaseMelee, DT_TFWeaponBaseMelee )
  29. BEGIN_NETWORK_TABLE( CTFWeaponBaseMelee, DT_TFWeaponBaseMelee )
  30. END_NETWORK_TABLE()
  31. BEGIN_PREDICTION_DATA( CTFWeaponBaseMelee )
  32. END_PREDICTION_DATA()
  33. LINK_ENTITY_TO_CLASS( tf_weaponbase_melee, CTFWeaponBaseMelee );
  34. // Server specific.
  35. #if !defined( CLIENT_DLL )
  36. BEGIN_DATADESC( CTFWeaponBaseMelee )
  37. DEFINE_THINKFUNC( Smack )
  38. END_DATADESC()
  39. #endif
  40. #ifndef CLIENT_DLL
  41. ConVar tf_meleeattackforcescale( "tf_meleeattackforcescale", "80.0", FCVAR_CHEAT | FCVAR_GAMEDLL | FCVAR_DEVELOPMENTONLY );
  42. #endif
  43. #ifdef _DEBUG
  44. extern ConVar tf_weapon_criticals_force_random;
  45. #endif // _DEBUG
  46. //=============================================================================
  47. //
  48. // TFWeaponBase Melee functions.
  49. //
  50. // -----------------------------------------------------------------------------
  51. // Purpose: Constructor.
  52. // -----------------------------------------------------------------------------
  53. CTFWeaponBaseMelee::CTFWeaponBaseMelee()
  54. {
  55. WeaponReset();
  56. }
  57. // -----------------------------------------------------------------------------
  58. // Purpose:
  59. // -----------------------------------------------------------------------------
  60. void CTFWeaponBaseMelee::WeaponReset( void )
  61. {
  62. BaseClass::WeaponReset();
  63. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
  64. m_flSmackTime = -1.0f;
  65. m_bConnected = false;
  66. m_bMiniCrit = false;
  67. }
  68. // -----------------------------------------------------------------------------
  69. // Purpose:
  70. // -----------------------------------------------------------------------------
  71. bool CTFWeaponBaseMelee::CanHolster( void ) const
  72. {
  73. // For fist users, energy buffs come from steak sandviches which lock us into attacking with melee.
  74. CTFPlayer *pPlayer = GetTFPlayerOwner();
  75. if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_CANNOT_SWITCH_FROM_MELEE ) )
  76. return false;
  77. return BaseClass::CanHolster();
  78. }
  79. // -----------------------------------------------------------------------------
  80. // Purpose:
  81. // -----------------------------------------------------------------------------
  82. void CTFWeaponBaseMelee::Precache()
  83. {
  84. BaseClass::Precache();
  85. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  86. {
  87. char szMeleeSoundStr[128] = "MVM_";
  88. const char *shootsound = GetShootSound( MELEE_HIT );
  89. if ( shootsound && shootsound[0] )
  90. {
  91. V_strcat(szMeleeSoundStr, shootsound, sizeof( szMeleeSoundStr ));
  92. CBaseEntity::PrecacheScriptSound( szMeleeSoundStr );
  93. }
  94. }
  95. CBaseEntity::PrecacheScriptSound("MVM_Weapon_Default.HitFlesh");
  96. }
  97. // -----------------------------------------------------------------------------
  98. // Purpose:
  99. // -----------------------------------------------------------------------------
  100. void CTFWeaponBaseMelee::Spawn()
  101. {
  102. Precache();
  103. // Get the weapon information.
  104. WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( GetClassname() );
  105. Assert( hWpnInfo != GetInvalidWeaponInfoHandle() );
  106. CTFWeaponInfo *pWeaponInfo = dynamic_cast< CTFWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
  107. Assert( pWeaponInfo && "Failed to get CTFWeaponInfo in melee weapon spawn" );
  108. m_pWeaponInfo = pWeaponInfo;
  109. Assert( m_pWeaponInfo );
  110. // No ammo.
  111. m_iClip1 = -1;
  112. BaseClass::Spawn();
  113. }
  114. // -----------------------------------------------------------------------------
  115. // Purpose:
  116. // -----------------------------------------------------------------------------
  117. bool CTFWeaponBaseMelee::Holster( CBaseCombatWeapon *pSwitchingTo )
  118. {
  119. m_flSmackTime = -1.0f;
  120. if ( GetPlayerOwner() )
  121. {
  122. GetPlayerOwner()->m_flNextAttack = gpGlobals->curtime + 0.5;
  123. }
  124. int iSelfMark = 0;
  125. CALL_ATTRIB_HOOK_INT( iSelfMark, self_mark_for_death );
  126. if ( iSelfMark )
  127. {
  128. CTFPlayer *pPlayer = GetTFPlayerOwner();
  129. if ( pPlayer )
  130. {
  131. pPlayer->m_Shared.AddCond( TF_COND_MARKEDFORDEATH_SILENT, iSelfMark );
  132. }
  133. }
  134. return BaseClass::Holster( pSwitchingTo );
  135. }
  136. int CTFWeaponBaseMelee::GetSwingRange( void )
  137. {
  138. CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
  139. if ( pOwner && pOwner->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) )
  140. {
  141. return 128;
  142. }
  143. else
  144. {
  145. int iIsSword = 0;
  146. CALL_ATTRIB_HOOK_INT( iIsSword, is_a_sword )
  147. if ( iIsSword )
  148. {
  149. return 72; // swords are typically 72
  150. }
  151. return 48;
  152. }
  153. }
  154. // -----------------------------------------------------------------------------
  155. // Purpose:
  156. // -----------------------------------------------------------------------------
  157. void CTFWeaponBaseMelee::PrimaryAttack()
  158. {
  159. // Get the current player.
  160. CTFPlayer *pPlayer = GetTFPlayerOwner();
  161. if ( !pPlayer )
  162. return;
  163. if ( !CanAttack() )
  164. return;
  165. // Set the weapon usage mode - primary, secondary.
  166. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
  167. m_bConnected = false;
  168. pPlayer->EndClassSpecialSkill();
  169. // Swing the weapon.
  170. Swing( pPlayer );
  171. m_bCurrentAttackIsDuringDemoCharge = pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT;
  172. if ( pPlayer->m_Shared.GetNextMeleeCrit() == MELEE_MINICRIT )
  173. {
  174. m_bMiniCrit = true;
  175. }
  176. else
  177. {
  178. m_bMiniCrit = false;
  179. }
  180. #ifdef STAGING_ONLY
  181. // Remove Cond if I attack
  182. if ( pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) )
  183. {
  184. pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST );
  185. }
  186. #endif
  187. #if !defined( CLIENT_DLL )
  188. pPlayer->SpeakWeaponFire();
  189. CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  190. if ( pPlayer->m_Shared.IsStealthed() && ShouldRemoveInvisibilityOnPrimaryAttack() )
  191. {
  192. pPlayer->RemoveInvisibility();
  193. }
  194. #endif
  195. }
  196. // -----------------------------------------------------------------------------
  197. // Purpose:
  198. // -----------------------------------------------------------------------------
  199. void CTFWeaponBaseMelee::SecondaryAttack()
  200. {
  201. // semi-auto behaviour
  202. if ( m_bInAttack2 )
  203. return;
  204. // Get the current player.
  205. CTFPlayer *pPlayer = GetTFPlayerOwner();
  206. if ( !pPlayer )
  207. return;
  208. pPlayer->DoClassSpecialSkill();
  209. m_bInAttack2 = true;
  210. #ifdef STAGING_ONLY
  211. // Remove Cond if I attack
  212. if ( pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) )
  213. {
  214. pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST );
  215. }
  216. #endif
  217. m_flNextSecondaryAttack = gpGlobals->curtime + 0.5;
  218. }
  219. //-----------------------------------------------------------------------------
  220. // Purpose:
  221. // Input : *pPlayer -
  222. //-----------------------------------------------------------------------------
  223. void CTFWeaponBaseMelee::Swing( CTFPlayer *pPlayer )
  224. {
  225. CalcIsAttackCritical();
  226. #ifdef GAME_DLL
  227. CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  228. #endif
  229. #ifdef CLIENT_DLL
  230. C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() );
  231. #endif
  232. // Play the melee swing and miss (whoosh) always.
  233. SendPlayerAnimEvent( pPlayer );
  234. DoViewModelAnimation();
  235. // Set next attack times.
  236. float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
  237. m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
  238. m_flNextSecondaryAttack = gpGlobals->curtime + flFireDelay;
  239. pPlayer->m_Shared.SetNextStealthTime( m_flNextSecondaryAttack );
  240. SetWeaponIdleTime( m_flNextPrimaryAttack + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeIdleEmpty );
  241. if ( IsCurrentAttackACrit() )
  242. {
  243. WeaponSound( BURST );
  244. }
  245. else
  246. {
  247. WeaponSound( MELEE_MISS );
  248. }
  249. #ifdef GAME_DLL
  250. // Remember if there are potential targets when we start our swing.
  251. // If there are, the player is exempt from taking "hurt self on miss" damage
  252. // if ALL of these players have died when our swing has finished, and we didn't hit.
  253. // This guards against me performing a "good" swing and being punished by a friend
  254. // killing my target "out from under me".
  255. CUtlVector< CTFPlayer * > enemyVector;
  256. CollectPlayers( &enemyVector, GetEnemyTeam( pPlayer->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
  257. m_potentialVictimVector.RemoveAll();
  258. const float looseSwingRange = 1.2f * GetSwingRange();
  259. for( int i=0; i<enemyVector.Count(); ++i )
  260. {
  261. Vector toVictim = enemyVector[i]->WorldSpaceCenter() - pPlayer->Weapon_ShootPosition();
  262. if ( toVictim.IsLengthLessThan( looseSwingRange ) )
  263. {
  264. m_potentialVictimVector.AddToTail( enemyVector[i] );
  265. }
  266. }
  267. #endif
  268. m_flSmackTime = gpGlobals->curtime + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flSmackDelay;
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Purpose:
  272. //-----------------------------------------------------------------------------
  273. void CTFWeaponBaseMelee::DoViewModelAnimation( void )
  274. {
  275. if ( IsCurrentAttackACrit() )
  276. {
  277. if ( SendWeaponAnim( ACT_VM_SWINGHARD ) )
  278. {
  279. // check that weapon has the activity
  280. return;
  281. }
  282. }
  283. Activity act = ( m_iWeaponMode == TF_WEAPON_PRIMARY_MODE ) ? ACT_VM_HITCENTER : ACT_VM_SWINGHARD;
  284. SendWeaponAnim( act );
  285. }
  286. //-----------------------------------------------------------------------------
  287. // Purpose: Allow melee weapons to send different anim events
  288. // Input : -
  289. //-----------------------------------------------------------------------------
  290. void CTFWeaponBaseMelee::SendPlayerAnimEvent( CTFPlayer *pPlayer )
  291. {
  292. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
  293. }
  294. // -----------------------------------------------------------------------------
  295. void CTFWeaponBaseMelee::ItemPreFrame( void )
  296. {
  297. int iSelfMark = 0;
  298. CALL_ATTRIB_HOOK_INT( iSelfMark, self_mark_for_death );
  299. if ( iSelfMark )
  300. {
  301. CTFPlayer *pPlayer = GetTFPlayerOwner();
  302. if ( pPlayer )
  303. {
  304. pPlayer->m_Shared.AddCond( TF_COND_MARKEDFORDEATH_SILENT, iSelfMark );
  305. }
  306. }
  307. return BaseClass::ItemPreFrame();
  308. }
  309. //-----------------------------------------------------------------------------
  310. // Purpose:
  311. // Input : -
  312. //-----------------------------------------------------------------------------
  313. void CTFWeaponBaseMelee::ItemPostFrame()
  314. {
  315. // Check for smack.
  316. if ( m_flSmackTime > 0.0f && gpGlobals->curtime > m_flSmackTime )
  317. {
  318. Smack();
  319. m_flSmackTime = -1.0f;
  320. CTFPlayer *pPlayer = GetTFPlayerOwner();
  321. if ( pPlayer )
  322. {
  323. pPlayer->m_Shared.SetNextMeleeCrit( MELEE_NOCRIT );
  324. }
  325. }
  326. BaseClass::ItemPostFrame();
  327. }
  328. bool CTFWeaponBaseMelee::DoSwingTraceInternal( trace_t &trace, bool bCleave, CUtlVector< trace_t >* pTargetTraceVector )
  329. {
  330. // Setup a volume for the melee weapon to be swung - approx size, so all melee behave the same.
  331. static Vector vecSwingMinsBase( -18, -18, -18 );
  332. static Vector vecSwingMaxsBase( 18, 18, 18 );
  333. float fBoundsScale = 1.0f;
  334. CALL_ATTRIB_HOOK_FLOAT( fBoundsScale, melee_bounds_multiplier );
  335. Vector vecSwingMins = vecSwingMinsBase * fBoundsScale;
  336. Vector vecSwingMaxs = vecSwingMaxsBase * fBoundsScale;
  337. // Get the current player.
  338. CTFPlayer *pPlayer = GetTFPlayerOwner();
  339. if ( !pPlayer )
  340. return false;
  341. // Setup the swing range.
  342. float fSwingRange = GetSwingRange();
  343. // Scale the range and bounds by the model scale if they're larger
  344. // Not scaling down the range for smaller models because midgets need all the help they can get
  345. if ( pPlayer->GetModelScale() > 1.0f )
  346. {
  347. fSwingRange *= pPlayer->GetModelScale();
  348. vecSwingMins *= pPlayer->GetModelScale();
  349. vecSwingMaxs *= pPlayer->GetModelScale();
  350. }
  351. CALL_ATTRIB_HOOK_FLOAT( fSwingRange, melee_range_multiplier );
  352. Vector vecForward;
  353. AngleVectors( pPlayer->EyeAngles(), &vecForward );
  354. Vector vecSwingStart = pPlayer->Weapon_ShootPosition();
  355. Vector vecSwingEnd = vecSwingStart + vecForward * fSwingRange;
  356. // In MvM, melee hits from the robot team wont hit teammates to ensure mobs of melee bots don't
  357. // swarm so tightly they hit each other and no-one else
  358. bool bDontHitTeammates = pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && TFGameRules()->IsMannVsMachineMode();
  359. CTraceFilterIgnoreTeammates ignoreTeammatesFilter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() );
  360. if ( bCleave )
  361. {
  362. Ray_t ray;
  363. ray.Init( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs );
  364. CBaseEntity *pList[256];
  365. int nTargetCount = UTIL_EntitiesAlongRay( pList, ARRAYSIZE( pList ), ray, FL_CLIENT|FL_OBJECT );
  366. int nHitCount = 0;
  367. for ( int i=0; i<nTargetCount; ++i )
  368. {
  369. CBaseEntity *pTarget = pList[i];
  370. if ( pTarget == pPlayer )
  371. {
  372. // don't hit yourself
  373. continue;
  374. }
  375. if ( bDontHitTeammates && pTarget->GetTeamNumber() == pPlayer->GetTeamNumber() )
  376. {
  377. // don't hit teammate
  378. continue;
  379. }
  380. if ( pTargetTraceVector )
  381. {
  382. trace_t tr;
  383. UTIL_TraceModel( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, pTarget, COLLISION_GROUP_NONE, &tr );
  384. pTargetTraceVector->AddToTail();
  385. pTargetTraceVector->Tail() = tr;
  386. }
  387. nHitCount++;
  388. }
  389. return nHitCount > 0;
  390. }
  391. else
  392. {
  393. bool bSapperHit = false;
  394. // if this weapon can damage sappers, do that trace first
  395. int iDmgSappers = 0;
  396. CALL_ATTRIB_HOOK_INT( iDmgSappers, set_dmg_apply_to_sapper );
  397. if ( iDmgSappers != 0 )
  398. {
  399. CTraceFilterIgnorePlayers ignorePlayersFilter( NULL, COLLISION_GROUP_NONE );
  400. UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &ignorePlayersFilter, &trace );
  401. if ( trace.fraction >= 1.0 )
  402. {
  403. UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &ignorePlayersFilter, &trace );
  404. }
  405. if ( trace.fraction < 1.0f &&
  406. trace.m_pEnt &&
  407. trace.m_pEnt->IsBaseObject() &&
  408. trace.m_pEnt->GetTeamNumber() == pPlayer->GetTeamNumber() )
  409. {
  410. CBaseObject *pObject = static_cast< CBaseObject* >( trace.m_pEnt );
  411. if ( pObject->HasSapper() )
  412. {
  413. bSapperHit = true;
  414. }
  415. }
  416. }
  417. if ( !bSapperHit )
  418. {
  419. // See if we hit anything.
  420. if ( bDontHitTeammates )
  421. {
  422. UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &ignoreTeammatesFilter, &trace );
  423. }
  424. else
  425. {
  426. CTraceFilterIgnoreFriendlyCombatItems filter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() );
  427. UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &filter, &trace );
  428. }
  429. if ( trace.fraction >= 1.0 )
  430. {
  431. if ( bDontHitTeammates )
  432. {
  433. UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &ignoreTeammatesFilter, &trace );
  434. }
  435. else
  436. {
  437. CTraceFilterIgnoreFriendlyCombatItems filter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() );
  438. UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &filter, &trace );
  439. }
  440. if ( trace.fraction < 1.0 )
  441. {
  442. // Calculate the point of intersection of the line (or hull) and the object we hit
  443. // This is and approximation of the "best" intersection
  444. CBaseEntity *pHit = trace.m_pEnt;
  445. if ( !pHit || pHit->IsBSPModel() )
  446. {
  447. // Why duck hull min/max?
  448. FindHullIntersection( vecSwingStart, trace, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, pPlayer );
  449. }
  450. // This is the point on the actual surface (the hull could have hit space)
  451. vecSwingEnd = trace.endpos;
  452. }
  453. }
  454. }
  455. return ( trace.fraction < 1.0f );
  456. }
  457. }
  458. bool CTFWeaponBaseMelee::DoSwingTrace( trace_t &trace )
  459. {
  460. return DoSwingTraceInternal( trace, false, NULL );
  461. }
  462. //-----------------------------------------------------------------------------
  463. // Purpose:
  464. // Output : float
  465. //-----------------------------------------------------------------------------
  466. bool CTFWeaponBaseMelee::OnSwingHit( trace_t &trace )
  467. {
  468. CTFPlayer *pPlayer = GetTFPlayerOwner();
  469. // NVNT if this is the client dll and the owner is the local player
  470. // Notify the haptics system the local player just hit something.
  471. #ifdef CLIENT_DLL
  472. if(pPlayer==C_TFPlayer::GetLocalTFPlayer() && haptics)
  473. haptics->ProcessHapticEvent(2,"Weapons","meleehit");
  474. #endif
  475. bool bHitEnemyPlayer = false;
  476. // Hit sound - immediate.
  477. if( trace.m_pEnt->IsPlayer() )
  478. {
  479. CTFPlayer *pTargetPlayer = ToTFPlayer( trace.m_pEnt );
  480. bool bPlayMvMHitOnly = false;
  481. // handle hitting a robot
  482. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() )
  483. {
  484. if ( pTargetPlayer && pTargetPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS && !pTargetPlayer->IsPlayer() )
  485. {
  486. bPlayMvMHitOnly = true;
  487. CBroadcastRecipientFilter filter;
  488. // CSingleUserRecipientFilter filter( ToBasePlayer( GetOwner() ) );
  489. // if ( IsPredicted() && CBaseEntity::GetPredictionPlayer() )
  490. // {
  491. // filter.UsePredictionRules();
  492. // }
  493. char szMeleeSoundStr[128] = "MVM_";
  494. const char *shootsound = GetShootSound( MELEE_HIT );
  495. if ( shootsound && shootsound[0] )
  496. {
  497. V_strcat(szMeleeSoundStr, shootsound, sizeof( szMeleeSoundStr ));
  498. CSoundParameters params;
  499. if ( CBaseEntity::GetParametersForSound( szMeleeSoundStr, params, NULL ) )
  500. {
  501. EmitSound( filter, GetOwner()->entindex(), szMeleeSoundStr, NULL );
  502. }
  503. else
  504. {
  505. EmitSound( filter, GetOwner()->entindex(), "MVM_Weapon_Default.HitFlesh", NULL );
  506. }
  507. }
  508. else
  509. {
  510. EmitSound( filter, GetOwner()->entindex(), "MVM_Weapon_Default.HitFlesh", NULL );
  511. }
  512. }
  513. }
  514. if(! bPlayMvMHitOnly )
  515. {
  516. WeaponSound( MELEE_HIT );
  517. }
  518. #if !defined (CLIENT_DLL)
  519. if ( pTargetPlayer->m_Shared.HasPasstimeBall() && g_pPasstimeLogic )
  520. {
  521. // This handles stealing the ball from teammates since there's no damage involved
  522. // TODO find a better place for this
  523. g_pPasstimeLogic->OnBallCarrierMeleeHit( pTargetPlayer, pPlayer );
  524. }
  525. if ( pPlayer->GetTeamNumber() != pTargetPlayer->GetTeamNumber() )
  526. {
  527. bHitEnemyPlayer = true;
  528. if ( TFGameRules()->IsIT( pPlayer ) )
  529. {
  530. IGameEvent *pEvent = gameeventmanager->CreateEvent( "tagged_player_as_it" );
  531. if ( pEvent )
  532. {
  533. pEvent->SetInt( "player", pPlayer->GetUserID() );
  534. gameeventmanager->FireEvent( pEvent, true );
  535. }
  536. // Tag! You're IT!
  537. TFGameRules()->SetIT( pTargetPlayer );
  538. pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_PLAYER_YES );
  539. UTIL_ClientPrintAll( HUD_PRINTTALK, "#TF_HALLOWEEN_BOSS_ANNOUNCE_TAG", pPlayer->GetPlayerName(), pTargetPlayer->GetPlayerName() );
  540. CSingleUserReliableRecipientFilter filter( pPlayer );
  541. pPlayer->EmitSound( filter, pPlayer->entindex(), "Player.TaggedOtherIT" );
  542. }
  543. }
  544. if ( pTargetPlayer->InSameTeam( pPlayer ) || pTargetPlayer->m_Shared.GetDisguiseTeam() == GetTeamNumber() )
  545. {
  546. int iSpeedBuffOnHit = 0;
  547. CALL_ATTRIB_HOOK_INT( iSpeedBuffOnHit, speed_buff_ally );
  548. if ( iSpeedBuffOnHit > 0 && trace.m_pEnt )
  549. {
  550. pTargetPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, 2.f );
  551. 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
  552. EconEntity_OnOwnerKillEaterEvent( this, pPlayer, pTargetPlayer, kKillEaterEvent_TeammatesWhipped ); // Strange
  553. }
  554. // Give health to teammates on hit
  555. int nGiveHealthOnHit = 0;
  556. CALL_ATTRIB_HOOK_INT( nGiveHealthOnHit, add_give_health_to_teammate_on_hit );
  557. if ( nGiveHealthOnHit != 0 )
  558. {
  559. // Always keep at least 1 health for ourselves
  560. nGiveHealthOnHit = Min( pPlayer->GetHealth() - 1, nGiveHealthOnHit );
  561. int nHealthGiven = pTargetPlayer->TakeHealth( nGiveHealthOnHit, DMG_GENERIC );
  562. if ( nHealthGiven > 0 )
  563. {
  564. // Subtract health given from my own
  565. CTakeDamageInfo info( pPlayer, pPlayer, this, nHealthGiven, DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
  566. pPlayer->TakeDamage( info );
  567. }
  568. }
  569. }
  570. #endif
  571. }
  572. else
  573. {
  574. WeaponSound( MELEE_HIT_WORLD );
  575. }
  576. DoMeleeDamage( trace.m_pEnt, trace );
  577. return bHitEnemyPlayer;
  578. }
  579. // -----------------------------------------------------------------------------
  580. // Purpose:
  581. // Note: Think function to delay the impact decal until the animation is finished
  582. // playing.
  583. // -----------------------------------------------------------------------------
  584. void CTFWeaponBaseMelee::Smack( void )
  585. {
  586. trace_t trace;
  587. CTFPlayer *pPlayer = GetTFPlayerOwner();
  588. if ( !pPlayer )
  589. return;
  590. #if !defined (CLIENT_DLL)
  591. // Move other players back to history positions based on local player's lag
  592. lagcompensation->StartLagCompensation( pPlayer, pPlayer->GetCurrentCommand() );
  593. #endif
  594. bool bHitEnemyPlayer = false;
  595. int nCleaveAttack = 0;
  596. CALL_ATTRIB_HOOK_INT( nCleaveAttack, melee_cleave_attack );
  597. bool bCleave = nCleaveAttack > 0;
  598. // We hit, setup the smack.
  599. CUtlVector<trace_t> targetTraceVector;
  600. if ( DoSwingTraceInternal( trace, bCleave, &targetTraceVector ) )
  601. {
  602. if ( bCleave )
  603. {
  604. for ( int i=0; i<targetTraceVector.Count(); ++i )
  605. {
  606. bHitEnemyPlayer |= OnSwingHit( targetTraceVector[i] );
  607. }
  608. }
  609. else
  610. {
  611. bHitEnemyPlayer = OnSwingHit( trace );
  612. }
  613. }
  614. else
  615. {
  616. // if ALL of my potential targets have been killed by someone else between the
  617. // time I started my swing and the time my swing would have landed, don't
  618. // punish me for it.
  619. bool bIsCleanMiss = true;
  620. #ifdef GAME_DLL
  621. for( int i=0; i<m_potentialVictimVector.Count(); ++i )
  622. {
  623. if ( m_potentialVictimVector[i] != NULL && m_potentialVictimVector[i]->IsAlive() )
  624. {
  625. bIsCleanMiss = false;
  626. break;
  627. }
  628. }
  629. #endif
  630. if ( bIsCleanMiss )
  631. {
  632. int iHitSelf = 0;
  633. CALL_ATTRIB_HOOK_INT( iHitSelf, hit_self_on_miss );
  634. if ( iHitSelf == 1 )
  635. {
  636. DoMeleeDamage( GetTFPlayerOwner(), trace, 0.5f );
  637. }
  638. }
  639. }
  640. #if !defined (CLIENT_DLL)
  641. // ACHIEVEMENT_TF_MEDIC_BONESAW_NOMISSES
  642. if ( GetWeaponID() == TF_WEAPON_BONESAW )
  643. {
  644. int iCount = pPlayer->GetPerLifeCounterKV( "medic_bonesaw_hits" );
  645. if ( bHitEnemyPlayer )
  646. {
  647. if ( ++iCount >= 5 )
  648. {
  649. pPlayer->AwardAchievement( ACHIEVEMENT_TF_MEDIC_BONESAW_NOMISSES );
  650. }
  651. }
  652. else
  653. {
  654. iCount = 0;
  655. }
  656. pPlayer->SetPerLifeCounterKV( "medic_bonesaw_hits", iCount );
  657. }
  658. lagcompensation->FinishLagCompensation( pPlayer );
  659. #endif
  660. }
  661. void CTFWeaponBaseMelee::DoMeleeDamage( CBaseEntity* ent, trace_t& trace )
  662. {
  663. DoMeleeDamage( ent, trace, 1.f );
  664. }
  665. void CTFWeaponBaseMelee::DoMeleeDamage( CBaseEntity* ent, trace_t& trace, float flDamageMod )
  666. {
  667. // Get the current player.
  668. CTFPlayer *pPlayer = GetTFPlayerOwner();
  669. if ( !pPlayer )
  670. return;
  671. Vector vecForward;
  672. AngleVectors( pPlayer->EyeAngles(), &vecForward );
  673. Vector vecSwingStart = pPlayer->Weapon_ShootPosition();
  674. Vector vecSwingEnd = vecSwingStart + vecForward * 48;
  675. #ifndef CLIENT_DLL
  676. // Do Damage.
  677. int iCustomDamage = GetDamageCustom();
  678. int iDmgType = DMG_MELEE | DMG_NEVERGIB | DMG_CLUB;
  679. int iCritFromBehind = 0;
  680. CALL_ATTRIB_HOOK_INT( iCritFromBehind, crit_from_behind );
  681. if ( iCritFromBehind > 0 )
  682. {
  683. Vector entForward;
  684. AngleVectors( ent->EyeAngles(), &entForward );
  685. Vector toEnt = ent->GetAbsOrigin() - pPlayer->GetAbsOrigin();
  686. toEnt.NormalizeInPlace();
  687. if ( DotProduct( toEnt, entForward ) > 0.7071f )
  688. {
  689. iDmgType |= DMG_CRITICAL;
  690. }
  691. }
  692. float flDamage = GetMeleeDamage( ent, &iDmgType, &iCustomDamage ) * flDamageMod;
  693. // Base melee damage increased because we disallow random crits in this mode. Without random crits, melee is underpowered
  694. if ( TFGameRules() && TFGameRules()->IsPowerupMode() )
  695. {
  696. if ( !IsCurrentAttackACrit() ) // Don't multiply base damage if attack is a crit
  697. {
  698. if ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
  699. {
  700. flDamage *= 1.9f;
  701. }
  702. // 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
  703. else if ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() != RUNE_STRENGTH && !pPlayer->m_Shared.IsShieldEquipped() )
  704. {
  705. flDamage *= 1.3f;
  706. }
  707. }
  708. }
  709. if ( IsCurrentAttackACrit() )
  710. {
  711. // TODO: Not removing the old critical path yet, but the new custom damage is marking criticals as well for melee now.
  712. iDmgType |= DMG_CRITICAL;
  713. }
  714. else if ( m_bMiniCrit )
  715. {
  716. iDmgType |= DMG_RADIUS_MAX; // Unused for melee, indicates this should be a minicrit.
  717. }
  718. CTakeDamageInfo info( pPlayer, pPlayer, this, flDamage, iDmgType, iCustomDamage );
  719. if ( fabs( flDamage ) >= 1.0f )
  720. {
  721. CalculateMeleeDamageForce( &info, vecForward, vecSwingEnd, 1.0f / flDamage * GetForceScale() );
  722. }
  723. else
  724. {
  725. info.SetDamageForce( vec3_origin );
  726. }
  727. ent->DispatchTraceAttack( info, vecForward, &trace );
  728. ApplyMultiDamage();
  729. OnEntityHit( ent, &info );
  730. bool bTruce = TFGameRules() && TFGameRules()->IsTruceActive() && pPlayer->IsTruceValidForEnt();
  731. if ( !bTruce )
  732. {
  733. int iCritsForceVictimToLaugh = 0;
  734. CALL_ATTRIB_HOOK_INT( iCritsForceVictimToLaugh, crit_forces_victim_to_laugh );
  735. if ( iCritsForceVictimToLaugh > 0 && ( IsCurrentAttackACrit() || iDmgType & DMG_CRITICAL ) )
  736. {
  737. CTFPlayer *pVictimPlayer = ToTFPlayer( ent );
  738. if ( pVictimPlayer && pVictimPlayer->CanBeForcedToLaugh() && ( pPlayer->GetTeamNumber() != pVictimPlayer->GetTeamNumber() ) )
  739. {
  740. // force victim to laugh!
  741. pVictimPlayer->Taunt( TAUNT_MISC_ITEM, MP_CONCEPT_TAUNT_LAUGH );
  742. // strange stat tracking
  743. EconEntity_OnOwnerKillEaterEvent( this,
  744. ToTFPlayer( GetOwner() ),
  745. pVictimPlayer,
  746. kKillEaterEvent_PlayerTickle );
  747. }
  748. }
  749. int iTickleEnemiesWieldingSameWeapon = 0;
  750. CALL_ATTRIB_HOOK_INT( iTickleEnemiesWieldingSameWeapon, tickle_enemies_wielding_same_weapon );
  751. if ( iTickleEnemiesWieldingSameWeapon > 0 )
  752. {
  753. CTFPlayer *pVictimPlayer = ToTFPlayer( ent );
  754. if ( pVictimPlayer && pVictimPlayer->CanBeForcedToLaugh() && ( pPlayer->GetTeamNumber() != pVictimPlayer->GetTeamNumber() ) )
  755. {
  756. CTFWeaponBase *myWeapon = pPlayer->GetActiveTFWeapon();
  757. CTFWeaponBase *theirWeapon = pVictimPlayer->GetActiveTFWeapon();
  758. if ( myWeapon && theirWeapon )
  759. {
  760. CEconItemView *myItem = myWeapon->GetAttributeContainer()->GetItem();
  761. CEconItemView *theirItem = theirWeapon->GetAttributeContainer()->GetItem();
  762. if ( myItem && theirItem && myItem->GetItemDefIndex() == theirItem->GetItemDefIndex() )
  763. {
  764. // force victim to laugh!
  765. pVictimPlayer->Taunt( TAUNT_MISC_ITEM, MP_CONCEPT_TAUNT_LAUGH );
  766. }
  767. }
  768. }
  769. }
  770. }
  771. if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT )
  772. {
  773. CTFPlayer *pVictimPlayer = ToTFPlayer( ent );
  774. if ( pVictimPlayer && !pVictimPlayer->InSameTeam( pPlayer ) )
  775. {
  776. CPASAttenuationFilter filter( pPlayer );
  777. Vector origin = pPlayer->GetAbsOrigin();
  778. Vector vecDir = pVictimPlayer->GetAbsOrigin() - origin;
  779. VectorNormalize( vecDir );
  780. if ( !pVictimPlayer->m_Shared.InCond( TF_COND_INVULNERABLE_USER_BUFF ) &&
  781. !pVictimPlayer->m_Shared.InCond( TF_COND_INVULNERABLE ) )
  782. {
  783. if ( pVictimPlayer->m_Shared.IsCarryingRune() )
  784. {
  785. pVictimPlayer->DropRune();
  786. ClientPrint( pVictimPlayer, HUD_PRINTCENTER, "#TF_Powerup_Knocked_Out" );
  787. }
  788. else if ( pVictimPlayer->HasTheFlag() )
  789. {
  790. pVictimPlayer->DropFlag();
  791. ClientPrint( pVictimPlayer, HUD_PRINTCENTER, "#TF_CTF_PlayerDrop" );
  792. }
  793. }
  794. EmitSound( filter, entindex(), "Powerup.Knockout_Melee_Hit" );
  795. pVictimPlayer->ApplyAirBlastImpulse( vecDir * 400.0f );
  796. }
  797. }
  798. #endif
  799. // Don't impact trace friendly players or objects
  800. if ( ent && ent->GetTeamNumber() != pPlayer->GetTeamNumber() )
  801. {
  802. #ifdef CLIENT_DLL
  803. UTIL_ImpactTrace( &trace, DMG_CLUB );
  804. #endif
  805. m_bConnected = true;
  806. }
  807. }
  808. #ifndef CLIENT_DLL
  809. //-----------------------------------------------------------------------------
  810. // Purpose:
  811. // Output : float
  812. //-----------------------------------------------------------------------------
  813. float CTFWeaponBaseMelee::GetForceScale( void )
  814. {
  815. return tf_meleeattackforcescale.GetFloat();
  816. }
  817. #endif
  818. //-----------------------------------------------------------------------------
  819. // Purpose:
  820. // Output : float
  821. //-----------------------------------------------------------------------------
  822. float CTFWeaponBaseMelee::GetMeleeDamage( CBaseEntity *pTarget, int* piDamageType, int* piCustomDamage )
  823. {
  824. float flDamage = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
  825. CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );
  826. int iCritDoesNoDamage = 0;
  827. CALL_ATTRIB_HOOK_INT( iCritDoesNoDamage, crit_does_no_damage );
  828. if ( iCritDoesNoDamage > 0 )
  829. {
  830. if ( IsCurrentAttackACrit() )
  831. {
  832. return 0.0f;
  833. }
  834. if ( piDamageType && *piDamageType & DMG_CRITICAL )
  835. {
  836. return 0.0f;
  837. }
  838. }
  839. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  840. if ( pPlayer )
  841. {
  842. float flHalfHealth = pPlayer->GetMaxHealth() * 0.5f;
  843. if ( pPlayer->GetHealth() < flHalfHealth )
  844. {
  845. CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg_bonus_while_half_dead );
  846. }
  847. else
  848. {
  849. CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg_penalty_while_half_alive );
  850. }
  851. // Some weapons change damage based on player's health
  852. float flReducedHealthBonus = 1.0f;
  853. CALL_ATTRIB_HOOK_FLOAT( flReducedHealthBonus, mult_dmg_with_reduced_health );
  854. if ( flReducedHealthBonus != 1.0f )
  855. {
  856. float flHealthFraction = clamp( pPlayer->HealthFraction(), 0.0f, 1.0f );
  857. flReducedHealthBonus = Lerp( flHealthFraction, flReducedHealthBonus, 1.0f );
  858. flDamage *= flReducedHealthBonus;
  859. }
  860. }
  861. return flDamage;
  862. }
  863. void CTFWeaponBaseMelee::OnEntityHit( CBaseEntity *pEntity, CTakeDamageInfo *info )
  864. {
  865. }
  866. //-----------------------------------------------------------------------------
  867. //
  868. //-----------------------------------------------------------------------------
  869. bool CTFWeaponBaseMelee::CalcIsAttackCriticalHelperNoCrits( void )
  870. {
  871. // This function was called because the tf_weapon_criticals ConVar is off, but if
  872. // melee crits are set to be forced on, then call the regular crit helper function.
  873. if ( tf_weapon_criticals_melee.GetInt() > 1 )
  874. {
  875. return CalcIsAttackCriticalHelper();
  876. }
  877. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  878. if ( !pPlayer )
  879. return false;
  880. m_bCurrentAttackIsDuringDemoCharge = pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT;
  881. if ( pPlayer->m_Shared.GetNextMeleeCrit() == MELEE_CRIT )
  882. {
  883. return true;
  884. }
  885. else
  886. {
  887. return BaseClass::CalcIsAttackCriticalHelperNoCrits();
  888. }
  889. }
  890. //-----------------------------------------------------------------------------
  891. // Purpose:
  892. //-----------------------------------------------------------------------------
  893. bool CTFWeaponBaseMelee::CalcIsAttackCriticalHelper( void )
  894. {
  895. // If melee crits are off, then check the NoCrits helper.
  896. if ( tf_weapon_criticals_melee.GetInt() == 0 )
  897. {
  898. return CalcIsAttackCriticalHelperNoCrits();
  899. }
  900. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
  901. if ( !pPlayer )
  902. return false;
  903. if ( !CanFireCriticalShot() )
  904. return false;
  905. // Crit boosted players fire all crits
  906. if ( pPlayer->m_Shared.IsCritBoosted() )
  907. return true;
  908. float flPlayerCritMult = pPlayer->GetCritMult();
  909. float flCritChance = TF_DAMAGE_CRIT_CHANCE_MELEE * flPlayerCritMult;
  910. CALL_ATTRIB_HOOK_FLOAT( flCritChance, mult_crit_chance );
  911. // mess with the crit chance seed so it's not based solely on the prediction seed
  912. int iMask = ( entindex() << 16 ) | ( pPlayer->entindex() << 8 );
  913. int iSeed = CBaseEntity::GetPredictionRandomSeed() ^ iMask;
  914. if ( iSeed != m_iCurrentSeed )
  915. {
  916. m_iCurrentSeed = iSeed;
  917. RandomSeed( m_iCurrentSeed );
  918. }
  919. m_bCurrentAttackIsDuringDemoCharge = pPlayer->m_Shared.GetNextMeleeCrit() != MELEE_NOCRIT;
  920. if ( pPlayer->m_Shared.GetNextMeleeCrit() == MELEE_CRIT )
  921. {
  922. return true;
  923. }
  924. // Regulate crit frequency to reduce client-side seed hacking
  925. float flDamage = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage;
  926. CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg );
  927. AddToCritBucket( flDamage );
  928. // Track each request
  929. m_nCritChecks++;
  930. bool bCrit = ( RandomInt( 0, WEAPON_RANDOM_RANGE-1 ) < ( flCritChance ) * WEAPON_RANDOM_RANGE );
  931. #ifdef _DEBUG
  932. // Force seed to always say yes
  933. if ( tf_weapon_criticals_force_random.GetInt() )
  934. {
  935. bCrit = true;
  936. }
  937. #endif // _DEBUG
  938. if ( bCrit )
  939. {
  940. // Seed says crit. Run it by the manager.
  941. bCrit = IsAllowedToWithdrawFromCritBucket( flDamage );
  942. }
  943. return bCrit;
  944. }
  945. //-----------------------------------------------------------------------------
  946. // Purpose:
  947. //-----------------------------------------------------------------------------
  948. char const *CTFWeaponBaseMelee::GetShootSound( int iIndex ) const
  949. {
  950. // Custom Melee weapons may override their hit effects
  951. if ( iIndex == MELEE_HIT )
  952. {
  953. const CEconItemView *pItem = GetAttributeContainer()->GetItem();
  954. if ( pItem->IsValid() )
  955. {
  956. const char *pszSound = pItem->GetStaticData()->GetCustomSound( GetTeamNumber(), 1 );
  957. if ( pszSound )
  958. return pszSound;
  959. }
  960. }
  961. return BaseClass::GetShootSound(iIndex);
  962. }