Team Fortress 2 Source Code as on 22/4/2020
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.

520 lines
14 KiB

  1. // NextBotLocomotionInterface.cpp
  2. // Common functionality for all NextBot locomotors
  3. // Author: Michael Booth, April 2005
  4. //========= Copyright Valve Corporation, All rights reserved. ============//
  5. #include "cbase.h"
  6. #include "BasePropDoor.h"
  7. #include "nav_area.h"
  8. #include "NextBot.h"
  9. #include "NextBotUtil.h"
  10. #include "NextBotLocomotionInterface.h"
  11. #include "NextBotBodyInterface.h"
  12. #include "tier0/vprof.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. // how far a bot must move to not be considered "stuck"
  16. #define STUCK_RADIUS 100.0f
  17. //----------------------------------------------------------------------------------------------------------
  18. /**
  19. * Reset to initial state
  20. */
  21. ILocomotion::ILocomotion( INextBot *bot ) : INextBotComponent( bot )
  22. {
  23. Reset();
  24. }
  25. ILocomotion::~ILocomotion()
  26. {
  27. }
  28. void ILocomotion::Reset( void )
  29. {
  30. INextBotComponent::Reset();
  31. m_motionVector = Vector( 1.0f, 0.0f, 0.0f );
  32. m_speed = 0.0f;
  33. m_groundMotionVector = m_motionVector;
  34. m_groundSpeed = m_speed;
  35. m_moveRequestTimer.Invalidate();
  36. m_isStuck = false;
  37. m_stuckTimer.Invalidate();
  38. m_stuckPos = vec3_origin;
  39. }
  40. //----------------------------------------------------------------------------------------------------------
  41. /**
  42. * Update internal state
  43. */
  44. void ILocomotion::Update( void )
  45. {
  46. StuckMonitor();
  47. // maintain motion vector and speed values
  48. const Vector &vel = GetVelocity();
  49. m_speed = vel.Length();
  50. m_groundSpeed = vel.AsVector2D().Length();
  51. const float velocityThreshold = 10.0f;
  52. if ( m_speed > velocityThreshold )
  53. {
  54. m_motionVector = vel / m_speed;
  55. }
  56. if ( m_groundSpeed > velocityThreshold )
  57. {
  58. m_groundMotionVector.x = vel.x / m_groundSpeed;
  59. m_groundMotionVector.y = vel.y / m_groundSpeed;
  60. m_groundMotionVector.z = 0.0f;
  61. }
  62. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  63. {
  64. // show motion vector
  65. NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_groundMotionVector, 3.0f, 100, 255, 0, 255, true, 0.1f );
  66. NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_motionVector, 5.0f, 255, 255, 0, 255, true, 0.1f );
  67. }
  68. }
  69. //----------------------------------------------------------------------------
  70. void ILocomotion::AdjustPosture( const Vector &moveGoal )
  71. {
  72. // This function has no effect if we're not standing or crouching
  73. IBody *body = GetBot()->GetBodyInterface();
  74. if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
  75. return;
  76. //
  77. // Stand or crouch as needed
  78. //
  79. // get bounding limits, ignoring step-upable height
  80. const Vector &mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() );
  81. const float halfSize = body->GetHullWidth()/2.0f;
  82. Vector standMaxs( halfSize, halfSize, body->GetStandHullHeight() );
  83. trace_t trace;
  84. NextBotTraversableTraceFilter filter( GetBot(), ILocomotion::IMMEDIATELY );
  85. // snap forward movement vector along floor
  86. const Vector &groundNormal = GetGroundNormal();
  87. const Vector &feet = GetFeet();
  88. Vector moveDir = moveGoal - feet;
  89. float moveLength = moveDir.NormalizeInPlace();
  90. Vector left( -moveDir.y, moveDir.x, 0.0f );
  91. Vector goal = feet + moveLength * CrossProduct( left, groundNormal ).Normalized();
  92. TraceHull( feet, goal, mins, standMaxs, body->GetSolidMask(), &filter, &trace );
  93. if ( trace.fraction >= 1.0f && !trace.startsolid )
  94. {
  95. // no collision while standing
  96. if ( body->IsActualPosture( IBody::CROUCH ) )
  97. {
  98. body->SetDesiredPosture( IBody::STAND );
  99. }
  100. return;
  101. }
  102. if ( body->IsActualPosture( IBody::CROUCH ) )
  103. return;
  104. // crouch hull check
  105. Vector crouchMaxs( halfSize, halfSize, body->GetCrouchHullHeight() );
  106. TraceHull( feet, goal, mins, crouchMaxs, body->GetSolidMask(), &filter, &trace );
  107. if ( trace.fraction >= 1.0f && !trace.startsolid )
  108. {
  109. // no collision while crouching
  110. body->SetDesiredPosture( IBody::CROUCH );
  111. }
  112. }
  113. //----------------------------------------------------------------------------------------------------------
  114. /**
  115. * Move directly towards the given position
  116. */
  117. void ILocomotion::Approach( const Vector &goalPos, float goalWeight )
  118. {
  119. // there is a desire to move
  120. m_moveRequestTimer.Start();
  121. }
  122. //----------------------------------------------------------------------------------------------------------
  123. /**
  124. * Move the bot to the precise given position immediately
  125. */
  126. void ILocomotion::DriveTo( const Vector &pos )
  127. {
  128. // there is a desire to move
  129. m_moveRequestTimer.Start();
  130. }
  131. //----------------------------------------------------------------------------------------------------------
  132. /**
  133. * Return true if this locomotor could potentially move along the line given.
  134. * If false is returned, fraction of walkable ray is returned in 'fraction'
  135. */
  136. bool ILocomotion::IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when, float *fraction ) const
  137. {
  138. VPROF_BUDGET( "Locomotion::IsPotentiallyTraversable", "NextBotExpensive" );
  139. // if 'to' is high above us, it's not directly traversable
  140. // Adding a bit of fudge room to allow for floating point roundoff errors
  141. if ( ( to.z - from.z ) > GetMaxJumpHeight() + 0.1f )
  142. {
  143. Vector along = to - from;
  144. along.NormalizeInPlace();
  145. if ( along.z > GetTraversableSlopeLimit() )
  146. {
  147. if ( fraction )
  148. {
  149. *fraction = 0.0f;
  150. }
  151. return false;
  152. }
  153. }
  154. trace_t result;
  155. NextBotTraversableTraceFilter filter( GetBot(), when );
  156. // use a small hull since we cannot simulate collision resolution and avoidance along the way
  157. const float probeSize = 0.25f * GetBot()->GetBodyInterface()->GetHullWidth(); // Cant be TOO small, or open stairwells/grates/etc will cause problems
  158. const float probeZ = GetStepHeight();
  159. Vector hullMin( -probeSize, -probeSize, probeZ );
  160. Vector hullMax( probeSize, probeSize, GetBot()->GetBodyInterface()->GetCrouchHullHeight() );
  161. TraceHull( from, to, hullMin, hullMax, GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
  162. /*
  163. if ( result.DidHit() )
  164. {
  165. NDebugOverlay::SweptBox( from, result.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 9999.9f );
  166. NDebugOverlay::SweptBox( result.endpos, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 9999.9f );
  167. }
  168. else
  169. {
  170. NDebugOverlay::SweptBox( from, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 0.1f );
  171. }
  172. */
  173. if ( fraction )
  174. {
  175. *fraction = result.fraction;
  176. }
  177. return ( result.fraction >= 1.0f ) && ( !result.startsolid );
  178. }
  179. //----------------------------------------------------------------------------------------------------------
  180. /**
  181. * Return true if there is a possible "gap" that will need to be jumped over
  182. * If true is returned, fraction of ray before gap is returned in 'fraction'
  183. */
  184. bool ILocomotion::HasPotentialGap( const Vector &from, const Vector &desiredTo, float *fraction ) const
  185. {
  186. VPROF_BUDGET( "Locomotion::HasPotentialGap", "NextBot" );
  187. // find section of this ray that is actually traversable
  188. float traversableFraction;
  189. IsPotentiallyTraversable( from, desiredTo, IMMEDIATELY, &traversableFraction );
  190. // compute end of traversable ray
  191. Vector to = from + ( desiredTo - from ) * traversableFraction;
  192. Vector forward = to - from;
  193. float length = forward.NormalizeInPlace();
  194. IBody *body = GetBot()->GetBodyInterface();
  195. float step = body->GetHullWidth()/2.0f;
  196. // scan along the line checking for gaps
  197. Vector pos = from;
  198. Vector delta = step * forward;
  199. for( float t = 0.0f; t < (length + step); t += step )
  200. {
  201. if ( IsGap( pos, forward ) )
  202. {
  203. if ( fraction )
  204. {
  205. *fraction = ( t - step ) / ( length + step );
  206. }
  207. return true;
  208. }
  209. pos += delta;
  210. }
  211. if ( fraction )
  212. {
  213. *fraction = 1.0f;
  214. }
  215. return false;
  216. }
  217. //----------------------------------------------------------------------------------------------------------
  218. /**
  219. * Return true if there is a "gap" here when moving in the given direction.
  220. * A "gap" is a vertical dropoff that is too high to jump back up to.
  221. */
  222. bool ILocomotion::IsGap( const Vector &pos, const Vector &forward ) const
  223. {
  224. VPROF_BUDGET( "Locomotion::IsGap", "NextBotSpiky" );
  225. IBody *body = GetBot()->GetBodyInterface();
  226. //float halfWidth = ( body ) ? body->GetHullWidth()/2.0f : 1.0f;
  227. // can't really jump effectively when crouched anyhow
  228. //float hullHeight = ( body ) ? body->GetStandHullHeight() : 1.0f;
  229. // use a small hull since we cannot simulate collision resolution and avoidance along the way
  230. const float halfWidth = 1.0f;
  231. const float hullHeight = 1.0f;
  232. unsigned int mask = ( body ) ? body->GetSolidMask() : MASK_PLAYERSOLID;
  233. trace_t ground;
  234. NextBotTraceFilterIgnoreActors filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
  235. TraceHull( pos + Vector( 0, 0, GetStepHeight() ), // start up a bit to handle rough terrain
  236. pos + Vector( 0, 0, -GetMaxJumpHeight() ),
  237. Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
  238. mask, &filter, &ground );
  239. // int r,g,b;
  240. //
  241. // if ( ground.fraction >= 1.0f && !ground.startsolid )
  242. // {
  243. // r = 255, g = 0, b = 0;
  244. // }
  245. // else
  246. // {
  247. // r = 0, g = 255, b = 0;
  248. // }
  249. //
  250. // NDebugOverlay::SweptBox( pos,
  251. // pos + Vector( 0, 0, -GetStepHeight() ),
  252. // Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
  253. // vec3_angle,
  254. // r, g, b, 255, 3.0f );
  255. // if trace hit nothing, there's a gap ahead of us
  256. return ( ground.fraction >= 1.0f && !ground.startsolid );
  257. }
  258. //----------------------------------------------------------------------------------------------------------
  259. bool ILocomotion::IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when ) const
  260. {
  261. if ( obstacle->IsWorld() )
  262. return false;
  263. // assume bot will open a door in its path
  264. if ( FClassnameIs( obstacle, "prop_door*" ) || FClassnameIs( obstacle, "func_door*" ) )
  265. {
  266. CBasePropDoor *door = dynamic_cast< CBasePropDoor * >( obstacle );
  267. if ( door && door->IsDoorOpen() )
  268. {
  269. // open doors are obstacles
  270. return false;
  271. }
  272. return true;
  273. }
  274. // if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
  275. if ( FClassnameIs( obstacle, "func_brush" ) )
  276. {
  277. CFuncBrush *brush = (CFuncBrush *)obstacle;
  278. switch ( brush->m_iSolidity )
  279. {
  280. case CFuncBrush::BRUSHSOLID_ALWAYS:
  281. return false;
  282. case CFuncBrush::BRUSHSOLID_NEVER:
  283. return true;
  284. case CFuncBrush::BRUSHSOLID_TOGGLE:
  285. return true;
  286. }
  287. }
  288. if ( when == IMMEDIATELY )
  289. {
  290. // special rules in specific games can immediately break some breakables, etc.
  291. return false;
  292. }
  293. // assume bot will EVENTUALLY break breakables in its path
  294. return GetBot()->IsAbleToBreak( obstacle );
  295. }
  296. //--------------------------------------------------------------------------------------------------------------
  297. bool ILocomotion::IsAreaTraversable( const CNavArea *baseArea ) const
  298. {
  299. return !baseArea->IsBlocked( GetBot()->GetEntity()->GetTeamNumber() );
  300. }
  301. //--------------------------------------------------------------------------------------------------------------
  302. /**
  303. * Reset stuck status to un-stuck
  304. */
  305. void ILocomotion::ClearStuckStatus( const char *reason )
  306. {
  307. if ( IsStuck() )
  308. {
  309. m_isStuck = false;
  310. // tell other components we're no longer stuck
  311. GetBot()->OnUnStuck();
  312. }
  313. // always reset stuck monitoring data in case we cleared preemptively are were not yet stuck
  314. m_stuckPos = GetFeet();
  315. m_stuckTimer.Start();
  316. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  317. {
  318. DevMsg( "%3.2f: ClearStuckStatus: %s %s\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), reason );
  319. }
  320. }
  321. //--------------------------------------------------------------------------------------------------------------
  322. /**
  323. * Stuck check
  324. */
  325. void ILocomotion::StuckMonitor( void )
  326. {
  327. // a timer is needed to smooth over a few frames of inactivity due to state changes, etc.
  328. // we only want to detect idle situations when the bot really doesn't "want" to move.
  329. const float idleTime = 0.25f;
  330. if ( m_moveRequestTimer.IsGreaterThen( idleTime ) )
  331. {
  332. // we have no desire to move, and therefore cannot emit stuck events
  333. // prepare our internal state for when the bot starts to move next
  334. m_stuckPos = GetFeet();
  335. m_stuckTimer.Start();
  336. return;
  337. }
  338. // if ( !IsOnGround() )
  339. // {
  340. // // can't be stuck when in-air
  341. // ClearStuckStatus( "Off the ground" );
  342. // return;
  343. // }
  344. // if ( IsUsingLadder() )
  345. // {
  346. // // can't be stuck when on a ladder (for now)
  347. // ClearStuckStatus( "On a ladder" );
  348. // return;
  349. // }
  350. if ( IsStuck() )
  351. {
  352. // we are/were stuck - have we moved enough to consider ourselves "dislodged"
  353. if ( GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
  354. {
  355. // we've just become un-stuck
  356. ClearStuckStatus( "UN-STUCK" );
  357. }
  358. else
  359. {
  360. // still stuck - periodically resend the event
  361. if ( m_stillStuckTimer.IsElapsed() )
  362. {
  363. m_stillStuckTimer.Start( 1.0f );
  364. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  365. {
  366. DevMsg( "%3.2f: %s STILL STUCK\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier() );
  367. NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 1.0f );
  368. }
  369. GetBot()->OnStuck();
  370. }
  371. }
  372. }
  373. else
  374. {
  375. // we're not stuck - yet
  376. if ( /*IsClimbingOrJumping() || */GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
  377. {
  378. // we have moved - reset anchor
  379. m_stuckPos = GetFeet();
  380. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  381. {
  382. NDebugOverlay::Cross3D( m_stuckPos, 3.0f, 255, 0, 255, true, 3.0f );
  383. }
  384. m_stuckTimer.Start();
  385. }
  386. else
  387. {
  388. // within stuck range of anchor. if we've been here too long, we're stuck
  389. if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
  390. {
  391. NDebugOverlay::Line( GetBot()->GetEntity()->WorldSpaceCenter(), m_stuckPos, 255, 0, 255, true, 0.1f );
  392. }
  393. float minMoveSpeed = 0.1f * GetDesiredSpeed() + 0.1f;
  394. float escapeTime = STUCK_RADIUS / minMoveSpeed;
  395. if ( m_stuckTimer.IsGreaterThen( escapeTime ) )
  396. {
  397. // we have taken too long - we're stuck
  398. m_isStuck = true;
  399. if ( GetBot()->IsDebugging( NEXTBOT_ERRORS ) )
  400. {
  401. 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 );
  402. NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 15.0f ), QAngle( -90.0f, 0, 0 ), 3.0f, 255, 255, 0, 255, true, 1.0f );
  403. NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 9999999.9f );
  404. }
  405. // tell other components we've become stuck
  406. GetBot()->OnStuck();
  407. }
  408. }
  409. }
  410. }
  411. //--------------------------------------------------------------------------------------------------------------
  412. const Vector &ILocomotion::GetFeet( void ) const
  413. {
  414. return GetBot()->GetEntity()->GetAbsOrigin();
  415. }