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.

792 lines
24 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "ammodef.h"
  9. #include "tier0/vprof.h"
  10. // memdbgon must be the last include file in a .cpp file!!!
  11. #include "tier0/memdbgon.h"
  12. //-----------------------------------------------------------------------------
  13. // Purpose: Switches to the best weapon that is also better than the given weapon.
  14. // Input : pCurrent - The current weapon used by the player.
  15. // Output : Returns true if the weapon was switched, false if there was no better
  16. // weapon to switch to.
  17. //-----------------------------------------------------------------------------
  18. bool CBaseCombatCharacter::SwitchToNextBestWeapon(CBaseCombatWeapon *pCurrent)
  19. {
  20. CBaseCombatWeapon *pNewWeapon = g_pGameRules->GetNextBestWeapon(this, pCurrent);
  21. if ( ( pNewWeapon != NULL ) && ( pNewWeapon != pCurrent ) )
  22. {
  23. return Weapon_Switch( pNewWeapon );
  24. }
  25. return false;
  26. }
  27. //-----------------------------------------------------------------------------
  28. // Purpose: Switches to the given weapon (providing it has ammo)
  29. // Input :
  30. // Output : true is switch succeeded
  31. //-----------------------------------------------------------------------------
  32. bool CBaseCombatCharacter::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex /*=0*/ )
  33. {
  34. if ( pWeapon == NULL )
  35. return false;
  36. // Already have it out?
  37. if ( m_hActiveWeapon.Get() == pWeapon )
  38. {
  39. if ( !m_hActiveWeapon->IsWeaponVisible() || m_hActiveWeapon->IsHolstered() )
  40. return m_hActiveWeapon->Deploy( );
  41. return false;
  42. }
  43. if (!Weapon_CanSwitchTo(pWeapon))
  44. {
  45. return false;
  46. }
  47. if ( m_hActiveWeapon )
  48. {
  49. if ( !m_hActiveWeapon->IsAlwaysActive() )
  50. {
  51. if ( !m_hActiveWeapon->Holster( pWeapon ) )
  52. {
  53. return false;
  54. }
  55. }
  56. }
  57. m_hActiveWeapon = pWeapon;
  58. return pWeapon->Deploy( );
  59. }
  60. //-----------------------------------------------------------------------------
  61. // Purpose: Returns whether or not we can switch to the given weapon.
  62. // Input : pWeapon -
  63. //-----------------------------------------------------------------------------
  64. bool CBaseCombatCharacter::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon )
  65. {
  66. if (IsPlayer())
  67. {
  68. CBasePlayer *pPlayer = (CBasePlayer *)this;
  69. #if !defined( CLIENT_DLL )
  70. IServerVehicle *pVehicle = pPlayer->GetVehicle();
  71. #else
  72. IClientVehicle *pVehicle = pPlayer->GetVehicle();
  73. #endif
  74. if (pVehicle && !pPlayer->UsingStandardWeaponsInVehicle())
  75. return false;
  76. }
  77. if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) )
  78. return false;
  79. if ( !pWeapon->CanDeploy() )
  80. return false;
  81. if ( m_hActiveWeapon )
  82. {
  83. if ( !m_hActiveWeapon->CanHolster() )
  84. return false;
  85. }
  86. return true;
  87. }
  88. //-----------------------------------------------------------------------------
  89. // Purpose:
  90. // Output : CBaseCombatWeapon
  91. //-----------------------------------------------------------------------------
  92. CBaseCombatWeapon *CBaseCombatCharacter::GetActiveWeapon() const
  93. {
  94. return m_hActiveWeapon;
  95. }
  96. //-----------------------------------------------------------------------------
  97. // Purpose:
  98. // Input : i -
  99. //-----------------------------------------------------------------------------
  100. CBaseCombatWeapon *CBaseCombatCharacter::GetWeapon( int i ) const
  101. {
  102. Assert( (i >= 0) && (i < MAX_WEAPONS) );
  103. return m_hMyWeapons[i].Get();
  104. }
  105. //-----------------------------------------------------------------------------
  106. // Purpose:
  107. // Input : iCount -
  108. // iAmmoIndex -
  109. //-----------------------------------------------------------------------------
  110. void CBaseCombatCharacter::RemoveAmmo( int iCount, int iAmmoIndex )
  111. {
  112. if (iCount <= 0)
  113. return;
  114. if ( iAmmoIndex < 0 )
  115. return;
  116. // Infinite ammo?
  117. if ( GetAmmoDef()->CanCarryInfiniteAmmo( iAmmoIndex ) )
  118. return;
  119. extern ConVar sv_infinite_ammo;
  120. if ( sv_infinite_ammo.GetInt() == 2 ) // infinite total ammo but magazine reloads are still required.
  121. return;
  122. // Ammo pickup sound
  123. m_iAmmo.Set( iAmmoIndex, MAX( m_iAmmo[iAmmoIndex] - iCount, 0 ) );
  124. }
  125. void CBaseCombatCharacter::RemoveAmmo( int iCount, const char *szName )
  126. {
  127. RemoveAmmo( iCount, GetAmmoDef()->Index(szName) );
  128. }
  129. //-----------------------------------------------------------------------------
  130. // Purpose:
  131. //-----------------------------------------------------------------------------
  132. void CBaseCombatCharacter::RemoveAllAmmo( )
  133. {
  134. for ( int i = 0; i < MAX_AMMO_SLOTS; i++ )
  135. {
  136. m_iAmmo.Set( i, 0 );
  137. }
  138. }
  139. //-----------------------------------------------------------------------------
  140. // FIXME: This is a sort of hack back-door only used by physgun!
  141. //-----------------------------------------------------------------------------
  142. void CBaseCombatCharacter::SetAmmoCount( int iCount, int iAmmoIndex )
  143. {
  144. // NOTE: No sound, no max check! Seems pretty bogus to me!
  145. m_iAmmo.Set( iAmmoIndex, iCount );
  146. }
  147. //-----------------------------------------------------------------------------
  148. // Purpose: Returns the amount of ammunition of a particular type owned
  149. // owned by the character
  150. // Input : Ammo Index
  151. // Output : The amount of ammo
  152. //-----------------------------------------------------------------------------
  153. int CBaseCombatCharacter::GetAmmoCount( int iAmmoIndex ) const
  154. {
  155. // DEPRECATED - Ido says please use GetReserveAmmoCount
  156. if ( iAmmoIndex == -1 )
  157. return 0;
  158. // Infinite ammo?
  159. if ( GetAmmoDef()->CanCarryInfiniteAmmo( iAmmoIndex ) )
  160. return 999;
  161. return m_iAmmo[ iAmmoIndex ];
  162. }
  163. //-----------------------------------------------------------------------------
  164. // Purpose: Returns the amount of ammunition of the specified type the character's carrying
  165. //-----------------------------------------------------------------------------
  166. int CBaseCombatCharacter::GetAmmoCount( char *szName ) const
  167. {
  168. return GetAmmoCount( GetAmmoDef()->Index(szName) );
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose: Returns weapon if already owns a weapon of this class
  172. //-----------------------------------------------------------------------------
  173. CBaseCombatWeapon* CBaseCombatCharacter::Weapon_OwnsThisType( const char *pszWeapon, int iSubType ) const
  174. {
  175. for ( int i = 0; i < MAX_WEAPONS; i++ )
  176. {
  177. if ( m_hMyWeapons[i].Get() && FClassnameIs( m_hMyWeapons[i], pszWeapon ) )
  178. {
  179. // Make sure it matches the subtype
  180. if ( m_hMyWeapons[i]->GetSubType() == iSubType )
  181. {
  182. return m_hMyWeapons[i];
  183. }
  184. }
  185. }
  186. return NULL;
  187. }
  188. //-----------------------------------------------------------------------------
  189. // Purpose: Returns the weapon (if any) in the requested slot
  190. // Input : slot - which slot to poll
  191. //-----------------------------------------------------------------------------
  192. CBaseCombatWeapon *CBaseCombatCharacter::Weapon_GetSlot( int slot ) const
  193. {
  194. int targetSlot = slot;
  195. // Check for that slot being occupied already
  196. for ( int i=0; i < MAX_WEAPONS; i++ )
  197. {
  198. if ( m_hMyWeapons[i].Get() != NULL )
  199. {
  200. // If the slots match, it's already occupied
  201. if ( m_hMyWeapons[i]->GetSlot() == targetSlot )
  202. return m_hMyWeapons[i];
  203. }
  204. }
  205. return NULL;
  206. }
  207. //-----------------------------------------------------------------------------
  208. // Purpose: Returns the weapon (if any) in the requested loadout position
  209. // Input : position - which slot to poll
  210. //-----------------------------------------------------------------------------
  211. CBaseCombatWeapon *CBaseCombatCharacter::Weapon_GetPosition( int position ) const
  212. {
  213. // Check for that slot being occupied already
  214. for ( int i = 0; i < MAX_WEAPONS; i++ )
  215. {
  216. if ( m_hMyWeapons[ i ].Get() != NULL )
  217. {
  218. CEconItemView * pItem = m_hMyWeapons[ i ]->GetEconItemView() ;
  219. if ( !pItem )
  220. continue;
  221. loadout_positions_t unItemPos = ( loadout_positions_t )pItem->GetItemDefinition()->GetLoadoutSlot( GetTeamNumber() );
  222. // If the slots match, it's already occupied
  223. if ( unItemPos == position )
  224. return m_hMyWeapons[ i ];
  225. }
  226. }
  227. return NULL;
  228. }
  229. int CBaseCombatCharacter::BloodColor()
  230. {
  231. return m_bloodColor;
  232. }
  233. //-----------------------------------------------------------------------------
  234. // Blood color (see BLOOD_COLOR_* macros in baseentity.h)
  235. //-----------------------------------------------------------------------------
  236. void CBaseCombatCharacter::SetBloodColor( int nBloodColor )
  237. {
  238. m_bloodColor = nBloodColor;
  239. }
  240. //-----------------------------------------------------------------------------
  241. /**
  242. The main visibility check. Checks all the entity specific reasons that could
  243. make IsVisible fail. Then checks points in space to get environmental reasons.
  244. This is LOS, plus invisibility and fog and smoke and such.
  245. */
  246. enum VisCacheResult_t
  247. {
  248. VISCACHE_UNKNOWN = 0,
  249. VISCACHE_IS_VISIBLE,
  250. VISCACHE_IS_NOT_VISIBLE,
  251. };
  252. enum
  253. {
  254. VIS_CACHE_INVALID = 0x80000000
  255. };
  256. #define VIS_CACHE_ENTRY_LIFE .090f
  257. class CCombatCharVisCache : public CAutoGameSystemPerFrame
  258. {
  259. public:
  260. virtual void FrameUpdatePreEntityThink();
  261. virtual void LevelShutdownPreEntity();
  262. int LookupVisibility( const CBaseCombatCharacter *pChar1, CBaseCombatCharacter *pChar2 );
  263. VisCacheResult_t HasVisibility( int iCache ) const;
  264. void RegisterVisibility( int iCache, bool bChar1SeesChar2, bool bChar2SeesChar1 );
  265. private:
  266. struct VisCacheEntry_t
  267. {
  268. CHandle< CBaseCombatCharacter > m_hEntity1;
  269. CHandle< CBaseCombatCharacter > m_hEntity2;
  270. float m_flTime;
  271. bool m_bEntity1CanSeeEntity2;
  272. bool m_bEntity2CanSeeEntity1;
  273. };
  274. class CVisCacheEntryLess
  275. {
  276. public:
  277. CVisCacheEntryLess( int ) {}
  278. bool operator!() const { return false; }
  279. bool operator()( const VisCacheEntry_t &lhs, const VisCacheEntry_t &rhs ) const
  280. {
  281. return ( memcmp( &lhs, &rhs, offsetof( VisCacheEntry_t, m_flTime ) ) < 0 );
  282. }
  283. };
  284. CUtlRBTree< VisCacheEntry_t, unsigned short, CVisCacheEntryLess > m_VisCache;
  285. mutable int m_nTestCount;
  286. mutable int m_nHitCount;
  287. };
  288. void CCombatCharVisCache::FrameUpdatePreEntityThink()
  289. {
  290. // Msg( "test: %d/%d\n", m_nHitCount, m_nTestCount );
  291. // Lazy retirement of vis cache
  292. // NOTE: 256 was chosen heuristically based on a playthrough where 200
  293. // was the max # in the viscache where nothing could be retired.
  294. if ( m_VisCache.Count() < 256 )
  295. return;
  296. int nMaxIndex = m_VisCache.MaxElement() - 1;
  297. for ( int i = 0; i < 8; ++i )
  298. {
  299. int n = RandomInt( 0, nMaxIndex );
  300. if ( !m_VisCache.IsValidIndex( n ) )
  301. continue;
  302. const VisCacheEntry_t &entry = m_VisCache[n];
  303. if ( !entry.m_hEntity1.IsValid() || !entry.m_hEntity2.IsValid() || ( gpGlobals->curtime - entry.m_flTime > 10.0f ) )
  304. {
  305. m_VisCache.RemoveAt( n );
  306. }
  307. }
  308. }
  309. void CCombatCharVisCache::LevelShutdownPreEntity()
  310. {
  311. m_VisCache.Purge();
  312. }
  313. int CCombatCharVisCache::LookupVisibility( const CBaseCombatCharacter *pChar1, CBaseCombatCharacter *pChar2 )
  314. {
  315. VisCacheEntry_t cacheEntry;
  316. if ( pChar1 < pChar2 )
  317. {
  318. cacheEntry.m_hEntity1 = pChar1;
  319. cacheEntry.m_hEntity2 = pChar2;
  320. }
  321. else
  322. {
  323. cacheEntry.m_hEntity1 = pChar2;
  324. cacheEntry.m_hEntity2 = pChar1;
  325. }
  326. int iCache = m_VisCache.Find( cacheEntry );
  327. if ( iCache == m_VisCache.InvalidIndex() )
  328. {
  329. if ( m_VisCache.Count() == m_VisCache.InvalidIndex() )
  330. return VIS_CACHE_INVALID;
  331. iCache = m_VisCache.Insert( cacheEntry );
  332. m_VisCache[iCache].m_flTime = gpGlobals->curtime - 2.0f * VIS_CACHE_ENTRY_LIFE;
  333. }
  334. return ( pChar1 < pChar2 ) ? iCache : - iCache - 1;
  335. }
  336. VisCacheResult_t CCombatCharVisCache::HasVisibility( int iCache ) const
  337. {
  338. if ( iCache == VIS_CACHE_INVALID )
  339. return VISCACHE_UNKNOWN;
  340. m_nTestCount++;
  341. bool bReverse = ( iCache < 0 );
  342. if ( bReverse )
  343. {
  344. iCache = - iCache - 1;
  345. }
  346. const VisCacheEntry_t &entry = m_VisCache[iCache];
  347. if ( gpGlobals->curtime - entry.m_flTime > VIS_CACHE_ENTRY_LIFE )
  348. return VISCACHE_UNKNOWN;
  349. m_nHitCount++;
  350. bool bIsVisible = !bReverse ? entry.m_bEntity1CanSeeEntity2 : entry.m_bEntity2CanSeeEntity1;
  351. return bIsVisible ? VISCACHE_IS_VISIBLE : VISCACHE_IS_NOT_VISIBLE;
  352. }
  353. void CCombatCharVisCache::RegisterVisibility( int iCache, bool bEntity1CanSeeEntity2, bool bEntity2CanSeeEntity1 )
  354. {
  355. if ( iCache == VIS_CACHE_INVALID )
  356. return;
  357. bool bReverse = ( iCache < 0 );
  358. if ( bReverse )
  359. {
  360. iCache = - iCache - 1;
  361. }
  362. VisCacheEntry_t &entry = m_VisCache[iCache];
  363. entry.m_flTime = gpGlobals->curtime;
  364. if ( !bReverse )
  365. {
  366. entry.m_bEntity1CanSeeEntity2 = bEntity1CanSeeEntity2;
  367. entry.m_bEntity2CanSeeEntity1 = bEntity2CanSeeEntity1;
  368. }
  369. else
  370. {
  371. entry.m_bEntity1CanSeeEntity2 = bEntity2CanSeeEntity1;
  372. entry.m_bEntity2CanSeeEntity1 = bEntity1CanSeeEntity2;
  373. }
  374. }
  375. static CCombatCharVisCache s_CombatCharVisCache;
  376. //-----------------------------------------------------------------------------
  377. // Purpose:
  378. //-----------------------------------------------------------------------------
  379. bool CBaseCombatCharacter::IsAbleToSee( const CBaseEntity *pEntity, FieldOfViewCheckType checkFOV )
  380. {
  381. CBaseCombatCharacter *pBCC = const_cast<CBaseEntity *>( pEntity )->MyCombatCharacterPointer();
  382. if ( pBCC )
  383. return IsAbleToSee( pBCC, checkFOV );
  384. // Test this every time; it's cheap.
  385. Vector vecEyePosition = EyePosition();
  386. Vector vecTargetPosition = pEntity->WorldSpaceCenter();
  387. #ifdef GAME_DLL
  388. Vector vecEyeToTarget;
  389. VectorSubtract( vecTargetPosition, vecEyePosition, vecEyeToTarget );
  390. float flDistToOther = VectorNormalize( vecEyeToTarget );
  391. // We can't see because they are too far in the fog
  392. if ( IsHiddenByFog( flDistToOther ) )
  393. return false;
  394. #endif
  395. if ( !ComputeLOS( vecEyePosition, vecTargetPosition ) )
  396. return false;
  397. #if defined(GAME_DLL) && defined(TERROR)
  398. if ( flDistToOther > NavObscureRange.GetFloat() )
  399. {
  400. const float flMaxDistance = 100.0f;
  401. TerrorNavArea *pTargetArea = static_cast< TerrorNavArea* >( TheNavMesh->GetNearestNavArea( vecTargetPosition, false, flMaxDistance ) );
  402. if ( !pTargetArea || pTargetArea->HasSpawnAttributes( TerrorNavArea::SPAWN_OBSCURED ) )
  403. return false;
  404. if ( ComputeTargetIsInDarkness( vecEyePosition, pTargetArea, vecTargetPosition ) )
  405. return false;
  406. }
  407. #endif
  408. return ( checkFOV != USE_FOV || IsInFieldOfView( vecTargetPosition ) );
  409. }
  410. static void ComputeSeeTestPosition( Vector *pEyePosition, CBaseCombatCharacter *pBCC )
  411. {
  412. #if defined(GAME_DLL) && defined(TERROR)
  413. if ( pBCC->IsPlayer() )
  414. {
  415. CTerrorPlayer *pPlayer = ToTerrorPlayer( pBCC );
  416. *pEyePosition = !pPlayer->IsDead() ? pPlayer->EyePosition() : pPlayer->GetDeathPosition();
  417. }
  418. else
  419. #endif
  420. {
  421. *pEyePosition = pBCC->EyePosition();
  422. }
  423. }
  424. bool CBaseCombatCharacter::IsAbleToSee( CBaseCombatCharacter *pBCC, FieldOfViewCheckType checkFOV )
  425. {
  426. Vector vecEyePosition, vecOtherEyePosition;
  427. ComputeSeeTestPosition( &vecEyePosition, this );
  428. ComputeSeeTestPosition( &vecOtherEyePosition, pBCC );
  429. #ifdef GAME_DLL
  430. Vector vecEyeToTarget;
  431. VectorSubtract( vecOtherEyePosition, vecEyePosition, vecEyeToTarget );
  432. float flDistToOther = VectorNormalize( vecEyeToTarget );
  433. // Test this every time; it's cheap.
  434. // We can't see because they are too far in the fog
  435. if ( IsHiddenByFog( flDistToOther ) )
  436. return false;
  437. #ifdef TERROR
  438. // Check this every time also, it's cheap; check to see if the enemy is in an obscured area.
  439. bool bIsInNavObscureRange = ( flDistToOther > NavObscureRange.GetFloat() );
  440. if ( bIsInNavObscureRange )
  441. {
  442. TerrorNavArea *pOtherNavArea = static_cast< TerrorNavArea* >( pBCC->GetLastKnownArea() );
  443. if ( !pOtherNavArea || pOtherNavArea->HasSpawnAttributes( TerrorNavArea::SPAWN_OBSCURED ) )
  444. return false;
  445. }
  446. #endif // TERROR
  447. #endif
  448. // Check if we have a cached-off visibility
  449. int iCache = s_CombatCharVisCache.LookupVisibility( this, pBCC );
  450. VisCacheResult_t nResult = s_CombatCharVisCache.HasVisibility( iCache );
  451. // Compute symmetric visibility
  452. if ( nResult == VISCACHE_UNKNOWN )
  453. {
  454. bool bThisCanSeeOther = false, bOtherCanSeeThis = false;
  455. if ( ComputeLOS( vecEyePosition, vecOtherEyePosition ) )
  456. {
  457. #if defined(GAME_DLL) && defined(TERROR)
  458. if ( !bIsInNavObscureRange )
  459. {
  460. bThisCanSeeOther = true, bOtherCanSeeThis = true;
  461. }
  462. else
  463. {
  464. bThisCanSeeOther = !ComputeTargetIsInDarkness( vecEyePosition, pBCC->GetLastKnownArea(), vecOtherEyePosition );
  465. bOtherCanSeeThis = !ComputeTargetIsInDarkness( vecOtherEyePosition, GetLastKnownArea(), vecEyePosition );
  466. }
  467. #else
  468. bThisCanSeeOther = true, bOtherCanSeeThis = true;
  469. #endif
  470. }
  471. s_CombatCharVisCache.RegisterVisibility( iCache, bThisCanSeeOther, bOtherCanSeeThis );
  472. nResult = bThisCanSeeOther ? VISCACHE_IS_VISIBLE : VISCACHE_IS_NOT_VISIBLE;
  473. }
  474. if ( nResult == VISCACHE_IS_VISIBLE )
  475. return ( checkFOV != USE_FOV || IsInFieldOfView( pBCC ) );
  476. return false;
  477. }
  478. class CTraceFilterNoCombatCharacters : public CTraceFilterSimple
  479. {
  480. public:
  481. CTraceFilterNoCombatCharacters( const IHandleEntity *passentity = NULL, int collisionGroup = COLLISION_GROUP_NONE )
  482. : CTraceFilterSimple( passentity, collisionGroup )
  483. {
  484. }
  485. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  486. {
  487. if ( CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ) )
  488. {
  489. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  490. if ( !pEntity )
  491. return NULL;
  492. if ( pEntity->MyCombatCharacterPointer() || pEntity->MyCombatWeaponPointer() )
  493. return false;
  494. // Honor BlockLOS - this lets us see through partially-broken doors, etc
  495. if ( !pEntity->BlocksLOS() )
  496. return false;
  497. return true;
  498. }
  499. return false;
  500. }
  501. };
  502. bool CBaseCombatCharacter::ComputeLOS( const Vector &vecEyePosition, const Vector &vecTarget ) const
  503. {
  504. // We simply can't see because the world is in the way.
  505. trace_t result;
  506. CTraceFilterNoCombatCharacters traceFilter( NULL, COLLISION_GROUP_NONE );
  507. UTIL_TraceLine( vecEyePosition, vecTarget, MASK_OPAQUE | CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_MONSTER, &traceFilter, &result );
  508. return ( result.fraction == 1.0f );
  509. }
  510. #if defined(GAME_DLL) && defined(TERROR)
  511. bool CBaseCombatCharacter::ComputeTargetIsInDarkness( const Vector &vecEyePosition, CNavArea *pTargetNavArea, const Vector &vecTargetPos ) const
  512. {
  513. if ( GetTeamNumber() != TEAM_SURVIVOR )
  514. return false;
  515. // Check light info
  516. const float flMinLightIntensity = 0.1f;
  517. if ( !pTargetNavArea || ( pTargetNavArea->GetLightIntensity() >= flMinLightIntensity ) )
  518. return false;
  519. CTraceFilterNoNPCsOrPlayer lightingFilter( this, COLLISION_GROUP_NONE );
  520. Vector vecSightDirection;
  521. VectorSubtract( vecTargetPos, vecEyePosition, vecSightDirection );
  522. VectorNormalize( vecSightDirection );
  523. trace_t result;
  524. UTIL_TraceLine( vecTargetPos, vecTargetPos + vecSightDirection * 32768.0f, MASK_L4D_VISION, &lightingFilter, &result );
  525. if ( ( result.fraction < 1.0f ) && ( ( result.surface.flags & SURF_SKY ) == 0 ) )
  526. {
  527. const float flMaxDistance = 100.0f;
  528. TerrorNavArea *pFarArea = (TerrorNavArea *)TheNavMesh->GetNearestNavArea( result.endpos, false, flMaxDistance );
  529. // Target is in darkness, the wall behind him is too, and we are too far away
  530. if ( pFarArea && pFarArea->GetLightIntensity() < flMinLightIntensity )
  531. return true;
  532. }
  533. return false;
  534. }
  535. #endif
  536. //-----------------------------------------------------------------------------
  537. /**
  538. Return true if our view direction is pointing at the given target,
  539. within the cosine of the angular tolerance. LINE OF SIGHT IS NOT CHECKED.
  540. */
  541. bool CBaseCombatCharacter::IsLookingTowards( const CBaseEntity *target, float cosTolerance ) const
  542. {
  543. return IsLookingTowards( target->WorldSpaceCenter(), cosTolerance ) || IsLookingTowards( target->EyePosition(), cosTolerance ) || IsLookingTowards( target->GetAbsOrigin(), cosTolerance );
  544. }
  545. //-----------------------------------------------------------------------------
  546. /**
  547. Return true if our view direction is pointing at the given target,
  548. within the cosine of the angular tolerance. LINE OF SIGHT IS NOT CHECKED.
  549. */
  550. bool CBaseCombatCharacter::IsLookingTowards( const Vector &target, float cosTolerance ) const
  551. {
  552. Vector toTarget = target - EyePosition();
  553. toTarget.NormalizeInPlace();
  554. Vector forward;
  555. AngleVectors( EyeAngles(), &forward );
  556. return ( DotProduct( forward, toTarget ) >= cosTolerance );
  557. }
  558. //-----------------------------------------------------------------------------
  559. /**
  560. Returns true if we are looking towards something within a tolerence determined
  561. by our field of view
  562. */
  563. bool CBaseCombatCharacter::IsInFieldOfView( CBaseEntity *entity ) const
  564. {
  565. CBasePlayer *pPlayer = ToBasePlayer( const_cast< CBaseCombatCharacter* >( this ) );
  566. float flTolerance = pPlayer ? cos( DEG2RAD( pPlayer->GetFOV() * 0.5f ) ) : BCC_DEFAULT_LOOK_TOWARDS_TOLERANCE;
  567. Vector vecForward;
  568. Vector vecEyePosition = EyePosition();
  569. AngleVectors( EyeAngles(), &vecForward );
  570. // FIXME: Use a faster check than this!
  571. // Check 3 spots, or else when standing right next to someone looking at their eyes,
  572. // the angle will be too great to see their center.
  573. Vector vecToTarget = entity->GetAbsOrigin() - vecEyePosition;
  574. vecToTarget.NormalizeInPlace();
  575. if ( DotProduct( vecForward, vecToTarget ) >= flTolerance )
  576. return true;
  577. vecToTarget = entity->WorldSpaceCenter() - vecEyePosition;
  578. vecToTarget.NormalizeInPlace();
  579. if ( DotProduct( vecForward, vecToTarget ) >= flTolerance )
  580. return true;
  581. vecToTarget = entity->EyePosition() - vecEyePosition;
  582. vecToTarget.NormalizeInPlace();
  583. return ( DotProduct( vecForward, vecToTarget ) >= flTolerance );
  584. }
  585. //-----------------------------------------------------------------------------
  586. /**
  587. Returns true if we are looking towards something within a tolerence determined
  588. by our field of view
  589. */
  590. bool CBaseCombatCharacter::IsInFieldOfView( const Vector &pos ) const
  591. {
  592. CBasePlayer *pPlayer = ToBasePlayer( const_cast< CBaseCombatCharacter* >( this ) );
  593. if ( pPlayer )
  594. return IsLookingTowards( pos, cos( DEG2RAD( pPlayer->GetFOV() * 0.5f ) ) );
  595. return IsLookingTowards( pos );
  596. }
  597. //-----------------------------------------------------------------------------
  598. /**
  599. Strictly checks Line of Sight only.
  600. */
  601. bool CBaseCombatCharacter::IsLineOfSightClear( CBaseEntity *entity, LineOfSightCheckType checkType ) const
  602. {
  603. #ifdef CLIENT_DLL
  604. if ( entity->MyCombatCharacterPointer() )
  605. return IsLineOfSightClear( entity->EyePosition(), checkType, entity );
  606. return IsLineOfSightClear( entity->WorldSpaceCenter(), checkType, entity );
  607. #else
  608. // FIXME: Should we do the same check here as the client does?
  609. return IsLineOfSightClear( entity->WorldSpaceCenter(), checkType, entity ) || IsLineOfSightClear( entity->EyePosition(), checkType, entity ) || IsLineOfSightClear( entity->GetAbsOrigin(), checkType, entity );
  610. #endif
  611. }
  612. //-----------------------------------------------------------------------------
  613. /**
  614. Strictly checks Line of Sight only.
  615. */
  616. static bool TraceFilterNoCombatCharacters( IHandleEntity *pServerEntity, int contentsMask )
  617. {
  618. // Honor BlockLOS also to allow seeing through partially-broken doors
  619. CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
  620. return ( entity->MyCombatCharacterPointer() == NULL && !entity->MyCombatWeaponPointer() && entity->BlocksLOS() );
  621. }
  622. bool CBaseCombatCharacter::IsLineOfSightClear( const Vector &pos, LineOfSightCheckType checkType, CBaseEntity *entityToIgnore ) const
  623. {
  624. #if defined(GAME_DLL) && defined(COUNT_BCC_LOS)
  625. static int count, frame;
  626. if ( frame != gpGlobals->framecount )
  627. {
  628. Msg( ">> %d\n", count );
  629. frame = gpGlobals->framecount;
  630. count = 0;
  631. }
  632. count++;
  633. #endif
  634. if( checkType == IGNORE_ACTORS )
  635. {
  636. // use the query cache unless it causes problems
  637. #if defined(GAME_DLL) && defined(TERROR)
  638. return IsLineOfSightBetweenTwoEntitiesClear( const_cast<CBaseCombatCharacter *>(this), EOFFSET_MODE_EYEPOSITION,
  639. entityToIgnore, EOFFSET_MODE_WORLDSPACE_CENTER,
  640. entityToIgnore, COLLISION_GROUP_NONE,
  641. MASK_L4D_VISION, TraceFilterNoCombatCharacters, 1.0 );
  642. #else
  643. trace_t trace;
  644. CTraceFilterNoCombatCharacters traceFilter( entityToIgnore, COLLISION_GROUP_NONE );
  645. UTIL_TraceLine( EyePosition(), pos, MASK_OPAQUE | CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_MONSTER, &traceFilter, &trace );
  646. return trace.fraction == 1.0f;
  647. #endif
  648. }
  649. else
  650. {
  651. trace_t trace;
  652. CTraceFilterSkipTwoEntities traceFilter( this, entityToIgnore, COLLISION_GROUP_NONE );
  653. UTIL_TraceLine( EyePosition(), pos, MASK_OPAQUE | CONTENTS_IGNORE_NODRAW_OPAQUE, &traceFilter, &trace );
  654. return trace.fraction == 1.0f;
  655. }
  656. }