diff --git a/mp/src/game/shared/baseplayer_shared.cpp b/mp/src/game/shared/baseplayer_shared.cpp index cba09eb7c..87fc50214 100644 --- a/mp/src/game/shared/baseplayer_shared.cpp +++ b/mp/src/game/shared/baseplayer_shared.cpp @@ -1065,205 +1065,218 @@ float IntervalDistance( float x, float x0, float x1 ) return 0; } +float EstimatedDistanceSquared(const Vector &point, const CBaseEntity *pEntity) +{ + Vector nearestPoint; + pEntity->CollisionProp()->CalcNearestPoint( point, &nearestPoint ); + return (nearestPoint - point).LengthSqr(); +} + CBaseEntity *CBasePlayer::FindUseEntity() { - Vector forward, up; - EyeVectors( &forward, NULL, &up ); - - trace_t tr; - // Search for objects in a sphere (tests for entities that are not solid, yet still useable) + Vector forward; + EyeVectors( &forward, NULL, NULL ); Vector searchCenter = EyePosition(); - // NOTE: Some debris objects are useable too, so hit those as well - // A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. - int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; + // Some debris objects are +usable, and clip brushes can be made into + // +usable entities. + int usableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; + + // However, we occasionally need to ignore clip brushes so that objects + // inside of or beyond them can be +used. + const int usableContentsIgnoreClip = usableContents & ~CONTENTS_PLAYERCLIP; #ifdef CSTRIKE_DLL - useableContents = MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS; + usableContents = MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS; #endif #ifdef HL1_DLL - useableContents = MASK_SOLID; -#endif -#ifndef CLIENT_DLL - CBaseEntity *pFoundByTrace = NULL; + usableContents = MASK_SOLID; #endif - // UNDONE: Might be faster to just fold this range into the sphere query - CBaseEntity *pObject = NULL; + // First, try to hit an entity directly in front of the player. + trace_t directTrace; + UTIL_TraceLine( searchCenter, searchCenter + PLAYER_USE_RADIUS * forward, usableContents, this, COLLISION_GROUP_NONE, &directTrace ); - float nearestDist = FLT_MAX; - // try the hit entity if there is one, or the ground entity if there isn't. CBaseEntity *pNearest = NULL; + CBaseEntity *pObject = directTrace.m_pEnt; + bool bUsable = IsUseableEntity( pObject, 0 ); - const int NUM_TANGENTS = 8; - // trace a box at successive angles down - // forward, 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15 - const float tangents[NUM_TANGENTS] = { 0, 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f }; - for ( int i = 0; i < NUM_TANGENTS; i++ ) + if ( !bUsable && (directTrace.contents & CONTENTS_PLAYERCLIP) ) { - if ( i == 0 ) - { - UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); - } - else - { - Vector down = forward - tangents[i]*up; - VectorNormalize(down); - UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr ); - } - pObject = tr.m_pEnt; + // If we hit a non-usable clip brush, then try tracing again, ignoring + // clip brushes, so that object inside of or beyond them can be +used. -#ifndef CLIENT_DLL - pFoundByTrace = pObject; -#endif - bool bUsable = IsUseableEntity(pObject, 0); - while ( pObject && !bUsable && pObject->GetMoveParent() ) - { - pObject = pObject->GetMoveParent(); - bUsable = IsUseableEntity(pObject, 0); - } + // NOTE: If a usable clip brush is behind a non-usable clip brush, + // then we won't be able to +use it, unfortunately. This is difficult + // to workaround. For example, the non-usable clip brush cannot be added + // to a list of ignored entities, because it is part of the worldspawn, + // and we cannot ignore that. + UTIL_TraceLine( searchCenter, searchCenter + PLAYER_USE_RADIUS * forward, usableContentsIgnoreClip, this, COLLISION_GROUP_NONE, &directTrace ); - if ( bUsable ) - { - Vector delta = tr.endpos - tr.startpos; - float centerZ = CollisionProp()->WorldSpaceCenter().z; - delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z ); - float dist = delta.Length(); - if ( dist < PLAYER_USE_RADIUS ) - { -#ifndef CLIENT_DLL - - if ( sv_debug_player_use.GetBool() ) - { - NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); - NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); - } - - if ( pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) - { - // If about to select an NPC, do a more thorough check to ensure - // that we're selecting the right one from a group. - pObject = DoubleCheckUseNPC( pObject, searchCenter, forward ); - } -#endif - if ( sv_debug_player_use.GetBool() ) - { - Msg( "Trace using: %s\n", pObject ? pObject->GetDebugName() : "no usable entity found" ); - } - - pNearest = pObject; - - // if this is directly under the cursor just return it now - if ( i == 0 ) - return pObject; - } - } + pObject = directTrace.m_pEnt; + bUsable = IsUseableEntity( pObject, 0 ); } - // check ground entity first - // if you've got a useable ground entity, then shrink the cone of this search to 45 degrees - // otherwise, search out in a 90 degree cone (hemisphere) - if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) ) + // If the object is not usable, determine if a move ancestor is. + while ( pObject && !bUsable && pObject->GetMoveParent() ) { - pNearest = GetGroundEntity(); + pObject = pObject->GetMoveParent(); + bUsable = IsUseableEntity( pObject, 0 ); } - if ( pNearest ) + + if ( bUsable ) { - // estimate nearest object by distance from the view vector - Vector point; - pNearest->CollisionProp()->CalcNearestPoint( searchCenter, &point ); - nearestDist = CalcDistanceToLine( point, searchCenter, forward ); - if ( sv_debug_player_use.GetBool() ) - { - Msg("Trace found %s, dist %.2f\n", pNearest->GetClassname(), nearestDist ); - } - } - - for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) - { - if ( !pObject ) - continue; - - if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) ) - continue; - - // see if it's more roughly in front of the player than previous guess - Vector point; - pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); - - Vector dir = point - searchCenter; - VectorNormalize(dir); - float dot = DotProduct( dir, forward ); - - // Need to be looking at the object more or less - if ( dot < 0.8 ) - continue; - - float dist = CalcDistanceToLine( point, searchCenter, forward ); + pNearest = pObject; if ( sv_debug_player_use.GetBool() ) { - Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), dist ); + const float distSquared = EstimatedDistanceSquared( searchCenter, pNearest ); + Msg( "Line trace found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( distSquared ) ); } + } + else + { + // Any objects directly in front of us weren't usable and close enough. + // Next, determine if the ground entity is usable. + float nearestDistSquared = FLT_MAX; - if ( dist < nearestDist ) + if ( GetGroundEntity() && IsUseableEntity( GetGroundEntity(), FCAP_USE_ONGROUND ) ) { - // Since this has purely been a radius search to this point, we now - // make sure the object isn't behind glass or a grate. - trace_t trCheckOccluded; - UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); + pNearest = GetGroundEntity(); + nearestDistSquared = EstimatedDistanceSquared( searchCenter, pNearest ); - if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) + if ( sv_debug_player_use.GetBool() ) { - pNearest = pObject; - nearestDist = dist; + Msg( "Ground query found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( nearestDistSquared ) ); } } + + // Next, determine which of the reachable and usable objects in the cone + // volume directly in front of player is closest, and whether or not any + // is closer than the ground entity. + for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + if ( !IsUseableEntity( pObject, 0 ) ) + continue; + + // Determine if the object is nearer than the previous nearest object. + Vector nearestPoint; + pObject->CollisionProp()->CalcNearestPoint( searchCenter, &nearestPoint ); + + Vector dir = nearestPoint - searchCenter; + VectorNormalize(dir); + + // Need to be looking at the object more or less. + // NOTE: If the closest point on the object happens to be off to the + // side, even though the object is predominantly if front of the player, + // then it will be rejected, unfortunately. + if ( DotProduct( dir, forward ) < 0.8 ) + continue; + + const float distSquared = (nearestPoint - searchCenter).LengthSqr(); + + if ( sv_debug_player_use.GetBool() ) + { + Msg( "Cone query found usable entity: %s, distance: %.2f\n", pObject->GetDebugName(), sqrt( distSquared ) ); + } + + if ( distSquared < nearestDistSquared ) + { + // The object is inside the cone, but it may be blocked by another + // object. Verify that we can trace to the object directly. + + // NOTE: this traces to a particular point on the object's collision + // prop. If the trace to that point happens to be blocked by another + // object, even though other nearby visible points aren't, then we + // won't be able to +use the object, unfortunately. + + // We ignore clip brushes here so that objects in or behind clip + // brushes can be +used. If the current object itself is a usable + // clip brush, this trace will fail to hit it, but unless it is + // blocked, the trace fraction will typically be 1.0 anyway, and + // pNearest will be set to the usable clip brush object. + trace_t tr; + UTIL_TraceLine( searchCenter, nearestPoint, usableContentsIgnoreClip, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction == 1.0 || tr.m_pEnt == pObject ) + { + pNearest = pObject; + nearestDistSquared = distSquared; + } + } + } + } #ifndef CLIENT_DLL if ( !pNearest ) { - // Haven't found anything near the player to use, nor any NPC's at distance. - // Check to see if the player is trying to select an NPC through a rail, fence, or other 'see-though' volume. - trace_t trAllies; - UTIL_TraceLine( searchCenter, searchCenter + forward * PLAYER_USE_RADIUS, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &trAllies ); + // If we haven't found an object that the player can use yet, + // allow a player to use an NPC through 'see-through' volumes + // (rails, fenches, windows, grates, etc.). + UTIL_TraceLine( searchCenter, searchCenter + PLAYER_USE_RADIUS * forward, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &directTrace ); + pObject = directTrace.m_pEnt; - if ( trAllies.m_pEnt && IsUseableEntity( trAllies.m_pEnt, 0 ) && trAllies.m_pEnt->MyNPCPointer() && trAllies.m_pEnt->MyNPCPointer()->IsPlayerAlly( this ) ) + if ( pObject && IsUseableEntity( pObject, 0 ) && pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) { // This is an NPC, take it! - pNearest = trAllies.m_pEnt; + pNearest = pObject; + + if ( sv_debug_player_use.GetBool() ) + { + const float distSquared = EstimatedDistanceSquared( searchCenter, pNearest ); + Msg( "Line trace found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( distSquared ) ); + } } } - if ( pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) + if ( pNearest == directTrace.m_pEnt && pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) { - pNearest = DoubleCheckUseNPC( pNearest, searchCenter, forward ); - } + // If about to select an NPC with a line trace, do a more thorough + // check to ensure that we're selecting the right one from a group. + // Lengthen trace slightly to account for the fact that we're + // tracing for hitboxes, which are usually farther away than OBBs. + trace_t tr; + UTIL_TraceLine( searchCenter, searchCenter + 1.1 * PLAYER_USE_RADIUS * forward, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + pObject = tr.m_pEnt; + if( pObject != pNearest && pObject && pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) + { + // Player is selecting a different NPC through some negative space + // in the first NPC's hitboxes (between legs, over shoulder, etc). + pNearest = tr.m_pEnt; + directTrace = tr; + + if ( sv_debug_player_use.GetBool() ) + { + const float distSquared = EstimatedDistanceSquared( searchCenter, pNearest ); + Msg( "Hitbox line trace found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( distSquared ) ); + } + } + } +#endif + + // Draw debug overlays and print debug messages. if ( sv_debug_player_use.GetBool() ) { +#ifndef CLIENT_DLL if ( !pNearest ) { - NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 ); - NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 ); + NDebugOverlay::Line( searchCenter, directTrace.endpos, 255, 0, 0, true, 30 ); + NDebugOverlay::Cross3D( directTrace.endpos, 16, 255, 0, 0, true, 30 ); } - else if ( pNearest == pFoundByTrace ) + else if ( pNearest == directTrace.m_pEnt ) { - NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); - NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); + NDebugOverlay::Line( searchCenter, directTrace.endpos, 0, 255, 0, true, 30 ); + NDebugOverlay::Cross3D( directTrace.endpos, 16, 0, 255, 0, true, 30 ); } else { NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 ); } - } #endif - - if ( sv_debug_player_use.GetBool() ) - { - Msg( "Radial using: %s\n", pNearest ? pNearest->GetDebugName() : "no usable entity found" ); + Msg( "Using: %s\n", pNearest ? pNearest->GetDebugName() : "no usable entity found" ); } return pNearest; @@ -1298,7 +1311,7 @@ void CBasePlayer::PlayerUse ( void ) EyeVectors( &forward, NULL, &up ); trace_t tr; - // Search for objects in a sphere (tests for entities that are not solid, yet still useable) + // Search for objects in a sphere (tests for entities that are not solid, yet still usable) Vector searchCenter = EyePosition(); CUsePushFilter filter; @@ -2081,4 +2094,3 @@ bool fogparams_t::operator !=( const fogparams_t& other ) const return false; } - diff --git a/sp/src/game/shared/baseplayer_shared.cpp b/sp/src/game/shared/baseplayer_shared.cpp index cba09eb7c..87fc50214 100644 --- a/sp/src/game/shared/baseplayer_shared.cpp +++ b/sp/src/game/shared/baseplayer_shared.cpp @@ -1065,205 +1065,218 @@ float IntervalDistance( float x, float x0, float x1 ) return 0; } +float EstimatedDistanceSquared(const Vector &point, const CBaseEntity *pEntity) +{ + Vector nearestPoint; + pEntity->CollisionProp()->CalcNearestPoint( point, &nearestPoint ); + return (nearestPoint - point).LengthSqr(); +} + CBaseEntity *CBasePlayer::FindUseEntity() { - Vector forward, up; - EyeVectors( &forward, NULL, &up ); - - trace_t tr; - // Search for objects in a sphere (tests for entities that are not solid, yet still useable) + Vector forward; + EyeVectors( &forward, NULL, NULL ); Vector searchCenter = EyePosition(); - // NOTE: Some debris objects are useable too, so hit those as well - // A button, etc. can be made out of clip brushes, make sure it's +useable via a traceline, too. - int useableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; + // Some debris objects are +usable, and clip brushes can be made into + // +usable entities. + int usableContents = MASK_SOLID | CONTENTS_DEBRIS | CONTENTS_PLAYERCLIP; + + // However, we occasionally need to ignore clip brushes so that objects + // inside of or beyond them can be +used. + const int usableContentsIgnoreClip = usableContents & ~CONTENTS_PLAYERCLIP; #ifdef CSTRIKE_DLL - useableContents = MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS; + usableContents = MASK_NPCSOLID_BRUSHONLY | MASK_OPAQUE_AND_NPCS; #endif #ifdef HL1_DLL - useableContents = MASK_SOLID; -#endif -#ifndef CLIENT_DLL - CBaseEntity *pFoundByTrace = NULL; + usableContents = MASK_SOLID; #endif - // UNDONE: Might be faster to just fold this range into the sphere query - CBaseEntity *pObject = NULL; + // First, try to hit an entity directly in front of the player. + trace_t directTrace; + UTIL_TraceLine( searchCenter, searchCenter + PLAYER_USE_RADIUS * forward, usableContents, this, COLLISION_GROUP_NONE, &directTrace ); - float nearestDist = FLT_MAX; - // try the hit entity if there is one, or the ground entity if there isn't. CBaseEntity *pNearest = NULL; + CBaseEntity *pObject = directTrace.m_pEnt; + bool bUsable = IsUseableEntity( pObject, 0 ); - const int NUM_TANGENTS = 8; - // trace a box at successive angles down - // forward, 45 deg, 30 deg, 20 deg, 15 deg, 10 deg, -10, -15 - const float tangents[NUM_TANGENTS] = { 0, 1, 0.57735026919f, 0.3639702342f, 0.267949192431f, 0.1763269807f, -0.1763269807f, -0.267949192431f }; - for ( int i = 0; i < NUM_TANGENTS; i++ ) + if ( !bUsable && (directTrace.contents & CONTENTS_PLAYERCLIP) ) { - if ( i == 0 ) - { - UTIL_TraceLine( searchCenter, searchCenter + forward * 1024, useableContents, this, COLLISION_GROUP_NONE, &tr ); - } - else - { - Vector down = forward - tangents[i]*up; - VectorNormalize(down); - UTIL_TraceHull( searchCenter, searchCenter + down * 72, -Vector(16,16,16), Vector(16,16,16), useableContents, this, COLLISION_GROUP_NONE, &tr ); - } - pObject = tr.m_pEnt; + // If we hit a non-usable clip brush, then try tracing again, ignoring + // clip brushes, so that object inside of or beyond them can be +used. -#ifndef CLIENT_DLL - pFoundByTrace = pObject; -#endif - bool bUsable = IsUseableEntity(pObject, 0); - while ( pObject && !bUsable && pObject->GetMoveParent() ) - { - pObject = pObject->GetMoveParent(); - bUsable = IsUseableEntity(pObject, 0); - } + // NOTE: If a usable clip brush is behind a non-usable clip brush, + // then we won't be able to +use it, unfortunately. This is difficult + // to workaround. For example, the non-usable clip brush cannot be added + // to a list of ignored entities, because it is part of the worldspawn, + // and we cannot ignore that. + UTIL_TraceLine( searchCenter, searchCenter + PLAYER_USE_RADIUS * forward, usableContentsIgnoreClip, this, COLLISION_GROUP_NONE, &directTrace ); - if ( bUsable ) - { - Vector delta = tr.endpos - tr.startpos; - float centerZ = CollisionProp()->WorldSpaceCenter().z; - delta.z = IntervalDistance( tr.endpos.z, centerZ + CollisionProp()->OBBMins().z, centerZ + CollisionProp()->OBBMaxs().z ); - float dist = delta.Length(); - if ( dist < PLAYER_USE_RADIUS ) - { -#ifndef CLIENT_DLL - - if ( sv_debug_player_use.GetBool() ) - { - NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); - NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); - } - - if ( pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) - { - // If about to select an NPC, do a more thorough check to ensure - // that we're selecting the right one from a group. - pObject = DoubleCheckUseNPC( pObject, searchCenter, forward ); - } -#endif - if ( sv_debug_player_use.GetBool() ) - { - Msg( "Trace using: %s\n", pObject ? pObject->GetDebugName() : "no usable entity found" ); - } - - pNearest = pObject; - - // if this is directly under the cursor just return it now - if ( i == 0 ) - return pObject; - } - } + pObject = directTrace.m_pEnt; + bUsable = IsUseableEntity( pObject, 0 ); } - // check ground entity first - // if you've got a useable ground entity, then shrink the cone of this search to 45 degrees - // otherwise, search out in a 90 degree cone (hemisphere) - if ( GetGroundEntity() && IsUseableEntity(GetGroundEntity(), FCAP_USE_ONGROUND) ) + // If the object is not usable, determine if a move ancestor is. + while ( pObject && !bUsable && pObject->GetMoveParent() ) { - pNearest = GetGroundEntity(); + pObject = pObject->GetMoveParent(); + bUsable = IsUseableEntity( pObject, 0 ); } - if ( pNearest ) + + if ( bUsable ) { - // estimate nearest object by distance from the view vector - Vector point; - pNearest->CollisionProp()->CalcNearestPoint( searchCenter, &point ); - nearestDist = CalcDistanceToLine( point, searchCenter, forward ); - if ( sv_debug_player_use.GetBool() ) - { - Msg("Trace found %s, dist %.2f\n", pNearest->GetClassname(), nearestDist ); - } - } - - for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) - { - if ( !pObject ) - continue; - - if ( !IsUseableEntity( pObject, FCAP_USE_IN_RADIUS ) ) - continue; - - // see if it's more roughly in front of the player than previous guess - Vector point; - pObject->CollisionProp()->CalcNearestPoint( searchCenter, &point ); - - Vector dir = point - searchCenter; - VectorNormalize(dir); - float dot = DotProduct( dir, forward ); - - // Need to be looking at the object more or less - if ( dot < 0.8 ) - continue; - - float dist = CalcDistanceToLine( point, searchCenter, forward ); + pNearest = pObject; if ( sv_debug_player_use.GetBool() ) { - Msg("Radius found %s, dist %.2f\n", pObject->GetClassname(), dist ); + const float distSquared = EstimatedDistanceSquared( searchCenter, pNearest ); + Msg( "Line trace found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( distSquared ) ); } + } + else + { + // Any objects directly in front of us weren't usable and close enough. + // Next, determine if the ground entity is usable. + float nearestDistSquared = FLT_MAX; - if ( dist < nearestDist ) + if ( GetGroundEntity() && IsUseableEntity( GetGroundEntity(), FCAP_USE_ONGROUND ) ) { - // Since this has purely been a radius search to this point, we now - // make sure the object isn't behind glass or a grate. - trace_t trCheckOccluded; - UTIL_TraceLine( searchCenter, point, useableContents, this, COLLISION_GROUP_NONE, &trCheckOccluded ); + pNearest = GetGroundEntity(); + nearestDistSquared = EstimatedDistanceSquared( searchCenter, pNearest ); - if ( trCheckOccluded.fraction == 1.0 || trCheckOccluded.m_pEnt == pObject ) + if ( sv_debug_player_use.GetBool() ) { - pNearest = pObject; - nearestDist = dist; + Msg( "Ground query found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( nearestDistSquared ) ); } } + + // Next, determine which of the reachable and usable objects in the cone + // volume directly in front of player is closest, and whether or not any + // is closer than the ground entity. + for ( CEntitySphereQuery sphere( searchCenter, PLAYER_USE_RADIUS ); ( pObject = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) + { + if ( !IsUseableEntity( pObject, 0 ) ) + continue; + + // Determine if the object is nearer than the previous nearest object. + Vector nearestPoint; + pObject->CollisionProp()->CalcNearestPoint( searchCenter, &nearestPoint ); + + Vector dir = nearestPoint - searchCenter; + VectorNormalize(dir); + + // Need to be looking at the object more or less. + // NOTE: If the closest point on the object happens to be off to the + // side, even though the object is predominantly if front of the player, + // then it will be rejected, unfortunately. + if ( DotProduct( dir, forward ) < 0.8 ) + continue; + + const float distSquared = (nearestPoint - searchCenter).LengthSqr(); + + if ( sv_debug_player_use.GetBool() ) + { + Msg( "Cone query found usable entity: %s, distance: %.2f\n", pObject->GetDebugName(), sqrt( distSquared ) ); + } + + if ( distSquared < nearestDistSquared ) + { + // The object is inside the cone, but it may be blocked by another + // object. Verify that we can trace to the object directly. + + // NOTE: this traces to a particular point on the object's collision + // prop. If the trace to that point happens to be blocked by another + // object, even though other nearby visible points aren't, then we + // won't be able to +use the object, unfortunately. + + // We ignore clip brushes here so that objects in or behind clip + // brushes can be +used. If the current object itself is a usable + // clip brush, this trace will fail to hit it, but unless it is + // blocked, the trace fraction will typically be 1.0 anyway, and + // pNearest will be set to the usable clip brush object. + trace_t tr; + UTIL_TraceLine( searchCenter, nearestPoint, usableContentsIgnoreClip, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction == 1.0 || tr.m_pEnt == pObject ) + { + pNearest = pObject; + nearestDistSquared = distSquared; + } + } + } + } #ifndef CLIENT_DLL if ( !pNearest ) { - // Haven't found anything near the player to use, nor any NPC's at distance. - // Check to see if the player is trying to select an NPC through a rail, fence, or other 'see-though' volume. - trace_t trAllies; - UTIL_TraceLine( searchCenter, searchCenter + forward * PLAYER_USE_RADIUS, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &trAllies ); + // If we haven't found an object that the player can use yet, + // allow a player to use an NPC through 'see-through' volumes + // (rails, fenches, windows, grates, etc.). + UTIL_TraceLine( searchCenter, searchCenter + PLAYER_USE_RADIUS * forward, MASK_OPAQUE_AND_NPCS, this, COLLISION_GROUP_NONE, &directTrace ); + pObject = directTrace.m_pEnt; - if ( trAllies.m_pEnt && IsUseableEntity( trAllies.m_pEnt, 0 ) && trAllies.m_pEnt->MyNPCPointer() && trAllies.m_pEnt->MyNPCPointer()->IsPlayerAlly( this ) ) + if ( pObject && IsUseableEntity( pObject, 0 ) && pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) { // This is an NPC, take it! - pNearest = trAllies.m_pEnt; + pNearest = pObject; + + if ( sv_debug_player_use.GetBool() ) + { + const float distSquared = EstimatedDistanceSquared( searchCenter, pNearest ); + Msg( "Line trace found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( distSquared ) ); + } } } - if ( pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) + if ( pNearest == directTrace.m_pEnt && pNearest && pNearest->MyNPCPointer() && pNearest->MyNPCPointer()->IsPlayerAlly( this ) ) { - pNearest = DoubleCheckUseNPC( pNearest, searchCenter, forward ); - } + // If about to select an NPC with a line trace, do a more thorough + // check to ensure that we're selecting the right one from a group. + // Lengthen trace slightly to account for the fact that we're + // tracing for hitboxes, which are usually farther away than OBBs. + trace_t tr; + UTIL_TraceLine( searchCenter, searchCenter + 1.1 * PLAYER_USE_RADIUS * forward, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + pObject = tr.m_pEnt; + if( pObject != pNearest && pObject && pObject->MyNPCPointer() && pObject->MyNPCPointer()->IsPlayerAlly( this ) ) + { + // Player is selecting a different NPC through some negative space + // in the first NPC's hitboxes (between legs, over shoulder, etc). + pNearest = tr.m_pEnt; + directTrace = tr; + + if ( sv_debug_player_use.GetBool() ) + { + const float distSquared = EstimatedDistanceSquared( searchCenter, pNearest ); + Msg( "Hitbox line trace found usable entity: %s, distance: %.2f\n", pNearest->GetDebugName(), sqrt( distSquared ) ); + } + } + } +#endif + + // Draw debug overlays and print debug messages. if ( sv_debug_player_use.GetBool() ) { +#ifndef CLIENT_DLL if ( !pNearest ) { - NDebugOverlay::Line( searchCenter, tr.endpos, 255, 0, 0, true, 30 ); - NDebugOverlay::Cross3D( tr.endpos, 16, 255, 0, 0, true, 30 ); + NDebugOverlay::Line( searchCenter, directTrace.endpos, 255, 0, 0, true, 30 ); + NDebugOverlay::Cross3D( directTrace.endpos, 16, 255, 0, 0, true, 30 ); } - else if ( pNearest == pFoundByTrace ) + else if ( pNearest == directTrace.m_pEnt ) { - NDebugOverlay::Line( searchCenter, tr.endpos, 0, 255, 0, true, 30 ); - NDebugOverlay::Cross3D( tr.endpos, 16, 0, 255, 0, true, 30 ); + NDebugOverlay::Line( searchCenter, directTrace.endpos, 0, 255, 0, true, 30 ); + NDebugOverlay::Cross3D( directTrace.endpos, 16, 0, 255, 0, true, 30 ); } else { NDebugOverlay::Box( pNearest->WorldSpaceCenter(), Vector(-8, -8, -8), Vector(8, 8, 8), 0, 255, 0, true, 30 ); } - } #endif - - if ( sv_debug_player_use.GetBool() ) - { - Msg( "Radial using: %s\n", pNearest ? pNearest->GetDebugName() : "no usable entity found" ); + Msg( "Using: %s\n", pNearest ? pNearest->GetDebugName() : "no usable entity found" ); } return pNearest; @@ -1298,7 +1311,7 @@ void CBasePlayer::PlayerUse ( void ) EyeVectors( &forward, NULL, &up ); trace_t tr; - // Search for objects in a sphere (tests for entities that are not solid, yet still useable) + // Search for objects in a sphere (tests for entities that are not solid, yet still usable) Vector searchCenter = EyePosition(); CUsePushFilter filter; @@ -2081,4 +2094,3 @@ bool fogparams_t::operator !=( const fogparams_t& other ) const return false; } -