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.
345 lines
8.5 KiB
345 lines
8.5 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "fx_cs_shared.h" |
|
#include "weapon_csbase.h" |
|
|
|
#ifndef CLIENT_DLL |
|
#include "ilagcompensationmanager.h" |
|
#endif |
|
|
|
ConVar weapon_accuracy_logging( "weapon_accuracy_logging", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE ); |
|
|
|
#ifdef CLIENT_DLL |
|
|
|
#include "fx_impact.h" |
|
|
|
// this is a cheap ripoff from CBaseCombatWeapon::WeaponSound(): |
|
void FX_WeaponSound( |
|
int iPlayerIndex, |
|
WeaponSound_t sound_type, |
|
const Vector &vOrigin, |
|
CCSWeaponInfo *pWeaponInfo, float flSoundTime ) |
|
{ |
|
|
|
// If we have some sounds from the weapon classname.txt file, play a random one of them |
|
const char *shootsound = pWeaponInfo->aShootSounds[ sound_type ]; |
|
if ( !shootsound || !shootsound[0] ) |
|
return; |
|
|
|
CBroadcastRecipientFilter filter; // this is client side only |
|
if ( !te->CanPredict() ) |
|
return; |
|
|
|
CBaseEntity::EmitSound( filter, iPlayerIndex, shootsound, &vOrigin, flSoundTime ); |
|
} |
|
|
|
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, |
|
WeaponSound_t sound_type, |
|
const Vector &vOrigin, |
|
CCSWeaponInfo *pWeaponInfo, float flSoundTime ) {}; |
|
|
|
#endif |
|
|
|
|
|
// 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, |
|
const Vector &vOrigin, |
|
const QAngle &vAngles, |
|
int iWeaponID, |
|
int iMode, |
|
int iSeed, |
|
float fInaccuracy, |
|
float fSpread, |
|
float flSoundTime |
|
) |
|
{ |
|
bool bDoEffects = true; |
|
|
|
#ifdef CLIENT_DLL |
|
C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) ); |
|
#else |
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) ); |
|
#endif |
|
|
|
const char * weaponAlias = WeaponIDToAlias( iWeaponID ); |
|
|
|
if ( !weaponAlias ) |
|
{ |
|
DevMsg("FX_FireBullets: weapon alias for ID %i not found\n", iWeaponID ); |
|
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, weaponAlias, fInaccuracy, fSpread, fInaccuracy + fSpread, iMode, fVelocity, iSeed, szFlags); |
|
} |
|
#endif |
|
|
|
char wpnName[128]; |
|
Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", weaponAlias ); |
|
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName ); |
|
|
|
if ( hWpnInfo == GetInvalidWeaponInfoHandle() ) |
|
{ |
|
DevMsg("FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", wpnName ); |
|
return; |
|
} |
|
|
|
CCSWeaponInfo *pWeaponInfo = static_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) ); |
|
|
|
// Do the firing animation event. |
|
if ( pPlayer && !pPlayer->IsDormant() ) |
|
{ |
|
if ( iMode == Primary_Mode ) |
|
pPlayer->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY ); |
|
else |
|
pPlayer->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_SECONDARY ); |
|
} |
|
|
|
#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, |
|
vOrigin, |
|
vAngles, |
|
iWeaponID, |
|
iMode, |
|
iSeed, |
|
fInaccuracy, |
|
fSpread |
|
); |
|
|
|
|
|
// Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation. |
|
pPlayer->NoteWeaponFired(); |
|
|
|
bDoEffects = false; // no effects on server |
|
#endif |
|
|
|
iSeed++; |
|
|
|
int iDamage = pWeaponInfo->m_iDamage; |
|
float flRange = pWeaponInfo->m_flRange; |
|
int iPenetration = pWeaponInfo->m_iPenetration; |
|
float flRangeModifier = pWeaponInfo->m_flRangeModifier; |
|
int iAmmoType = pWeaponInfo->iAmmoType; |
|
|
|
WeaponSound_t sound_type = SINGLE; |
|
|
|
// CS HACK, tweak some weapon values based on primary/secondary mode |
|
|
|
if ( iWeaponID == WEAPON_GLOCK ) |
|
{ |
|
if ( iMode == Secondary_Mode ) |
|
{ |
|
iDamage = 18; // reduced power for burst shots |
|
flRangeModifier = 0.9f; |
|
} |
|
} |
|
else if ( iWeaponID == WEAPON_M4A1 ) |
|
{ |
|
if ( iMode == Secondary_Mode ) |
|
{ |
|
flRangeModifier = 0.95f; // slower bullets in silenced mode |
|
sound_type = SPECIAL1; |
|
} |
|
} |
|
else if ( iWeaponID == WEAPON_USP ) |
|
{ |
|
if ( iMode == Secondary_Mode ) |
|
{ |
|
iDamage = 30; // reduced damage in silenced mode |
|
sound_type = SPECIAL1; |
|
} |
|
} |
|
|
|
if ( bDoEffects) |
|
{ |
|
FX_WeaponSound( iPlayerIndex, sound_type, vOrigin, pWeaponInfo, flSoundTime ); |
|
} |
|
|
|
|
|
// 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, pPlayer->GetCurrentCommand() ); |
|
#endif |
|
|
|
RandomSeed( iSeed ); // init random system with this seed |
|
|
|
// Get accuracy displacement |
|
float fTheta0 = RandomFloat(0.0f, 2.0f * M_PI); |
|
float fRadius0 = RandomFloat(0.0f, fInaccuracy); |
|
float x0 = fRadius0 * cosf(fTheta0); |
|
float y0 = fRadius0 * sinf(fTheta0); |
|
|
|
const int kMaxBullets = 16; |
|
float x1[kMaxBullets], y1[kMaxBullets]; |
|
Assert(pWeaponInfo->m_iBullets <= kMaxBullets); |
|
|
|
// the RNG can be desynchronized by FireBullet(), so pre-generate all spread offsets |
|
for ( int iBullet=0; iBullet < pWeaponInfo->m_iBullets; iBullet++ ) |
|
{ |
|
float fTheta1 = RandomFloat(0.0f, 2.0f * M_PI); |
|
float fRadius1 = RandomFloat(0.0f, fSpread); |
|
x1[iBullet] = fRadius1 * cosf(fTheta1); |
|
y1[iBullet] = fRadius1 * sinf(fTheta1); |
|
} |
|
|
|
for ( int iBullet=0; iBullet < pWeaponInfo->m_iBullets; iBullet++ ) |
|
{ |
|
pPlayer->FireBullet( |
|
vOrigin, |
|
vAngles, |
|
flRange, |
|
iPenetration, |
|
iAmmoType, |
|
iDamage, |
|
flRangeModifier, |
|
pPlayer, |
|
bDoEffects, |
|
x0 + x1[iBullet], y0 + y1[iBullet] ); |
|
} |
|
|
|
#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->GetPlayerAnimState()->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY ); |
|
} |
|
break; |
|
|
|
case PLANTBOMB_ABORT: |
|
{ |
|
pPlayer->GetPlayerAnimState()->DoAnimationEvent( 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 |
|
} |
|
|
|
|