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.
732 lines
22 KiB
732 lines
22 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "ammodef.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Switches to the best weapon that is also better than the given weapon. |
|
// Input : pCurrent - The current weapon used by the player. |
|
// Output : Returns true if the weapon was switched, false if there was no better |
|
// weapon to switch to. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseCombatCharacter::SwitchToNextBestWeapon(CBaseCombatWeapon *pCurrent) |
|
{ |
|
CBaseCombatWeapon *pNewWeapon = g_pGameRules->GetNextBestWeapon(this, pCurrent); |
|
|
|
if ( ( pNewWeapon != NULL ) && ( pNewWeapon != pCurrent ) ) |
|
{ |
|
return Weapon_Switch( pNewWeapon ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Switches to the given weapon (providing it has ammo) |
|
// Input : |
|
// Output : true is switch succeeded |
|
//----------------------------------------------------------------------------- |
|
bool CBaseCombatCharacter::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex /*=0*/ ) |
|
{ |
|
if ( pWeapon == NULL ) |
|
return false; |
|
|
|
// Already have it out? |
|
if ( m_hActiveWeapon.Get() == pWeapon ) |
|
{ |
|
if ( !m_hActiveWeapon->IsWeaponVisible() || m_hActiveWeapon->IsHolstered() ) |
|
return m_hActiveWeapon->Deploy( ); |
|
return false; |
|
} |
|
|
|
if (!Weapon_CanSwitchTo(pWeapon)) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( m_hActiveWeapon ) |
|
{ |
|
if ( !m_hActiveWeapon->Holster( pWeapon ) ) |
|
return false; |
|
} |
|
|
|
m_hActiveWeapon = pWeapon; |
|
|
|
return pWeapon->Deploy( ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns whether or not we can switch to the given weapon. |
|
// Input : pWeapon - |
|
//----------------------------------------------------------------------------- |
|
bool CBaseCombatCharacter::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) |
|
{ |
|
if (IsPlayer()) |
|
{ |
|
CBasePlayer *pPlayer = (CBasePlayer *)this; |
|
#if !defined( CLIENT_DLL ) |
|
IServerVehicle *pVehicle = pPlayer->GetVehicle(); |
|
#else |
|
IClientVehicle *pVehicle = pPlayer->GetVehicle(); |
|
#endif |
|
if (pVehicle && !pPlayer->UsingStandardWeaponsInVehicle()) |
|
return false; |
|
} |
|
|
|
if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) ) |
|
return false; |
|
|
|
if ( !pWeapon->CanDeploy() ) |
|
return false; |
|
|
|
if ( m_hActiveWeapon ) |
|
{ |
|
if ( !m_hActiveWeapon->CanHolster() ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CBaseCombatWeapon |
|
//----------------------------------------------------------------------------- |
|
CBaseCombatWeapon *CBaseCombatCharacter::GetActiveWeapon() const |
|
{ |
|
return m_hActiveWeapon; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : iCount - |
|
// iAmmoIndex - |
|
//----------------------------------------------------------------------------- |
|
void CBaseCombatCharacter::RemoveAmmo( int iCount, int iAmmoIndex ) |
|
{ |
|
if (iCount <= 0) |
|
return; |
|
|
|
// Infinite ammo? |
|
if ( GetAmmoDef()->MaxCarry( iAmmoIndex ) == INFINITE_AMMO ) |
|
return; |
|
|
|
// Ammo pickup sound |
|
m_iAmmo.Set( iAmmoIndex, MAX( m_iAmmo[iAmmoIndex] - iCount, 0 ) ); |
|
} |
|
|
|
void CBaseCombatCharacter::RemoveAmmo( int iCount, const char *szName ) |
|
{ |
|
RemoveAmmo( iCount, GetAmmoDef()->Index(szName) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseCombatCharacter::RemoveAllAmmo( ) |
|
{ |
|
for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) |
|
{ |
|
m_iAmmo.Set( i, 0 ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME: This is a sort of hack back-door only used by physgun! |
|
//----------------------------------------------------------------------------- |
|
void CBaseCombatCharacter::SetAmmoCount( int iCount, int iAmmoIndex ) |
|
{ |
|
// NOTE: No sound, no max check! Seems pretty bogus to me! |
|
m_iAmmo.Set( iAmmoIndex, iCount ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the amount of ammunition of a particular type owned |
|
// owned by the character |
|
// Input : Ammo Index |
|
// Output : The amount of ammo |
|
//----------------------------------------------------------------------------- |
|
int CBaseCombatCharacter::GetAmmoCount( int iAmmoIndex ) const |
|
{ |
|
if ( iAmmoIndex == -1 ) |
|
return 0; |
|
|
|
// Infinite ammo? |
|
if ( GetAmmoDef()->MaxCarry( iAmmoIndex ) == INFINITE_AMMO ) |
|
return 999; |
|
|
|
return m_iAmmo[ iAmmoIndex ]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns the amount of ammunition of the specified type the character's carrying |
|
//----------------------------------------------------------------------------- |
|
int CBaseCombatCharacter::GetAmmoCount( char *szName ) const |
|
{ |
|
return GetAmmoCount( GetAmmoDef()->Index(szName) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns weapon if already owns a weapon of this class |
|
//----------------------------------------------------------------------------- |
|
CBaseCombatWeapon* CBaseCombatCharacter::Weapon_OwnsThisType( const char *pszWeapon, int iSubType ) const |
|
{ |
|
// Check for duplicates |
|
for (int i=0;i<MAX_WEAPONS;i++) |
|
{ |
|
if ( m_hMyWeapons[i].Get() && FClassnameIs( m_hMyWeapons[i], pszWeapon ) ) |
|
{ |
|
// Make sure it matches the subtype |
|
if ( m_hMyWeapons[i]->GetSubType() == iSubType ) |
|
return m_hMyWeapons[i]; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
|
|
int CBaseCombatCharacter::BloodColor() |
|
{ |
|
return m_bloodColor; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Blood color (see BLOOD_COLOR_* macros in baseentity.h) |
|
//----------------------------------------------------------------------------- |
|
void CBaseCombatCharacter::SetBloodColor( int nBloodColor ) |
|
{ |
|
m_bloodColor = nBloodColor; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
The main visibility check. Checks all the entity specific reasons that could |
|
make IsVisible fail. Then checks points in space to get environmental reasons. |
|
This is LOS, plus invisibility and fog and smoke and such. |
|
*/ |
|
|
|
enum VisCacheResult_t |
|
{ |
|
VISCACHE_UNKNOWN = 0, |
|
VISCACHE_IS_VISIBLE, |
|
VISCACHE_IS_NOT_VISIBLE, |
|
}; |
|
|
|
enum |
|
{ |
|
VIS_CACHE_INVALID = 0x80000000 |
|
}; |
|
|
|
#define VIS_CACHE_ENTRY_LIFE .090f |
|
|
|
class CCombatCharVisCache : public CAutoGameSystemPerFrame |
|
{ |
|
public: |
|
virtual void FrameUpdatePreEntityThink(); |
|
virtual void LevelShutdownPreEntity(); |
|
|
|
int LookupVisibility( const CBaseCombatCharacter *pChar1, CBaseCombatCharacter *pChar2 ); |
|
VisCacheResult_t HasVisibility( int iCache ) const; |
|
void RegisterVisibility( int iCache, bool bChar1SeesChar2, bool bChar2SeesChar1 ); |
|
|
|
private: |
|
struct VisCacheEntry_t |
|
{ |
|
CHandle< CBaseCombatCharacter > m_hEntity1; |
|
CHandle< CBaseCombatCharacter > m_hEntity2; |
|
float m_flTime; |
|
bool m_bEntity1CanSeeEntity2; |
|
bool m_bEntity2CanSeeEntity1; |
|
}; |
|
|
|
class CVisCacheEntryLess |
|
{ |
|
public: |
|
CVisCacheEntryLess( int ) {} |
|
bool operator!() const { return false; } |
|
bool operator()( const VisCacheEntry_t &lhs, const VisCacheEntry_t &rhs ) const |
|
{ |
|
return ( memcmp( &lhs, &rhs, offsetof( VisCacheEntry_t, m_flTime ) ) < 0 ); |
|
} |
|
}; |
|
|
|
CUtlRBTree< VisCacheEntry_t, unsigned short, CVisCacheEntryLess > m_VisCache; |
|
|
|
mutable int m_nTestCount; |
|
mutable int m_nHitCount; |
|
}; |
|
|
|
void CCombatCharVisCache::FrameUpdatePreEntityThink() |
|
{ |
|
// Msg( "test: %d/%d\n", m_nHitCount, m_nTestCount ); |
|
|
|
// Lazy retirement of vis cache |
|
// NOTE: 256 was chosen heuristically based on a playthrough where 200 |
|
// was the max # in the viscache where nothing could be retired. |
|
if ( m_VisCache.Count() < 256 ) |
|
return; |
|
|
|
int nMaxIndex = m_VisCache.MaxElement() - 1; |
|
for ( int i = 0; i < 8; ++i ) |
|
{ |
|
int n = RandomInt( 0, nMaxIndex ); |
|
if ( !m_VisCache.IsValidIndex( n ) ) |
|
continue; |
|
|
|
const VisCacheEntry_t &entry = m_VisCache[n]; |
|
if ( !entry.m_hEntity1.IsValid() || !entry.m_hEntity2.IsValid() || ( gpGlobals->curtime - entry.m_flTime > 10.0f ) ) |
|
{ |
|
m_VisCache.RemoveAt( n ); |
|
} |
|
} |
|
} |
|
|
|
void CCombatCharVisCache::LevelShutdownPreEntity() |
|
{ |
|
m_VisCache.Purge(); |
|
} |
|
|
|
int CCombatCharVisCache::LookupVisibility( const CBaseCombatCharacter *pChar1, CBaseCombatCharacter *pChar2 ) |
|
{ |
|
VisCacheEntry_t cacheEntry; |
|
if ( pChar1 < pChar2 ) |
|
{ |
|
cacheEntry.m_hEntity1 = pChar1; |
|
cacheEntry.m_hEntity2 = pChar2; |
|
} |
|
else |
|
{ |
|
cacheEntry.m_hEntity1 = pChar2; |
|
cacheEntry.m_hEntity2 = pChar1; |
|
} |
|
|
|
int iCache = m_VisCache.Find( cacheEntry ); |
|
if ( iCache == m_VisCache.InvalidIndex() ) |
|
{ |
|
if ( m_VisCache.Count() == m_VisCache.InvalidIndex() ) |
|
return VIS_CACHE_INVALID; |
|
|
|
iCache = m_VisCache.Insert( cacheEntry ); |
|
m_VisCache[iCache].m_flTime = gpGlobals->curtime - 2.0f * VIS_CACHE_ENTRY_LIFE; |
|
} |
|
|
|
return ( pChar1 < pChar2 ) ? iCache : - iCache - 1; |
|
} |
|
|
|
VisCacheResult_t CCombatCharVisCache::HasVisibility( int iCache ) const |
|
{ |
|
if ( iCache == VIS_CACHE_INVALID ) |
|
return VISCACHE_UNKNOWN; |
|
|
|
m_nTestCount++; |
|
|
|
bool bReverse = ( iCache < 0 ); |
|
if ( bReverse ) |
|
{ |
|
iCache = - iCache - 1; |
|
} |
|
|
|
const VisCacheEntry_t &entry = m_VisCache[iCache]; |
|
if ( gpGlobals->curtime - entry.m_flTime > VIS_CACHE_ENTRY_LIFE ) |
|
return VISCACHE_UNKNOWN; |
|
|
|
m_nHitCount++; |
|
|
|
bool bIsVisible = !bReverse ? entry.m_bEntity1CanSeeEntity2 : entry.m_bEntity2CanSeeEntity1; |
|
return bIsVisible ? VISCACHE_IS_VISIBLE : VISCACHE_IS_NOT_VISIBLE; |
|
} |
|
|
|
void CCombatCharVisCache::RegisterVisibility( int iCache, bool bEntity1CanSeeEntity2, bool bEntity2CanSeeEntity1 ) |
|
{ |
|
if ( iCache == VIS_CACHE_INVALID ) |
|
return; |
|
|
|
bool bReverse = ( iCache < 0 ); |
|
if ( bReverse ) |
|
{ |
|
iCache = - iCache - 1; |
|
} |
|
|
|
VisCacheEntry_t &entry = m_VisCache[iCache]; |
|
entry.m_flTime = gpGlobals->curtime; |
|
if ( !bReverse ) |
|
{ |
|
entry.m_bEntity1CanSeeEntity2 = bEntity1CanSeeEntity2; |
|
entry.m_bEntity2CanSeeEntity1 = bEntity2CanSeeEntity1; |
|
} |
|
else |
|
{ |
|
entry.m_bEntity1CanSeeEntity2 = bEntity2CanSeeEntity1; |
|
entry.m_bEntity2CanSeeEntity1 = bEntity1CanSeeEntity2; |
|
} |
|
} |
|
|
|
static CCombatCharVisCache s_CombatCharVisCache; |
|
|
|
bool CBaseCombatCharacter::IsAbleToSee( const CBaseEntity *pEntity, FieldOfViewCheckType checkFOV ) |
|
{ |
|
CBaseCombatCharacter *pBCC = const_cast<CBaseEntity *>( pEntity )->MyCombatCharacterPointer(); |
|
if ( pBCC ) |
|
return IsAbleToSee( pBCC, checkFOV ); |
|
|
|
// Test this every time; it's cheap. |
|
Vector vecEyePosition = EyePosition(); |
|
Vector vecTargetPosition = pEntity->WorldSpaceCenter(); |
|
|
|
#ifdef GAME_DLL |
|
Vector vecEyeToTarget; |
|
VectorSubtract( vecTargetPosition, vecEyePosition, vecEyeToTarget ); |
|
float flDistToOther = VectorNormalize( vecEyeToTarget ); |
|
|
|
// We can't see because they are too far in the fog |
|
if ( IsHiddenByFog( flDistToOther ) ) |
|
return false; |
|
#endif |
|
|
|
if ( !ComputeLOS( vecEyePosition, vecTargetPosition ) ) |
|
return false; |
|
|
|
#if defined(GAME_DLL) && defined(TERROR) |
|
if ( flDistToOther > NavObscureRange.GetFloat() ) |
|
{ |
|
const float flMaxDistance = 100.0f; |
|
TerrorNavArea *pTargetArea = static_cast< TerrorNavArea* >( TheNavMesh->GetNearestNavArea( vecTargetPosition, false, flMaxDistance ) ); |
|
if ( !pTargetArea || pTargetArea->HasSpawnAttributes( TerrorNavArea::SPAWN_OBSCURED ) ) |
|
return false; |
|
|
|
if ( ComputeTargetIsInDarkness( vecEyePosition, pTargetArea, vecTargetPosition ) ) |
|
return false; |
|
} |
|
#endif |
|
|
|
return ( checkFOV != USE_FOV || IsInFieldOfView( vecTargetPosition ) ); |
|
} |
|
|
|
static void ComputeSeeTestPosition( Vector *pEyePosition, CBaseCombatCharacter *pBCC ) |
|
{ |
|
#if defined(GAME_DLL) && defined(TERROR) |
|
if ( pBCC->IsPlayer() ) |
|
{ |
|
CTerrorPlayer *pPlayer = ToTerrorPlayer( pBCC ); |
|
*pEyePosition = !pPlayer->IsDead() ? pPlayer->EyePosition() : pPlayer->GetDeathPosition(); |
|
} |
|
else |
|
#endif |
|
{ |
|
*pEyePosition = pBCC->EyePosition(); |
|
} |
|
} |
|
|
|
bool CBaseCombatCharacter::IsAbleToSee( CBaseCombatCharacter *pBCC, FieldOfViewCheckType checkFOV ) |
|
{ |
|
Vector vecEyePosition, vecOtherEyePosition; |
|
ComputeSeeTestPosition( &vecEyePosition, this ); |
|
ComputeSeeTestPosition( &vecOtherEyePosition, pBCC ); |
|
|
|
#ifdef GAME_DLL |
|
Vector vecEyeToTarget; |
|
VectorSubtract( vecOtherEyePosition, vecEyePosition, vecEyeToTarget ); |
|
float flDistToOther = VectorNormalize( vecEyeToTarget ); |
|
|
|
// Test this every time; it's cheap. |
|
// We can't see because they are too far in the fog |
|
if ( IsHiddenByFog( flDistToOther ) ) |
|
return false; |
|
|
|
#ifdef TERROR |
|
// Check this every time also, it's cheap; check to see if the enemy is in an obscured area. |
|
bool bIsInNavObscureRange = ( flDistToOther > NavObscureRange.GetFloat() ); |
|
if ( bIsInNavObscureRange ) |
|
{ |
|
TerrorNavArea *pOtherNavArea = static_cast< TerrorNavArea* >( pBCC->GetLastKnownArea() ); |
|
if ( !pOtherNavArea || pOtherNavArea->HasSpawnAttributes( TerrorNavArea::SPAWN_OBSCURED ) ) |
|
return false; |
|
} |
|
#endif // TERROR |
|
#endif |
|
|
|
// Check if we have a cached-off visibility |
|
int iCache = s_CombatCharVisCache.LookupVisibility( this, pBCC ); |
|
VisCacheResult_t nResult = s_CombatCharVisCache.HasVisibility( iCache ); |
|
|
|
// Compute symmetric visibility |
|
if ( nResult == VISCACHE_UNKNOWN ) |
|
{ |
|
bool bThisCanSeeOther = false, bOtherCanSeeThis = false; |
|
if ( ComputeLOS( vecEyePosition, vecOtherEyePosition ) ) |
|
{ |
|
#if defined(GAME_DLL) && defined(TERROR) |
|
if ( !bIsInNavObscureRange ) |
|
{ |
|
bThisCanSeeOther = true, bOtherCanSeeThis = true; |
|
} |
|
else |
|
{ |
|
bThisCanSeeOther = !ComputeTargetIsInDarkness( vecEyePosition, pBCC->GetLastKnownArea(), vecOtherEyePosition ); |
|
bOtherCanSeeThis = !ComputeTargetIsInDarkness( vecOtherEyePosition, GetLastKnownArea(), vecEyePosition ); |
|
} |
|
#else |
|
bThisCanSeeOther = true, bOtherCanSeeThis = true; |
|
#endif |
|
} |
|
|
|
s_CombatCharVisCache.RegisterVisibility( iCache, bThisCanSeeOther, bOtherCanSeeThis ); |
|
nResult = bThisCanSeeOther ? VISCACHE_IS_VISIBLE : VISCACHE_IS_NOT_VISIBLE; |
|
} |
|
|
|
if ( nResult == VISCACHE_IS_VISIBLE ) |
|
return ( checkFOV != USE_FOV || IsInFieldOfView( pBCC ) ); |
|
|
|
return false; |
|
} |
|
|
|
class CTraceFilterNoCombatCharacters : public CTraceFilterSimple |
|
{ |
|
public: |
|
CTraceFilterNoCombatCharacters( const IHandleEntity *passentity = NULL, int collisionGroup = COLLISION_GROUP_NONE ) |
|
: CTraceFilterSimple( passentity, collisionGroup ) |
|
{ |
|
} |
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
if ( CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ) ) |
|
{ |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
if ( !pEntity ) |
|
return NULL; |
|
|
|
if ( pEntity->MyCombatCharacterPointer() || pEntity->MyCombatWeaponPointer() ) |
|
return false; |
|
|
|
// Honor BlockLOS - this lets us see through partially-broken doors, etc |
|
if ( !pEntity->BlocksLOS() ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
}; |
|
|
|
bool CBaseCombatCharacter::ComputeLOS( const Vector &vecEyePosition, const Vector &vecTarget ) const |
|
{ |
|
// We simply can't see because the world is in the way. |
|
trace_t result; |
|
CTraceFilterNoCombatCharacters traceFilter( NULL, COLLISION_GROUP_NONE ); |
|
UTIL_TraceLine( vecEyePosition, vecTarget, MASK_OPAQUE | CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_MONSTER, &traceFilter, &result ); |
|
return ( result.fraction == 1.0f ); |
|
} |
|
|
|
#if defined(GAME_DLL) && defined(TERROR) |
|
bool CBaseCombatCharacter::ComputeTargetIsInDarkness( const Vector &vecEyePosition, CNavArea *pTargetNavArea, const Vector &vecTargetPos ) const |
|
{ |
|
if ( GetTeamNumber() != TEAM_SURVIVOR ) |
|
return false; |
|
|
|
// Check light info |
|
const float flMinLightIntensity = 0.1f; |
|
|
|
if ( !pTargetNavArea || ( pTargetNavArea->GetLightIntensity() >= flMinLightIntensity ) ) |
|
return false; |
|
|
|
CTraceFilterNoNPCsOrPlayer lightingFilter( this, COLLISION_GROUP_NONE ); |
|
|
|
Vector vecSightDirection; |
|
VectorSubtract( vecTargetPos, vecEyePosition, vecSightDirection ); |
|
VectorNormalize( vecSightDirection ); |
|
|
|
trace_t result; |
|
UTIL_TraceLine( vecTargetPos, vecTargetPos + vecSightDirection * 32768.0f, MASK_L4D_VISION, &lightingFilter, &result ); |
|
if ( ( result.fraction < 1.0f ) && ( ( result.surface.flags & SURF_SKY ) == 0 ) ) |
|
{ |
|
const float flMaxDistance = 100.0f; |
|
TerrorNavArea *pFarArea = (TerrorNavArea *)TheNavMesh->GetNearestNavArea( result.endpos, false, flMaxDistance ); |
|
|
|
// Target is in darkness, the wall behind him is too, and we are too far away |
|
if ( pFarArea && pFarArea->GetLightIntensity() < flMinLightIntensity ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
#endif |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
Return true if our view direction is pointing at the given target, |
|
within the cosine of the angular tolerance. LINE OF SIGHT IS NOT CHECKED. |
|
*/ |
|
bool CBaseCombatCharacter::IsLookingTowards( const CBaseEntity *target, float cosTolerance ) const |
|
{ |
|
return IsLookingTowards( target->WorldSpaceCenter(), cosTolerance ) || IsLookingTowards( target->EyePosition(), cosTolerance ) || IsLookingTowards( target->GetAbsOrigin(), cosTolerance ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
Return true if our view direction is pointing at the given target, |
|
within the cosine of the angular tolerance. LINE OF SIGHT IS NOT CHECKED. |
|
*/ |
|
bool CBaseCombatCharacter::IsLookingTowards( const Vector &target, float cosTolerance ) const |
|
{ |
|
Vector toTarget = target - EyePosition(); |
|
toTarget.NormalizeInPlace(); |
|
|
|
Vector forward; |
|
AngleVectors( EyeAngles(), &forward ); |
|
|
|
return ( DotProduct( forward, toTarget ) >= cosTolerance ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
Returns true if we are looking towards something within a tolerence determined |
|
by our field of view |
|
*/ |
|
bool CBaseCombatCharacter::IsInFieldOfView( CBaseEntity *entity ) const |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( const_cast< CBaseCombatCharacter* >( this ) ); |
|
float flTolerance = pPlayer ? cos( (float)pPlayer->GetFOV() * 0.5f ) : BCC_DEFAULT_LOOK_TOWARDS_TOLERANCE; |
|
|
|
Vector vecForward; |
|
Vector vecEyePosition = EyePosition(); |
|
AngleVectors( EyeAngles(), &vecForward ); |
|
|
|
// FIXME: Use a faster check than this! |
|
|
|
// Check 3 spots, or else when standing right next to someone looking at their eyes, |
|
// the angle will be too great to see their center. |
|
Vector vecToTarget = entity->GetAbsOrigin() - vecEyePosition; |
|
vecToTarget.NormalizeInPlace(); |
|
if ( DotProduct( vecForward, vecToTarget ) >= flTolerance ) |
|
return true; |
|
|
|
vecToTarget = entity->WorldSpaceCenter() - vecEyePosition; |
|
vecToTarget.NormalizeInPlace(); |
|
if ( DotProduct( vecForward, vecToTarget ) >= flTolerance ) |
|
return true; |
|
|
|
vecToTarget = entity->EyePosition() - vecEyePosition; |
|
vecToTarget.NormalizeInPlace(); |
|
return ( DotProduct( vecForward, vecToTarget ) >= flTolerance ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
Returns true if we are looking towards something within a tolerence determined |
|
by our field of view |
|
*/ |
|
bool CBaseCombatCharacter::IsInFieldOfView( const Vector &pos ) const |
|
{ |
|
CBasePlayer *pPlayer = ToBasePlayer( const_cast< CBaseCombatCharacter* >( this ) ); |
|
|
|
if ( pPlayer ) |
|
return IsLookingTowards( pos, cos( (float)pPlayer->GetFOV() * 0.5f ) ); |
|
|
|
return IsLookingTowards( pos ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
Strictly checks Line of Sight only. |
|
*/ |
|
|
|
bool CBaseCombatCharacter::IsLineOfSightClear( CBaseEntity *entity, LineOfSightCheckType checkType ) const |
|
{ |
|
#ifdef CLIENT_DLL |
|
if ( entity->MyCombatCharacterPointer() ) |
|
return IsLineOfSightClear( entity->EyePosition(), checkType, entity ); |
|
return IsLineOfSightClear( entity->WorldSpaceCenter(), checkType, entity ); |
|
#else |
|
// FIXME: Should we do the same check here as the client does? |
|
return IsLineOfSightClear( entity->WorldSpaceCenter(), checkType, entity ) || IsLineOfSightClear( entity->EyePosition(), checkType, entity ) || IsLineOfSightClear( entity->GetAbsOrigin(), checkType, entity ); |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
/** |
|
Strictly checks Line of Sight only. |
|
*/ |
|
static bool TraceFilterNoCombatCharacters( IHandleEntity *pServerEntity, int contentsMask ) |
|
{ |
|
// Honor BlockLOS also to allow seeing through partially-broken doors |
|
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity ); |
|
return ( entity->MyCombatCharacterPointer() == NULL && !entity->MyCombatWeaponPointer() && entity->BlocksLOS() ); |
|
} |
|
|
|
bool CBaseCombatCharacter::IsLineOfSightClear( const Vector &pos, LineOfSightCheckType checkType, CBaseEntity *entityToIgnore ) const |
|
{ |
|
#if defined(GAME_DLL) && defined(COUNT_BCC_LOS) |
|
static int count, frame; |
|
if ( frame != gpGlobals->framecount ) |
|
{ |
|
Msg( ">> %d\n", count ); |
|
frame = gpGlobals->framecount; |
|
count = 0; |
|
} |
|
count++; |
|
#endif |
|
if( checkType == IGNORE_ACTORS ) |
|
{ |
|
|
|
// use the query cache unless it causes problems |
|
#if defined(GAME_DLL) && defined(TERROR) |
|
return IsLineOfSightBetweenTwoEntitiesClear( const_cast<CBaseCombatCharacter *>(this), EOFFSET_MODE_EYEPOSITION, |
|
entityToIgnore, EOFFSET_MODE_WORLDSPACE_CENTER, |
|
entityToIgnore, COLLISION_GROUP_NONE, |
|
MASK_L4D_VISION, TraceFilterNoCombatCharacters, 1.0 ); |
|
#else |
|
trace_t trace; |
|
CTraceFilterNoCombatCharacters traceFilter( entityToIgnore, COLLISION_GROUP_NONE ); |
|
UTIL_TraceLine( EyePosition(), pos, MASK_OPAQUE | CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_MONSTER, &traceFilter, &trace ); |
|
|
|
return trace.fraction == 1.0f; |
|
#endif |
|
} |
|
else |
|
{ |
|
trace_t trace; |
|
CTraceFilterSkipTwoEntities traceFilter( this, entityToIgnore, COLLISION_GROUP_NONE ); |
|
UTIL_TraceLine( EyePosition(), pos, MASK_OPAQUE | CONTENTS_IGNORE_NODRAW_OPAQUE, &traceFilter, &trace ); |
|
|
|
return trace.fraction == 1.0f; |
|
} |
|
} |
|
|
|
|
|
/* |
|
//--------------------------------------------------------------------------------------------------------------------------- |
|
surfacedata_t * CBaseCombatCharacter::GetGroundSurface( void ) const |
|
{ |
|
Vector start( vec3_origin ); |
|
Vector end( 0, 0, -64 ); |
|
|
|
Vector vecMins, vecMaxs; |
|
CollisionProp()->WorldSpaceAABB( &vecMins, &vecMaxs ); |
|
|
|
Ray_t ray; |
|
ray.Init( start, end, vecMins, vecMaxs ); |
|
|
|
trace_t trace; |
|
UTIL_TraceRay( ray, MASK_SOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace ); |
|
|
|
if ( trace.fraction == 1.0f ) |
|
return NULL; // no ground |
|
|
|
return physprops->GetSurfaceData( trace.surface.surfaceProps ); |
|
} |
|
*/
|
|
|