summaryrefslogtreecommitdiff
path: root/game/server/NextBot/NextBotLocomotionInterface.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/NextBot/NextBotLocomotionInterface.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'game/server/NextBot/NextBotLocomotionInterface.cpp')
-rw-r--r--game/server/NextBot/NextBotLocomotionInterface.cpp520
1 files changed, 520 insertions, 0 deletions
diff --git a/game/server/NextBot/NextBotLocomotionInterface.cpp b/game/server/NextBot/NextBotLocomotionInterface.cpp
new file mode 100644
index 0000000..9acc865
--- /dev/null
+++ b/game/server/NextBot/NextBotLocomotionInterface.cpp
@@ -0,0 +1,520 @@
+// NextBotLocomotionInterface.cpp
+// Common functionality for all NextBot locomotors
+// Author: Michael Booth, April 2005
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+
+#include "BasePropDoor.h"
+
+#include "nav_area.h"
+#include "NextBot.h"
+#include "NextBotUtil.h"
+#include "NextBotLocomotionInterface.h"
+#include "NextBotBodyInterface.h"
+
+#include "tier0/vprof.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// how far a bot must move to not be considered "stuck"
+#define STUCK_RADIUS 100.0f
+
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Reset to initial state
+ */
+ILocomotion::ILocomotion( INextBot *bot ) : INextBotComponent( bot )
+{
+ Reset();
+}
+
+ILocomotion::~ILocomotion()
+{
+}
+
+void ILocomotion::Reset( void )
+{
+ INextBotComponent::Reset();
+
+ m_motionVector = Vector( 1.0f, 0.0f, 0.0f );
+ m_speed = 0.0f;
+ m_groundMotionVector = m_motionVector;
+ m_groundSpeed = m_speed;
+
+ m_moveRequestTimer.Invalidate();
+
+ m_isStuck = false;
+ m_stuckTimer.Invalidate();
+ m_stuckPos = vec3_origin;
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Update internal state
+ */
+void ILocomotion::Update( void )
+{
+ StuckMonitor();
+
+ // maintain motion vector and speed values
+ const Vector &vel = GetVelocity();
+ m_speed = vel.Length();
+ m_groundSpeed = vel.AsVector2D().Length();
+
+ const float velocityThreshold = 10.0f;
+ if ( m_speed > velocityThreshold )
+ {
+ m_motionVector = vel / m_speed;
+ }
+
+ if ( m_groundSpeed > velocityThreshold )
+ {
+ m_groundMotionVector.x = vel.x / m_groundSpeed;
+ m_groundMotionVector.y = vel.y / m_groundSpeed;
+ m_groundMotionVector.z = 0.0f;
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ // show motion vector
+ NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_groundMotionVector, 3.0f, 100, 255, 0, 255, true, 0.1f );
+ NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_motionVector, 5.0f, 255, 255, 0, 255, true, 0.1f );
+ }
+}
+
+
+//----------------------------------------------------------------------------
+void ILocomotion::AdjustPosture( const Vector &moveGoal )
+{
+ // This function has no effect if we're not standing or crouching
+ IBody *body = GetBot()->GetBodyInterface();
+ if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
+ return;
+
+ //
+ // Stand or crouch as needed
+ //
+
+ // get bounding limits, ignoring step-upable height
+ const Vector &mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() );
+
+ const float halfSize = body->GetHullWidth()/2.0f;
+ Vector standMaxs( halfSize, halfSize, body->GetStandHullHeight() );
+
+ trace_t trace;
+ NextBotTraversableTraceFilter filter( GetBot(), ILocomotion::IMMEDIATELY );
+
+ // snap forward movement vector along floor
+ const Vector &groundNormal = GetGroundNormal();
+ const Vector &feet = GetFeet();
+ Vector moveDir = moveGoal - feet;
+ float moveLength = moveDir.NormalizeInPlace();
+ Vector left( -moveDir.y, moveDir.x, 0.0f );
+ Vector goal = feet + moveLength * CrossProduct( left, groundNormal ).Normalized();
+
+ TraceHull( feet, goal, mins, standMaxs, body->GetSolidMask(), &filter, &trace );
+
+ if ( trace.fraction >= 1.0f && !trace.startsolid )
+ {
+ // no collision while standing
+ if ( body->IsActualPosture( IBody::CROUCH ) )
+ {
+ body->SetDesiredPosture( IBody::STAND );
+ }
+ return;
+ }
+
+ if ( body->IsActualPosture( IBody::CROUCH ) )
+ return;
+
+ // crouch hull check
+ Vector crouchMaxs( halfSize, halfSize, body->GetCrouchHullHeight() );
+
+ TraceHull( feet, goal, mins, crouchMaxs, body->GetSolidMask(), &filter, &trace );
+
+ if ( trace.fraction >= 1.0f && !trace.startsolid )
+ {
+ // no collision while crouching
+ body->SetDesiredPosture( IBody::CROUCH );
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Move directly towards the given position
+ */
+void ILocomotion::Approach( const Vector &goalPos, float goalWeight )
+{
+ // there is a desire to move
+ m_moveRequestTimer.Start();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Move the bot to the precise given position immediately
+ */
+void ILocomotion::DriveTo( const Vector &pos )
+{
+ // there is a desire to move
+ m_moveRequestTimer.Start();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this locomotor could potentially move along the line given.
+ * If false is returned, fraction of walkable ray is returned in 'fraction'
+ */
+bool ILocomotion::IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when, float *fraction ) const
+{
+ VPROF_BUDGET( "Locomotion::IsPotentiallyTraversable", "NextBotExpensive" );
+
+ // if 'to' is high above us, it's not directly traversable
+ // Adding a bit of fudge room to allow for floating point roundoff errors
+ if ( ( to.z - from.z ) > GetMaxJumpHeight() + 0.1f )
+ {
+ Vector along = to - from;
+ along.NormalizeInPlace();
+ if ( along.z > GetTraversableSlopeLimit() )
+ {
+ if ( fraction )
+ {
+ *fraction = 0.0f;
+ }
+ return false;
+ }
+ }
+
+ trace_t result;
+ NextBotTraversableTraceFilter filter( GetBot(), when );
+
+ // use a small hull since we cannot simulate collision resolution and avoidance along the way
+ const float probeSize = 0.25f * GetBot()->GetBodyInterface()->GetHullWidth(); // Cant be TOO small, or open stairwells/grates/etc will cause problems
+ const float probeZ = GetStepHeight();
+
+ Vector hullMin( -probeSize, -probeSize, probeZ );
+ Vector hullMax( probeSize, probeSize, GetBot()->GetBodyInterface()->GetCrouchHullHeight() );
+ TraceHull( from, to, hullMin, hullMax, GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
+
+/*
+ if ( result.DidHit() )
+ {
+ NDebugOverlay::SweptBox( from, result.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 9999.9f );
+ NDebugOverlay::SweptBox( result.endpos, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 9999.9f );
+ }
+ else
+ {
+ NDebugOverlay::SweptBox( from, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 0.1f );
+ }
+*/
+
+ if ( fraction )
+ {
+ *fraction = result.fraction;
+ }
+
+ return ( result.fraction >= 1.0f ) && ( !result.startsolid );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Return true if there is a possible "gap" that will need to be jumped over
+ * If true is returned, fraction of ray before gap is returned in 'fraction'
+ */
+bool ILocomotion::HasPotentialGap( const Vector &from, const Vector &desiredTo, float *fraction ) const
+{
+ VPROF_BUDGET( "Locomotion::HasPotentialGap", "NextBot" );
+
+ // find section of this ray that is actually traversable
+ float traversableFraction;
+ IsPotentiallyTraversable( from, desiredTo, IMMEDIATELY, &traversableFraction );
+
+ // compute end of traversable ray
+ Vector to = from + ( desiredTo - from ) * traversableFraction;
+
+ Vector forward = to - from;
+ float length = forward.NormalizeInPlace();
+
+ IBody *body = GetBot()->GetBodyInterface();
+
+ float step = body->GetHullWidth()/2.0f;
+
+ // scan along the line checking for gaps
+ Vector pos = from;
+ Vector delta = step * forward;
+ for( float t = 0.0f; t < (length + step); t += step )
+ {
+ if ( IsGap( pos, forward ) )
+ {
+ if ( fraction )
+ {
+ *fraction = ( t - step ) / ( length + step );
+ }
+
+ return true;
+ }
+
+ pos += delta;
+ }
+
+ if ( fraction )
+ {
+ *fraction = 1.0f;
+ }
+
+ return false;
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Return true if there is a "gap" here when moving in the given direction.
+ * A "gap" is a vertical dropoff that is too high to jump back up to.
+ */
+bool ILocomotion::IsGap( const Vector &pos, const Vector &forward ) const
+{
+ VPROF_BUDGET( "Locomotion::IsGap", "NextBotSpiky" );
+
+ IBody *body = GetBot()->GetBodyInterface();
+
+ //float halfWidth = ( body ) ? body->GetHullWidth()/2.0f : 1.0f;
+
+ // can't really jump effectively when crouched anyhow
+ //float hullHeight = ( body ) ? body->GetStandHullHeight() : 1.0f;
+
+ // use a small hull since we cannot simulate collision resolution and avoidance along the way
+ const float halfWidth = 1.0f;
+ const float hullHeight = 1.0f;
+
+ unsigned int mask = ( body ) ? body->GetSolidMask() : MASK_PLAYERSOLID;
+
+ trace_t ground;
+
+ NextBotTraceFilterIgnoreActors filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
+
+ TraceHull( pos + Vector( 0, 0, GetStepHeight() ), // start up a bit to handle rough terrain
+ pos + Vector( 0, 0, -GetMaxJumpHeight() ),
+ Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
+ mask, &filter, &ground );
+
+// int r,g,b;
+//
+// if ( ground.fraction >= 1.0f && !ground.startsolid )
+// {
+// r = 255, g = 0, b = 0;
+// }
+// else
+// {
+// r = 0, g = 255, b = 0;
+// }
+//
+// NDebugOverlay::SweptBox( pos,
+// pos + Vector( 0, 0, -GetStepHeight() ),
+// Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
+// vec3_angle,
+// r, g, b, 255, 3.0f );
+
+ // if trace hit nothing, there's a gap ahead of us
+ return ( ground.fraction >= 1.0f && !ground.startsolid );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+bool ILocomotion::IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when ) const
+{
+ if ( obstacle->IsWorld() )
+ return false;
+
+ // assume bot will open a door in its path
+ if ( FClassnameIs( obstacle, "prop_door*" ) || FClassnameIs( obstacle, "func_door*" ) )
+ {
+ CBasePropDoor *door = dynamic_cast< CBasePropDoor * >( obstacle );
+
+ if ( door && door->IsDoorOpen() )
+ {
+ // open doors are obstacles
+ return false;
+ }
+
+ return true;
+ }
+
+ // if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
+ if ( FClassnameIs( obstacle, "func_brush" ) )
+ {
+ CFuncBrush *brush = (CFuncBrush *)obstacle;
+
+ switch ( brush->m_iSolidity )
+ {
+ case CFuncBrush::BRUSHSOLID_ALWAYS:
+ return false;
+ case CFuncBrush::BRUSHSOLID_NEVER:
+ return true;
+ case CFuncBrush::BRUSHSOLID_TOGGLE:
+ return true;
+ }
+ }
+
+ if ( when == IMMEDIATELY )
+ {
+ // special rules in specific games can immediately break some breakables, etc.
+ return false;
+ }
+
+ // assume bot will EVENTUALLY break breakables in its path
+ return GetBot()->IsAbleToBreak( obstacle );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+bool ILocomotion::IsAreaTraversable( const CNavArea *baseArea ) const
+{
+ return !baseArea->IsBlocked( GetBot()->GetEntity()->GetTeamNumber() );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset stuck status to un-stuck
+ */
+void ILocomotion::ClearStuckStatus( const char *reason )
+{
+ if ( IsStuck() )
+ {
+ m_isStuck = false;
+
+ // tell other components we're no longer stuck
+ GetBot()->OnUnStuck();
+ }
+
+ // always reset stuck monitoring data in case we cleared preemptively are were not yet stuck
+ m_stuckPos = GetFeet();
+ m_stuckTimer.Start();
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ DevMsg( "%3.2f: ClearStuckStatus: %s %s\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), reason );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Stuck check
+ */
+void ILocomotion::StuckMonitor( void )
+{
+ // a timer is needed to smooth over a few frames of inactivity due to state changes, etc.
+ // we only want to detect idle situations when the bot really doesn't "want" to move.
+ const float idleTime = 0.25f;
+ if ( m_moveRequestTimer.IsGreaterThen( idleTime ) )
+ {
+ // we have no desire to move, and therefore cannot emit stuck events
+
+ // prepare our internal state for when the bot starts to move next
+ m_stuckPos = GetFeet();
+ m_stuckTimer.Start();
+
+ return;
+ }
+
+// if ( !IsOnGround() )
+// {
+// // can't be stuck when in-air
+// ClearStuckStatus( "Off the ground" );
+// return;
+// }
+
+// if ( IsUsingLadder() )
+// {
+// // can't be stuck when on a ladder (for now)
+// ClearStuckStatus( "On a ladder" );
+// return;
+// }
+
+ if ( IsStuck() )
+ {
+ // we are/were stuck - have we moved enough to consider ourselves "dislodged"
+ if ( GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
+ {
+ // we've just become un-stuck
+ ClearStuckStatus( "UN-STUCK" );
+ }
+ else
+ {
+ // still stuck - periodically resend the event
+ if ( m_stillStuckTimer.IsElapsed() )
+ {
+ m_stillStuckTimer.Start( 1.0f );
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ DevMsg( "%3.2f: %s STILL STUCK\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier() );
+ NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 1.0f );
+ }
+
+ GetBot()->OnStuck();
+ }
+ }
+ }
+ else
+ {
+ // we're not stuck - yet
+
+ if ( /*IsClimbingOrJumping() || */GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
+ {
+ // we have moved - reset anchor
+ m_stuckPos = GetFeet();
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::Cross3D( m_stuckPos, 3.0f, 255, 0, 255, true, 3.0f );
+ }
+
+ m_stuckTimer.Start();
+ }
+ else
+ {
+ // within stuck range of anchor. if we've been here too long, we're stuck
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::Line( GetBot()->GetEntity()->WorldSpaceCenter(), m_stuckPos, 255, 0, 255, true, 0.1f );
+ }
+
+ float minMoveSpeed = 0.1f * GetDesiredSpeed() + 0.1f;
+ float escapeTime = STUCK_RADIUS / minMoveSpeed;
+ if ( m_stuckTimer.IsGreaterThen( escapeTime ) )
+ {
+ // we have taken too long - we're stuck
+ m_isStuck = true;
+
+ if ( GetBot()->IsDebugging( NEXTBOT_ERRORS ) )
+ {
+ DevMsg( "%3.2f: %s STUCK at position( %3.2f, %3.2f, %3.2f )\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), m_stuckPos.x, m_stuckPos.y, m_stuckPos.z );
+
+ NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 15.0f ), QAngle( -90.0f, 0, 0 ), 3.0f, 255, 255, 0, 255, true, 1.0f );
+ NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 9999999.9f );
+ }
+
+ // tell other components we've become stuck
+ GetBot()->OnStuck();
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+const Vector &ILocomotion::GetFeet( void ) const
+{
+ return GetBot()->GetEntity()->GetAbsOrigin();
+}
+