Counter Strike : Global Offensive Source Code
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.

618 lines
18 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "fx_cs_shared.h"
  8. #include "weapon_csbase.h"
  9. #include "rumble_shared.h"
  10. #ifndef CLIENT_DLL
  11. #include "ilagcompensationmanager.h"
  12. #endif
  13. ConVar weapon_accuracy_logging( "weapon_accuracy_logging", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_ARCHIVE );
  14. ConVar steam_controller_haptics( "steam_controller_haptics", "1", FCVAR_RELEASE );
  15. ConVar weapon_near_empty_sound( "weapon_near_empty_sound", "1", FCVAR_REPLICATED | FCVAR_CHEAT );
  16. ConVar weapon_debug_max_inaccuracy( "weapon_debug_max_inaccuracy", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY | FCVAR_CHEAT, "Force all shots to have maximum inaccuracy" );
  17. 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" );
  18. ConVar snd_max_pitch_shift_inaccuracy("snd_max_pitch_shift_inaccuracy", "0.08", 0);
  19. #ifdef CLIENT_DLL
  20. #include "fx_impact.h"
  21. #include "c_rumble.h"
  22. #include "inputsystem/iinputsystem.h"
  23. // NOTE: This has to be the last file included!
  24. #include "tier0/memdbgon.h"
  25. // this is a cheap ripoff from CBaseCombatWeapon::WeaponSound():
  26. void FX_WeaponSound(
  27. int iPlayerIndex,
  28. uint16 nItemDefIndex,
  29. WeaponSound_t sound_type,
  30. const Vector &vOrigin,
  31. const CCSWeaponInfo *pWeaponInfo,
  32. float flSoundTime,
  33. int nPitch )
  34. {
  35. // If we have some sounds from the weapon classname.txt file, play a random one of them
  36. const char *shootsound = pWeaponInfo->aShootSounds[ sound_type ];
  37. // Get the item definition
  38. const CEconItemDefinition *pDef = ( nItemDefIndex > 0 ) ? GetItemSchema()->GetItemDefinition( nItemDefIndex ) : NULL;
  39. if ( pDef )
  40. {
  41. const char *pszTempSound = pDef->GetWeaponReplacementSound( sound_type );
  42. if ( pszTempSound )
  43. {
  44. shootsound = pszTempSound;
  45. }
  46. }
  47. if ( !shootsound || !shootsound[0] )
  48. return;
  49. CBroadcastRecipientFilter filter; // this is client side only
  50. if ( !te->CanPredict() )
  51. return;
  52. EmitSound_t params;
  53. params.m_pSoundName = shootsound;
  54. params.m_flSoundTime = flSoundTime;
  55. params.m_pOrigin = &vOrigin;
  56. params.m_pflSoundDuration = nullptr;
  57. params.m_bWarnOnDirectWaveReference = true;
  58. params.m_nPitch = nPitch;
  59. if (nPitch != PITCH_NORM)
  60. {
  61. params.m_nFlags = params.m_nFlags | SND_OVERRIDE_PITCH;
  62. }
  63. CBaseEntity::EmitSound( filter, iPlayerIndex, params );
  64. }
  65. class CGroupedSound
  66. {
  67. public:
  68. string_t m_SoundName;
  69. Vector m_vPos;
  70. };
  71. CUtlVector<CGroupedSound> g_GroupedSounds;
  72. // Called by the ImpactSound function.
  73. void ShotgunImpactSoundGroup( const char *pSoundName, const Vector &vEndPos )
  74. {
  75. int i;
  76. // Don't play the sound if it's too close to another impact sound.
  77. for ( i=0; i < g_GroupedSounds.Count(); i++ )
  78. {
  79. CGroupedSound *pSound = &g_GroupedSounds[i];
  80. if ( vEndPos.DistToSqr( pSound->m_vPos ) < 300*300 )
  81. {
  82. if ( Q_stricmp( pSound->m_SoundName, pSoundName ) == 0 )
  83. return;
  84. }
  85. }
  86. // Ok, play the sound and add it to the list.
  87. CLocalPlayerFilter filter;
  88. C_BaseEntity::EmitSound( filter, NULL, pSoundName, &vEndPos );
  89. i = g_GroupedSounds.AddToTail();
  90. g_GroupedSounds[i].m_SoundName = pSoundName;
  91. g_GroupedSounds[i].m_vPos = vEndPos;
  92. }
  93. void StartGroupingSounds()
  94. {
  95. Assert( g_GroupedSounds.Count() == 0 );
  96. SetImpactSoundRoute( ShotgunImpactSoundGroup );
  97. }
  98. void EndGroupingSounds()
  99. {
  100. g_GroupedSounds.Purge();
  101. SetImpactSoundRoute( NULL );
  102. }
  103. #else
  104. #include "te_shotgun_shot.h"
  105. // Server doesn't play sounds anyway.
  106. void StartGroupingSounds() {}
  107. void EndGroupingSounds() {}
  108. void FX_WeaponSound ( int iPlayerIndex,
  109. uint16 nItemDefIndex,
  110. WeaponSound_t sound_type,
  111. const Vector &vOrigin,
  112. const CCSWeaponInfo *pWeaponInfo, float flSoundTime, int nPitch ) {};
  113. #endif
  114. ConVar debug_aim_angle("debug_aim_angle", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY);
  115. // This runs on both the client and the server.
  116. // On the server, it only does the damage calculations.
  117. // On the client, it does all the effects.
  118. void FX_FireBullets(
  119. int iPlayerIndex,
  120. uint16 nItemDefIndex,
  121. const Vector &vOrigin,
  122. const QAngle &vAngles,
  123. CSWeaponID iWeaponID,
  124. int iMode,
  125. int iSeed,
  126. float fInaccuracy,
  127. float fSpread,
  128. float fAccuracyFishtail,
  129. float flSoundTime,
  130. WeaponSound_t sound_type,
  131. float flRecoilIndex
  132. )
  133. {
  134. bool bDoEffects = true;
  135. if ( fInaccuracy > 1.0f )
  136. fInaccuracy = 1.0f;
  137. #ifdef CLIENT_DLL
  138. C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) );
  139. #else
  140. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) );
  141. #endif
  142. if ( !pPlayer || iPlayerIndex < 0 )
  143. {
  144. // probably an env_gunfire
  145. const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo( iWeaponID );
  146. FX_WeaponSound( iPlayerIndex, nItemDefIndex, sound_type, vOrigin, pWeaponInfo, flSoundTime, PITCH_NORM );
  147. #ifndef CLIENT_DLL
  148. // if this is server code, send the effect over to client as temp entity
  149. // Dispatch one message for all the bullet impacts and sounds.
  150. TE_FireBullets(
  151. -1,
  152. nItemDefIndex,
  153. vOrigin,
  154. vAngles,
  155. iWeaponID,
  156. iMode,
  157. iSeed,
  158. fInaccuracy,
  159. fSpread,
  160. fAccuracyFishtail,
  161. sound_type,
  162. flRecoilIndex
  163. );
  164. #endif
  165. return;
  166. }
  167. #ifdef CLIENT_DLL
  168. CWeaponCSBase* pClientWeapon = pPlayer ? pPlayer->GetActiveCSWeapon() : NULL;
  169. if ( pClientWeapon )
  170. {
  171. if ( gpGlobals->curtime - pClientWeapon->m_flLastClientFireBulletTime > 0.02f ) // this should be enough even for the negev, at ~1000 rof
  172. {
  173. pClientWeapon->m_flLastClientFireBulletTime = gpGlobals->curtime;
  174. }
  175. else
  176. {
  177. return; // we already traced this shot on the client!
  178. }
  179. }
  180. #endif
  181. QAngle adjustedAngles = vAngles;
  182. adjustedAngles.y += fAccuracyFishtail;
  183. if ( pPlayer && debug_aim_angle.GetBool() )
  184. {
  185. QAngle old = pPlayer->EyeAngles() + pPlayer->GetAimPunchAngle();
  186. #ifdef CLIENT_DLL
  187. DevMsg("Client ");
  188. #else
  189. DevMsg("Server ");
  190. #endif
  191. DevMsg("old: %f %f new: %f %f\n",
  192. old[YAW], old[PITCH],
  193. vAngles[YAW], vAngles[PITCH]
  194. );
  195. if ( debug_aim_angle.GetInt() == 2 )
  196. {
  197. adjustedAngles = old;
  198. }
  199. }
  200. const CEconItemDefinition* pItemDef = GetItemSchema()->GetItemDefinition( nItemDefIndex );
  201. if ( !pItemDef )
  202. {
  203. DevMsg( "FX_FireBullets: GetItemDefinition failed for defindex %d\n", nItemDefIndex );
  204. return;
  205. }
  206. #if !defined(CLIENT_DLL)
  207. if ( weapon_accuracy_logging.GetBool() )
  208. {
  209. char szFlags[256];
  210. V_strcpy(szFlags, " ");
  211. // #if defined(CLIENT_DLL)
  212. // V_strcat(szFlags, "CLIENT ", sizeof(szFlags));
  213. // #else
  214. // V_strcat(szFlags, "SERVER ", sizeof(szFlags));
  215. // #endif
  216. //
  217. if ( pPlayer->GetMoveType() == MOVETYPE_LADDER )
  218. V_strcat(szFlags, "LADDER ", sizeof(szFlags));
  219. if ( FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) )
  220. V_strcat(szFlags, "GROUND ", sizeof(szFlags));
  221. if ( FBitSet( pPlayer->GetFlags(), FL_DUCKING) )
  222. V_strcat(szFlags, "DUCKING ", sizeof(szFlags));
  223. float fVelocity = pPlayer->GetAbsVelocity().Length2D();
  224. Msg("FireBullets @ %10f [ %s ]: inaccuracy=%f spread=%f max dispersion=%f mode=%2i vel=%10f seed=%3i %s\n",
  225. gpGlobals->curtime, pItemDef->GetItemBaseName(), fInaccuracy, fSpread, fInaccuracy + fSpread, iMode, fVelocity, iSeed, szFlags);
  226. }
  227. #endif
  228. WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( pItemDef->GetItemClass() );
  229. if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
  230. {
  231. DevMsg("FX_FireBullets: LookupWeaponInfoSlot failed for weapon %s\n", pItemDef->GetItemBaseName() );
  232. return;
  233. }
  234. CCSWeaponInfo *pWeaponInfo = static_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
  235. if ( !pWeaponInfo )
  236. {
  237. DevMsg( "FX_FireBullets: GetFileWeaponInfoFromHandle failed for weapon %s\n", pItemDef->GetItemBaseName() );
  238. return;
  239. }
  240. // Do the firing animation event.
  241. #ifndef CLIENT_DLL
  242. if ( pPlayer && !pPlayer->IsDormant() )
  243. {
  244. if ( iMode == Primary_Mode )
  245. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY );
  246. else
  247. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_SECONDARY );
  248. }
  249. #endif // CLIENT_DLL
  250. #ifdef CLIENT_DLL
  251. if ( pPlayer && pPlayer->m_bUseNewAnimstate )
  252. {
  253. pPlayer->ProcessMuzzleFlashEvent();
  254. }
  255. #endif
  256. #ifndef CLIENT_DLL
  257. // if this is server code, send the effect over to client as temp entity
  258. // Dispatch one message for all the bullet impacts and sounds.
  259. TE_FireBullets(
  260. iPlayerIndex,
  261. nItemDefIndex,
  262. vOrigin,
  263. vAngles,
  264. iWeaponID,
  265. iMode,
  266. iSeed,
  267. fInaccuracy,
  268. fSpread,
  269. fAccuracyFishtail,
  270. sound_type,
  271. flRecoilIndex
  272. );
  273. // Let the player remember the usercmd he fired a weapon on. Assists in making decisions about lag compensation.
  274. if ( pPlayer )
  275. pPlayer->NoteWeaponFired();
  276. bDoEffects = false; // no effects on server
  277. #endif
  278. iSeed++;
  279. CWeaponCSBase* pWeapon = pPlayer ? pPlayer->GetActiveCSWeapon() : NULL;
  280. CEconItemView* pItem = pWeapon ? pWeapon->GetEconItemView() : NULL;
  281. int iDamage = pWeaponInfo->GetDamage( pItem );
  282. float flRange = pWeaponInfo->GetRange( pItem );
  283. float flPenetration = pWeaponInfo->GetPenetration( pItem );
  284. float flRangeModifier = pWeaponInfo->GetRangeModifier( pItem );
  285. int iAmmoType = pWeaponInfo->GetPrimaryAmmoType( pItem );
  286. if ( bDoEffects)
  287. {
  288. static const float MaxPitchShiftInaccuracy = 0.05f;
  289. float flPitchShift = pWeaponInfo->GetInaccuracyPitchShift() * (fInaccuracy < MaxPitchShiftInaccuracy ? fInaccuracy : MaxPitchShiftInaccuracy);
  290. if ( sound_type == SINGLE && pWeaponInfo->GetInaccuracyAltSoundThreshhold() > 0.0f && fInaccuracy < pWeaponInfo->GetInaccuracyAltSoundThreshhold() )
  291. {
  292. sound_type = SINGLE_ACCURATE;
  293. flPitchShift = 0.0f;
  294. }
  295. FX_WeaponSound( iPlayerIndex, nItemDefIndex, sound_type, vOrigin, pWeaponInfo, flSoundTime, PITCH_NORM + int(flPitchShift) );
  296. // If the gun's nearly empty, also play a subtle "nearly-empty" sound, since the weapon
  297. // is lighter and acoustically different when weighed down by fewer bullets.
  298. // But really it's so you get a fun low ammo warning from an audio cue.
  299. if ( weapon_near_empty_sound.GetBool() &&
  300. pWeapon && pWeapon->GetMaxClip1() > 1 && // not a single-shot weapon
  301. (((float)pWeapon->m_iClip1) / ((float)pWeapon->GetMaxClip1()) <= 0.2) ) // 20% or fewer bullets remaining
  302. {
  303. FX_WeaponSound( iPlayerIndex, nItemDefIndex, NEARLYEMPTY, vOrigin, pWeaponInfo, flSoundTime, PITCH_NORM );
  304. }
  305. }
  306. // Fire bullets, calculate impacts & effects
  307. if ( !pPlayer )
  308. return;
  309. StartGroupingSounds();
  310. #ifdef GAME_DLL
  311. pPlayer->StartNewBulletGroup();
  312. #endif
  313. #if !defined (CLIENT_DLL)
  314. // Move other players back to history positions based on local player's lag
  315. lagcompensation->StartLagCompensation( pPlayer, LAG_COMPENSATE_HITBOXES_ALONG_RAY, vOrigin, vAngles, flRange );
  316. #endif
  317. // [sbodenbender] rumble when shooting
  318. // since we are handling bullet fx in CS differently than other titles, call
  319. // rumble effect directly instead of Player::RumbleEffect
  320. //=============================================================================
  321. #if defined (CLIENT_DLL)
  322. if (pPlayer && pPlayer->IsLocalPlayer() && pWeaponInfo && pWeaponInfo->GetBullets() > 0)
  323. {
  324. int rumbleEffect = pWeaponInfo->iRumbleEffect;
  325. if( rumbleEffect != RUMBLE_INVALID )
  326. {
  327. RumbleEffect( XBX_GetUserId( pPlayer->GetSplitScreenPlayerSlot() ), rumbleEffect, 0, RUMBLE_FLAG_RESTART );
  328. }
  329. if ( rumbleEffect != RUMBLE_INVALID && rumbleEffect <= 6 && steam_controller_haptics.GetBool() && g_pInputSystem->IsSteamControllerActive() && steamapicontext->SteamController() )
  330. {
  331. ControllerHandle_t handles[MAX_STEAM_CONTROLLERS];
  332. int nControllers = steamapicontext->SteamController()->GetConnectedControllers( handles );
  333. for ( int i = 0; i < nControllers; ++i )
  334. {
  335. steamapicontext->SteamController()->TriggerHapticPulse( handles[ i ], k_ESteamControllerPad_Right, (2000*rumbleEffect)/5 );
  336. steamapicontext->SteamController()->TriggerHapticPulse( handles[ i ], k_ESteamControllerPad_Left, (2000*rumbleEffect)/5 );
  337. }
  338. }
  339. }
  340. #endif
  341. bool bForceMaxInaccuracy = weapon_debug_max_inaccuracy.GetBool();
  342. bool bForceInaccuracyDirection = weapon_debug_inaccuracy_only_up.GetBool();
  343. RandomSeed( iSeed ); // init random system with this seed
  344. // Accuracy curve density adjustment FOR R8 REVOLVER SECONDARY FIRE, NEGEV WILD BEAST
  345. float flRadiusCurveDensity = RandomFloat();
  346. if ( nItemDefIndex == 64 && iMode == Secondary_Mode ) /*R8 REVOLVER SECONDARY FIRE*/
  347. {
  348. flRadiusCurveDensity = 1.0f - flRadiusCurveDensity*flRadiusCurveDensity;
  349. }
  350. if ( nItemDefIndex == 28 && flRecoilIndex < 3 ) /*NEGEV WILD BEAST*/
  351. {
  352. for ( int j = 3; j > flRecoilIndex; -- j )
  353. {
  354. flRadiusCurveDensity *= flRadiusCurveDensity;
  355. }
  356. flRadiusCurveDensity = 1.0f - flRadiusCurveDensity;
  357. }
  358. if ( bForceMaxInaccuracy )
  359. flRadiusCurveDensity = 1.0f;
  360. // Get accuracy displacement
  361. float fTheta0 = RandomFloat(0.0f, 2.0f * M_PI);
  362. if ( bForceInaccuracyDirection )
  363. fTheta0 = M_PI * 0.5f;
  364. float fRadius0 = flRadiusCurveDensity * fInaccuracy;
  365. float x0 = fRadius0 * cosf(fTheta0);
  366. float y0 = fRadius0 * sinf(fTheta0);
  367. const int kMaxBullets = 16;
  368. float x1[kMaxBullets], y1[kMaxBullets];
  369. Assert(pWeaponInfo->GetBullets() <= kMaxBullets);
  370. // the RNG can be desynchronized by FireBullet(), so pre-generate all spread offsets
  371. for ( int iBullet=0; iBullet < pWeaponInfo->GetBullets(); iBullet++ )
  372. {
  373. // Spread curve density adjustment for R8 REVOLVER SECONDARY FIRE, NEGEV WILD BEAST
  374. float flSpreadCurveDensity = RandomFloat();
  375. if ( nItemDefIndex == 64 && iMode == Secondary_Mode )
  376. {
  377. flSpreadCurveDensity = 1.0f - flSpreadCurveDensity*flSpreadCurveDensity;
  378. }
  379. if ( nItemDefIndex == 28 && flRecoilIndex < 3 ) /*NEGEV WILD BEAST*/
  380. {
  381. for ( int j = 3; j > flRecoilIndex; --j )
  382. {
  383. flSpreadCurveDensity *= flSpreadCurveDensity;
  384. }
  385. flSpreadCurveDensity = 1.0f - flSpreadCurveDensity;
  386. }
  387. if ( bForceMaxInaccuracy )
  388. flSpreadCurveDensity = 1.0f;
  389. float fTheta1 = RandomFloat(0.0f, 2.0f * M_PI);
  390. if ( bForceInaccuracyDirection )
  391. fTheta1 = M_PI * 0.5f;
  392. float fRadius1 = flSpreadCurveDensity * fSpread;
  393. x1[iBullet] = fRadius1 * cosf(fTheta1);
  394. y1[iBullet] = fRadius1 * sinf(fTheta1);
  395. }
  396. #if !defined( CLIENT_DLL )
  397. { /// Make sure take damage listener stays in scope only for the duration of FireBullet loop below!
  398. class CFireBulletTakeDamageListener : public CCSPlayer::ITakeDamageListener
  399. {
  400. public:
  401. CFireBulletTakeDamageListener( CCSPlayer *pPlayerShooting ) :
  402. m_pPlayerShooting(pPlayerShooting),
  403. m_bEnemyHit( false ),
  404. m_bShotFiredAndOnTargetRecorded( false )
  405. {}
  406. virtual void OnTakeDamageListenerCallback( CCSPlayer *pVictim, CTakeDamageInfo &infoTweakable ) OVERRIDE
  407. {
  408. if ( m_pPlayerShooting && pVictim->IsOtherEnemy( m_pPlayerShooting ) )
  409. {
  410. m_bEnemyHit = true;
  411. if ( infoTweakable.GetDamageType() & DMG_HEADSHOT )
  412. {
  413. m_rbHsPlayers.InsertIfNotFound( pVictim ); // remember that at least one pellet hit a headshot
  414. }
  415. else if ( m_rbHsPlayers.Find( pVictim ) != m_rbHsPlayers.InvalidIndex() )
  416. {
  417. #if 0
  418. DevMsg( "DMG: Pellet modified for headshot visualization %s -> %s = (0x%08X +hs)\n",
  419. m_pPlayerShooting ? m_pPlayerShooting->GetPlayerName() : "[unknown]",
  420. pVictim->GetPlayerName(), infoTweakable.GetDamageType() );
  421. #endif
  422. infoTweakable.SetDamageType( infoTweakable.GetDamageType() | DMG_HEADSHOT ); // since previous pellets hit a headshot we visualize it as a headshot
  423. }
  424. // Since we know that bullet was fired and that we hit the target
  425. // we should record the accuracy stats right now, otherwise we may TerminateRound
  426. // based on a kill from this bullet and not have this data recorded
  427. RecordShotFiredAndOnTargetData();
  428. }
  429. }
  430. void BulletBurstCompleted()
  431. {
  432. RecordShotFiredAndOnTargetData();
  433. }
  434. private:
  435. void RecordShotFiredAndOnTargetData()
  436. {
  437. if ( m_bShotFiredAndOnTargetRecorded )
  438. return;
  439. m_bShotFiredAndOnTargetRecorded = true;
  440. if ( m_pPlayerShooting && CSGameRules() && !CSGameRules()->IsWarmupPeriod() && !m_pPlayerShooting->IsBot() )
  441. {
  442. // Track in QMM total number of shots that connected with an opponent
  443. if ( CCSGameRules::CQMMPlayerData_t *pQMM = CSGameRules()->QueuedMatchmakingPlayersDataFind( m_pPlayerShooting->GetHumanPlayerAccountID() ) )
  444. {
  445. ++pQMM->m_numShotsFiredTotal;
  446. if ( m_bEnemyHit )
  447. ++pQMM->m_numShotsOnTargetTotal;
  448. }
  449. }
  450. }
  451. private:
  452. CCSPlayer *m_pPlayerShooting;
  453. bool m_bEnemyHit;
  454. bool m_bShotFiredAndOnTargetRecorded;
  455. CUtlRBTree< CCSPlayer *, int, CDefLess< CCSPlayer * > > m_rbHsPlayers; // players who were dinked in the head as part of this bullet batch
  456. } fbtdl( pPlayer );
  457. #endif
  458. for ( int iBullet=0; iBullet < pWeaponInfo->GetBullets(); iBullet++ )
  459. {
  460. if ( !pPlayer )
  461. break;
  462. int nPenetrationCount = 4;
  463. pPlayer->FireBullet(
  464. vOrigin,
  465. adjustedAngles,
  466. flRange,
  467. flPenetration,
  468. nPenetrationCount,
  469. iAmmoType,
  470. iDamage,
  471. flRangeModifier,
  472. pPlayer,
  473. bDoEffects,
  474. x0 + x1[iBullet], y0 + y1[iBullet]
  475. );
  476. }
  477. #if !defined( CLIENT_DLL )
  478. fbtdl.BulletBurstCompleted();
  479. } /// Closes the lifetime scope of take damage listener in scope only for the duration of FireBullet loop above.
  480. #endif
  481. #if !defined (CLIENT_DLL)
  482. lagcompensation->FinishLagCompensation( pPlayer );
  483. #endif
  484. EndGroupingSounds();
  485. }
  486. // This runs on both the client and the server.
  487. // On the server, it dispatches a TE_PlantBomb to visible clients.
  488. // On the client, it plays the planting animation.
  489. void FX_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option )
  490. {
  491. #ifdef CLIENT_DLL
  492. C_CSPlayer *pPlayer = ToCSPlayer( ClientEntityList().GetBaseEntity( iPlayerIndex ) );
  493. #else
  494. CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( iPlayerIndex) );
  495. #endif
  496. // Do the firing animation event.
  497. if ( pPlayer && !pPlayer->IsDormant() )
  498. {
  499. switch ( option )
  500. {
  501. case PLANTBOMB_PLANT:
  502. {
  503. pPlayer->DoAnimStateEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY );
  504. }
  505. break;
  506. case PLANTBOMB_ABORT:
  507. {
  508. pPlayer->DoAnimStateEvent( PLAYERANIMEVENT_CLEAR_FIRING );
  509. }
  510. break;
  511. }
  512. }
  513. #ifndef CLIENT_DLL
  514. // if this is server code, send the effect over to client as temp entity
  515. // Dispatch one message for all the bullet impacts and sounds.
  516. TE_PlantBomb( iPlayerIndex, vOrigin, option );
  517. #endif
  518. }