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.

3224 lines
102 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "weapon_csbase.h"
  8. #include "decals.h"
  9. #include "cs_gamerules.h"
  10. #include "weapon_c4.h"
  11. #include "in_buttons.h"
  12. #include "datacache/imdlcache.h"
  13. #include "GameStats.h"
  14. #include "commonmacros.h"
  15. #ifdef CLIENT_DLL
  16. #include "c_cs_player.h"
  17. #include "c_triggers.h"
  18. #include "c_plantedc4.h"
  19. #include "inputsystem/iinputsystem.h"
  20. #include "prediction.h"
  21. #include "c_cs_hostage.h"
  22. #include "c_cs_team.h"
  23. #include "gametypes.h"
  24. #include "c_cs_playerresource.h"
  25. #define CRecipientFilter C_RecipientFilter
  26. #define CCSPlayerResource C_CS_PlayerResource
  27. #else
  28. #include "cs_player.h"
  29. #include "soundent.h"
  30. #include "bot/cs_bot.h"
  31. #include "keyvalues.h"
  32. #include "triggers.h"
  33. #include "cs_gamestats.h"
  34. #include "recipientfilter.h"
  35. #include "cs_simple_hostage.h"
  36. #include "predicted_viewmodel.h"
  37. #include "cs_team.h"
  38. #include "cs_player_resource.h"
  39. #endif
  40. #include "cs_playeranimstate.h"
  41. #include "basecombatweapon_shared.h"
  42. #include "util_shared.h"
  43. #include "takedamageinfo.h"
  44. #include "effect_dispatch_data.h"
  45. #include "engine/ivdebugoverlay.h"
  46. #include "obstacle_pushaway.h"
  47. #include "props_shared.h"
  48. #include "ammodef.h"
  49. #include "platforminputdevice.h"
  50. #include "SoundEmitterSystem/isoundemittersystembase.h"
  51. #include "obstacle_pushaway.h"
  52. #include "gametypes.h"
  53. #include "playerdecals_signature.h"
  54. // memdbgon must be the last include file in a .cpp file!!!
  55. #include "tier0/memdbgon.h"
  56. ConVar sv_penetration_type( "sv_penetration_type", "1", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY, "What type of penertration to use. 0 = old CS, 1 = new penetration" );
  57. ConVar sv_showimpacts_penetration( "sv_showimpacts_penetration", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Shows extra data when bullets penetrate. (use sv_showimpacts_time to increase time shown)" );
  58. ConVar sv_showbullethits("sv_showbullethits", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "" );
  59. ConVar sv_showimpacts("sv_showimpacts", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Shows client (red) and server (blue) bullet impact point (1=both, 2=client-only, 3=server-only)" );
  60. ConVar sv_showimpacts_time("sv_showimpacts_time", "4", FCVAR_REPLICATED | FCVAR_RELEASE, "Duration bullet impact indicators remain before disappearing", true, 0.0f, true, 10.0f );
  61. ConVar sv_showplayerhitboxes( "sv_showplayerhitboxes", "0", FCVAR_REPLICATED, "Show lag compensated hitboxes for the specified player index whenever a player fires." );
  62. // friendly fire damage scalers
  63. ConVar ff_damage_reduction_grenade( "ff_damage_reduction_grenade", "0.25", FCVAR_REPLICATED | FCVAR_RELEASE, "How much to reduce damage done to teammates by a thrown grenade. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)" );
  64. ConVar ff_damage_reduction_grenade_self( "ff_damage_reduction_grenade_self", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "How much to damage a player does to himself with his own grenade. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)" );
  65. ConVar ff_damage_reduction_bullets( "ff_damage_reduction_bullets", "0.1", FCVAR_REPLICATED | FCVAR_RELEASE, "How much to reduce damage done to teammates when shot. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)" );
  66. ConVar ff_damage_reduction_other( "ff_damage_reduction_other", "0.25", FCVAR_REPLICATED | FCVAR_RELEASE, "How much to reduce damage done to teammates by things other than bullets and grenades. Range is from 0 - 1 (with 1 being damage equal to what is done to an enemy)" );
  67. ConVar ff_damage_bullet_penetration( "ff_damage_bullet_penetration", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "If friendly fire is off, this will scale the penetration power and damage a bullet does when penetrating another friendly player", true, 0.0f, true, 1.0f );
  68. ConVar sv_spec_use_tournament_content_standards( "sv_spec_use_tournament_content_standards", "0.0", FCVAR_REPLICATED | FCVAR_RELEASE );
  69. #if defined( CLIENT_DLL )
  70. ConVar cl_spec_use_tournament_content_standards( "cl_spec_use_tournament_content_standards", "0.0", FCVAR_RELEASE );
  71. #endif
  72. extern ConVar mp_teammates_are_enemies;
  73. extern ConVar mp_use_respawn_waves;
  74. extern ConVar mp_respawn_on_death_ct;
  75. extern ConVar mp_respawn_on_death_t;
  76. extern ConVar mp_buy_allow_grenades;
  77. extern ConVar mp_buy_anywhere;
  78. extern ConVar mp_buy_during_immunity;
  79. extern ConVar mp_free_armor;
  80. #define CS_MASK_SHOOT (MASK_SOLID|CONTENTS_DEBRIS)
  81. #define MAX_PENETRATION_DISTANCE 90 // this is 7.5 feet
  82. #define CS_MAX_WALLBANG_TRAIL_LENGTH 800
  83. void DispatchEffect( const char *pName, const CEffectData &data );
  84. #if defined( CLIENT_DLL )
  85. #define CPlantedC4 C_PlantedC4
  86. extern void StartParticleEffect( const CEffectData &data, int nSplitScreenPlayerSlot = -1 );
  87. #endif
  88. #if defined( _DEBUG ) && !defined( CLIENT_DLL )
  89. // This is some extra code to collect weapon accuracy stats:
  90. struct bulletdata_s
  91. {
  92. float timedelta; // time delta since first shot of this round
  93. float derivation; // derivation for first shoot view angle
  94. int count;
  95. };
  96. #define STATS_MAX_BULLETS 50
  97. static bulletdata_s s_bullet_stats[STATS_MAX_BULLETS];
  98. Vector s_firstImpact = Vector(0,0,0);
  99. float s_firstTime = 0;
  100. float s_LastTime = 0;
  101. int s_bulletCount = 0;
  102. void ResetBulletStats()
  103. {
  104. s_firstTime = 0;
  105. s_LastTime = 0;
  106. s_bulletCount = 0;
  107. s_firstImpact = Vector(0,0,0);
  108. Q_memset( s_bullet_stats, 0, sizeof(s_bullet_stats) );
  109. }
  110. void PrintBulletStats()
  111. {
  112. for (int i=0; i<STATS_MAX_BULLETS; i++ )
  113. {
  114. if (s_bullet_stats[i].count == 0)
  115. break;
  116. Msg("%3i;%3i;%.4f;%.4f\n", i, s_bullet_stats[i].count,
  117. s_bullet_stats[i].timedelta, s_bullet_stats[i].derivation );
  118. }
  119. }
  120. void AddBulletStat( float time, float dist, Vector &impact )
  121. {
  122. if ( time > s_LastTime + 2.0f )
  123. {
  124. // time delta since last shoot is bigger than 2 seconds, start new row
  125. s_LastTime = s_firstTime = time;
  126. s_bulletCount = 0;
  127. s_firstImpact = impact;
  128. }
  129. else
  130. {
  131. s_LastTime = time;
  132. s_bulletCount++;
  133. }
  134. if ( s_bulletCount >= STATS_MAX_BULLETS )
  135. s_bulletCount = STATS_MAX_BULLETS -1;
  136. if ( dist < 1 )
  137. dist = 1;
  138. int i = s_bulletCount;
  139. float offset = VectorLength( s_firstImpact - impact );
  140. float timedelta = time - s_firstTime;
  141. float derivation = offset / dist;
  142. float weight = (float)s_bullet_stats[i].count/(float)(s_bullet_stats[i].count+1);
  143. s_bullet_stats[i].timedelta *= weight;
  144. s_bullet_stats[i].timedelta += (1.0f-weight) * timedelta;
  145. s_bullet_stats[i].derivation *= weight;
  146. s_bullet_stats[i].derivation += (1.0f-weight) * derivation;
  147. s_bullet_stats[i].count++;
  148. }
  149. CON_COMMAND( stats_bullets_reset, "Reset bullet stats")
  150. {
  151. ResetBulletStats();
  152. }
  153. CON_COMMAND( stats_bullets_print, "Print bullet stats")
  154. {
  155. PrintBulletStats();
  156. }
  157. #endif
  158. Vector CCSPlayer::Weapon_ShootPosition()
  159. {
  160. Vector vecPos = BaseClass::Weapon_ShootPosition();
  161. // fail out to un-altered position
  162. if ( !m_bUseNewAnimstate || !m_PlayerAnimStateCSGO )
  163. return vecPos;
  164. // warning: the modify eye position call will query and set up bones
  165. // on the game server it is called when giving weapon items or firing bullets
  166. m_PlayerAnimStateCSGO->ModifyEyePosition( vecPos );
  167. return vecPos;
  168. }
  169. bool CCSPlayer::IsInBuyZone()
  170. {
  171. if ( mp_buy_anywhere.GetInt() == 1 ||
  172. mp_buy_anywhere.GetInt() == GetTeamNumber() )
  173. return true;//m_bGunGameImmunity;
  174. return m_bInBuyZone;
  175. }
  176. bool CCSPlayer::IsInBuyPeriod()
  177. {
  178. if ( mp_buy_during_immunity.GetInt() == 1 ||
  179. mp_buy_during_immunity.GetInt() == GetTeamNumber() )
  180. {
  181. return m_bGunGameImmunity;
  182. }
  183. else
  184. {
  185. return CSGameRules() ? !CSGameRules()->IsBuyTimeElapsed() : false;
  186. }
  187. }
  188. bool CCSPlayer::CanBuyDuringImmunity()
  189. {
  190. return ( mp_buy_during_immunity.GetInt() == 1 ) || ( mp_buy_during_immunity.GetInt() == GetTeamNumber() );
  191. }
  192. bool CCSPlayer::IsAbleToInstantRespawn( void )
  193. {
  194. if ( CSGameRules() )
  195. {
  196. switch( CSGameRules()->GetGamePhase() )
  197. {
  198. case GAMEPHASE_MATCH_ENDED:
  199. case GAMEPHASE_HALFTIME:
  200. return false;
  201. }
  202. if ( CSGameRules()->IsWarmupPeriod() )
  203. return true;
  204. }
  205. // if we use respawn waves AND the next respawn wave is past AND our team is able to respawn OR it is the warmup period
  206. return ( CSGameRules() && ( ( mp_respawn_on_death_ct.GetBool() && GetTeamNumber() == TEAM_CT ) ||
  207. ( mp_respawn_on_death_t.GetBool() && GetTeamNumber() == TEAM_TERRORIST ) ) );
  208. }
  209. char const * CCSPlayer::IsAbleToApplySpray( trace_t *ptr, Vector *pvecForward, Vector *pvecRight )
  210. {
  211. Vector forward, right;
  212. EyeVectors( &forward, &right );
  213. if ( pvecForward ) *pvecForward = forward;
  214. if ( pvecRight ) *pvecRight = right;
  215. if ( !IsAlive() || ( GetTeamNumber() != TEAM_CT && GetTeamNumber() != TEAM_TERRORIST ) )
  216. return "#SFUI_Notice_SprayPaint_NotAlive";
  217. trace_t tr;
  218. Vector vecTraceOriginLocation = Weapon_ShootPosition();
  219. Vector vecTraceTestMaxDepth = vecTraceOriginLocation + forward * 96;
  220. UTIL_TraceLine( vecTraceOriginLocation,
  221. vecTraceTestMaxDepth,
  222. CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_WINDOW,
  223. this, COLLISION_GROUP_NONE, &tr );
  224. if ( tr.fraction == 1.0 )
  225. return "#SFUI_Notice_SprayPaint_OutOfRange";
  226. // Have we hit something TOOLS/???
  227. if ( tr.surface.name && StringHasPrefix( tr.surface.name, "tools/" ) )
  228. return "#SFUI_Notice_SprayPaint_BadSurface";
  229. // Do a second trace to see if anything weird blocks the trace?
  230. // an example could be a destructible door, so spray would go through onto the wall behind it
  231. // then we should ignore the first trace obtained
  232. trace_t tr2;
  233. UTIL_TraceLine( vecTraceOriginLocation,
  234. vecTraceTestMaxDepth,
  235. CONTENTS_OPAQUE | CONTENTS_MONSTER,
  236. this, COLLISION_GROUP_NONE, &tr2 );
  237. if ( tr2.fraction + 0.001 < tr.fraction )
  238. return "#SFUI_Notice_SprayPaint_Obstructed";
  239. //
  240. // Now that we have the location+normal of the trace hit do some validation checks for
  241. // whether that's a good way to apply a decal
  242. //
  243. Vector vecHitToEye = ( vecTraceOriginLocation - tr.endpos );
  244. if ( vecHitToEye.LengthSqr() < 0.5f )
  245. return "#SFUI_Notice_SprayPaint_TooClose";
  246. /*vec_t lenToHit = */ vecHitToEye.NormalizeInPlace();
  247. if ( tr.plane.normal.Dot( vecHitToEye ) < 0.1f )
  248. return "#SFUI_Notice_SprayPaint_GrazingAngle"; // angle too grazing (or backfacing normal), 0.1 = 85 degrees
  249. if ( ptr )
  250. *ptr = tr;
  251. return NULL; // all trace tests passed
  252. }
  253. float CCSPlayer::GetPlayerMaxSpeed()
  254. {
  255. if ( GetMoveType() == MOVETYPE_NONE )
  256. {
  257. return CS_PLAYER_SPEED_STOPPED;
  258. }
  259. if ( IsObserver() )
  260. {
  261. // Player gets speed bonus in observer mode
  262. return CS_PLAYER_SPEED_OBSERVER;
  263. }
  264. bool bValidMoveState = ( State_Get() == STATE_ACTIVE || State_Get() == STATE_OBSERVER_MODE );
  265. if ( !bValidMoveState || m_bIsDefusing || m_bIsGrabbingHostage || (CSGameRules()->IsFreezePeriod() && !m_bCanMoveDuringFreezePeriod) )
  266. {
  267. // Player should not move during the freeze period
  268. return CS_PLAYER_SPEED_STOPPED;
  269. }
  270. float speed = BaseClass::GetPlayerMaxSpeed();
  271. speed = MIN( CS_PLAYER_SPEED_RUN, speed );
  272. if ( IsVIP() == true ) // VIP is slow due to the armour he's wearing
  273. {
  274. speed = CS_PLAYER_SPEED_VIP;
  275. }
  276. else if ( m_hCarriedHostage != NULL )
  277. {
  278. speed = CS_PLAYER_SPEED_HAS_HOSTAGE;
  279. }
  280. else
  281. {
  282. CWeaponCSBase *pWeapon = dynamic_cast<CWeaponCSBase*>( GetActiveWeapon() );
  283. if ( pWeapon )
  284. {
  285. if ( HasShield() && IsShieldDrawn() )
  286. {
  287. speed = MIN( CS_PLAYER_SPEED_SHIELD, speed );
  288. }
  289. else
  290. {
  291. speed = MIN( pWeapon->GetMaxSpeed(), speed );
  292. }
  293. }
  294. }
  295. // if ( HasExosuit() )
  296. // speed *= CS_PLAYER_SPEED_EXOSUIT_RUN_BONUS;
  297. return speed;
  298. }
  299. void CCSPlayer::GiveCarriedHostage( EHANDLE hHostage )
  300. {
  301. if ( !IsAlive() )
  302. return;
  303. m_hCarriedHostage = hHostage;
  304. RefreshCarriedHostage( true );
  305. }
  306. void CCSPlayer::RefreshCarriedHostage( bool bForceCreate )
  307. {
  308. #ifndef CLIENT_DLL
  309. if ( m_hCarriedHostage == NULL )
  310. return;
  311. if ( m_hCarriedHostageProp == NULL )
  312. {
  313. CHostageCarriableProp *pHostageProp = dynamic_cast< CHostageCarriableProp* >( CreateEntityByName( "hostage_carriable_prop" ) );
  314. if ( pHostageProp )
  315. {
  316. pHostageProp->SetAbsOrigin( GetAbsOrigin() );
  317. pHostageProp->SetSolid( SOLID_NONE );
  318. pHostageProp->SetModel( "models/hostage/hostage_carry.mdl" );
  319. pHostageProp->SetModelName( MAKE_STRING( "models/hostage/hostage_carry.mdl" ) );
  320. pHostageProp->SetParent( this );
  321. pHostageProp->SetOwnerEntity( this );
  322. pHostageProp->FollowEntity( this );
  323. m_hCarriedHostageProp = pHostageProp;
  324. CRecipientFilter filter;
  325. filter.MakeReliable();
  326. filter.AddRecipient( this );
  327. UTIL_ClientPrintFilter( filter, HUD_PRINTCENTER, "#Cstrike_TitlesTXT_CarryingHostage" );
  328. }
  329. }
  330. if ( bForceCreate && GetViewModel( 1 ) )
  331. {
  332. CBaseViewModel *vm = GetViewModel( 1 );
  333. UTIL_Remove( vm );
  334. m_hViewModel.Set( 1, INVALID_EHANDLE );
  335. }
  336. CPredictedViewModel *vm = NULL;
  337. CBaseViewModel *pVM = GetViewModel( 1 );
  338. if ( pVM )
  339. vm = ( CPredictedViewModel * )pVM;
  340. else
  341. {
  342. vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" );
  343. bForceCreate = true;
  344. }
  345. if ( vm )
  346. {
  347. vm->SetAbsOrigin( GetAbsOrigin() );
  348. vm->SetOwner( this );
  349. vm->SetIndex( 1 );
  350. int nAct = ACT_VM_IDLE;
  351. if ( bForceCreate )
  352. {
  353. nAct = ACT_VM_DRAW;
  354. DispatchSpawn( vm );
  355. }
  356. vm->FollowEntity( this, false );
  357. vm->SetModel( "models/hostage/v_hostage_arm.mdl" );
  358. int idealSequence = vm->SelectWeightedSequence( ( Activity )nAct );
  359. if ( idealSequence >= 0 )
  360. {
  361. vm->SendViewModelMatchingSequence( idealSequence );
  362. }
  363. vm->SetShouldIgnoreOffsetAndAccuracy( true );
  364. m_hViewModel.Set( 1, vm );
  365. m_hHostageViewModel = vm;
  366. }
  367. #endif
  368. }
  369. void CCSPlayer::RemoveCarriedHostage( void )
  370. {
  371. m_hCarriedHostage = NULL;
  372. #ifndef CLIENT_DLL
  373. if ( m_hCarriedHostageProp )
  374. {
  375. CBaseAnimating *pHostageProp = dynamic_cast< CBaseAnimating* >( m_hCarriedHostageProp.Get() );
  376. if ( pHostageProp )
  377. {
  378. pHostageProp->FollowEntity( NULL );
  379. UTIL_Remove( pHostageProp );
  380. }
  381. m_hCarriedHostageProp = NULL;
  382. }
  383. if ( m_hHostageViewModel || dynamic_cast<CPredictedViewModel*>(GetViewModel( 1 )) )
  384. {
  385. CPredictedViewModel *pHostageVM = dynamic_cast< CPredictedViewModel* >( m_hHostageViewModel.Get() );
  386. if ( !pHostageVM )
  387. pHostageVM = dynamic_cast<CPredictedViewModel*>(GetViewModel( 1 ));
  388. if ( pHostageVM )
  389. {
  390. pHostageVM->FollowEntity( NULL );
  391. UTIL_Remove( pHostageVM );
  392. }
  393. m_hHostageViewModel = INVALID_EHANDLE;
  394. m_hViewModel.Set( 1, INVALID_EHANDLE );
  395. }
  396. #endif
  397. }
  398. void CCSPlayer::GetBulletTypeParameters(
  399. int iBulletType,
  400. float &fPenetrationPower,
  401. float &flPenetrationDistance )
  402. {
  403. if ( sv_penetration_type.GetInt() == 1 )
  404. {
  405. fPenetrationPower = 35;
  406. flPenetrationDistance = 3000.0;
  407. return;
  408. }
  409. //MIKETODO: make ammo types come from a script file.
  410. if ( IsAmmoType( iBulletType, BULLET_PLAYER_50AE ) )
  411. {
  412. fPenetrationPower = 30;
  413. flPenetrationDistance = 1000.0;
  414. }
  415. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_762MM ) )
  416. {
  417. fPenetrationPower = 39;
  418. flPenetrationDistance = 5000.0;
  419. }
  420. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_556MM ) ||
  421. IsAmmoType( iBulletType, BULLET_PLAYER_556MM_SMALL ) ||
  422. IsAmmoType( iBulletType, BULLET_PLAYER_556MM_BOX ) )
  423. {
  424. fPenetrationPower = 35;
  425. flPenetrationDistance = 4000.0;
  426. }
  427. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_338MAG ) )
  428. {
  429. fPenetrationPower = 45;
  430. flPenetrationDistance = 8000.0;
  431. }
  432. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_9MM ) )
  433. {
  434. fPenetrationPower = 21;
  435. flPenetrationDistance = 800.0;
  436. }
  437. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_BUCKSHOT ) )
  438. {
  439. fPenetrationPower = 0;
  440. flPenetrationDistance = 0.0;
  441. }
  442. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_45ACP ) )
  443. {
  444. fPenetrationPower = 15;
  445. flPenetrationDistance = 500.0;
  446. }
  447. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_357SIG ) ||
  448. IsAmmoType( iBulletType, BULLET_PLAYER_357SIG_SMALL ) ||
  449. IsAmmoType( iBulletType, BULLET_PLAYER_357SIG_P250 ) ||
  450. IsAmmoType( iBulletType, BULLET_PLAYER_357SIG_MIN ) )
  451. {
  452. fPenetrationPower = 25;
  453. flPenetrationDistance = 800.0;
  454. }
  455. else if ( IsAmmoType( iBulletType, BULLET_PLAYER_57MM ) )
  456. {
  457. fPenetrationPower = 30;
  458. flPenetrationDistance = 2000.0;
  459. }
  460. else if ( IsAmmoType( iBulletType, AMMO_TYPE_TASERCHARGE ) )
  461. {
  462. fPenetrationPower = 0;
  463. flPenetrationDistance = 0.0;
  464. }
  465. else
  466. {
  467. // What kind of ammo is this?
  468. Assert( false );
  469. fPenetrationPower = 0;
  470. flPenetrationDistance = 0.0;
  471. }
  472. }
  473. static bool TraceToExit( Vector start, Vector dir, Vector &end, trace_t &trEnter, trace_t &trExit, float flStepSize, float flMaxDistance )
  474. {
  475. float flDistance = 0;
  476. Vector last = start;
  477. int nStartContents = 0;
  478. while ( flDistance <= flMaxDistance )
  479. {
  480. flDistance += flStepSize;
  481. end = start + ( flDistance * dir );
  482. Vector vecTrEnd = end - ( flStepSize * dir );
  483. if ( nStartContents == 0 )
  484. nStartContents = UTIL_PointContents( end, CS_MASK_SHOOT|CONTENTS_HITBOX );
  485. int nCurrentContents = UTIL_PointContents( end, CS_MASK_SHOOT|CONTENTS_HITBOX );
  486. if ( (nCurrentContents & CS_MASK_SHOOT) == 0 || ((nCurrentContents & CONTENTS_HITBOX) && nStartContents != nCurrentContents) )
  487. {
  488. // this gets a bit more complicated and expensive when we have to deal with displacements
  489. UTIL_TraceLine( end, vecTrEnd, CS_MASK_SHOOT|CONTENTS_HITBOX, NULL, &trExit );
  490. // we exited the wall into a player's hitbox
  491. if ( trExit.startsolid == true && (trExit.surface.flags & SURF_HITBOX)/*( nStartContents & CONTENTS_HITBOX ) == 0 && (nCurrentContents & CONTENTS_HITBOX)*/ )
  492. {
  493. // do another trace, but skip the player to get the actual exit surface
  494. UTIL_TraceLine( end, start, CS_MASK_SHOOT, trExit.m_pEnt, COLLISION_GROUP_NONE, &trExit );
  495. if ( trExit.DidHit() && trExit.startsolid == false )
  496. {
  497. end = trExit.endpos;
  498. return true;
  499. }
  500. }
  501. else if ( trExit.DidHit() && trExit.startsolid == false )
  502. {
  503. bool bStartIsNodraw = !!( trEnter.surface.flags & (SURF_NODRAW) );
  504. bool bExitIsNodraw = !!( trExit.surface.flags & (SURF_NODRAW) );
  505. if ( bExitIsNodraw && IsBreakableEntity( trExit.m_pEnt ) && IsBreakableEntity( trEnter.m_pEnt ) )
  506. {
  507. // we have a case where we have a breakable object, but the mapper put a nodraw on the backside
  508. end = trExit.endpos;
  509. return true;
  510. }
  511. else if ( bExitIsNodraw == false || (bStartIsNodraw && bExitIsNodraw) ) // exit nodraw is only valid if our entrace is also nodraw
  512. {
  513. Vector vecNormal = trExit.plane.normal;
  514. float flDot = dir.Dot( vecNormal );
  515. if ( flDot <= 1.0f )
  516. {
  517. // get the real end pos
  518. end = end - ( (flStepSize * trExit.fraction) * dir );
  519. return true;
  520. }
  521. }
  522. }
  523. else if ( trEnter.DidHitNonWorldEntity() && IsBreakableEntity( trEnter.m_pEnt ) )
  524. {
  525. // if we hit a breakable, make the assumption that we broke it if we can't find an exit (hopefully..)
  526. // fake the end pos
  527. trExit = trEnter;
  528. trExit.endpos = start + ( 1.0f * dir );
  529. return true;
  530. }
  531. }
  532. }
  533. return false;
  534. }
  535. inline void UTIL_TraceLineIgnoreTwoEntities( const Vector& vecAbsStart, const Vector& vecAbsEnd, unsigned int mask,
  536. const IHandleEntity *ignore, const IHandleEntity *ignore2, int collisionGroup, trace_t *ptr )
  537. {
  538. Ray_t ray;
  539. ray.Init( vecAbsStart, vecAbsEnd );
  540. CTraceFilterSkipTwoEntities traceFilter( ignore, ignore2, collisionGroup );
  541. enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
  542. if( r_visualizetraces.GetBool() )
  543. {
  544. DebugDrawLine( ptr->startpos, ptr->endpos, 255, 0, 0, true, -1.0f );
  545. }
  546. }
  547. #ifndef CLIENT_DLL
  548. void CCSPlayer::CheckForWeaponFiredAchievement()
  549. {
  550. // Removed the Smorgasbord achievement... for now
  551. }
  552. #endif
  553. bool CCSPlayer::IsPrimaryOrSecondaryWeapon( CSWeaponType nType )
  554. {
  555. if ( nType == WEAPONTYPE_PISTOL || nType == WEAPONTYPE_SUBMACHINEGUN || nType == WEAPONTYPE_RIFLE ||
  556. nType == WEAPONTYPE_SHOTGUN || nType == WEAPONTYPE_SNIPER_RIFLE || nType == WEAPONTYPE_MACHINEGUN )
  557. {
  558. return true;
  559. }
  560. return false;
  561. }
  562. bool CCSPlayer::IsOtherSameTeam( int nTeam )
  563. {
  564. return GetTeamNumber() == nTeam;
  565. }
  566. bool CCSPlayer::IsOtherEnemy( CCSPlayer *pPlayer )
  567. {
  568. if ( !pPlayer )
  569. return false;
  570. // we are never an enemy of ourselves
  571. if ( entindex() == pPlayer->entindex() )
  572. return false;
  573. int nOtherTeam = pPlayer->GetAssociatedTeamNumber();
  574. int nTeam = GetAssociatedTeamNumber();
  575. if ( mp_teammates_are_enemies.GetBool() && nTeam == nOtherTeam )
  576. {
  577. return true;
  578. }
  579. return nTeam != nOtherTeam;
  580. }
  581. uint32 CCSPlayer::GetActiveQuestID( void ) const
  582. {
  583. uint32 unQuestID =
  584. #if defined ( CLIENT_DLL )
  585. CSInventoryManager()->GetLocalCSInventory() ? CSInventoryManager()->GetLocalCSInventory()->GetActiveQuestID() : 0;
  586. #else
  587. m_Inventory.GetActiveQuestID();
  588. #endif
  589. return unQuestID;
  590. }
  591. #if defined( CLIENT_DLL )
  592. static bool ClientThinksQuestIsOk(CCSGameRules* pGameRules, const CCSPlayer* pPlayer)
  593. {
  594. if ( !pGameRules || !pPlayer )
  595. return false;
  596. // Should also be handled by QuestProgressReason set to QUEST_NONOFFICIAL_SERVER, but just to be safe, handle it here too.
  597. if ( !pGameRules->IsQuestEligible() )
  598. return false;
  599. uint32 unQuestID = pPlayer->GetActiveQuestID();
  600. if ( !unQuestID )
  601. return false;
  602. CEconQuestDefinition *pQuestDef = GetItemSchema()->GetQuestDefinition( unQuestID );
  603. if ( !pQuestDef )
  604. return false;
  605. // This is a clone of the server logic in Helper_InitializeQuestDataFromInventory() in cs_player.cpp
  606. //
  607. // Check the game mode
  608. //
  609. char const *szRequireGameType = NULL;
  610. if ( !g_pGameTypes->GetGameTypeFromMode( pQuestDef->GetGameMode(), szRequireGameType ) )
  611. return false;
  612. int nRequireGameType = -1;
  613. int nRequireGameMode = -1;
  614. if ( !g_pGameTypes->GetGameModeAndTypeIntsFromStrings( szRequireGameType, pQuestDef->GetGameMode(), nRequireGameType, nRequireGameMode ) )
  615. return false;
  616. if ( ( g_pGameTypes->GetCurrentGameType() != nRequireGameType ) || ( g_pGameTypes->GetCurrentGameMode() != nRequireGameMode ) )
  617. return false;
  618. //
  619. // check the map group
  620. //
  621. const char* szCurMapGroup = engine->GetMapGroupName();
  622. bool bHasMapGroupReq = pQuestDef->GetMapGroup() && pQuestDef->GetMapGroup()[0];
  623. if ( bHasMapGroupReq && stricmp( szCurMapGroup, pQuestDef->GetMapGroup() ) != 0 )
  624. return false;
  625. // check the map
  626. const char *szCurMapName = engine->GetLevelNameShort();
  627. bool bHasMapReq = pQuestDef->GetMap() && pQuestDef->GetMap()[0];
  628. if ( bHasMapReq && stricmp( szCurMapName, pQuestDef->GetMap() ) != 0 )
  629. return false;
  630. //
  631. // mapgroup, gamemode, and/or map are valid.
  632. // Note: We can't check the server quest here; co-op missions require the server to have been
  633. // started exactly for that mission. Assume the server is wrong here, we don't want to display
  634. // a de-sync message.
  635. if ( CSGameRules()->IsPlayingCooperativeGametype() )
  636. return false;
  637. // TODO: There are some longer-term things not checked here, but in quest rules, such as
  638. // cond_team_terrorist. Ideally we'd like to put those in your quest progression
  639. // reason too, but I'm leaving that for a future update. We should probably put
  640. // those outside of the quest evaluation expression into a separate set of state
  641. // that is easier to message failure reasons for than arbitrary conditional expressions.
  642. // Alright, we think we are allowed to make progress!
  643. return true;
  644. }
  645. #endif
  646. QuestProgress::Reason CCSPlayer::GetQuestProgressReason( void ) const
  647. {
  648. #if defined( CLIENT_DLL )
  649. // On the client, we want to compare our expected quest state with the server
  650. // Non-official server, don't bother
  651. if ( m_nQuestProgressReason == QuestProgress::QUEST_NONOFFICIAL_SERVER )
  652. return m_nQuestProgressReason;
  653. // If we are OK, skip the more expensive test
  654. if ( m_nQuestProgressReason == QuestProgress::QUEST_OK || m_nQuestProgressReason == QuestProgress::QUEST_NOT_ENOUGH_PLAYERS )
  655. return m_nQuestProgressReason;
  656. // If we get here, the server thinks we don't have a valid quest. Check if we believe it by looking at the state in our inventory.
  657. if ( CSGameRules() && ClientThinksQuestIsOk( CSGameRules(), this ) )
  658. {
  659. // The server doesn't initialize quests until after warmup, so during warmup we will trust that the client's state is valid.
  660. if ( CSGameRules()->IsWarmupPeriod() )
  661. return QuestProgress::QUEST_WARMUP;
  662. // Otherwise, the client thinks the quest should be able to make progress but the server doesn't. Notify the de-sync'd state.
  663. return QuestProgress::QUEST_NOT_SYNCED_WITH_SERVER;
  664. }
  665. #endif
  666. return m_nQuestProgressReason;
  667. }
  668. bool CCSPlayer::IsAssassinationTarget( void ) const
  669. {
  670. CCSPlayerResource* pCSPR =
  671. #if defined ( CLIENT_DLL )
  672. GetCSResources();
  673. #else
  674. CSPlayerResource();
  675. #endif
  676. if ( !pCSPR )
  677. return false;
  678. return pCSPR->IsAssassinationTarget( entindex() );
  679. }
  680. bool CCSPlayer::IsOtherEnemy( int nEntIndex )
  681. {
  682. CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( nEntIndex );
  683. if ( !pPlayer )
  684. {
  685. // client doesn't have a pointer to enemy players outside our PVS
  686. #if defined ( CLIENT_DLL )
  687. // we are never an enemy of ourselves
  688. if ( entindex() == nEntIndex )
  689. return false;
  690. C_CS_PlayerResource *pCSPR = GetCSResources();
  691. if ( pCSPR )
  692. {
  693. int nOtherTeam = pCSPR->GetTeam( nEntIndex );
  694. int nTeam = GetAssociatedTeamNumber();
  695. if ( mp_teammates_are_enemies.GetBool() && nTeam == nOtherTeam )
  696. {
  697. return true;
  698. }
  699. return nTeam != nOtherTeam;
  700. }
  701. #endif
  702. return false;
  703. }
  704. return IsOtherEnemy( pPlayer );
  705. }
  706. bool CCSPlayer::IsOtherEnemyAndPlaying( int nEntIndex )
  707. {
  708. int nOtherTeam = 0;
  709. CCSPlayer *pPlayer = (CCSPlayer*) UTIL_PlayerByIndex( nEntIndex );
  710. if ( !pPlayer )
  711. {
  712. #if defined ( CLIENT_DLL )
  713. C_CS_PlayerResource *pCSPR = GetCSResources();
  714. if ( !pCSPR )
  715. return false;
  716. nOtherTeam = pCSPR->GetTeam( nEntIndex );
  717. #else if
  718. return false;
  719. #endif
  720. }
  721. if ( ( GetTeamNumber() == TEAM_CT && nOtherTeam == TEAM_TERRORIST ) ||
  722. ( GetTeamNumber() == TEAM_TERRORIST && nOtherTeam == TEAM_CT ) )
  723. return true;
  724. return false;
  725. }
  726. bool CCSPlayer::CanPlayerBuy( bool display )
  727. {
  728. if ( !CSGameRules() )
  729. return false;
  730. // is the player alive?
  731. if ( m_lifeState != LIFE_ALIVE )
  732. {
  733. return false;
  734. }
  735. // is the player in a buy zone?
  736. if ( !IsInBuyZone() )
  737. {
  738. return false;
  739. }
  740. // Don't allow buying in the last few seconds of warmup because everybody should be freezed, but sometimes people aren't
  741. // also fixes buy on the very moment that round starts which might cause the bought weapon to spawn, but touched by the
  742. // player in the actual match time next frame and have a powerful gun for the first pistol round.
  743. if ( CSGameRules()->IsWarmupPeriod() && ( CSGameRules()->GetWarmupPeriodEndTime() - 3 < gpGlobals->curtime ) )
  744. return false;
  745. // Even if buytime has run out, if buy time is tied to immunity then we let the player buy if the menu is still open.
  746. if ( !IsInBuyPeriod() && CanBuyDuringImmunity() )
  747. return IsBuyMenuOpen();
  748. CCSGameRules* mp = CSGameRules();
  749. if ( mp->m_bCTCantBuy && ( GetTeamNumber() == TEAM_CT ) )
  750. {
  751. if ( display == true )
  752. ClientPrint( this, HUD_PRINTCENTER, "#CT_cant_buy" );
  753. return false;
  754. }
  755. if ( mp->m_bTCantBuy && ( GetTeamNumber() == TEAM_TERRORIST ) )
  756. {
  757. if ( display == true )
  758. ClientPrint( this, HUD_PRINTCENTER, "#Terrorist_cant_buy" );
  759. return false;
  760. }
  761. if ( CSGameRules()->IsPlayingCoopGuardian() )
  762. {
  763. if ( CSGameRules()->IsWarmupPeriod() == false &&
  764. CSGameRules()->m_flGuardianBuyUntilTime < gpGlobals->curtime )
  765. {
  766. int nTeam = CSGameRules()->IsHostageRescueMap() ? TEAM_TERRORIST : TEAM_CT;
  767. if ( GetTeamNumber() == nTeam )
  768. {
  769. if ( display == true )
  770. {
  771. #ifdef CLIENT_DLL
  772. static ConVarRef sv_buy_status_override_ref( "sv_buy_status_override" );
  773. int iBuyStatus = sv_buy_status_override_ref.GetInt();
  774. #else
  775. extern ConVar sv_buy_status_override;
  776. int iBuyStatus = sv_buy_status_override.GetInt();
  777. #endif
  778. if ( iBuyStatus > 0 && ( ( nTeam == TEAM_CT && iBuyStatus != 1 ) || ( nTeam == TEAM_TERRORIST && iBuyStatus != 2 ) ) )
  779. ClientPrint( this, HUD_PRINTCENTER, "#SFUI_BuyMenu_CantBuy" );
  780. else
  781. ClientPrint( this, HUD_PRINTCENTER, "#SFUI_BuyMenu_CantBuyTilNextWave" );
  782. }
  783. return false;
  784. }
  785. }
  786. }
  787. int buyTime = mp_buytime.GetInt();
  788. if ( mp->IsBuyTimeElapsed() && !CanBuyDuringImmunity() )
  789. {
  790. if ( display == true )
  791. {
  792. char strBuyTime[16];
  793. Q_snprintf( strBuyTime, sizeof( strBuyTime ), "%d", buyTime );
  794. ClientPrint( this, HUD_PRINTCENTER, "#Cant_buy", strBuyTime );
  795. }
  796. return false;
  797. }
  798. if ( mp->IsWarmupPeriod() && mp->IsPlayingCooperativeGametype() )
  799. return true;
  800. if ( mp->IsPlayingCoopMission() )
  801. return true;
  802. #ifdef GAME_DLL
  803. AssertMsg( !m_bIsVIP, " There's no VIP in CSGO. Yet. If implementing, fix this GAME_DLL only code." );
  804. if ( m_bIsVIP )
  805. {
  806. if ( display == true )
  807. ClientPrint( this, HUD_PRINTCENTER, "#VIP_cant_buy" );
  808. return false;
  809. }
  810. #endif
  811. return true;
  812. }
  813. bool CCSPlayer::GetUseConfigurationForHighPriorityUseEntity( CBaseEntity *pEntity, CConfigurationForHighPriorityUseEntity_t &cfg )
  814. {
  815. if ( dynamic_cast<CPlantedC4*>( pEntity ) )
  816. {
  817. if ( CSGameRules() && CSGameRules()->IsBombDefuseMap() &&
  818. ( this->GetTeamNumber() == TEAM_CT ) )
  819. {
  820. cfg.m_pEntity = pEntity;
  821. }
  822. else
  823. {
  824. // it's a high-priority entity, but not used by the player team
  825. cfg.m_pEntity = NULL;
  826. }
  827. cfg.m_ePriority = cfg.k_EPriority_Bomb;
  828. cfg.m_eDistanceCheckType = cfg.k_EDistanceCheckType_2D;
  829. cfg.m_pos = pEntity->GetAbsOrigin() + Vector( 0, 0, 3 );
  830. cfg.m_flMaxUseDistance = 62; // Cannot use if > 62 units away
  831. cfg.m_flLosCheckDistance = 36; // Check LOS if > 36 units away (2D)
  832. cfg.m_flDotCheckAngle = -0.7; // 0.7 taken from Goldsrc, +/- ~45 degrees
  833. cfg.m_flDotCheckAngleMax = -0.5; // 0.3 for it going outside the range during continuous use (120-degree cone)
  834. return true;
  835. }
  836. else if ( dynamic_cast<CHostage*>( pEntity ) )
  837. {
  838. cfg.m_pEntity = pEntity;
  839. cfg.m_ePriority = cfg.k_EPriority_Hostage;
  840. cfg.m_eDistanceCheckType = cfg.k_EDistanceCheckType_3D;
  841. cfg.m_pos = pEntity->EyePosition();
  842. cfg.m_flMaxUseDistance = 62; // Cannot use if > 62 units away
  843. cfg.m_flLosCheckDistance = 32; // Check LOS if > 32 units away (2D)
  844. cfg.m_flDotCheckAngle = -0.7; // 0.7 taken from Goldsrc, +/- ~45 degrees
  845. cfg.m_flDotCheckAngleMax = -0.5; // 0.5 for it going outside the range during continuous use (120-degree cone)
  846. return true;
  847. }
  848. return false;
  849. }
  850. CBaseEntity *CCSPlayer::GetUsableHighPriorityEntity( void )
  851. {
  852. // This is done separately since there might be something blocking our LOS to it
  853. // but we might want to use it anyway if it's close enough. This should eliminate
  854. // the vast majority of bomb placement exploits (places where the bomb can be planted
  855. // but can't be "used". This also mimics goldsrc cstrike behavior.
  856. CBaseEntity *pEntsNearPlayer[64];
  857. // 64 is the distance in Goldsrc. However since Goldsrc did distance from the player's origin and we're doing distance from the player's eye, make the radius a bit bigger.
  858. int iEntsNearPlayer = UTIL_EntitiesInSphere( pEntsNearPlayer, 64, EyePosition(), 72, FL_OBJECT );
  859. if( iEntsNearPlayer != 0 )
  860. {
  861. CConfigurationForHighPriorityUseEntity_t cfgBestHighPriorityEntity;
  862. cfgBestHighPriorityEntity.m_pEntity = NULL;
  863. cfgBestHighPriorityEntity.m_ePriority = cfgBestHighPriorityEntity.k_EPriority_Default;
  864. for( int i = 0; i != iEntsNearPlayer; ++i )
  865. {
  866. CBaseEntity *pEntity = pEntsNearPlayer[i];
  867. Assert( pEntity != NULL );
  868. CConfigurationForHighPriorityUseEntity_t cfgUseSettings;
  869. if ( !GetUseConfigurationForHighPriorityUseEntity( pEntity, cfgUseSettings ) )
  870. continue; // not a high-priority entity
  871. if ( !cfgUseSettings.m_pEntity )
  872. continue; // not used by the player
  873. if ( cfgUseSettings.m_ePriority < cfgBestHighPriorityEntity.m_ePriority )
  874. continue; // we already have a higher priority entity
  875. if ( !cfgUseSettings.UseByPlayerNow( this, cfgUseSettings.k_EPlayerUseType_Start ) )
  876. continue; // cannot start use by the player right now
  877. // This high-priority entity passes the checks, remember it as best
  878. if ( cfgUseSettings.IsBetterForUseThan( cfgBestHighPriorityEntity ) )
  879. cfgBestHighPriorityEntity = cfgUseSettings;
  880. }
  881. return cfgBestHighPriorityEntity.m_pEntity;
  882. }
  883. return NULL;
  884. }
  885. bool CConfigurationForHighPriorityUseEntity_t::IsBetterForUseThan( CConfigurationForHighPriorityUseEntity_t const &other ) const
  886. {
  887. if ( !m_pEntity )
  888. return false;
  889. if ( !other.m_pEntity )
  890. return true;
  891. if ( m_ePriority < other.m_ePriority )
  892. return false;
  893. if ( m_ePriority > other.m_ePriority )
  894. return true;
  895. if ( m_flDotCheckAngleMax < other.m_flDotCheckAngleMax ) // We are looking at it with a better angle
  896. return true;
  897. if ( m_flMaxUseDistance < other.m_flMaxUseDistance ) // This entity is closer to user
  898. return true;
  899. return false;
  900. }
  901. bool CConfigurationForHighPriorityUseEntity_t::UseByPlayerNow( CCSPlayer *pPlayer, EPlayerUseType_t ePlayerUseType )
  902. {
  903. if ( !pPlayer )
  904. return false;
  905. // entity is close enough, now make sure the player is facing the bomb.
  906. float flDistTo = FLT_MAX;
  907. switch ( m_eDistanceCheckType )
  908. {
  909. case k_EDistanceCheckType_2D:
  910. flDistTo = pPlayer->EyePosition().AsVector2D().DistTo( m_pos.AsVector2D() );
  911. break;
  912. case k_EDistanceCheckType_3D:
  913. flDistTo = pPlayer->EyePosition().DistTo( m_pos );
  914. break;
  915. default:
  916. Assert( false );
  917. }
  918. // UTIL_EntitiesInSphere gives strange results where I can find it when my eyes are at an angle, but not when I'm right on top of it
  919. // because of that, make sure it's in our radius, but check the 2d los and make sure we are as close or closer than we need to be in 1.6
  920. if ( flDistTo > m_flMaxUseDistance )
  921. return false;
  922. // if it's more than 36 units away (2d), we should check LOS
  923. if ( flDistTo > m_flLosCheckDistance )
  924. {
  925. trace_t tr;
  926. UTIL_TraceLine( pPlayer->EyePosition(), m_pos, (MASK_VISIBLE|CONTENTS_WATER|CONTENTS_SLIME), pPlayer, COLLISION_GROUP_DEBRIS, &tr );
  927. // if we can't trace to the bomb at this distance, then we fail
  928. if ( tr.fraction < 0.98 )
  929. return false;
  930. }
  931. Vector vecLOS = pPlayer->EyePosition() - m_pos;
  932. Vector forward;
  933. AngleVectors( pPlayer->EyeAngles(), &forward, NULL, NULL );
  934. vecLOS.NormalizeInPlace();
  935. float flDot = DotProduct(forward, vecLOS);
  936. float flCheckAngle = ( ePlayerUseType == k_EPlayerUseType_Start ) ? m_flDotCheckAngle : m_flDotCheckAngleMax;
  937. if ( flDot >= flCheckAngle )
  938. return false;
  939. // Remember the actual settings of this entity
  940. m_flDotCheckAngle = m_flDotCheckAngleMax = flDot;
  941. m_flLosCheckDistance = m_flMaxUseDistance = flDistTo;
  942. return true;
  943. }
  944. ConVar sv_server_verify_blood_on_player( "sv_server_verify_blood_on_player", "1", FCVAR_CHEAT | FCVAR_REPLICATED );
  945. #ifndef CLIENT_DLL
  946. static const int kMaxNumPenetrationsSupported = 4;
  947. struct DelayedDamageInfoData_t
  948. {
  949. CTakeDamageInfo m_info;
  950. trace_t m_tr;
  951. typedef CUtlVectorFixedGrowable< DelayedDamageInfoData_t, kMaxNumPenetrationsSupported > Array;
  952. };
  953. #endif
  954. void CCSPlayer::FireBullet(
  955. Vector vecSrc, // shooting postion
  956. const QAngle &shootAngles, //shooting angle
  957. float flDistance, // max distance
  958. float flPenetration, // the power of the penetration
  959. int nPenetrationCount,
  960. int iBulletType, // ammo type
  961. int iDamage, // base damage
  962. float flRangeModifier, // damage range modifier
  963. CBaseEntity *pevAttacker, // shooter
  964. bool bDoEffects,
  965. float xSpread, float ySpread
  966. )
  967. {
  968. float fCurrentDamage = iDamage; // damage of the bullet at it's current trajectory
  969. float flCurrentDistance = 0.0; //distance that the bullet has traveled so far
  970. Vector vecDirShooting, vecRight, vecUp;
  971. AngleVectors( shootAngles, &vecDirShooting, &vecRight, &vecUp );
  972. // MIKETODO: put all the ammo parameters into a script file and allow for CS-specific params.
  973. float flPenetrationPower = 0; // thickness of a wall that this bullet can penetrate
  974. float flPenetrationDistance = 0; // distance at which the bullet is capable of penetrating a wall
  975. float flDamageModifier = 0.5f; // default modification of bullets power after they go through a wall.
  976. float flPenetrationModifier = 1.0f;
  977. GetBulletTypeParameters( iBulletType, flPenetrationPower, flPenetrationDistance );
  978. // we use the max penetrations on this gun to figure out how much penetration it's capable of
  979. if ( sv_penetration_type.GetInt() == 1 )
  980. flPenetrationPower = flPenetration;
  981. if ( !pevAttacker )
  982. pevAttacker = this; // the default attacker is ourselves
  983. // add the spray
  984. Vector vecDir = vecDirShooting + xSpread * vecRight + ySpread * vecUp;
  985. VectorNormalize( vecDir );
  986. //Adrian: visualize server/client player positions
  987. //This is used to show where the lag compesator thinks the player should be at.
  988. #if 0
  989. for ( int k = 1; k <= gpGlobals->maxClients; k++ )
  990. {
  991. CBasePlayer *clientClass = (CBasePlayer *)CBaseEntity::Instance( k );
  992. if ( clientClass == NULL )
  993. continue;
  994. if ( k == entindex() )
  995. continue;
  996. #ifdef CLIENT_DLL
  997. debugoverlay->AddBoxOverlay( clientClass->GetAbsOrigin(), clientClass->WorldAlignMins(), clientClass->WorldAlignMaxs(), QAngle( 0, 0, 0), 255,0,0,127, 4 );
  998. #else
  999. NDebugOverlay::Box( clientClass->GetAbsOrigin(), clientClass->WorldAlignMins(), clientClass->WorldAlignMaxs(), 0,0,255,127, 4 );
  1000. #endif
  1001. }
  1002. #endif
  1003. #ifndef CLIENT_DLL
  1004. // [pfreese] Track number player entities killed with this bullet
  1005. int iPenetrationKills = 0;
  1006. int numPlayersHit = 0;
  1007. // [menglish] Increment the shots fired for this player
  1008. CCS_GameStats.Event_ShotFired( this, GetActiveWeapon() );
  1009. m_bulletsFiredSinceLastSpawn++;
  1010. CheckForWeaponFiredAchievement();
  1011. #endif
  1012. bool bFirstHit = true;
  1013. const CBaseCombatCharacter *lastPlayerHit = NULL; // this includes players, bots, and hostages
  1014. #ifdef CLIENT_DLL
  1015. Vector vecWallBangHitStart, vecWallBangHitEnd;
  1016. vecWallBangHitStart.Init();
  1017. vecWallBangHitEnd.Init();
  1018. bool bWallBangStarted = false;
  1019. bool bWallBangEnded = false;
  1020. bool bWallBangHeavyVersion = false;
  1021. #endif
  1022. bool bBulletHitPlayer = false;
  1023. MDLCACHE_CRITICAL_SECTION();
  1024. #ifndef CLIENT_DLL
  1025. DelayedDamageInfoData_t::Array arrPendingDamage;
  1026. #endif
  1027. bool bShotHitTeammate = false;
  1028. float flDist_aim = 0;
  1029. Vector vHitLocation = Vector( 0,0,0 );
  1030. while ( fCurrentDamage > 0 )
  1031. {
  1032. Vector vecEnd = vecSrc + vecDir * (flDistance-flCurrentDistance);
  1033. trace_t tr; // main enter bullet trace
  1034. UTIL_TraceLineIgnoreTwoEntities( vecSrc, vecEnd, CS_MASK_SHOOT|CONTENTS_HITBOX, this, lastPlayerHit, COLLISION_GROUP_NONE, &tr );
  1035. {
  1036. CTraceFilterSkipTwoEntities filter( this, lastPlayerHit, COLLISION_GROUP_NONE );
  1037. // Check for player hitboxes extending outside their collision bounds
  1038. const float rayExtension = 40.0f;
  1039. UTIL_ClipTraceToPlayers( vecSrc, vecEnd + vecDir * rayExtension, CS_MASK_SHOOT|CONTENTS_HITBOX, &filter, &tr );
  1040. }
  1041. if ( !flDist_aim )
  1042. {
  1043. flDist_aim = ( tr.fraction != 1.0 ) ? ( tr.startpos - tr.endpos ).Length() : 0;
  1044. }
  1045. if ( flDist_aim )
  1046. {
  1047. vHitLocation = tr.endpos;
  1048. }
  1049. lastPlayerHit = dynamic_cast<const CBaseCombatCharacter *>(tr.m_pEnt);
  1050. #ifndef CLIENT_DLL
  1051. if ( sv_showbullethits.GetInt() == 1 && !lastPlayerHit )
  1052. {
  1053. trace_t tr_bulletmiss;
  1054. UTIL_TraceLine( vecSrc, vecEnd, CS_MASK_SHOOT, this, COLLISION_GROUP_NONE, &tr_bulletmiss );
  1055. CCSPlayer *playerMissed = ToCSPlayer( tr_bulletmiss.m_pEnt );
  1056. if ( tr_bulletmiss.DidHit() && !tr_bulletmiss.startsolid && playerMissed )
  1057. {
  1058. Vector vecPelvisPos;
  1059. QAngle angTemp;
  1060. playerMissed->GetBonePosition( 0, vecPelvisPos, angTemp );
  1061. Vector vecMissPos;
  1062. CalcClosestPointOnLine( vecPelvisPos, vecSrc, vecEnd, vecMissPos );
  1063. CStudioHdr *pStudioHdr = playerMissed->GetModelPtr();
  1064. mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( playerMissed->m_nHitboxSet );
  1065. float flClosestHitboxDistance = FLT_MAX;
  1066. Vector vecClosestHitboxPos = vecMissPos;
  1067. int nClosestHitboxIndex = -1;
  1068. Vector vecPos;
  1069. QAngle angAng;
  1070. for ( int n = 0; n < set->numhitboxes; n++ )
  1071. {
  1072. mstudiobbox_t *pbox = set->pHitbox( n );
  1073. playerMissed->GetHitboxBonePosition( pbox->bone, vecPos, angAng, pbox->angOffsetOrientation );
  1074. float flDist = vecPos.DistToSqr( tr_bulletmiss.endpos );
  1075. if ( flDist < flClosestHitboxDistance )
  1076. {
  1077. flClosestHitboxDistance = flDist;
  1078. vecClosestHitboxPos = vecPos;
  1079. nClosestHitboxIndex = n;
  1080. }
  1081. }
  1082. if ( nClosestHitboxIndex > -1 )
  1083. {
  1084. CalcClosestPointOnLine( vecClosestHitboxPos, vecSrc, vecEnd, vecMissPos );
  1085. Vector vecMissDir = (vecEnd - vecSrc).Normalized();
  1086. vecMissPos += vecMissDir * 15;
  1087. int nBoneIndex = playerMissed->GetHitboxBone( nClosestHitboxIndex );
  1088. // build a matrix from the trace hit start and end position
  1089. matrix3x4_t matWorldSpaceBulletHit;
  1090. VectorMatrix( vecSrc - vecEnd, matWorldSpaceBulletHit );
  1091. PositionMatrix( vecMissPos, matWorldSpaceBulletHit );
  1092. // get the transform of the bone that owns the hitbox
  1093. matrix3x4_t matBoneToWorldTransform;
  1094. playerMissed->GetBoneTransform( nBoneIndex, matBoneToWorldTransform );
  1095. // get the local transform of the hit transform relative to the bone transform
  1096. matrix3x4_t matHitLocal;
  1097. MatrixInvert( matBoneToWorldTransform, matHitLocal );
  1098. MatrixMultiply( matHitLocal, matWorldSpaceBulletHit, matHitLocal );
  1099. Vector vecPosTemp;
  1100. QAngle angAngTemp;
  1101. MatrixAngles( matHitLocal, angAngTemp, vecPosTemp );
  1102. IGameEvent * bullet_hit_marker_event = gameeventmanager->CreateEvent( "add_bullet_hit_marker" );
  1103. if ( bullet_hit_marker_event )
  1104. {
  1105. Vector vecBHitStart = vecSrc - (vecUp * 4) + (vecRight * 5);
  1106. bullet_hit_marker_event->SetInt( "userid", playerMissed->GetUserID() );
  1107. bullet_hit_marker_event->SetInt( "bone", nBoneIndex );
  1108. bullet_hit_marker_event->SetFloat( "pos_x", vecPosTemp.x );
  1109. bullet_hit_marker_event->SetFloat( "pos_y", vecPosTemp.y );
  1110. bullet_hit_marker_event->SetFloat( "pos_z", vecPosTemp.z );
  1111. bullet_hit_marker_event->SetFloat( "ang_x", angAngTemp.x );
  1112. bullet_hit_marker_event->SetFloat( "ang_y", angAngTemp.y );
  1113. bullet_hit_marker_event->SetFloat( "ang_z", angAngTemp.z );
  1114. bullet_hit_marker_event->SetFloat( "start_x", vecBHitStart.x );
  1115. bullet_hit_marker_event->SetFloat( "start_y", vecBHitStart.y );
  1116. bullet_hit_marker_event->SetFloat( "start_z", vecBHitStart.z );
  1117. bullet_hit_marker_event->SetBool( "hit", false );
  1118. gameeventmanager->FireEvent( bullet_hit_marker_event );
  1119. }
  1120. }
  1121. }
  1122. }
  1123. #endif
  1124. if ( lastPlayerHit )
  1125. {
  1126. if ( lastPlayerHit->GetTeamNumber() == GetTeamNumber() )
  1127. {
  1128. bShotHitTeammate = true;
  1129. }
  1130. bBulletHitPlayer = true;
  1131. }
  1132. if ( tr.fraction == 1.0f )
  1133. break; // we didn't hit anything, stop tracing shoot
  1134. #ifdef CLIENT_DLL
  1135. if ( !bWallBangStarted && !bBulletHitPlayer )
  1136. {
  1137. vecWallBangHitStart = tr.endpos;
  1138. vecWallBangHitEnd = tr.endpos;
  1139. bWallBangStarted = true;
  1140. if ( fCurrentDamage > 20 )
  1141. bWallBangHeavyVersion = true;
  1142. }
  1143. else if ( !bWallBangEnded )
  1144. {
  1145. vecWallBangHitEnd = tr.endpos;
  1146. if ( bBulletHitPlayer )
  1147. bWallBangEnded = true;
  1148. }
  1149. #endif
  1150. #if defined( _DEBUG ) && !defined( CLIENT_DLL )
  1151. if ( bFirstHit )
  1152. AddBulletStat( gpGlobals->realtime, VectorLength( vecSrc-tr.endpos), tr.endpos );
  1153. #endif
  1154. #ifdef CLIENT_DLL
  1155. //// ACCURACY DEBUG INFO
  1156. //
  1157. if ( bFirstHit )
  1158. {
  1159. extern ConVar cl_weapon_debug_print_accuracy;
  1160. extern ConVar cl_weapon_debug_show_accuracy;
  1161. extern ConVar cl_weapon_debug_show_accuracy_duration;
  1162. if ( ( cl_weapon_debug_print_accuracy.GetBool( ) || cl_weapon_debug_show_accuracy.GetBool( ) ) && this->IsLocalPlayer( ) )
  1163. {
  1164. CWeaponCSBase *weapon = dynamic_cast< CWeaponCSBase * >( GetActiveWeapon( ) );
  1165. // const CCSWeaponInfo& weaponInfo = weapon->GetCSWpnData();
  1166. //Vector vecDirShooting, vecRight, vecUp;
  1167. // AngleVectors( GetFinalAimAngle(), &vecDirShooting, &vecRight, &vecUp );
  1168. // float flInaccuracyMove = weaponInfo.GetInaccuracyMove( weapon->m_weaponMode, weapon->GetEconItemView() );
  1169. // Vector vecDirInaccMove = vecDirShooting + flInaccuracyMove * vecRight + flInaccuracyMove * vecUp;
  1170. // VectorNormalize( vecDirInaccMove );
  1171. // float flDotInaccMove = DotProduct( vecDirShooting.Normalized(), vecDirInaccMove.Normalized() );
  1172. // float flAngleInaccMove = flDotInaccMove < 0.0f ? -acos( flDotInaccMove ) : acos( flDotInaccMove );
  1173. // Msg( "Movement Inaccuracy : %.2f deg.\n", RAD2DEG( flAngleInaccMove ) );
  1174. //
  1175. // float flInaccuracyStand = weaponInfo.GetInaccuracyStand( weapon->m_weaponMode, weapon->GetEconItemView() );
  1176. // Vector vecDirInaccStand = vecDirShooting + flInaccuracyStand * vecRight + flInaccuracyStand * vecUp;
  1177. // VectorNormalize( vecDirInaccStand );
  1178. // float flDotInaccStand = DotProduct( vecDirShooting.Normalized(), vecDirInaccStand.Normalized() );
  1179. // float flAngleInaccStand = flDotInaccStand < 0.0f ? -acos( flDotInaccStand ) : acos( flDotInaccStand );
  1180. // Msg( "Standing Inaccuracy : %.2f deg.\n", RAD2DEG( flAngleInaccStand ) );
  1181. //
  1182. // float flInaccuracyLadder = weaponInfo.GetInaccuracyLadder( weapon->m_weaponMode, weapon->GetEconItemView() );
  1183. // Vector vecDirInaccLadder = vecDirShooting + flInaccuracyLadder * vecRight + flInaccuracyLadder * vecUp;
  1184. // VectorNormalize( vecDirInaccLadder );
  1185. // float flDotInaccLadder = DotProduct( vecDirShooting.Normalized(), vecDirInaccLadder.Normalized() );
  1186. // float flAngleInaccLadder = flDotInaccLadder < 0.0f ? -acos( flDotInaccLadder ) : acos( flDotInaccLadder );
  1187. // Msg( "Ladder Inaccuracy : %.2f deg.\n", RAD2DEG( flAngleInaccLadder ) );
  1188. //
  1189. // float flInaccuracyFire = weaponInfo.GetInaccuracyFire( weapon->m_weaponMode, weapon->GetEconItemView() );
  1190. // Vector vecDirInaccFire = vecDirShooting + flInaccuracyFire * vecRight + flInaccuracyFire * vecUp;
  1191. // VectorNormalize( vecDirInaccFire );
  1192. // float flDotInaccFire = DotProduct( vecDirShooting.Normalized(), vecDirInaccFire.Normalized() );
  1193. // float flAngleInaccFire = flDotInaccFire < 0.0f ? -acos( flDotInaccFire ) : acos( flDotInaccFire );
  1194. // Msg( "Firing Inaccuracy : %.2f deg.\n", RAD2DEG( flAngleInaccFire ) );
  1195. //
  1196. float fInaccuracy = weapon->GetInaccuracy( );
  1197. // Vector vecDirInaccuracy = vecDirShooting + fInaccuracy * vecRight + fInaccuracy * vecUp;
  1198. // VectorNormalize( vecDirInaccuracy );
  1199. // float flDotInaccuracy = DotProduct( vecDirShooting.Normalized(), vecDirInaccuracy.Normalized() );
  1200. // float flAngleInaccuracy = flDotInaccuracy < 0.0f ? -acos( flDotInaccuracy ) : acos( flDotInaccuracy );
  1201. //
  1202. float fSpread = weapon->GetSpread( );
  1203. // Vector vecDirSpread = vecDirShooting + fSpread * vecRight + fSpread * vecUp;
  1204. // VectorNormalize( vecDirSpread );
  1205. // float flDotSpread = DotProduct( vecDirShooting.Normalized(), vecDirSpread.Normalized() );
  1206. // float flAngleSpread = flDotSpread < 0.0f ? -acos( flDotSpread ) : acos( flDotSpread );
  1207. // Msg( "Spread : %.2f deg.\n", RAD2DEG( flAngleSpread ) );
  1208. const float kAccurateRadius = 0.5f * 12; // 12 inch dinner plate
  1209. float fFinalInaccuracy = fInaccuracy + fSpread;
  1210. // Calculate effective range:
  1211. // ----| -
  1212. // ----- | ^
  1213. // ----- | |
  1214. // ----- | accurateRadius
  1215. // ----- | |
  1216. // ----| | | vecUp * (accurateradius / inaccuracy)
  1217. // ----- | vecUp * inaccuracy | v * inaccuracy
  1218. // ------------|------------------------| - = vecUp * accurateRadius
  1219. // vecDirShooting
  1220. // |<--- 1 --->|
  1221. // |<-------- effective range --------->| = accurateradius / inaccuracy
  1222. float flEffectiveRange = fFinalInaccuracy > 0.00001f ? (kAccurateRadius / fFinalInaccuracy) : 1000000.0f;
  1223. // float flAngleInaccFinal = atanf(fFinalInaccuracy);
  1224. //Msg( "Inaccuracy : %.2f deg.\n", RAD2DEG( flAngleInaccFinal ) );
  1225. if ( cl_weapon_debug_show_accuracy.GetInt( ) == 1 ) // head sized circle at effective range
  1226. {
  1227. NDebugOverlay::Line( vecSrc, vecSrc + ( flEffectiveRange * vecDirShooting.Normalized( ) ), 100, 100, 100, false, cl_weapon_debug_show_accuracy_duration.GetFloat( ) );
  1228. // shift the color deeper into the recoil sequence.
  1229. int nColorFromRecoilIndex = Max( 255 - ( (int)weapon->m_flRecoilIndex * 20 ), 0 );
  1230. NDebugOverlay::Circle( vecSrc + ( flEffectiveRange * vecDirShooting.Normalized( ) ), kAccurateRadius/*inches radius*/, 255 /*r*/, nColorFromRecoilIndex /*g*/, nColorFromRecoilIndex /*b*/, 255 /*a*/, false /*no depth test*/, cl_weapon_debug_show_accuracy_duration.GetFloat( ) /*duration*/ );
  1231. }
  1232. else if ( cl_weapon_debug_show_accuracy.GetInt( ) == 2 ) // size of inaccuracy on surface
  1233. {
  1234. int nColorFromRecoilIndex = Max( 255 - ( ( int )weapon->m_flRecoilIndex * 20 ), 0 );
  1235. // trace the shot as intended without inaccuracy
  1236. Vector vecEnd = vecSrc + vecDirShooting.Normalized( ) * flDistance;
  1237. trace_t tr_intended;
  1238. UTIL_TraceLine( vecSrc, vecEnd, CS_MASK_SHOOT|CONTENTS_HITBOX, this, COLLISION_GROUP_NONE, &tr_intended );
  1239. NDebugOverlay::Line( vecSrc, tr_intended.endpos, 100, 100, 100, false, cl_weapon_debug_show_accuracy_duration.GetFloat( ) );
  1240. if ( tr_intended.fraction != 1.0 )
  1241. {
  1242. float flRadius = fFinalInaccuracy * vecSrc.DistTo( tr_intended.endpos );
  1243. NDebugOverlay::Circle( tr_intended.endpos, flRadius /*inches radius*/, 255 /*r*/, nColorFromRecoilIndex /*g*/, nColorFromRecoilIndex /*b*/, 255 /*a*/, true /*no depth test*/, cl_weapon_debug_show_accuracy_duration.GetFloat( ) /*duration*/ );
  1244. }
  1245. }
  1246. // // trace actual shot
  1247. // Vector vecEndShot = vecSrc + vecDir * flDistance;
  1248. // trace_t tr_actual;
  1249. // UTIL_TraceLineIgnoreTwoEntities( vecSrc, vecEndShot, CS_MASK_SHOOT | CONTENTS_HITBOX, this, NULL, COLLISION_GROUP_NONE, &tr_actual );
  1250. // {
  1251. // CTraceFilterSkipTwoEntities filter( this, lastPlayerHit, COLLISION_GROUP_NONE );
  1252. //
  1253. // // Check for player hitboxes extending outside their collision bounds
  1254. // const float rayExtension = 40.0f;
  1255. // UTIL_ClipTraceToPlayers( vecSrc, vecEndShot + vecDir * rayExtension, CS_MASK_SHOOT | CONTENTS_HITBOX, &filter, &tr_actual );
  1256. // }
  1257. // float flDist_actual = ( tr_aim.fraction != 1.0 ) ? ( tr_actual.startpos - tr_actual.endpos ).Length() : 0;
  1258. // float flMissedDist = ( tr_actual.fraction != 1.0 && tr_aim.fraction != 1.0 ) ? ( tr_aim.endpos - tr_actual.endpos ).Length() : 0;
  1259. // float flDotShot = DotProduct( vecDirShooting.Normalized(), vecDir.Normalized() );
  1260. // float flAngleShot = flDotShot < 0.0f ? -acos( flDotShot ) : acos( flDotShot );
  1261. // Msg( "-- Shot info --\n" );
  1262. if ( cl_weapon_debug_print_accuracy.GetInt( ) == 1 )
  1263. {
  1264. Msg( "\nAccurate range: %.2fm", flEffectiveRange * 0.0254 );
  1265. if ( flDist_aim ) Msg( " Distance to target: %.2fm\n", flDist_aim * 0.0254 );
  1266. }
  1267. else if ( cl_weapon_debug_print_accuracy.GetInt( ) == 2 )
  1268. {
  1269. if ( weapon->m_flRecoilIndex == 0 )
  1270. {
  1271. Msg( "\ntime bullet range recovery inaccuracy\n" );
  1272. }
  1273. Msg( "%.5f %d %.5f %.10f %.10f\n", gpGlobals->curtime, (int)weapon->m_flRecoilIndex, flEffectiveRange * 0.0254, weapon->GetRecoveryTime( ), fFinalInaccuracy );
  1274. }
  1275. // if ( flDist_actual ) Msg( "Actual shot distance: %.0f\" (%.2f\')\n", flDist_actual, flDist_actual / 12.0 );
  1276. // if ( flMissedDist ) Msg( "Missed by: %.0f\" (%.2f\')\n", flMissedDist, flMissedDist / 12.0 );
  1277. // Msg( "\n" );
  1278. // Msg( "Nominal inaccuracy: %.2f degrees\n", RAD2DEG( flAngleInaccFinal ) );
  1279. // Msg( "Actual shot inaccuracy: %.2f degrees\n", RAD2DEG( flAngleShot ) );
  1280. }
  1281. }
  1282. #endif // CLIENT_DLL
  1283. bFirstHit = false;
  1284. #ifndef CLIENT_DLL
  1285. //
  1286. // Propogate a bullet impact event
  1287. // @todo Add this for shotgun pellets (which dont go thru here)
  1288. //
  1289. IGameEvent * event = gameeventmanager->CreateEvent( "bullet_impact" );
  1290. if ( event )
  1291. {
  1292. event->SetInt( "userid", GetUserID() );
  1293. event->SetFloat( "x", tr.endpos.x );
  1294. event->SetFloat( "y", tr.endpos.y );
  1295. event->SetFloat( "z", tr.endpos.z );
  1296. gameeventmanager->FireEvent( event );
  1297. }
  1298. #endif
  1299. FirePerfStatsEvent( PERF_STATS_BULLET ); // client-only event
  1300. /************* MATERIAL DETECTION ***********/
  1301. surfacedata_t *pSurfaceData = physprops->GetSurfaceData( tr.surface.surfaceProps );
  1302. int iEnterMaterial = pSurfaceData->game.material;
  1303. flPenetrationModifier = pSurfaceData->game.penetrationModifier;
  1304. flDamageModifier = pSurfaceData->game.damageModifier;
  1305. bool hitGrate = ( tr.contents & CONTENTS_GRATE ) != 0;
  1306. #ifdef CLIENT_DLL
  1307. if ( sv_showimpacts.GetInt() == 1 || sv_showimpacts.GetInt() == 2 )
  1308. {
  1309. // draw red client impact markers
  1310. debugoverlay->AddBoxOverlay( tr.endpos, Vector(-2,-2,-2), Vector(2,2,2), QAngle( 0, 0, 0), 255,0,0,127, sv_showimpacts_time.GetFloat() );
  1311. }
  1312. // bullet registration recording ( client )
  1313. // This code allowed us to measure discrepency between client and server bullet hits.
  1314. // It became obsolete when we started using a separate seed for client and server
  1315. // to eliminate 'rage' hacks.
  1316. //
  1317. if ( this == C_CSPlayer::GetLocalCSPlayer())
  1318. {
  1319. m_vecBulletVerifyListClient.AddToTail( clientHitVerify_t( tr.endpos, gpGlobals->realtime, gpGlobals->curtime + 10.0f ) );
  1320. }
  1321. #else
  1322. // bullet registration recording ( server )
  1323. // This code allowed us to measure discrepency between client and server bullet hits.
  1324. // It became obsolete when we started using a separate seed for client and server
  1325. // to eliminate 'rage' hacks.
  1326. //
  1327. CSingleUserRecipientFilter user( this );
  1328. CCSUsrMsg_ReportHit msg;
  1329. msg.set_pos_x( tr.endpos.x );
  1330. msg.set_pos_y( tr.endpos.y );
  1331. msg.set_pos_z( tr.endpos.z );
  1332. msg.set_timestamp( gpGlobals->realtime );
  1333. // only compare shots that were server hits
  1334. if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() )
  1335. {
  1336. SendUserMessage( user, CS_UM_ReportHit, msg );
  1337. }
  1338. // end bullet registration recording
  1339. if ( sv_showbullethits.GetInt() == 1 && tr.m_pEnt && tr.m_pEnt->IsPlayer() )
  1340. {
  1341. CCSPlayer *pPlayer = ToCSPlayer( tr.m_pEnt );
  1342. IGameEvent * bullet_hit_marker_event = gameeventmanager->CreateEvent( "add_bullet_hit_marker" );
  1343. if ( bullet_hit_marker_event )
  1344. {
  1345. Vector vecHitPos;
  1346. QAngle angHitAng;
  1347. int nBoneIndex;
  1348. pPlayer->GetBulletHitLocalBoneOffset( tr, nBoneIndex, vecHitPos, angHitAng );
  1349. Vector vecBHitStart = vecSrc - (vecUp * 4) + (vecRight * 5);
  1350. bullet_hit_marker_event->SetInt( "userid", pPlayer->GetUserID() );
  1351. bullet_hit_marker_event->SetInt( "bone", nBoneIndex );
  1352. bullet_hit_marker_event->SetFloat( "pos_x", vecHitPos.x );
  1353. bullet_hit_marker_event->SetFloat( "pos_y", vecHitPos.y );
  1354. bullet_hit_marker_event->SetFloat( "pos_z", vecHitPos.z );
  1355. bullet_hit_marker_event->SetFloat( "ang_x", angHitAng.x );
  1356. bullet_hit_marker_event->SetFloat( "ang_y", angHitAng.y );
  1357. bullet_hit_marker_event->SetFloat( "ang_z", angHitAng.z );
  1358. bullet_hit_marker_event->SetFloat( "start_x", vecBHitStart.x );
  1359. bullet_hit_marker_event->SetFloat( "start_y", vecBHitStart.y );
  1360. bullet_hit_marker_event->SetFloat( "start_z", vecBHitStart.z );
  1361. bullet_hit_marker_event->SetBool( "hit", true );
  1362. gameeventmanager->FireEvent( bullet_hit_marker_event );
  1363. }
  1364. }
  1365. if ( sv_showimpacts.GetInt() == 1 || sv_showimpacts.GetInt() == 3 )
  1366. {
  1367. // draw blue server impact markers
  1368. NDebugOverlay::Box( tr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,0,255,127, sv_showimpacts_time.GetFloat() );
  1369. }
  1370. #endif
  1371. // client-server hit comparison.
  1372. if ( tr.m_pEnt && tr.m_pEnt->IsPlayer() && !IsControllingBot() )
  1373. {
  1374. #ifndef CLIENT_DLL
  1375. if ( m_totalHitsOnServer < 255 ) // clamp at 8 bits
  1376. m_totalHitsOnServer++;
  1377. #else
  1378. if ( m_totalHitsOnClient < 255 ) // clamp at 8 bits
  1379. m_totalHitsOnClient++;
  1380. #endif
  1381. }
  1382. // draw green boxes where the shot originated from
  1383. //NDebugOverlay::Box( vecSrc, Vector(-1,-1,-1), Vector(1,1,1), 0,255,90,90, 10 );
  1384. //calculate the damage based on the distance the bullet travelled.
  1385. flCurrentDistance += tr.fraction * (flDistance-flCurrentDistance);
  1386. fCurrentDamage *= pow (flRangeModifier, (flCurrentDistance / 500));
  1387. #ifndef CLIENT_DLL
  1388. // the value of iPenetration when the round reached its max penetration distance
  1389. int nPenetrationAtMaxDistance = 0;
  1390. // save off how many penetrations this bullet had in case we reached max distance and stomp the value later
  1391. int const numPenetrationsInitiallyAllowedForThisBullet = nPenetrationCount;
  1392. #endif
  1393. // check if we reach penetration distance, no more penetrations after that
  1394. // or if our modifyer is super low, just stop the bullet
  1395. if ( (flCurrentDistance > flPenetrationDistance && flPenetration > 0 ) ||
  1396. flPenetrationModifier < 0.1 )
  1397. {
  1398. #ifndef CLIENT_DLL
  1399. nPenetrationAtMaxDistance = 0;
  1400. #endif
  1401. // Setting nPenetrationCount to zero prevents the bullet from penetrating object at max distance
  1402. // and will no longer trace beyond the exit point, however "numPenetrationsInitiallyAllowedForThisBullet"
  1403. // is saved off to allow correct determination whether the hit on the object at max distance had
  1404. // *previously* penetrated anything or not. In case of a direct hit over 3000 units the saved off
  1405. // value would be max penetrations value and will determine a direct hit and not a penetration hit.
  1406. // However it is important that all tracing further stops past this point (as the code does at
  1407. // the time of writing) because otherwise next trace will think that 4 penetrations have already
  1408. // occurred.
  1409. nPenetrationCount = 0;
  1410. }
  1411. #ifndef CLIENT_DLL
  1412. // This just keeps track of sounds for AIs (it doesn't play anything).
  1413. CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, tr.endpos, 400, 0.2f, this );
  1414. #endif
  1415. int iDamageType = DMG_BULLET | DMG_NEVERGIB;
  1416. CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon();
  1417. if ( pActiveWeapon && pActiveWeapon->IsA( WEAPON_TASER ) )
  1418. {
  1419. iDamageType = DMG_SHOCK | DMG_NEVERGIB;
  1420. }
  1421. if( bDoEffects )
  1422. {
  1423. // See if the bullet ended up underwater + started out of the water
  1424. if ( enginetrace->GetPointContents( tr.endpos, MASK_WATER ) & (CONTENTS_WATER|CONTENTS_SLIME) )
  1425. {
  1426. trace_t waterTrace;
  1427. UTIL_TraceLine( vecSrc, tr.endpos, (MASK_SHOT|CONTENTS_WATER|CONTENTS_SLIME), this, COLLISION_GROUP_NONE, &waterTrace );
  1428. if( waterTrace.allsolid != 1 )
  1429. {
  1430. CEffectData data;
  1431. data.m_vOrigin = waterTrace.endpos;
  1432. data.m_vNormal = waterTrace.plane.normal;
  1433. data.m_flScale = random->RandomFloat( 8, 12 );
  1434. if ( waterTrace.contents & CONTENTS_SLIME )
  1435. {
  1436. data.m_fFlags |= FX_WATER_IN_SLIME;
  1437. }
  1438. DispatchEffect( "gunshotsplash", data );
  1439. }
  1440. }
  1441. else
  1442. {
  1443. //Do Regular hit effects
  1444. // Don't decal nodraw surfaces
  1445. if ( !( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) ) )
  1446. {
  1447. //CBaseEntity *pEntity = tr.m_pEnt;
  1448. UTIL_ImpactTrace( &tr, iDamageType );
  1449. }
  1450. }
  1451. }
  1452. #ifndef CLIENT_DLL
  1453. // decal players on the server to eliminate the disparity between where the client thinks the decal went and where it actually went
  1454. // we want to eliminate the case where a player sees a blood decal on someone, but they are at 100 health
  1455. if ( sv_server_verify_blood_on_player.GetBool() && tr.DidHit() && tr.m_pEnt && tr.m_pEnt->IsPlayer() )
  1456. {
  1457. UTIL_ImpactTrace( &tr, iDamageType );
  1458. }
  1459. #endif
  1460. #ifdef CLIENT_DLL
  1461. // create the tracer
  1462. CreateWeaponTracer( vecSrc, tr.endpos );
  1463. #endif
  1464. // add damage to entity that we hit
  1465. #ifndef CLIENT_DLL
  1466. CBaseEntity *pEntity = tr.m_pEnt;
  1467. //
  1468. // DAMAGE MUST BE DEFERRED TILL LATER IF WE DECIDE TO SHIP IT
  1469. //
  1470. // if ( sv_shoot_dropped_grenades.GetBool() )
  1471. // {
  1472. // CBaseCSGrenade* pWeapon = dynamic_cast<CBaseCSGrenade*>( pEntity );
  1473. //Only detonate shot grenades if they have been dropped in the world longer than the grace period.
  1474. //This prevents shooting at players and they miraculously explode - because you shot their grenade the instant they died
  1475. // if ( pWeapon && gpGlobals->curtime > (pWeapon->m_flDroppedAtTime + sv_shoot_dropped_grenades_grace_time.GetFloat()) )
  1476. // {
  1477. // pWeapon->ShotDetonate( this, pWeapon->GetCSWpnData() );
  1478. // pWeapon->AddSolidFlags( FSOLID_NOT_SOLID );
  1479. // pWeapon->AddEffects( EF_NODRAW );
  1480. // UTIL_Remove( pWeapon );
  1481. // }
  1482. // }
  1483. // [pfreese] Check if enemy players were killed by this bullet, and if so,
  1484. // add them to the iPenetrationKills count
  1485. DelayedDamageInfoData_t &delayedDamage = arrPendingDamage.Element( arrPendingDamage.AddToTail() );
  1486. delayedDamage.m_tr = tr;
  1487. int nObjectsPenetrated = kMaxNumPenetrationsSupported - ( numPenetrationsInitiallyAllowedForThisBullet + nPenetrationAtMaxDistance );
  1488. CTakeDamageInfo &info = delayedDamage.m_info;
  1489. info.Set( pevAttacker, pevAttacker, GetActiveWeapon(), fCurrentDamage, iDamageType, 0, nObjectsPenetrated );
  1490. // [dkorus] note: This is the number of players hit up to this point, not the total number this bullet WILL hit.
  1491. info.SetDamagedOtherPlayers( numPlayersHit );
  1492. // Set the bullet ID so that we can later track all the enemies that are damage by the same bullet
  1493. info.SetBulletID( GetBulletGroup(), pActiveWeapon ? (int)pActiveWeapon->m_flRecoilIndex : 0 );
  1494. info.SetAmmoType( iBulletType );
  1495. CalculateBulletDamageForce( &info, iBulletType, vecDir, tr.endpos );
  1496. bool bWasAlive = pEntity->IsAlive();
  1497. // === Damage applied later ===
  1498. if ( bWasAlive && pEntity->IsPlayer() && IsOtherEnemy( pEntity->entindex() ) )
  1499. {
  1500. numPlayersHit++;
  1501. }
  1502. if ( sv_showimpacts_penetration.GetInt() > 0 )
  1503. {
  1504. char text[4];
  1505. Q_snprintf( text, sizeof( text ), "^" );
  1506. char text2[32];
  1507. Q_snprintf( text2, sizeof( text2 ), "%s%d", ( sv_showimpacts_penetration.GetInt() == 2 ) ? "" : "DAMAGE APPLIED: ", (int)ceil(fCurrentDamage) );
  1508. char text3[32];
  1509. // convert to meters
  1510. //(100%% of shots will fall within a 30cm circle.)
  1511. float flDistMeters = ( flCurrentDistance*0.0254 );
  1512. if ( flDistMeters >= 1.0 )
  1513. Q_snprintf( text3, sizeof( text3 ), "%s%0.1fm", ( sv_showimpacts_penetration.GetInt() == 2 ) ? "" : "TOTAL DISTANCE: ", flDistMeters );
  1514. else
  1515. Q_snprintf( text3, sizeof( text3 ), "%s%0.1fcm", ( sv_showimpacts_penetration.GetInt() == 2 ) ? "" : "TOTAL DISTANCE: ", flDistMeters/0.01 );
  1516. Vector textPos = tr.endpos;
  1517. NDebugOverlay::EntityTextAtPosition( textPos, 1, text, sv_showimpacts_time.GetFloat(), 225, 128, 64, 255 );
  1518. NDebugOverlay::EntityTextAtPosition( textPos, 2, text2, sv_showimpacts_time.GetFloat(), 255, 64, 0, 255 );
  1519. NDebugOverlay::EntityTextAtPosition( textPos, 3, text3, sv_showimpacts_time.GetFloat(), 255, 128, 64, 255 );
  1520. NDebugOverlay::Box( tr.endpos, Vector( -0.8, -0.8, -0.8 ), Vector( 0.8, 0.8, 0.8 ), 255, 100, 50, 64, sv_showimpacts_time.GetFloat() );
  1521. }
  1522. #endif
  1523. // [dkorus] note: values are changed inside of HandleBulletPenetration
  1524. bool bulletStopped = HandleBulletPenetration( flPenetration, iEnterMaterial, hitGrate, tr, vecDir, pSurfaceData, flPenetrationModifier,
  1525. flDamageModifier, bDoEffects, iDamageType, flPenetrationPower, nPenetrationCount, vecSrc, flDistance,
  1526. flCurrentDistance, fCurrentDamage );
  1527. // [dkorus] bulletStopped is true if the bullet can no longer continue penetrating materials
  1528. if ( bulletStopped )
  1529. break;
  1530. }
  1531. #ifndef CLIENT_DLL
  1532. if ( bBulletHitPlayer && !bShotHitTeammate )
  1533. { // Guarantee that the bullet that hit an enemy trumps the player viewangles
  1534. // that are locked in for the duration of the server simulation ticks
  1535. m_iLockViewanglesTickNumber = gpGlobals->tickcount;
  1536. m_qangLockViewangles = pl.v_angle;
  1537. }
  1538. #endif
  1539. #ifndef CLIENT_DLL
  1540. FOR_EACH_VEC( arrPendingDamage, idxDamage )
  1541. {
  1542. ClearMultiDamage();
  1543. CTakeDamageInfo &info = arrPendingDamage[idxDamage].m_info;
  1544. trace_t &tr = arrPendingDamage[idxDamage].m_tr;
  1545. CBaseEntity *pEntity = tr.m_pEnt;
  1546. bool bWasAlive = pEntity->IsAlive();
  1547. pEntity->DispatchTraceAttack( info, vecDir, &tr );
  1548. TraceAttackToTriggers( info, tr.startpos, tr.endpos, vecDir );
  1549. ApplyMultiDamage();
  1550. if ( bWasAlive && !pEntity->IsAlive() && pEntity->IsPlayer() && IsOtherEnemy( pEntity->entindex() ) )
  1551. {
  1552. ++iPenetrationKills;
  1553. }
  1554. }
  1555. #endif
  1556. #ifdef CLIENT_DLL
  1557. if ( bWallBangStarted )
  1558. {
  1559. float flWallBangLength = (vecWallBangHitEnd - vecWallBangHitStart).Length();
  1560. if ( flWallBangLength > 0 && flWallBangLength < CS_MAX_WALLBANG_TRAIL_LENGTH )
  1561. {
  1562. QAngle temp;
  1563. VectorAngles( vecWallBangHitEnd - vecWallBangHitStart, temp );
  1564. CEffectData data;
  1565. data.m_vOrigin = vecWallBangHitStart;
  1566. data.m_vStart = vecWallBangHitEnd;
  1567. data.m_vAngles = temp;
  1568. //data.m_vNormal = vecWallBangHitStart - vecWallBangHitEnd;
  1569. data.m_flScale = 1.0f;
  1570. //why is particle system index stored on m_nHitBox?
  1571. if ( bWallBangHeavyVersion )
  1572. {
  1573. data.m_nHitBox = GetParticleSystemIndex( "impact_wallbang_heavy" );
  1574. }
  1575. else
  1576. {
  1577. data.m_nHitBox = GetParticleSystemIndex( "impact_wallbang_light" );
  1578. }
  1579. StartParticleEffect( data );
  1580. //debugoverlay->AddLineOverlay( vecWallBangHitStart, vecWallBangHitEnd, 0, 255, 0, false, 3 );
  1581. }
  1582. }
  1583. #endif
  1584. #ifndef CLIENT_DLL
  1585. // [pfreese] If we killed at least two enemies with a single bullet, award the
  1586. // TWO_WITH_ONE_SHOT achievement
  1587. if ( iPenetrationKills >= 2 )
  1588. {
  1589. AwardAchievement( CSKillTwoWithOneShot );
  1590. }
  1591. #endif
  1592. }
  1593. // [dkorus] helper for FireBullet
  1594. // changes iPenetration to updated value
  1595. // returns TRUE if we should stop processing more hits after this one
  1596. // returns FALSE if we can continue processing
  1597. bool CCSPlayer::HandleBulletPenetration( float &flPenetration,
  1598. int &iEnterMaterial,
  1599. bool &hitGrate,
  1600. trace_t &tr,
  1601. Vector &vecDir,
  1602. surfacedata_t *pSurfaceData,
  1603. float flPenetrationModifier,
  1604. float flDamageModifier,
  1605. bool bDoEffects,
  1606. int iDamageType,
  1607. float flPenetrationPower,
  1608. int &nPenetrationCount,
  1609. Vector &vecSrc,
  1610. float flDistance,
  1611. float flCurrentDistance,
  1612. float &fCurrentDamage)
  1613. {
  1614. bool bIsNodraw = !!( tr.surface.flags & (SURF_NODRAW) );
  1615. bool bFailedPenetrate = false;
  1616. // check if bullet can penetrarte another entity
  1617. if ( nPenetrationCount == 0 && !hitGrate && !bIsNodraw
  1618. && iEnterMaterial != CHAR_TEX_GLASS && iEnterMaterial != CHAR_TEX_GRATE )
  1619. bFailedPenetrate = true; // no, stop
  1620. // If we hit a grate with iPenetration == 0, stop on the next thing we hit
  1621. if ( flPenetration <= 0 || nPenetrationCount <= 0 )
  1622. bFailedPenetrate = true;
  1623. Vector penetrationEnd;
  1624. // find exact penetration exit
  1625. trace_t exitTr;
  1626. if ( !TraceToExit( tr.endpos, vecDir, penetrationEnd, tr, exitTr, 4, MAX_PENETRATION_DISTANCE ) )
  1627. {
  1628. // ended in solid
  1629. if ( (UTIL_PointContents ( tr.endpos, CS_MASK_SHOOT ) & CS_MASK_SHOOT) == 0 )
  1630. {
  1631. bFailedPenetrate = true;
  1632. }
  1633. }
  1634. if ( bFailedPenetrate == true )
  1635. {
  1636. float flTraceDistance = VectorLength( penetrationEnd - tr.endpos );
  1637. // this is copy pasted from below, it should probably be its own function
  1638. float flPenMod = MAX( 0, ( 1 / flPenetrationModifier ) );
  1639. float flPercentDamageChunk = fCurrentDamage * 0.15;
  1640. float flDamageLostImpact = flPercentDamageChunk + MAX( 0, ( 3/ flPenetrationPower ) * 1.18 ) * (flPenMod * 2.8);
  1641. float flLostDamageObject = ( ( flPenMod * ( flTraceDistance*flTraceDistance ) ) / 24 );
  1642. float flTotalLostDamage = flDamageLostImpact + flLostDamageObject;
  1643. DisplayPenetrationDebug( tr.endpos, penetrationEnd, flTraceDistance, fCurrentDamage, flDamageLostImpact, flTotalLostDamage, tr.surface.surfaceProps, -100 );
  1644. return true;
  1645. }
  1646. //debugoverlay->AddBoxOverlay( exitTr.endpos, Vector(-1,-1,-1), Vector(1,1,1), QAngle( 0, 0, 0), 255,255,0,127, 400 );
  1647. // get material at exit point
  1648. surfacedata_t *pExitSurfaceData = physprops->GetSurfaceData( exitTr.surface.surfaceProps );
  1649. int iExitMaterial = pExitSurfaceData->game.material;
  1650. // new penetration method
  1651. if ( sv_penetration_type.GetInt() == 1 )
  1652. {
  1653. // percent of total damage lost automatically on impacting a surface
  1654. float flDamLostPercent = 0.16;
  1655. // since some railings in de_inferno are CONTENTS_GRATE but CHAR_TEX_CONCRETE, we'll trust the
  1656. // CONTENTS_GRATE and use a high damage modifier.
  1657. if ( hitGrate || bIsNodraw || iEnterMaterial == CHAR_TEX_GLASS || iEnterMaterial == CHAR_TEX_GRATE )
  1658. {
  1659. // If we're a concrete grate (TOOLS/TOOLSINVISIBLE texture) allow more penetrating power.
  1660. if ( iEnterMaterial == CHAR_TEX_GLASS || iEnterMaterial == CHAR_TEX_GRATE )
  1661. {
  1662. flPenetrationModifier = 3.0f;
  1663. flDamLostPercent = 0.05;
  1664. }
  1665. else
  1666. flPenetrationModifier = 1.0f;
  1667. flDamageModifier = 0.99f;
  1668. }
  1669. else if ( iEnterMaterial == CHAR_TEX_FLESH && ff_damage_reduction_bullets.GetFloat() == 0
  1670. && tr.m_pEnt && tr.m_pEnt->IsPlayer() && tr.m_pEnt->GetTeamNumber() == GetTeamNumber() )
  1671. {
  1672. if ( ff_damage_bullet_penetration.GetFloat() == 0 )
  1673. {
  1674. // don't allow penetrating players when FF is off
  1675. flPenetrationModifier = 0;
  1676. return true;
  1677. }
  1678. flPenetrationModifier = ff_damage_bullet_penetration.GetFloat();
  1679. flDamageModifier = ff_damage_bullet_penetration.GetFloat();
  1680. }
  1681. else
  1682. {
  1683. // check the exit material and average the exit and entrace values
  1684. float flExitPenetrationModifier = pExitSurfaceData->game.penetrationModifier;
  1685. float flExitDamageModifier = pExitSurfaceData->game.damageModifier;
  1686. flPenetrationModifier = (flPenetrationModifier + flExitPenetrationModifier)/2;
  1687. flDamageModifier = (flDamageModifier + flExitDamageModifier)/2;
  1688. }
  1689. // if enter & exit point is wood we assume this is
  1690. // a hollow crate and give a penetration bonus
  1691. if ( iEnterMaterial == iExitMaterial )
  1692. {
  1693. if( iExitMaterial == CHAR_TEX_WOOD || iExitMaterial == CHAR_TEX_CARDBOARD )
  1694. {
  1695. flPenetrationModifier = 3;
  1696. }
  1697. else if ( iExitMaterial == CHAR_TEX_PLASTIC )
  1698. {
  1699. flPenetrationModifier = 2;
  1700. }
  1701. }
  1702. float flTraceDistance = VectorLength( exitTr.endpos - tr.endpos );
  1703. float flPenMod = MAX( 0, ( 1 / flPenetrationModifier ));
  1704. float flPercentDamageChunk = fCurrentDamage * flDamLostPercent;
  1705. float flPenWepMod = flPercentDamageChunk + MAX( 0, ( 3/ flPenetrationPower ) * 1.25 ) * (flPenMod * 3.0);
  1706. float flLostDamageObject = ((flPenMod * (flTraceDistance*flTraceDistance)) / 24);
  1707. float flTotalLostDamage = flPenWepMod + flLostDamageObject;
  1708. if ( sv_showimpacts_penetration.GetInt() > 0 )
  1709. {
  1710. Vector vecStart = tr.endpos;
  1711. Vector vecEnd = penetrationEnd;
  1712. float flTotalTraceDistance = VectorLength( penetrationEnd - tr.endpos );
  1713. DisplayPenetrationDebug( vecStart, vecEnd, flTotalTraceDistance, fCurrentDamage, flPenWepMod, flTotalLostDamage, tr.surface.surfaceProps, exitTr.surface.surfaceProps );
  1714. }
  1715. // reduce damage power each time we hit something other than a grate
  1716. fCurrentDamage -= MAX( 0, flTotalLostDamage );
  1717. if ( fCurrentDamage < 1 )
  1718. return true;
  1719. // penetration was successful
  1720. // bullet did penetrate object, exit Decal
  1721. if ( bDoEffects )
  1722. {
  1723. UTIL_ImpactTrace( &exitTr, iDamageType );
  1724. }
  1725. #ifndef CLIENT_DLL
  1726. // decal players on the server to eliminate the disparity between where the client thinks the decal went and where it actually went
  1727. // we want to eliminate the case where a player sees a blood decal on someone, but they are at 100 health
  1728. if ( sv_server_verify_blood_on_player.GetBool() && tr.DidHit() && tr.m_pEnt && tr.m_pEnt->IsPlayer() )
  1729. {
  1730. UTIL_ImpactTrace( &tr, iDamageType );
  1731. }
  1732. #endif
  1733. //setup new start end parameters for successive trace
  1734. //flPenetrationPower -= (flTraceDistance/2) / flPenMod;
  1735. flCurrentDistance += flTraceDistance;
  1736. // NDebugOverlay::Box( exitTr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,127, 8 );
  1737. vecSrc = exitTr.endpos;
  1738. flDistance = (flDistance - flCurrentDistance) * 0.5;
  1739. nPenetrationCount--;
  1740. return false;
  1741. }
  1742. else
  1743. {
  1744. // since some railings in de_inferno are CONTENTS_GRATE but CHAR_TEX_CONCRETE, we'll trust the
  1745. // CONTENTS_GRATE and use a high damage modifier.
  1746. if ( hitGrate || bIsNodraw )
  1747. {
  1748. // If we're a concrete grate (TOOLS/TOOLSINVISIBLE texture) allow more penetrating power.
  1749. flPenetrationModifier = 1.0f;
  1750. flDamageModifier = 0.99f;
  1751. }
  1752. else
  1753. {
  1754. // Check the exit material to see if it is has less penetration than the entrance material.
  1755. float flExitPenetrationModifier = pExitSurfaceData->game.penetrationModifier;
  1756. float flExitDamageModifier = pExitSurfaceData->game.damageModifier;
  1757. if ( flExitPenetrationModifier < flPenetrationModifier )
  1758. {
  1759. flPenetrationModifier = flExitPenetrationModifier;
  1760. }
  1761. if ( flExitDamageModifier < flDamageModifier )
  1762. {
  1763. flDamageModifier = flExitDamageModifier;
  1764. }
  1765. }
  1766. // if enter & exit point is wood or metal we assume this is
  1767. // a hollow crate or barrel and give a penetration bonus
  1768. if ( iEnterMaterial == iExitMaterial )
  1769. {
  1770. if ( iExitMaterial == CHAR_TEX_WOOD ||
  1771. iExitMaterial == CHAR_TEX_METAL )
  1772. {
  1773. flPenetrationModifier *= 2;
  1774. }
  1775. }
  1776. float flTraceDistance = VectorLength( exitTr.endpos - tr.endpos );
  1777. // check if bullet has enough power to penetrate this distance for this material
  1778. if ( flTraceDistance > ( flPenetrationPower * flPenetrationModifier ) )
  1779. return true; // bullet hasn't enough power to penetrate this distance
  1780. // reduce damage power each time we hit something other than a grate
  1781. fCurrentDamage *= flDamageModifier;
  1782. // penetration was successful
  1783. // bullet did penetrate object, exit Decal
  1784. if ( bDoEffects )
  1785. {
  1786. UTIL_ImpactTrace( &exitTr, iDamageType );
  1787. }
  1788. #ifndef CLIENT_DLL
  1789. // decal players on the server to eliminate the disparity between where the client thinks the decal went and where it actually went
  1790. // we want to eliminate the case where a player sees a blood decal on someone, but they are at 100 health
  1791. if ( sv_server_verify_blood_on_player.GetBool() && tr.DidHit() && tr.m_pEnt && tr.m_pEnt->IsPlayer() )
  1792. {
  1793. UTIL_ImpactTrace( &tr, iDamageType );
  1794. }
  1795. #endif
  1796. //setup new start end parameters for successive trace
  1797. flPenetrationPower -= flTraceDistance / flPenetrationModifier;
  1798. flCurrentDistance += flTraceDistance;
  1799. // NDebugOverlay::Box( exitTr.endpos, Vector(-2,-2,-2), Vector(2,2,2), 0,255,0,127, 8 );
  1800. vecSrc = exitTr.endpos;
  1801. flDistance = ( flDistance - flCurrentDistance ) * 0.5;
  1802. // reduce penetration counter
  1803. nPenetrationCount--;
  1804. return false;
  1805. }
  1806. }
  1807. void CCSPlayer::DisplayPenetrationDebug( Vector vecEnter, Vector vecExit, float flDistance, float flInitialDamage, float flDamageLostImpact, float flTotalLostDamage, short nEnterSurf, short nExitSurf )
  1808. {
  1809. #ifndef CLIENT_DLL
  1810. if ( sv_showimpacts_penetration.GetInt() > 0 )
  1811. {
  1812. Vector vecStart = vecEnter;
  1813. Vector vecEnd = vecExit;
  1814. float flTotalTraceDistance = VectorLength( vecExit - vecEnter );
  1815. //float flEnd = flTotalTraceDistance;
  1816. //short nExitSurf = exitTr.surface.surfaceProps;
  1817. if ( flTotalLostDamage >= flInitialDamage )
  1818. {
  1819. nExitSurf = -100;
  1820. float flLostDamageObject = (flTotalLostDamage - flDamageLostImpact) ;
  1821. //float flLostLinear = sqrt(flDistance * (flLostDamageObject)) * 24;
  1822. float flFrac = MAX( 0, ( flInitialDamage - flDamageLostImpact ) / flLostDamageObject );
  1823. vecEnd = ( vecEnd - vecStart );
  1824. VectorNormalize( vecEnd );
  1825. vecEnd = vecStart + ( vecEnd*flTotalTraceDistance*flFrac );
  1826. if ( flDamageLostImpact >= flInitialDamage )
  1827. {
  1828. flDistance = 0;
  1829. vecStart = vecEnd;
  1830. }
  1831. flTotalLostDamage = ( int )ceil( flInitialDamage );
  1832. }
  1833. Vector textPos = vecEnd;
  1834. char text[64];
  1835. if ( flTotalLostDamage < flInitialDamage )
  1836. {
  1837. float flDistMeters = ( flDistance*0.0254 );
  1838. if ( flDistMeters >= 1.0 )
  1839. Q_snprintf( text, sizeof( text ), "%s%0.1fm", ( sv_showimpacts_penetration.GetInt() == 2 ) ? "" : "THICKNESS: ", flDistMeters );
  1840. else
  1841. Q_snprintf( text, sizeof( text ), "%s%0.1fcm", ( sv_showimpacts_penetration.GetInt() == 2 ) ? "" : "THICKNESS: ", flDistMeters / 0.01 );
  1842. }
  1843. else
  1844. {
  1845. Q_snprintf( text, sizeof( text ), "%s", "STOPPED!" );
  1846. }
  1847. NDebugOverlay::EntityTextAtPosition( textPos, -3, text, sv_showimpacts_time.GetFloat(), 220, 128, 128, 255 );
  1848. char text3[64];
  1849. Q_snprintf( text3, sizeof( text3 ), "%s%0.1f", ( sv_showimpacts_penetration.GetInt() == 2 ) ? "-" : "LOST DAMAGE: ", flTotalLostDamage );
  1850. NDebugOverlay::EntityTextAtPosition( textPos, -2, text3, sv_showimpacts_time.GetFloat(), 90, 22, 0, 160 );
  1851. char textmat1[64];
  1852. Q_snprintf( textmat1, sizeof( textmat1 ), "%s", physprops->GetPropName( nEnterSurf ) );
  1853. NDebugOverlay::EntityTextAtPosition( vecStart, -1, textmat1, sv_showimpacts_time.GetFloat(), 0, 255, 0, 128 );
  1854. if ( nExitSurf != -100 )
  1855. {
  1856. NDebugOverlay::Box( vecStart, Vector( -0.4, -0.4, -0.4 ), Vector( 0.4, 0.4, 0.4 ), 0, 255, 0, 128, sv_showimpacts_time.GetFloat() );
  1857. char textmat2[64];
  1858. Q_snprintf( textmat2, sizeof( textmat2 ), "%s", ( nExitSurf == -1 ) ? "" : physprops->GetPropName( nExitSurf ) );
  1859. NDebugOverlay::Box( vecEnd, Vector( -0.4, -0.4, -0.4 ), Vector( 0.4, 0.4, 0.4 ), 0, 128, 255, 128, sv_showimpacts_time.GetFloat() );
  1860. NDebugOverlay::EntityTextAtPosition( vecEnd, -1, textmat2, sv_showimpacts_time.GetFloat(), 0, 128, 255, 128 );
  1861. if ( flDistance > 0 && vecStart != vecEnd )
  1862. NDebugOverlay::Line( vecStart, vecEnd, 0, 190, 190, true, sv_showimpacts_time.GetFloat() );
  1863. }
  1864. else
  1865. {
  1866. // different color
  1867. NDebugOverlay::Box( vecStart, Vector( -0.4, -0.4, -0.4 ), Vector( 0.4, 0.4, 0.4 ), 160, 255, 0, 128, sv_showimpacts_time.GetFloat() );
  1868. NDebugOverlay::Line( vecStart, vecEnd, 190, 190, 0, true, sv_showimpacts_time.GetFloat() );
  1869. }
  1870. }
  1871. #endif
  1872. }
  1873. void CCSPlayer::ImpactTrace( trace_t *pTrace, int iDamageType, char *pCustomImpactName )
  1874. {
  1875. #ifdef CLIENT_DLL
  1876. if ( sv_server_verify_blood_on_player.GetBool() )
  1877. return;
  1878. #endif
  1879. static ConVar *violence_hblood = cvar->FindVar( "violence_hblood" );
  1880. if ( violence_hblood && !violence_hblood->GetBool() )
  1881. return;
  1882. VPROF( "CCSPlayer::ImpactTrace" );
  1883. Assert( pTrace->m_pEnt );
  1884. CBaseEntity *pEntity = pTrace->m_pEnt;
  1885. // Build the impact data
  1886. CEffectData data;
  1887. data.m_vOrigin = pTrace->endpos;
  1888. data.m_vStart = pTrace->startpos;
  1889. data.m_nSurfaceProp = pTrace->surface.surfaceProps;
  1890. if ( data.m_nSurfaceProp < 0 )
  1891. {
  1892. data.m_nSurfaceProp = 0;
  1893. }
  1894. data.m_nDamageType = iDamageType;
  1895. data.m_nHitBox = pTrace->hitbox;
  1896. #ifdef CLIENT_DLL
  1897. data.m_hEntity = ClientEntityList().EntIndexToHandle( pEntity->entindex() );
  1898. #else
  1899. data.m_nEntIndex = pEntity->entindex();
  1900. data.m_fFlags |= EFFECTDATA_SERVER_IGNOREPREDICTIONCULL;
  1901. if ( sv_server_verify_blood_on_player.GetBool() )
  1902. {
  1903. data.m_vOrigin -= GetAbsOrigin();
  1904. data.m_vStart -= GetAbsOrigin();
  1905. data.m_bPositionsAreRelativeToEntity = true;
  1906. }
  1907. #endif
  1908. // Send it on its way
  1909. if ( !pCustomImpactName )
  1910. {
  1911. DispatchEffect( "Impact", data );
  1912. }
  1913. else
  1914. {
  1915. DispatchEffect( pCustomImpactName, data );
  1916. }
  1917. }
  1918. #ifdef CLIENT_DLL
  1919. void TE_DynamicLight( IRecipientFilter& filter, float delay,
  1920. const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay, int nLightIndex = LIGHT_INDEX_TE_DYNAMIC );
  1921. void CCSPlayer::CreateWeaponTracer( Vector vecStart, Vector vecEnd )
  1922. {
  1923. int iTracerFreq = 1;
  1924. C_WeaponCSBase *pWeapon = GetActiveCSWeapon();
  1925. if ( pWeapon )
  1926. {
  1927. // if this is a local player, start at attachment on view model
  1928. // else start on attachment on weapon model
  1929. int iEntIndex = entindex();
  1930. int iUseAttachment = TRACER_DONT_USE_ATTACHMENT;
  1931. int iAttachment = 1;
  1932. C_CSPlayer *pLocalPlayer = NULL;
  1933. bool bUseObserverTarget = false;
  1934. FOR_EACH_VALID_SPLITSCREEN_PLAYER( hh )
  1935. {
  1936. ACTIVE_SPLITSCREEN_PLAYER_GUARD( hh );
  1937. pLocalPlayer = C_CSPlayer::GetLocalCSPlayer();
  1938. if ( !pLocalPlayer )
  1939. continue;
  1940. if ( pLocalPlayer->GetObserverTarget() == this &&
  1941. pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE &&
  1942. !pLocalPlayer->IsInObserverInterpolation() )
  1943. {
  1944. bUseObserverTarget = true;
  1945. break;
  1946. }
  1947. }
  1948. C_BaseCombatWeapon *pActiveWeapon = GetActiveWeapon();
  1949. C_BaseViewModel *pViewModel = GetViewModel(0);
  1950. CBaseWeaponWorldModel *pWeaponWorldModel = NULL;
  1951. if ( pActiveWeapon && ( !pViewModel || this->ShouldDraw() ) )
  1952. pWeaponWorldModel = pActiveWeapon->GetWeaponWorldModel();
  1953. if ( pWeaponWorldModel && pWeaponWorldModel->HasDormantOwner() )
  1954. {
  1955. // This is likely a player firing from around a corner, where this client can't see them.
  1956. // Don't modify the tracer start position, since our local world weapon model position is not reliable.
  1957. }
  1958. else if (pWeaponWorldModel)
  1959. {
  1960. iAttachment = pWeaponWorldModel->LookupAttachment( "muzzle_flash" );
  1961. if ( iAttachment > 0 )
  1962. pWeaponWorldModel->GetAttachment( iAttachment, vecStart );
  1963. }
  1964. else if ( pViewModel )
  1965. {
  1966. iAttachment = pViewModel->LookupAttachment( "1" );
  1967. pViewModel->GetAttachment( iAttachment, vecStart );
  1968. }
  1969. // bail if we're at the origin
  1970. if ( vecStart.LengthSqr() <= 0 )
  1971. return;
  1972. // muzzle flash dynamic light
  1973. m_GlowObject.SetGlowAlphaPulseOverdrive( 0.3f );
  1974. CPVSFilter filter( vecStart );
  1975. TE_DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 5, 70, 0.05, 768 );
  1976. int nBulletNumber = (pWeapon->GetMaxClip1() - pWeapon->Clip1()) + 1;
  1977. iTracerFreq = pWeapon->GetCSWpnData().GetTracerFrequency( pWeapon->GetEconItemView(), pWeapon->m_weaponMode );
  1978. if ( ( iTracerFreq != 0 ) && ( nBulletNumber % iTracerFreq ) == 0 )
  1979. {
  1980. const char *pszTracerEffect = GetTracerType();
  1981. if ( pszTracerEffect && pszTracerEffect[0] )
  1982. {
  1983. UTIL_ParticleTracer( pszTracerEffect, vecStart, vecEnd, iEntIndex, iUseAttachment, true );
  1984. }
  1985. }
  1986. else
  1987. {
  1988. // just do the whiz sound
  1989. FX_TracerSound( vecStart, vecEnd, TRACER_TYPE_DEFAULT );
  1990. }
  1991. }
  1992. }
  1993. #endif
  1994. void CCSPlayer::UpdateStepSound( surfacedata_t *psurface, const Vector &vecOrigin, const Vector &vecVelocity )
  1995. {
  1996. if ( IsBot() && IsDormant() )
  1997. return;
  1998. if (!IsAlive())
  1999. return;
  2000. float speedSqr = vecVelocity.LengthSqr();
  2001. float flWalkSpeed = (CS_PLAYER_SPEED_RUN * CS_PLAYER_SPEED_WALK_MODIFIER);
  2002. if ( ( speedSqr < flWalkSpeed * flWalkSpeed ) || m_bIsWalking )
  2003. {
  2004. if ( speedSqr < 10.0 )
  2005. {
  2006. // If we stop, reset the step sound tracking.
  2007. // This makes step sounds play a consistent time after
  2008. // we start running making it easier to co-ordinate suit and
  2009. // step sounds.
  2010. SetStepSoundTime( STEPSOUNDTIME_NORMAL, false );
  2011. }
  2012. return; // player is not running, no footsteps
  2013. }
  2014. BaseClass::UpdateStepSound( psurface, vecOrigin, vecVelocity );
  2015. }
  2016. ConVar weapon_recoil_view_punch_extra( "weapon_recoil_view_punch_extra", "0.055", FCVAR_RELEASE | FCVAR_CHEAT | FCVAR_REPLICATED, "Additional (non-aim) punch added to view from recoil" );
  2017. void CCSPlayer::KickBack( float fAngle, float fMagnitude )
  2018. {
  2019. QAngle angleVelocity(0,0,0);
  2020. angleVelocity[YAW] = -sinf(DEG2RAD(fAngle)) * fMagnitude;
  2021. angleVelocity[PITCH] = -cosf(DEG2RAD(fAngle)) * fMagnitude;
  2022. angleVelocity += m_Local.m_aimPunchAngleVel.Get();
  2023. SetAimPunchAngleVelocity( angleVelocity );
  2024. // this bit gives additional punch to the view (screen shake) to make the kick back a bit more visceral
  2025. QAngle viewPunch = GetViewPunchAngle();
  2026. float fViewPunchMagnitude = fMagnitude * weapon_recoil_view_punch_extra.GetFloat();
  2027. viewPunch[YAW] -= sinf(DEG2RAD(fAngle)) * fViewPunchMagnitude;
  2028. viewPunch[PITCH] -= cosf(DEG2RAD(fAngle)) * fViewPunchMagnitude;
  2029. SetViewPunchAngle(viewPunch);
  2030. }
  2031. QAngle CCSPlayer::GetAimPunchAngle()
  2032. {
  2033. #ifdef CLIENT_DLL
  2034. if ( PlatformInputDevice::IsInputDeviceAPointer( g_pInputSystem->GetCurrentInputDevice() ) )
  2035. #else
  2036. if ( PlatformInputDevice::IsInputDeviceAPointer( GetPlayerInputDevice() ) )
  2037. #endif
  2038. {
  2039. return m_Local.m_aimPunchAngle.Get() * weapon_recoil_scale_motion_controller.GetFloat();
  2040. }
  2041. else
  2042. {
  2043. return m_Local.m_aimPunchAngle.Get() * weapon_recoil_scale.GetFloat();
  2044. }
  2045. }
  2046. QAngle CCSPlayer::GetRawAimPunchAngle() const
  2047. {
  2048. return m_Local.m_aimPunchAngle.Get();
  2049. }
  2050. //-----------------------------------------------------------------------------
  2051. // Purpose:
  2052. // Output : int
  2053. //-----------------------------------------------------------------------------
  2054. int CCSPlayer::GetDefaultCrouchedFOV( void ) const
  2055. {
  2056. #ifdef _GAMECONSOLE
  2057. return GetDefaultFOV() - 5;
  2058. #else
  2059. return GetDefaultFOV();
  2060. #endif
  2061. }
  2062. bool CCSPlayer::CanMove() const
  2063. {
  2064. // When we're in intro camera mode, it's important to return false here
  2065. // so our physics object doesn't fall out of the world.
  2066. if ( GetMoveType() == MOVETYPE_NONE )
  2067. return false;
  2068. if ( IsObserver() )
  2069. return true; // observers can move all the time
  2070. bool bValidMoveState = (State_Get() == STATE_ACTIVE || State_Get() == STATE_OBSERVER_MODE);
  2071. if ( m_bIsDefusing || m_bIsGrabbingHostage || !bValidMoveState || (CSGameRules()->IsFreezePeriod() && !m_bCanMoveDuringFreezePeriod) )
  2072. {
  2073. return false;
  2074. }
  2075. else
  2076. {
  2077. // Can't move while planting C4.
  2078. CC4 *pC4 = dynamic_cast< CC4* >( GetActiveWeapon() );
  2079. if ( pC4 && pC4->m_bStartedArming )
  2080. return false;
  2081. return true;
  2082. }
  2083. }
  2084. unsigned int CCSPlayer::PhysicsSolidMaskForEntity( void ) const
  2085. {
  2086. if ( !CSGameRules()->IsTeammateSolid() )
  2087. {
  2088. switch ( GetTeamNumber() )
  2089. {
  2090. case TEAM_UNASSIGNED:
  2091. return MASK_PLAYERSOLID;
  2092. case LAST_SHARED_TEAM:
  2093. return MASK_PLAYERSOLID;
  2094. case TEAM_TERRORIST:
  2095. return MASK_PLAYERSOLID | CONTENTS_TEAM1;
  2096. case TEAM_CT:
  2097. return MASK_PLAYERSOLID | CONTENTS_TEAM2;
  2098. }
  2099. }
  2100. return MASK_PLAYERSOLID;
  2101. }
  2102. void CCSPlayer::OnJump( float fImpulse )
  2103. {
  2104. CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon();
  2105. if ( pActiveWeapon != NULL )
  2106. pActiveWeapon->OnJump(fImpulse);
  2107. }
  2108. void CCSPlayer::OnLand( float fVelocity )
  2109. {
  2110. CWeaponCSBase* pActiveWeapon = GetActiveCSWeapon();
  2111. if (pActiveWeapon != NULL)
  2112. pActiveWeapon->OnLand(fVelocity);
  2113. if ( fVelocity > 270 )
  2114. {
  2115. CRecipientFilter filter;
  2116. #if defined( CLIENT_DLL )
  2117. filter.AddRecipient( this );
  2118. if ( prediction->InPrediction() )
  2119. {
  2120. // Only use these rules when in prediction.
  2121. filter.UsePredictionRules();
  2122. }
  2123. #else
  2124. filter.AddAllPlayers();
  2125. // the client plays it's own sound
  2126. filter.RemoveRecipient( this );
  2127. #endif
  2128. EmitSound(filter, entindex(), "Default.Land");
  2129. if (!m_pSurfaceData)
  2130. return;
  2131. unsigned short stepSoundName = m_pSurfaceData->sounds.runStepLeft;
  2132. if (!stepSoundName)
  2133. return;
  2134. IPhysicsSurfaceProps *physprops = MoveHelper()->GetSurfaceProps();
  2135. const char *pRawSoundName = physprops->GetString(stepSoundName);
  2136. char szStep[512];
  2137. if (GetTeamNumber() == TEAM_TERRORIST)
  2138. {
  2139. Q_snprintf(szStep, sizeof(szStep), "t_%s", pRawSoundName);
  2140. }
  2141. else
  2142. {
  2143. Q_snprintf(szStep, sizeof(szStep), "ct_%s", pRawSoundName);
  2144. }
  2145. EmitSound(filter, entindex(), szStep);
  2146. }
  2147. }
  2148. //-------------------------------------------------------------------------------------------------------------------------------
  2149. /**
  2150. * Track the last time we were on a ladder, along with the ladder's normal and where we
  2151. * were grabbing it, so we don't reach behind us and grab it again as we are trying to
  2152. * dismount.
  2153. */
  2154. void CCSPlayer::SurpressLadderChecks( const Vector& pos, const Vector& normal )
  2155. {
  2156. m_ladderSurpressionTimer.Start( 1.0f );
  2157. m_lastLadderPos = pos;
  2158. m_lastLadderNormal = normal;
  2159. }
  2160. //-------------------------------------------------------------------------------------------------------------------------------
  2161. /**
  2162. * Prevent us from re-grabbing the same ladder we were just on:
  2163. * - if the timer is elapsed, let us grab again
  2164. * - if the normal is different, let us grab
  2165. * - if the 2D pos is very different, let us grab, since it's probably a different ladder
  2166. */
  2167. bool CCSPlayer::CanGrabLadder( const Vector& pos, const Vector& normal )
  2168. {
  2169. if ( m_ladderSurpressionTimer.GetRemainingTime() <= 0.0f )
  2170. {
  2171. return true;
  2172. }
  2173. const float MaxDist = 64.0f;
  2174. if ( pos.AsVector2D().DistToSqr( m_lastLadderPos.AsVector2D() ) < MaxDist * MaxDist )
  2175. {
  2176. return false;
  2177. }
  2178. if ( normal != m_lastLadderNormal )
  2179. {
  2180. return true;
  2181. }
  2182. return false;
  2183. }
  2184. void CCSPlayer::SetAnimation( PLAYER_ANIM playerAnim )
  2185. {
  2186. // In CS, its CPlayerAnimState object manages ALL the animation state.
  2187. return;
  2188. }
  2189. CWeaponCSBase* CCSPlayer::CSAnim_GetActiveWeapon()
  2190. {
  2191. return GetActiveCSWeapon();
  2192. }
  2193. bool CCSPlayer::CSAnim_CanMove()
  2194. {
  2195. return CanMove();
  2196. }
  2197. int CCSPlayer::GetCarryLimit( CSWeaponID weaponId )
  2198. {
  2199. const CCSWeaponInfo *pWeaponInfo = GetWeaponInfo( weaponId );
  2200. if ( pWeaponInfo == NULL )
  2201. return 0;
  2202. if ( pWeaponInfo->GetWeaponType() == WEAPONTYPE_GRENADE )
  2203. {
  2204. return GetAmmoDef()->MaxCarry( pWeaponInfo->GetPrimaryAmmoType( ), this ); // We still use player-stored ammo for grenades.
  2205. }
  2206. return 1;
  2207. }
  2208. AcquireResult::Type CCSPlayer::CanAcquire( CSWeaponID weaponId, AcquireMethod::Type acquireMethod, CEconItemView *pItem )
  2209. {
  2210. const CCSWeaponInfo *pWeaponInfo = NULL;
  2211. if ( weaponId == WEAPON_NONE && (pItem == NULL || !pItem->IsValid()) )
  2212. return AcquireResult::InvalidItem;
  2213. if ( pItem && pItem->IsValid() )
  2214. {
  2215. weaponId = WeaponIdFromString( pItem->GetStaticData()->GetItemClass() );
  2216. if ( weaponId == WEAPON_NONE )
  2217. return AcquireResult::InvalidItem;
  2218. pWeaponInfo = GetWeaponInfo( weaponId );
  2219. }
  2220. else
  2221. pWeaponInfo = GetWeaponInfo( weaponId );
  2222. if ( pWeaponInfo == NULL )
  2223. return AcquireResult::InvalidItem;
  2224. AcquireResult::Type nGamerulesResult = CSGameRules()->IsWeaponAllowed( pWeaponInfo, GetTeamNumber(), pItem );
  2225. if ( nGamerulesResult != AcquireResult::Allowed )
  2226. {
  2227. return nGamerulesResult;
  2228. }
  2229. int nType = pWeaponInfo->GetWeaponType( pItem );
  2230. // if ( acquireMethod == AcquireMethod::Buy )
  2231. // {
  2232. // bool bFoundInLoadout = false;
  2233. // int nStartSearchPos = LOADOUT_POSITION_INVALID;
  2234. //
  2235. // if ( nType == WEAPONTYPE_PISTOL )
  2236. // nStartSearchPos = LOADOUT_POSITION_SECONDARY0;
  2237. // else if ( nType == WEAPONTYPE_SHOTGUN || nType == WEAPONTYPE_MACHINEGUN )
  2238. // nStartSearchPos = LOADOUT_POSITION_HEAVY0;
  2239. // else if ( nType == WEAPONTYPE_SUBMACHINEGUN )
  2240. // nStartSearchPos = LOADOUT_POSITION_SMG0;
  2241. // else if ( nType == WEAPONTYPE_RIFLE || nType == WEAPONTYPE_SNIPER_RIFLE )
  2242. // nStartSearchPos = LOADOUT_POSITION_RIFLE0;
  2243. //
  2244. // // make sure that we have this item equipped in our inventory loadout
  2245. // for ( int i = nStartSearchPos; i < (nStartSearchPos+6); ++i )
  2246. // {
  2247. // CEconItemView *pItemView = Inventory()->GetItemInLoadout( GetTeamNumber(), i );
  2248. // if ( pItemView && pItemView->GetStaticData() )
  2249. // {
  2250. // if ( pItemView == pItem || ( V_stricmp( pItemView->GetStaticData()->GetDefinitionName(), WeaponIdAsString( weaponId ) ) == 0 ) )
  2251. // {
  2252. // bFoundInLoadout = true;
  2253. // break;
  2254. // }
  2255. // }
  2256. // }
  2257. // if ( nStartSearchPos != LOADOUT_POSITION_INVALID && !bFoundInLoadout )
  2258. // return AcquireResult::NotAllowedForPurchase;
  2259. // }
  2260. if ( nType == WEAPONTYPE_GRENADE )
  2261. {
  2262. if ( mp_buy_allow_grenades.GetBool() == false )
  2263. {
  2264. if ( acquireMethod == AcquireMethod::Buy )
  2265. return AcquireResult::NotAllowedForPurchase;
  2266. }
  2267. // make sure we aren't exceeding the ammo max for this grenade type
  2268. int carryLimitThisGrenade = GetCarryLimit( weaponId );
  2269. int carryLimitAllGrenades = ammo_grenade_limit_total.GetInt();
  2270. CBaseCombatWeapon* pGrenadeWeapon = ( pItem && pItem->IsValid() ) ? CSWeapon_OwnsThisType( pItem ) : Weapon_OwnsThisType( WeaponIdAsString( weaponId ) );
  2271. if ( pGrenadeWeapon != NULL )
  2272. {
  2273. int nAmmoType = pGrenadeWeapon->GetPrimaryAmmoType();
  2274. if( nAmmoType != -1 )
  2275. {
  2276. int thisGrenadeCarried = GetAmmoCount(nAmmoType );
  2277. if ( thisGrenadeCarried >= carryLimitThisGrenade )
  2278. {
  2279. return AcquireResult::ReachedGrenadeTypeLimit;
  2280. }
  2281. }
  2282. }
  2283. // count how many grenades of any type the player is currently carrying
  2284. int allGrenadesCarried = 0;
  2285. for ( int i = 0; i < MAX_WEAPONS; ++i )
  2286. {
  2287. CWeaponCSBase* pWeapon = dynamic_cast<CWeaponCSBase*>( GetWeapon( i) );
  2288. if ( pWeapon != NULL && pWeapon->IsKindOf( WEAPONTYPE_GRENADE ) )
  2289. {
  2290. int nAmmoType = pWeapon->GetPrimaryAmmoType();
  2291. if( nAmmoType != -1 )
  2292. {
  2293. allGrenadesCarried += GetAmmoCount( nAmmoType );
  2294. }
  2295. }
  2296. }
  2297. if ( allGrenadesCarried >= carryLimitAllGrenades )
  2298. {
  2299. return AcquireResult::ReachedGrenadeTotalLimit;
  2300. }
  2301. // don't allow players with an inferno spawning weapon to pick up another inferno spawning weapon
  2302. if ( weaponId == WEAPON_INCGRENADE )
  2303. {
  2304. if ( Weapon_OwnsThisType( "weapon_molotov" ) )
  2305. return AcquireResult::AlreadyOwned;
  2306. }
  2307. else if ( weaponId == WEAPON_MOLOTOV )
  2308. {
  2309. if ( Weapon_OwnsThisType( "weapon_incgrenade" ) )
  2310. return AcquireResult::AlreadyOwned;
  2311. }
  2312. }
  2313. else if ( nType == WEAPONTYPE_STACKABLEITEM )
  2314. {
  2315. int carryLimit = GetAmmoDef()->MaxCarry( pWeaponInfo->GetPrimaryAmmoType(), this );
  2316. CBaseCombatWeapon* pItemWeapon = ( pItem && pItem->IsValid() ) ? CSWeapon_OwnsThisType( pItem ) : Weapon_OwnsThisType( WeaponIdAsString( weaponId ) );
  2317. if ( pItemWeapon != NULL )
  2318. {
  2319. int nAmmoType = pItemWeapon->GetPrimaryAmmoType();
  2320. if ( nAmmoType != -1 )
  2321. {
  2322. int thisCarried = GetAmmoCount( nAmmoType );
  2323. if ( thisCarried >= carryLimit )
  2324. {
  2325. return AcquireResult::ReachedGrenadeTypeLimit;
  2326. }
  2327. }
  2328. }
  2329. }
  2330. else if ( weaponId == ITEM_KEVLAR )
  2331. {
  2332. if ( mp_free_armor.GetBool() )
  2333. {
  2334. if ( acquireMethod == AcquireMethod::Buy )
  2335. return AcquireResult::NotAllowedForPurchase;
  2336. }
  2337. if ( ArmorValue() >= 100 )
  2338. {
  2339. return AcquireResult::AlreadyOwned;
  2340. }
  2341. }
  2342. else if ( weaponId == ITEM_ASSAULTSUIT )
  2343. {
  2344. if ( mp_free_armor.GetBool() )
  2345. {
  2346. if ( acquireMethod == AcquireMethod::Buy )
  2347. return AcquireResult::NotAllowedForPurchase;
  2348. }
  2349. if ( m_bHasHelmet && !m_bHasHeavyArmor /*&& ArmorValue() >= 100*/ )
  2350. {
  2351. return AcquireResult::AlreadyOwned;
  2352. }
  2353. }
  2354. else if ( weaponId == ITEM_HEAVYASSAULTSUIT )
  2355. {
  2356. if ( m_bHasHeavyArmor && ArmorValue() >= 200 )
  2357. {
  2358. return AcquireResult::AlreadyOwned;
  2359. }
  2360. }
  2361. else if ( weaponId == ITEM_DEFUSER || weaponId == ITEM_CUTTERS )
  2362. {
  2363. if ( CSGameRules() && (CSGameRules()->IsPlayingGunGameDeathmatch() || CSGameRules()->IsPlayingCoopGuardian()) )
  2364. {
  2365. if ( acquireMethod == AcquireMethod::Buy )
  2366. return AcquireResult::NotAllowedForPurchase;
  2367. }
  2368. if ( m_bHasDefuser )
  2369. return AcquireResult::AlreadyOwned;
  2370. }
  2371. else if ( weaponId == WEAPON_C4 )
  2372. {
  2373. // TODO[pmf]: Data drive this from the scripts
  2374. if ( acquireMethod == AcquireMethod::Buy )
  2375. return AcquireResult::NotAllowedForPurchase;
  2376. }
  2377. else if ( CSWeapon_OwnsThisType( pItem ) )
  2378. {
  2379. return AcquireResult::AlreadyOwned;
  2380. }
  2381. extern ConVar mp_weapons_allow_zeus;
  2382. extern ConVar mp_weapons_allow_typecount;
  2383. // special case for limiting taser to classic casual; data drive this if it becomes more complex
  2384. if ( weaponId == WEAPON_TASER )
  2385. {
  2386. if ( !mp_weapons_allow_zeus.GetBool() )
  2387. return AcquireResult::NotAllowedForPurchase;
  2388. else if ( ( mp_weapons_allow_zeus.GetInt() > 0 ) && ( m_iWeaponPurchasesThisRound[ weaponId ] >= mp_weapons_allow_zeus.GetInt() ) )
  2389. return AcquireResult::AlreadyPurchased;
  2390. else
  2391. return AcquireResult::Allowed;
  2392. }
  2393. // additional constraints for purchasing weapons
  2394. if ( acquireMethod == AcquireMethod::Buy )
  2395. {
  2396. if ( pWeaponInfo->GetUsedByTeam( pItem ) != TEAM_UNASSIGNED && GetTeamNumber() != pWeaponInfo->GetUsedByTeam( pItem ) )
  2397. {
  2398. return AcquireResult::NotAllowedByTeam;
  2399. }
  2400. // special case for flashbangs - no limit
  2401. if ( weaponId == WEAPON_FLASHBANG )
  2402. {
  2403. return AcquireResult::Allowed;
  2404. }
  2405. // don't allow purchasing multiple grenades of a given type per round (even if the player throws the purchased one)
  2406. if ( pWeaponInfo->GetWeaponType() == WEAPONTYPE_GRENADE && !CSGameRules()->IsPlayingCooperativeGametype() )
  2407. {
  2408. // limit the number of purchases to one more than the number we are allowed to carry
  2409. int carryLimitThisGrenade = GetAmmoDef()->MaxCarry( pWeaponInfo->GetPrimaryAmmoType(), this ); // We still use player-stored ammo for grenades.
  2410. // for smoke grenade, we are only allow to buy exactly the amount we are allowed to carry, with other weapons, we can purchase one more than what we can carry per round
  2411. if ( weaponId == WEAPON_SMOKEGRENADE && carryLimitThisGrenade > 0 )
  2412. carryLimitThisGrenade--;
  2413. if ( m_iWeaponPurchasesThisRound[weaponId] > carryLimitThisGrenade )
  2414. return AcquireResult::AlreadyPurchased;
  2415. }
  2416. if ( weaponId == WEAPON_KNIFE || weaponId == WEAPON_KNIFE_GG )
  2417. {
  2418. return AcquireResult::NotAllowedForPurchase;
  2419. }
  2420. // Validate that each player can buy only so many instances of same weapon type
  2421. if ( mp_weapons_allow_typecount.GetInt() == 0 )
  2422. return AcquireResult::NotAllowedForPurchase;
  2423. else if ( ( mp_weapons_allow_typecount.GetInt() > 0 ) && ( m_iWeaponPurchasesThisRound[weaponId] >= mp_weapons_allow_typecount.GetInt() ) )
  2424. return AcquireResult::AlreadyPurchased;
  2425. }
  2426. return AcquireResult::Allowed;
  2427. }
  2428. //-----------------------------------------------------------------------------
  2429. // Purpose: Returns weapon if already owns a weapon of this class
  2430. //-----------------------------------------------------------------------------
  2431. CWeaponCSBase* CCSPlayer::CSWeapon_OwnsThisType( CEconItemView *pItem ) const
  2432. {
  2433. /** Removed for partner depot **/
  2434. return NULL;
  2435. }
  2436. //************************************
  2437. // Determine the current cash cost of a weapon for this particular player
  2438. // Parameter: CSWeaponID weaponId
  2439. //************************************
  2440. int CCSPlayer::GetWeaponPrice( CSWeaponID weaponId, const CEconItemView *pWepView ) const
  2441. {
  2442. Assert( pWepView || ( weaponId != WEAPON_NONE ) );
  2443. if ( !pWepView && ( weaponId == WEAPON_NONE ) )
  2444. return -1;
  2445. bool bHasFullArmor = (ArmorValue() >= 100);
  2446. // special case handling for reduced cost of Kevlar + helmet
  2447. if ( weaponId == ITEM_ASSAULTSUIT )
  2448. {
  2449. int iCost = ITEM_PRICE_ASSAULTSUIT;
  2450. if ( bHasFullArmor && !m_bHasHelmet )
  2451. iCost -= ITEM_PRICE_KEVLAR;
  2452. //else if ( m_bHasHelmet )
  2453. // iCost = ITEM_PRICE_KEVLAR;
  2454. return iCost;
  2455. }
  2456. // special case handling for reduced cost of Kevlar
  2457. if ( weaponId == ITEM_KEVLAR )
  2458. {
  2459. int iCost = ITEM_PRICE_KEVLAR;
  2460. return iCost;
  2461. }
  2462. // special case handling for reduced cost of Kevlar
  2463. if ( weaponId == ITEM_HEAVYASSAULTSUIT )
  2464. {
  2465. int iCost = ITEM_PRICE_HEAVYASSAULTSUIT;
  2466. return iCost;
  2467. }
  2468. // if ( weaponId == ITEM_EXOSUIT )
  2469. // {
  2470. // int iCost = ITEM_PRICE_EXOSUIT;
  2471. //
  2472. // return iCost;
  2473. // }
  2474. const CCSWeaponInfo* pWeaponInfo = GetWeaponInfo( weaponId );
  2475. return ( pWeaponInfo ) ? pWeaponInfo->GetWeaponPrice( pWepView ) : 0;
  2476. }
  2477. bool CCSPlayer::HasWeaponOfType( int nWeaponID ) const
  2478. {
  2479. for ( int i = 0; i < WeaponCount(); ++i )
  2480. {
  2481. CWeaponCSBase *pWeapon = dynamic_cast< CWeaponCSBase* > ( GetWeapon( i ) );
  2482. if ( pWeapon && pWeapon->GetCSWeaponID() == nWeaponID )
  2483. {
  2484. return true;
  2485. }
  2486. }
  2487. return false;
  2488. }
  2489. #if defined ( GAME_DLL ) || defined ( ENABLE_CLIENT_INVENTORIES_FOR_OTHER_PLAYERS )
  2490. CEconItemView *CCSPlayer::GetEquippedItemInLoadoutSlotOrBaseItem( int iLoadoutSlot )
  2491. {
  2492. CEconItemView *pBaseItem = CSInventoryManager()->GetBaseItemForTeam( GetTeamNumber(), iLoadoutSlot );
  2493. bool bRandomCosmetics = false;
  2494. // Bots and controlled bots always return the base item unless we're randomizing
  2495. if ( !bRandomCosmetics && ( IsBot() || IsControllingBot() ) )
  2496. return pBaseItem;
  2497. CEconItemView *pResult = Inventory()->GetInventoryItemByItemID( m_EquippedLoadoutItemIndices[iLoadoutSlot] );
  2498. if ( !pResult || !pResult->IsValid() )
  2499. {
  2500. pResult = pBaseItem;
  2501. }
  2502. return pResult;
  2503. }
  2504. #endif
  2505. bool CCSPlayer::UpdateDispatchLayer( CAnimationLayer *pLayer, CStudioHdr *pWeaponStudioHdr, int iSequence )
  2506. {
  2507. if ( !pWeaponStudioHdr || !pLayer )
  2508. {
  2509. if ( pLayer )
  2510. pLayer->m_nDispatchedDst = ACT_INVALID;
  2511. return false;
  2512. }
  2513. if ( pLayer->m_pDispatchedStudioHdr != pWeaponStudioHdr || pLayer->m_nDispatchedSrc != iSequence || pLayer->m_nDispatchedDst >= pWeaponStudioHdr->GetNumSeq() )
  2514. {
  2515. pLayer->m_pDispatchedStudioHdr = pWeaponStudioHdr;
  2516. pLayer->m_nDispatchedSrc = iSequence;
  2517. if ( pWeaponStudioHdr )
  2518. {
  2519. const char *pszSeqName = GetSequenceName( iSequence );
  2520. #ifdef DEBUG
  2521. if ( V_stristr( pszSeqName, "default" ) )
  2522. {
  2523. AssertMsg( false, "Warning: weapon is attempting to play its default sequence as a dispatched anim.\n" );
  2524. }
  2525. #endif
  2526. // check if the weapon has a CT or T specific version of this sequence (denoted by a _t or ct suffix)
  2527. if ( GetTeamNumber() == TEAM_TERRORIST )
  2528. {
  2529. char pszLayerNameT[128];
  2530. V_sprintf_safe( pszLayerNameT, "%s_t", pszSeqName );
  2531. int nTeamSpecificSequenceIndex = pWeaponStudioHdr->LookupSequence( pszLayerNameT );
  2532. if ( nTeamSpecificSequenceIndex > 0 )
  2533. {
  2534. pLayer->m_nDispatchedDst = nTeamSpecificSequenceIndex;
  2535. return true;
  2536. }
  2537. }
  2538. pLayer->m_nDispatchedDst = pWeaponStudioHdr->LookupSequence( pszSeqName );
  2539. }
  2540. else
  2541. {
  2542. pLayer->m_nDispatchedDst = ACT_INVALID;
  2543. }
  2544. }
  2545. return (pLayer->m_nDispatchedDst > 0 );
  2546. }
  2547. bool CCSPlayer::UpdateLayerWeaponDispatch( CAnimationLayer *pLayer, int iSequence )
  2548. {
  2549. CBaseCombatWeapon *pWeapon = GetActiveWeapon();
  2550. if ( pWeapon )
  2551. {
  2552. CBaseWeaponWorldModel *pWeaponWorldModel = pWeapon->GetWeaponWorldModel();
  2553. if ( pWeaponWorldModel )
  2554. {
  2555. return UpdateDispatchLayer( pLayer, pWeaponWorldModel->GetModelPtr(), iSequence );
  2556. }
  2557. }
  2558. return UpdateDispatchLayer( pLayer, NULL, iSequence );
  2559. }
  2560. float CCSPlayer::GetLayerSequenceCycleRate( CAnimationLayer *pLayer, int iSequence )
  2561. {
  2562. UpdateLayerWeaponDispatch( pLayer, iSequence );
  2563. if ( pLayer->m_nDispatchedDst != ACT_INVALID )
  2564. {
  2565. // weapon world model overrides rate
  2566. return GetSequenceCycleRate( pLayer->m_pDispatchedStudioHdr, pLayer->m_nDispatchedDst );
  2567. }
  2568. return BaseClass::GetLayerSequenceCycleRate( pLayer, iSequence );
  2569. }
  2570. //--------------------------------------------------------------------------------------------------------------
  2571. #define MATERIAL_NAME_LENGTH 16
  2572. #ifdef GAME_DLL
  2573. class CFootstepControl : public CBaseTrigger
  2574. {
  2575. public:
  2576. DECLARE_CLASS( CFootstepControl, CBaseTrigger );
  2577. DECLARE_DATADESC();
  2578. DECLARE_SERVERCLASS();
  2579. virtual int UpdateTransmitState( void );
  2580. virtual void Spawn( void );
  2581. CNetworkVar( string_t, m_source );
  2582. CNetworkVar( string_t, m_destination );
  2583. };
  2584. LINK_ENTITY_TO_CLASS( func_footstep_control, CFootstepControl );
  2585. BEGIN_DATADESC( CFootstepControl )
  2586. DEFINE_KEYFIELD( m_source, FIELD_STRING, "Source" ),
  2587. DEFINE_KEYFIELD( m_destination, FIELD_STRING, "Destination" ),
  2588. END_DATADESC()
  2589. IMPLEMENT_SERVERCLASS_ST( CFootstepControl, DT_FootstepControl )
  2590. SendPropStringT( SENDINFO(m_source) ),
  2591. SendPropStringT( SENDINFO(m_destination) ),
  2592. END_SEND_TABLE()
  2593. int CFootstepControl::UpdateTransmitState( void )
  2594. {
  2595. return SetTransmitState( FL_EDICT_ALWAYS );
  2596. }
  2597. void CFootstepControl::Spawn( void )
  2598. {
  2599. InitTrigger();
  2600. }
  2601. #else
  2602. //--------------------------------------------------------------------------------------------------------------
  2603. class C_FootstepControl : public C_BaseTrigger
  2604. {
  2605. public:
  2606. DECLARE_CLASS( C_FootstepControl, C_BaseTrigger );
  2607. DECLARE_CLIENTCLASS();
  2608. C_FootstepControl( void );
  2609. ~C_FootstepControl();
  2610. char m_source[MATERIAL_NAME_LENGTH];
  2611. char m_destination[MATERIAL_NAME_LENGTH];
  2612. };
  2613. IMPLEMENT_CLIENTCLASS_DT(C_FootstepControl, DT_FootstepControl, CFootstepControl)
  2614. RecvPropString( RECVINFO(m_source) ),
  2615. RecvPropString( RECVINFO(m_destination) ),
  2616. END_RECV_TABLE()
  2617. CUtlVector< C_FootstepControl * > s_footstepControllers;
  2618. C_FootstepControl::C_FootstepControl( void )
  2619. {
  2620. s_footstepControllers.AddToTail( this );
  2621. }
  2622. C_FootstepControl::~C_FootstepControl()
  2623. {
  2624. s_footstepControllers.FindAndRemove( this );
  2625. }
  2626. surfacedata_t * CCSPlayer::GetFootstepSurface( const Vector &origin, const char *surfaceName )
  2627. {
  2628. for ( int i=0; i<s_footstepControllers.Count(); ++i )
  2629. {
  2630. C_FootstepControl *control = s_footstepControllers[i];
  2631. if ( FStrEq( control->m_source, surfaceName ) )
  2632. {
  2633. if ( control->CollisionProp()->IsPointInBounds( origin ) )
  2634. {
  2635. return physprops->GetSurfaceData( physprops->GetSurfaceIndex( control->m_destination ) );
  2636. }
  2637. }
  2638. }
  2639. return physprops->GetSurfaceData( physprops->GetSurfaceIndex( surfaceName ) );
  2640. }
  2641. #endif