initial
This commit is contained in:
618
game/shared/cstrike15/fx_cs_shared.cpp
Normal file
618
game/shared/cstrike15/fx_cs_shared.cpp
Normal file
@@ -0,0 +1,618 @@
|
||||
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "fx_cs_shared.h"
|
||||
#include "weapon_csbase.h"
|
||||
#include "rumble_shared.h"
|
||||
|
||||
#ifndef CLIENT_DLL
|
||||
#include "ilagcompensationmanager.h"
|
||||
#endif
|
||||
|
||||
ConVar weapon_accuracy_logging( "weapon_accuracy_logging", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE );
|
||||
ConVar steam_controller_haptics( "steam_controller_haptics", "1", FCVAR_RELEASE );
|
||||
ConVar weapon_near_empty_sound( "weapon_near_empty_sound", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
|
||||
ConVar weapon_debug_max_inaccuracy( "weapon_debug_max_inaccuracy", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Force all shots to have maximum inaccuracy" );
|
||||
ConVar weapon_debug_inaccuracy_only_up( "weapon_debug_inaccuracy_only_up", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Force weapon inaccuracy to be in exactly the up direction" );
|
||||
ConVar snd_max_pitch_shift_inaccuracy("snd_max_pitch_shift_inaccuracy", "0.08", 0);
|
||||
|
||||
#ifdef CLIENT_DLL
|
||||
|
||||
#include "fx_impact.h"
|
||||
#include "c_rumble.h"
|
||||
#include "inputsystem/iinputsystem.h"
|
||||
|
||||
// NOTE: This has to be the last file included!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
// this is a cheap ripoff from CBaseCombatWeapon::WeaponSound():
|
||||
void FX_WeaponSound(
|
||||
int iPlayerIndex,
|
||||
uint16 nItemDefIndex,
|
||||
WeaponSound_t sound_type,
|
||||
const Vector &vOrigin,
|
||||
const CCSWeaponInfo *pWeaponInfo,
|
||||
float flSoundTime,
|
||||
int nPitch )
|
||||
{
|
||||
// If we have some sounds from the weapon classname.txt file, play a random one of them
|
||||
const char *shootsound = pWeaponInfo->aShootSounds[ sound_type ];
|
||||
|
||||
// Get the item definition
|
||||
const CEconItemDefinition *pDef = ( nItemDefIndex > 0 ) ? GetItemSchema()->GetItemDefinition( nItemDefIndex ) : NULL;
|
||||
if ( pDef )
|
||||
{
|
||||
const char *pszTempSound = pDef->GetWeaponReplacementSound( sound_type );
|
||||
if ( pszTempSound )
|
||||
{
|
||||
shootsound = pszTempSound;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !shootsound || !shootsound[0] )
|
||||
return;
|
||||
|
||||
CBroadcastRecipientFilter filter; // this is client side only
|
||||
if ( !te->CanPredict() )
|
||||
return;
|
||||
|
||||
EmitSound_t params;
|
||||
params.m_pSoundName = shootsound;
|
||||
params.m_flSoundTime = flSoundTime;
|
||||
params.m_pOrigin = &vOrigin;
|
||||
params.m_pflSoundDuration = nullptr;
|
||||
params.m_bWarnOnDirectWaveReference = true;
|
||||
params.m_nPitch = nPitch;
|
||||
|
||||
if (nPitch != PITCH_NORM)
|
||||
{
|
||||
params.m_nFlags = params.m_nFlags | SND_OVERRIDE_PITCH;
|
||||
}
|
||||
|
||||
CBaseEntity::EmitSound( filter, iPlayerIndex, params );
|
||||
}
|
||||
|
||||
class CGroupedSound
|
||||
{
|
||||
public:
|
||||
string_t m_SoundName;
|
||||
Vector m_vPos;
|
||||
};
|
||||
|
||||
CUtlVector<CGroupedSound> g_GroupedSounds;
|
||||
|
||||
|
||||
// Called by the ImpactSound function.
|
||||
void ShotgunImpactSoundGroup( const char *pSoundName, const Vector &vEndPos )
|
||||
{
|
||||
int i;
|
||||
// Don't play the sound if it's too close to another impact sound.
|
||||
for ( i=0; i < g_GroupedSounds.Count(); i++ )
|
||||
{
|
||||
CGroupedSound *pSound = &g_GroupedSounds[i];
|
||||
|
||||
if ( vEndPos.DistToSqr( pSound->m_vPos ) < 300*300 )
|
||||
{
|
||||
if ( Q_stricmp( pSound->m_SoundName, pSoundName ) == 0 )
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, play the sound and add it to the list.
|
||||
CLocalPlayerFilter filter;
|
||||
C_BaseEntity::EmitSound( filter, NULL, pSoundName, &vEndPos );
|
||||
|
||||
i = g_GroupedSounds.AddToTail();
|
||||
g_GroupedSounds[i].m_SoundName = pSoundName;
|
||||
g_GroupedSounds[i].m_vPos = vEndPos;
|
||||
}
|
||||
|
||||
|
||||
void StartGroupingSounds()
|
||||
{
|
||||
Assert( g_GroupedSounds.Count() == 0 );
|
||||
SetImpactSoundRoute( ShotgunImpactSoundGroup );
|
||||
}
|
||||
|
||||
|
||||
void EndGroupingSounds()
|
||||
{
|
||||
g_GroupedSounds.Purge();
|
||||
SetImpactSoundRoute( NULL );
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include "te_shotgun_shot.h"
|
||||
|
||||
// Server doesn't play sounds anyway.
|
||||
void StartGroupingSounds() {}
|
||||
void EndGroupingSounds() {}
|
||||
void FX_WeaponSound ( int iPlayerIndex,
|
||||
uint16 nItemDefIndex,
|
||||
WeaponSound_t sound_type,
|
||||
const Vector &vOrigin,
|
||||
const CCSWeaponInfo *pWeaponInfo, float flSoundTime, int nPitch ) {};
|
||||
|
||||
#endif
|
||||
|
||||
ConVar debug_aim_angle("debug_aim_angle", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY);
|
||||
|
||||
// This runs on both the client and the server.
|
||||
// On the server, it only does the damage calculations.
|
||||
// On the client, it does all the effects.
|
||||
void FX_FireBullets(
|
||||
int iPlayerIndex,
|
||||
uint16 nItemDefIndex,
|
||||
const Vector &vOrigin,
|
||||
const QAngle &vAngles,
|
||||
CSWeaponID iWeaponID,
|
||||
int iMode,
|
||||
int iSeed,
|
||||
float fInaccuracy,
|
||||
float fSpread,
|
||||
float fAccuracyFishtail,
|
||||
float flSoundTime,
|
||||
WeaponSound_t sound_type,
|
||||
float flRecoilIndex
|
||||
)
|
||||
{
|
||||
bool bDoEffects = true;
|
||||
|
||||
if ( fInaccuracy > 1.0f )
|
||||
fInaccuracy = 1.0f;
|
||||
|
||||
#ifdef CLIENT_DLL
|
||||
C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) );
|
||||
#else
|
||||
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) );
|
||||
#endif
|
||||
|
||||
if ( !pPlayer || iPlayerIndex < 0 )
|
||||
{
|
||||
// probably an env_gunfire
|
||||
const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo( iWeaponID );
|
||||
FX_WeaponSound( iPlayerIndex, nItemDefIndex, sound_type, vOrigin, pWeaponInfo, flSoundTime, PITCH_NORM );
|
||||
#ifndef CLIENT_DLL
|
||||
// if this is server code, send the effect over to client as temp entity
|
||||
// Dispatch one message for all the bullet impacts and sounds.
|
||||
TE_FireBullets(
|
||||
-1,
|
||||
nItemDefIndex,
|
||||
vOrigin,
|
||||
vAngles,
|
||||
iWeaponID,
|
||||
iMode,
|
||||
iSeed,
|
||||
fInaccuracy,
|
||||
fSpread,
|
||||
fAccuracyFishtail,
|
||||
sound_type,
|
||||
flRecoilIndex
|
||||
);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef CLIENT_DLL
|
||||
CWeaponCSBase* pClientWeapon = pPlayer ? pPlayer->GetActiveCSWeapon() : NULL;
|
||||
if ( pClientWeapon )
|
||||
{
|
||||
if ( gpGlobals->curtime - pClientWeapon->m_flLastClientFireBulletTime > 0.02f ) // this should be enough even for the negev, at ~1000 rof
|
||||
{
|
||||
pClientWeapon->m_flLastClientFireBulletTime = gpGlobals->curtime;
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // we already traced this shot on the client!
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QAngle adjustedAngles = vAngles;
|
||||
adjustedAngles.y += fAccuracyFishtail;
|
||||
|
||||
if ( pPlayer && debug_aim_angle.GetBool() )
|
||||
{
|
||||
QAngle old = pPlayer->EyeAngles() + pPlayer->GetAimPunchAngle();
|
||||
#ifdef CLIENT_DLL
|
||||
DevMsg("Client ");
|
||||
#else
|
||||
DevMsg("Server ");
|
||||
#endif
|
||||
DevMsg("old: %f %f new: %f %f\n",
|
||||
old[YAW], old[PITCH],
|
||||
vAngles[YAW], vAngles[PITCH]
|
||||
);
|
||||
if ( debug_aim_angle.GetInt() == 2 )
|
||||
{
|
||||
adjustedAngles = old;
|
||||
}
|
||||
}
|
||||
|
||||
const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( nItemDefIndex );
|
||||
if ( !pItemDef )
|
||||
{
|
||||
DevMsg( "FX_FireBullets: GetItemDefinition failed for defindex %d\n", nItemDefIndex );
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(CLIENT_DLL)
|
||||
if ( weapon_accuracy_logging.GetBool() )
|
||||
{
|
||||
char szFlags[256];
|
||||
|
||||
V_strcpy(szFlags, " ");
|
||||
|
||||
// #if defined(CLIENT_DLL)
|
||||
// V_strcat(szFlags, "CLIENT ", sizeof(szFlags));
|
||||
// #else
|
||||
// V_strcat(szFlags, "SERVER ", sizeof(szFlags));
|
||||
// #endif
|
||||
//
|
||||
if ( pPlayer->GetMoveType() == MOVETYPE_LADDER )
|
||||
V_strcat(szFlags, "LADDER ", sizeof(szFlags));
|
||||
|
||||
if ( FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) )
|
||||
V_strcat(szFlags, "GROUND ", sizeof(szFlags));
|
||||
|
||||
if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING) )
|
||||
V_strcat(szFlags, "DUCKING ", sizeof(szFlags));
|
||||
|
||||
float fVelocity = pPlayer->GetAbsVelocity().Length2D();
|
||||
|
||||
Msg("FireBullets @ %10f [ %s ]: inaccuracy=%f spread=%f max dispersion=%f mode=%2i vel=%10f seed=%3i %s\n",
|
||||
gpGlobals->curtime, pItemDef->GetItemBaseName(), fInaccuracy, fSpread, fInaccuracy + fSpread, iMode, fVelocity, iSeed, szFlags);
|
||||
}
|
||||
#endif
|
||||
|
||||
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pItemDef->GetItemClass() );
|
||||
|
||||
if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
|
||||
{
|
||||
DevMsg("FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", pItemDef->GetItemBaseName() );
|
||||
return;
|
||||
}
|
||||
|
||||
CCSWeaponInfo *pWeaponInfo = static_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
|
||||
if ( !pWeaponInfo )
|
||||
{
|
||||
DevMsg( "FX_FireBullets: GetFileWeaponInfoFromHandle failed for weapon %s\n", pItemDef->GetItemBaseName() );
|
||||
return;
|
||||
}
|
||||
|
||||
// Do the firing animation event.
|
||||
#ifndef CLIENT_DLL
|
||||
if ( pPlayer && !pPlayer->IsDormant() )
|
||||
{
|
||||
if ( iMode == Primary_Mode )
|
||||
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY );
|
||||
else
|
||||
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_SECONDARY );
|
||||
}
|
||||
#endif // CLIENT_DLL
|
||||
|
||||
|
||||
#ifdef CLIENT_DLL
|
||||
if ( pPlayer && pPlayer->m_bUseNewAnimstate )
|
||||
{
|
||||
pPlayer->ProcessMuzzleFlashEvent();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef CLIENT_DLL
|
||||
// if this is server code, send the effect over to client as temp entity
|
||||
// Dispatch one message for all the bullet impacts and sounds.
|
||||
TE_FireBullets(
|
||||
iPlayerIndex,
|
||||
nItemDefIndex,
|
||||
vOrigin,
|
||||
vAngles,
|
||||
iWeaponID,
|
||||
iMode,
|
||||
iSeed,
|
||||
fInaccuracy,
|
||||
fSpread,
|
||||
fAccuracyFishtail,
|
||||
sound_type,
|
||||
flRecoilIndex
|
||||
);
|
||||
|
||||
|
||||
// Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
|
||||
if ( pPlayer )
|
||||
pPlayer->NoteWeaponFired();
|
||||
|
||||
bDoEffects = false; // no effects on server
|
||||
#endif
|
||||
|
||||
iSeed++;
|
||||
|
||||
CWeaponCSBase* pWeapon = pPlayer ? pPlayer->GetActiveCSWeapon() : NULL;
|
||||
CEconItemView* pItem = pWeapon ? pWeapon->GetEconItemView() : NULL;
|
||||
|
||||
int iDamage = pWeaponInfo->GetDamage( pItem );
|
||||
float flRange = pWeaponInfo->GetRange( pItem );
|
||||
float flPenetration = pWeaponInfo->GetPenetration( pItem );
|
||||
float flRangeModifier = pWeaponInfo->GetRangeModifier( pItem );
|
||||
int iAmmoType = pWeaponInfo->GetPrimaryAmmoType( pItem );
|
||||
|
||||
if ( bDoEffects)
|
||||
{
|
||||
static const float MaxPitchShiftInaccuracy = 0.05f;
|
||||
float flPitchShift = pWeaponInfo->GetInaccuracyPitchShift() * (fInaccuracy < MaxPitchShiftInaccuracy ? fInaccuracy : MaxPitchShiftInaccuracy);
|
||||
|
||||
if ( sound_type == SINGLE && pWeaponInfo->GetInaccuracyAltSoundThreshhold() > 0.0f && fInaccuracy < pWeaponInfo->GetInaccuracyAltSoundThreshhold() )
|
||||
{
|
||||
sound_type = SINGLE_ACCURATE;
|
||||
flPitchShift = 0.0f;
|
||||
}
|
||||
|
||||
FX_WeaponSound( iPlayerIndex, nItemDefIndex, sound_type, vOrigin, pWeaponInfo, flSoundTime, PITCH_NORM + int(flPitchShift) );
|
||||
|
||||
// If the gun's nearly empty, also play a subtle "nearly-empty" sound, since the weapon
|
||||
// is lighter and acoustically different when weighed down by fewer bullets.
|
||||
// But really it's so you get a fun low ammo warning from an audio cue.
|
||||
if ( weapon_near_empty_sound.GetBool() &&
|
||||
pWeapon && pWeapon->GetMaxClip1() > 1 && // not a single-shot weapon
|
||||
(((float)pWeapon->m_iClip1) / ((float)pWeapon->GetMaxClip1()) <= 0.2) ) // 20% or fewer bullets remaining
|
||||
{
|
||||
FX_WeaponSound( iPlayerIndex, nItemDefIndex, NEARLYEMPTY, vOrigin, pWeaponInfo, flSoundTime, PITCH_NORM );
|
||||
}
|
||||
}
|
||||
|
||||
// Fire bullets, calculate impacts & effects
|
||||
|
||||
if ( !pPlayer )
|
||||
return;
|
||||
|
||||
StartGroupingSounds();
|
||||
|
||||
#ifdef GAME_DLL
|
||||
pPlayer->StartNewBulletGroup();
|
||||
#endif
|
||||
|
||||
#if !defined (CLIENT_DLL)
|
||||
// Move other players back to history positions based on local player's lag
|
||||
lagcompensation->StartLagCompensation( pPlayer, LAG_COMPENSATE_HITBOXES_ALONG_RAY, vOrigin, vAngles, flRange );
|
||||
#endif
|
||||
|
||||
// [sbodenbender] rumble when shooting
|
||||
// since we are handling bullet fx in CS differently than other titles, call
|
||||
// rumble effect directly instead of Player::RumbleEffect
|
||||
//=============================================================================
|
||||
|
||||
|
||||
#if defined (CLIENT_DLL)
|
||||
if (pPlayer && pPlayer->IsLocalPlayer() && pWeaponInfo && pWeaponInfo->GetBullets() > 0)
|
||||
{
|
||||
int rumbleEffect = pWeaponInfo->iRumbleEffect;
|
||||
|
||||
if( rumbleEffect != RUMBLE_INVALID )
|
||||
{
|
||||
RumbleEffect( XBX_GetUserId( pPlayer->GetSplitScreenPlayerSlot() ), rumbleEffect, 0, RUMBLE_FLAG_RESTART );
|
||||
}
|
||||
|
||||
if ( rumbleEffect != RUMBLE_INVALID && rumbleEffect <= 6 && steam_controller_haptics.GetBool() && g_pInputSystem->IsSteamControllerActive() && steamapicontext->SteamController() )
|
||||
{
|
||||
ControllerHandle_t handles[MAX_STEAM_CONTROLLERS];
|
||||
int nControllers = steamapicontext->SteamController()->GetConnectedControllers( handles );
|
||||
|
||||
for ( int i = 0; i < nControllers; ++i )
|
||||
{
|
||||
steamapicontext->SteamController()->TriggerHapticPulse( handles[ i ], k_ESteamControllerPad_Right, (2000*rumbleEffect)/5 );
|
||||
steamapicontext->SteamController()->TriggerHapticPulse( handles[ i ], k_ESteamControllerPad_Left, (2000*rumbleEffect)/5 );
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();
|
||||
bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();
|
||||
|
||||
RandomSeed( iSeed ); // init random system with this seed
|
||||
|
||||
// Accuracy curve density adjustment FOR R8 REVOLVER SECONDARY FIRE, NEGEV WILD BEAST
|
||||
float flRadiusCurveDensity = RandomFloat();
|
||||
if ( nItemDefIndex == 64 && iMode == Secondary_Mode ) /*R8 REVOLVER SECONDARY FIRE*/
|
||||
{
|
||||
flRadiusCurveDensity = 1.0f - flRadiusCurveDensity*flRadiusCurveDensity;
|
||||
}
|
||||
if ( nItemDefIndex == 28 && flRecoilIndex < 3 ) /*NEGEV WILD BEAST*/
|
||||
{
|
||||
for ( int j = 3; j > flRecoilIndex; -- j )
|
||||
{
|
||||
flRadiusCurveDensity *= flRadiusCurveDensity;
|
||||
}
|
||||
flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;
|
||||
}
|
||||
|
||||
if ( bForceMaxInaccuracy )
|
||||
flRadiusCurveDensity = 1.0f;
|
||||
|
||||
// Get accuracy displacement
|
||||
float fTheta0 = RandomFloat(0.0f, 2.0f * M_PI);
|
||||
|
||||
if ( bForceInaccuracyDirection )
|
||||
fTheta0 = M_PI * 0.5f;
|
||||
|
||||
float fRadius0 = flRadiusCurveDensity * fInaccuracy;
|
||||
float x0 = fRadius0 * cosf(fTheta0);
|
||||
float y0 = fRadius0 * sinf(fTheta0);
|
||||
|
||||
const int kMaxBullets = 16;
|
||||
float x1[kMaxBullets], y1[kMaxBullets];
|
||||
Assert(pWeaponInfo->GetBullets() <= kMaxBullets);
|
||||
|
||||
// the RNG can be desynchronized by FireBullet(), so pre-generate all spread offsets
|
||||
for ( int iBullet=0; iBullet < pWeaponInfo->GetBullets(); iBullet++ )
|
||||
{
|
||||
// Spread curve density adjustment for R8 REVOLVER SECONDARY FIRE, NEGEV WILD BEAST
|
||||
float flSpreadCurveDensity = RandomFloat();
|
||||
if ( nItemDefIndex == 64 && iMode == Secondary_Mode )
|
||||
{
|
||||
flSpreadCurveDensity = 1.0f - flSpreadCurveDensity*flSpreadCurveDensity;
|
||||
}
|
||||
if ( nItemDefIndex == 28 && flRecoilIndex < 3 ) /*NEGEV WILD BEAST*/
|
||||
{
|
||||
for ( int j = 3; j > flRecoilIndex; --j )
|
||||
{
|
||||
flSpreadCurveDensity *= flSpreadCurveDensity;
|
||||
}
|
||||
flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;
|
||||
}
|
||||
|
||||
if ( bForceMaxInaccuracy )
|
||||
flSpreadCurveDensity = 1.0f;
|
||||
|
||||
float fTheta1 = RandomFloat(0.0f, 2.0f * M_PI);
|
||||
if ( bForceInaccuracyDirection )
|
||||
fTheta1 = M_PI * 0.5f;
|
||||
|
||||
float fRadius1 = flSpreadCurveDensity * fSpread;
|
||||
x1[iBullet] = fRadius1 * cosf(fTheta1);
|
||||
y1[iBullet] = fRadius1 * sinf(fTheta1);
|
||||
}
|
||||
|
||||
#if !defined( CLIENT_DLL )
|
||||
{ /// Make sure take damage listener stays in scope only for the duration of FireBullet loop below!
|
||||
class CFireBulletTakeDamageListener : public CCSPlayer::ITakeDamageListener
|
||||
{
|
||||
public:
|
||||
CFireBulletTakeDamageListener( CCSPlayer *pPlayerShooting ) :
|
||||
m_pPlayerShooting(pPlayerShooting),
|
||||
m_bEnemyHit( false ),
|
||||
m_bShotFiredAndOnTargetRecorded( false )
|
||||
{}
|
||||
virtual void OnTakeDamageListenerCallback( CCSPlayer *pVictim, CTakeDamageInfo &infoTweakable ) OVERRIDE
|
||||
{
|
||||
if ( m_pPlayerShooting && pVictim->IsOtherEnemy( m_pPlayerShooting ) )
|
||||
{
|
||||
m_bEnemyHit = true;
|
||||
|
||||
if ( infoTweakable.GetDamageType() & DMG_HEADSHOT )
|
||||
{
|
||||
m_rbHsPlayers.InsertIfNotFound( pVictim ); // remember that at least one pellet hit a headshot
|
||||
}
|
||||
else if ( m_rbHsPlayers.Find( pVictim ) != m_rbHsPlayers.InvalidIndex() )
|
||||
{
|
||||
#if 0
|
||||
DevMsg( "DMG: Pellet modified for headshot visualization %s -> %s = (0x%08X +hs)\n",
|
||||
m_pPlayerShooting ? m_pPlayerShooting->GetPlayerName() : "[unknown]",
|
||||
pVictim->GetPlayerName(), infoTweakable.GetDamageType() );
|
||||
#endif
|
||||
infoTweakable.SetDamageType( infoTweakable.GetDamageType() | DMG_HEADSHOT ); // since previous pellets hit a headshot we visualize it as a headshot
|
||||
}
|
||||
|
||||
// Since we know that bullet was fired and that we hit the target
|
||||
// we should record the accuracy stats right now, otherwise we may TerminateRound
|
||||
// based on a kill from this bullet and not have this data recorded
|
||||
RecordShotFiredAndOnTargetData();
|
||||
}
|
||||
}
|
||||
void BulletBurstCompleted()
|
||||
{
|
||||
RecordShotFiredAndOnTargetData();
|
||||
}
|
||||
private:
|
||||
void RecordShotFiredAndOnTargetData()
|
||||
{
|
||||
if ( m_bShotFiredAndOnTargetRecorded )
|
||||
return;
|
||||
m_bShotFiredAndOnTargetRecorded = true;
|
||||
|
||||
if ( m_pPlayerShooting && CSGameRules() && !CSGameRules()->IsWarmupPeriod() && !m_pPlayerShooting->IsBot() )
|
||||
{
|
||||
// Track in QMM total number of shots that connected with an opponent
|
||||
if ( CCSGameRules::CQMMPlayerData_t *pQMM = CSGameRules()->QueuedMatchmakingPlayersDataFind( m_pPlayerShooting->GetHumanPlayerAccountID() ) )
|
||||
{
|
||||
++pQMM->m_numShotsFiredTotal;
|
||||
if ( m_bEnemyHit )
|
||||
++pQMM->m_numShotsOnTargetTotal;
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
CCSPlayer *m_pPlayerShooting;
|
||||
bool m_bEnemyHit;
|
||||
bool m_bShotFiredAndOnTargetRecorded;
|
||||
CUtlRBTree< CCSPlayer *, int, CDefLess< CCSPlayer * > > m_rbHsPlayers; // players who were dinked in the head as part of this bullet batch
|
||||
} fbtdl( pPlayer );
|
||||
#endif
|
||||
|
||||
for ( int iBullet=0; iBullet < pWeaponInfo->GetBullets(); iBullet++ )
|
||||
{
|
||||
if ( !pPlayer )
|
||||
break;
|
||||
|
||||
int nPenetrationCount = 4;
|
||||
|
||||
pPlayer->FireBullet(
|
||||
vOrigin,
|
||||
adjustedAngles,
|
||||
flRange,
|
||||
flPenetration,
|
||||
nPenetrationCount,
|
||||
iAmmoType,
|
||||
iDamage,
|
||||
flRangeModifier,
|
||||
pPlayer,
|
||||
bDoEffects,
|
||||
x0 + x1[iBullet], y0 + y1[iBullet]
|
||||
);
|
||||
}
|
||||
|
||||
#if !defined( CLIENT_DLL )
|
||||
fbtdl.BulletBurstCompleted();
|
||||
} /// Closes the lifetime scope of take damage listener in scope only for the duration of FireBullet loop above.
|
||||
#endif
|
||||
|
||||
#if !defined (CLIENT_DLL)
|
||||
lagcompensation->FinishLagCompensation( pPlayer );
|
||||
#endif
|
||||
|
||||
EndGroupingSounds();
|
||||
}
|
||||
|
||||
// This runs on both the client and the server.
|
||||
// On the server, it dispatches a TE_PlantBomb to visible clients.
|
||||
// On the client, it plays the planting animation.
|
||||
void FX_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option )
|
||||
{
|
||||
#ifdef CLIENT_DLL
|
||||
C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) );
|
||||
#else
|
||||
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) );
|
||||
#endif
|
||||
|
||||
// Do the firing animation event.
|
||||
if ( pPlayer && !pPlayer->IsDormant() )
|
||||
{
|
||||
switch ( option )
|
||||
{
|
||||
case PLANTBOMB_PLANT:
|
||||
{
|
||||
pPlayer->DoAnimStateEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY );
|
||||
}
|
||||
break;
|
||||
|
||||
case PLANTBOMB_ABORT:
|
||||
{
|
||||
pPlayer->DoAnimStateEvent( PLAYERANIMEVENT_CLEAR_FIRING );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef CLIENT_DLL
|
||||
// if this is server code, send the effect over to client as temp entity
|
||||
// Dispatch one message for all the bullet impacts and sounds.
|
||||
TE_PlantBomb( iPlayerIndex, vOrigin, option );
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user