diff options
Diffstat (limited to 'game/shared/tf/tf_weaponbase_melee.cpp')
| -rw-r--r-- | game/shared/tf/tf_weaponbase_melee.cpp | 1139 |
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); +} |