|
|
//========= 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); }
|