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.

985 lines
27 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. // Author: Michael S. Booth ([email protected]), 2003
  8. #include "cbase.h"
  9. #include "cs_bot.h"
  10. #include "obstacle_pushaway.h"
  11. #include "fmtstr.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. const float NearBreakableCheckDist = 20.0f;
  15. const float FarBreakableCheckDist = 300.0f;
  16. #define DEBUG_BREAKABLES 0
  17. #define DEBUG_DOORS 0
  18. //--------------------------------------------------------------------------------------------------------------
  19. #if DEBUG_BREAKABLES
  20. static void DrawOutlinedQuad( const Vector &p1,
  21. const Vector &p2,
  22. const Vector &p3,
  23. const Vector &p4,
  24. int r, int g, int b,
  25. float duration )
  26. {
  27. NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration );
  28. NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration );
  29. NDebugOverlay::Line( p1, p2, r, g, b, false, duration );
  30. NDebugOverlay::Line( p2, p3, r, g, b, false, duration );
  31. NDebugOverlay::Line( p3, p4, r, g, b, false, duration );
  32. NDebugOverlay::Line( p4, p1, r, g, b, false, duration );
  33. }
  34. ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" );
  35. #endif // DEBUG_BREAKABLES
  36. //--------------------------------------------------------------------------------------------------------------
  37. CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator )
  38. {
  39. CBaseEntity *entity = NULL;
  40. Ray_t ray;
  41. ray.Init( start, end, mins, maxs );
  42. ::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator );
  43. if ( enumerator->m_nAlreadyHit > 0 )
  44. {
  45. entity = enumerator->m_AlreadyHit[0];
  46. }
  47. #if DEBUG_BREAKABLES
  48. if ( entity )
  49. {
  50. DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() );
  51. }
  52. else
  53. {
  54. DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 );
  55. }
  56. #endif // DEBUG_BREAKABLES
  57. return entity;
  58. }
  59. //--------------------------------------------------------------------------------------------------------------
  60. /**
  61. * Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one.
  62. */
  63. CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck )
  64. {
  65. Vector goal;
  66. int pathIndex = FindPathPoint( distance, &goal, NULL );
  67. bool isDegeneratePath = ( pathIndex == m_pathLength );
  68. if ( isDegeneratePath )
  69. {
  70. goal = m_goalPosition;
  71. }
  72. goal.z += HalfHumanHeight;
  73. Vector mins, maxs;
  74. mins = Vector( 0, 0, -HalfHumanWidth );
  75. maxs = Vector( 0, 0, HalfHumanHeight );
  76. if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck )
  77. {
  78. mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
  79. maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
  80. }
  81. CBaseEntity *entity = NULL;
  82. if ( isDegeneratePath )
  83. {
  84. entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
  85. #if DEBUG_BREAKABLES
  86. if ( entity )
  87. {
  88. NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
  89. }
  90. #endif // DEBUG_BREAKABLES
  91. }
  92. else
  93. {
  94. int startIndex = MAX( 0, m_pathIndex );
  95. float distanceLeft = distance;
  96. // HACK: start with an index one lower than normal, so we can trace from the bot's location to the
  97. // start of the path nodes.
  98. for( int i=startIndex-1; i<m_pathLength-1; ++i )
  99. {
  100. Vector start, end;
  101. if ( i == startIndex - 1 )
  102. {
  103. start = GetAbsOrigin();
  104. end = m_path[i+1].pos;
  105. }
  106. else
  107. {
  108. start = m_path[i].pos;
  109. end = m_path[i+1].pos;
  110. if ( m_path[i+1].how == GO_LADDER_UP )
  111. {
  112. // Need two checks. First we'll check along the ladder
  113. start = m_path[i].pos;
  114. end = m_path[i+1].ladder->m_top;
  115. }
  116. else if ( m_path[i].how == GO_LADDER_UP )
  117. {
  118. start = m_path[i].ladder->m_top;
  119. }
  120. else if ( m_path[i+1].how == GO_LADDER_DOWN )
  121. {
  122. // Need two checks. First we'll check along the ladder
  123. start = m_path[i].pos;
  124. end = m_path[i+1].ladder->m_bottom;
  125. }
  126. else if ( m_path[i].how == GO_LADDER_DOWN )
  127. {
  128. start = m_path[i].ladder->m_bottom;
  129. }
  130. }
  131. float segmentLength = (start - end).Length();
  132. if ( distanceLeft - segmentLength < 0 )
  133. {
  134. // scale our segment back so we don't look too far
  135. Vector direction = end - start;
  136. direction.NormalizeInPlace();
  137. end = start + direction * distanceLeft;
  138. }
  139. entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
  140. if ( entity )
  141. {
  142. #if DEBUG_BREAKABLES
  143. NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
  144. #endif // DEBUG_BREAKABLES
  145. break;
  146. }
  147. if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder
  148. break;
  149. distanceLeft -= segmentLength;
  150. if ( distanceLeft < 0 )
  151. break;
  152. if ( i != startIndex - 1 && m_path[i+1].ladder )
  153. {
  154. // Now we'll check from the ladder out to the endpoint
  155. start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top;
  156. end = m_path[i+1].pos;
  157. entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
  158. if ( entity )
  159. {
  160. #if DEBUG_BREAKABLES
  161. NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
  162. #endif // DEBUG_BREAKABLES
  163. break;
  164. }
  165. }
  166. }
  167. }
  168. if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) )
  169. return NULL;
  170. return entity;
  171. }
  172. //--------------------------------------------------------------------------------------------------------------
  173. void CCSBot::PushawayTouch( CBaseEntity *pOther )
  174. {
  175. #if DEBUG_BREAKABLES
  176. NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f );
  177. #endif // DEBUG_BREAKABLES
  178. // if we're not stuck or crouched, we don't care
  179. if ( !m_isStuck && !IsCrouching() )
  180. return;
  181. // See if it's breakable
  182. CBaseEntity *props[1];
  183. CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
  184. enumerator.EnumElement( pOther );
  185. if ( enumerator.m_nAlreadyHit == 1 )
  186. {
  187. // it's breakable - try to shoot it.
  188. SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
  189. }
  190. }
  191. //--------------------------------------------------------------------------------------------------------------
  192. /**
  193. * Check for breakable physics props and other breakable entities. We do this here instead of catching them
  194. * in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also,
  195. * looking ahead like this lets us anticipate when we'll need to break something, and do it before being
  196. * stopped by it.
  197. */
  198. void CCSBot::BreakablesCheck( void )
  199. {
  200. SNPROF( "BreakablesCheck" );
  201. if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
  202. return;
  203. #if DEBUG_BREAKABLES
  204. /*
  205. // Debug code to visually mark all breakables near us
  206. {
  207. Ray_t ray;
  208. Vector origin = WorldSpaceCenter();
  209. Vector mins( -400, -400, -400 );
  210. Vector maxs( 400, 400, 400 );
  211. ray.Init( origin, origin, mins, maxs );
  212. CBaseEntity *props[40];
  213. CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
  214. ::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
  215. for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
  216. {
  217. CBaseEntity *prop = props[i];
  218. if ( prop && prop->m_takedamage == DAMAGE_YES )
  219. {
  220. CFmtStr msg;
  221. const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
  222. if ( prop->m_iHealth > 200 )
  223. {
  224. NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
  225. prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
  226. }
  227. else
  228. {
  229. NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
  230. prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
  231. }
  232. }
  233. }
  234. }
  235. */
  236. #endif // DEBUG_BREAKABLES
  237. if ( IsAttacking() )
  238. {
  239. // make sure we aren't running into a breakable trying to knife an enemy
  240. if ( IsUsingKnife() && m_enemy != NULL )
  241. {
  242. CBaseEntity *breakables[1];
  243. CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
  244. CBaseEntity *breakable = NULL;
  245. Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
  246. Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
  247. breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
  248. if ( breakable )
  249. {
  250. #if DEBUG_BREAKABLES
  251. NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
  252. #endif // DEBUG_BREAKABLES
  253. // look at it (chances are we'll already be looking at it, since it's between us and our enemy)
  254. SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
  255. // break it (again, don't wait: we don't have ammo, since we're using the knife, and we're looking mostly at it anyway)
  256. PrimaryAttack();
  257. }
  258. }
  259. return;
  260. }
  261. if ( !HasPath() )
  262. return;
  263. bool isNear = true;
  264. // Check just in front of us on the path
  265. CBaseEntity *breakables[4];
  266. CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
  267. CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
  268. // If we don't have an object right in front of us, check a ways out
  269. if ( !breakable )
  270. {
  271. breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
  272. isNear = false;
  273. }
  274. // Try to shoot a breakable we know about
  275. if ( breakable )
  276. {
  277. // look at it
  278. SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
  279. }
  280. // break it
  281. if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
  282. {
  283. if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
  284. {
  285. EquipBestWeapon( MUST_EQUIP );
  286. }
  287. else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
  288. {
  289. bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
  290. if ( !shouldShoot )
  291. {
  292. CBaseEntity *breakables[ 1 ];
  293. CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
  294. // compute the unit vector along our view
  295. Vector aimDir = GetViewVector();
  296. // trace the potential bullet's path
  297. trace_t result;
  298. UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  299. if ( result.DidHitNonWorldEntity() )
  300. {
  301. LOSbreakable.EnumElement( result.m_pEnt );
  302. if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[ 0 ] == breakable )
  303. {
  304. shouldShoot = true;
  305. }
  306. }
  307. }
  308. // Shoot out breakable if the LOS is clear, or if we're playing a mode with no friendly fire
  309. shouldShoot = shouldShoot && ( !TheCSBots()->AllowFriendlyFireDamage() || !IsFriendInLineOfFire() );
  310. if ( shouldShoot )
  311. {
  312. PrimaryAttack();
  313. }
  314. }
  315. }
  316. }
  317. //--------------------------------------------------------------------------------------------------------------
  318. /**
  319. * Check for doors that need +use to open.
  320. */
  321. void CCSBot::DoorCheck( void )
  322. {
  323. SNPROF( "DoorCheck" );
  324. if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
  325. return;
  326. if ( IsAttacking() && !IsUsingKnife() )
  327. {
  328. // If we're attacking with a gun or nade, don't bother with doors. If we're trying to
  329. // knife someone, we might need to open a door.
  330. m_isOpeningDoor = false;
  331. return;
  332. }
  333. if ( !HasPath() )
  334. return;
  335. // Find any doors that need a +use to open just in front of us along the path.
  336. CBaseEntity *doors[4];
  337. CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
  338. CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
  339. if ( door )
  340. {
  341. if ( !IsLookingAtSpot( PRIORITY_UNINTERRUPTABLE ) )
  342. {
  343. if ( !IsOpeningDoor() )
  344. {
  345. CBasePropDoor *pPropDoor = dynamic_cast<CBasePropDoor*>( door );
  346. if ( pPropDoor && pPropDoor->IsDoorLocked() )
  347. return;
  348. else if ( CBaseDoor *pFuncDoor = dynamic_cast< CBaseDoor * >( door ) )
  349. {
  350. if ( pFuncDoor->m_bLocked )
  351. return;
  352. }
  353. OpenDoor( door );
  354. }
  355. }
  356. }
  357. }
  358. //--------------------------------------------------------------------------------------------------------------
  359. /**
  360. * Reset the stuck-checker.
  361. */
  362. void CCSBot::ResetStuckMonitor( void )
  363. {
  364. if (m_isStuck)
  365. {
  366. if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
  367. {
  368. CBasePlayer *localPlayer = UTIL_GetListenServerHost();
  369. CSingleUserRecipientFilter filter( localPlayer );
  370. EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
  371. }
  372. }
  373. m_isStuck = false;
  374. m_stuckTimestamp = 0.0f;
  375. m_stuckJumpTimer.Invalidate();
  376. m_avgVelIndex = 0;
  377. m_avgVelCount = 0;
  378. m_nextCleanupCheckTimestamp = 0.0f;
  379. m_areaEnteredTimestamp = gpGlobals->curtime;
  380. }
  381. //--------------------------------------------------------------------------------------------------------------
  382. /**
  383. * Test if we have become stuck
  384. */
  385. void CCSBot::StuckCheck( void )
  386. {
  387. SNPROF("StuckCheck");
  388. if (m_isStuck)
  389. {
  390. // we are stuck - see if we have moved far enough to be considered unstuck
  391. Vector delta = GetAbsOrigin() - m_stuckSpot;
  392. const float unstuckRange = 75.0f;
  393. if (delta.IsLengthGreaterThan( unstuckRange ))
  394. {
  395. // we are no longer stuck
  396. ResetStuckMonitor();
  397. PrintIfWatched( "UN-STUCK\n" );
  398. }
  399. }
  400. else
  401. {
  402. // check if we are stuck
  403. // compute average velocity over a short period (for stuck check)
  404. Vector vel = GetAbsOrigin() - m_lastOrigin;
  405. // if we are jumping, ignore Z
  406. if (IsJumping())
  407. vel.z = 0.0f;
  408. // cannot be Length2D, or will break ladder movement (they are only Z)
  409. float moveDist = vel.Length();
  410. float deltaT = g_BotUpdateInterval;
  411. m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
  412. if (m_avgVelIndex == MAX_VEL_SAMPLES)
  413. m_avgVelIndex = 0;
  414. if (m_avgVelCount < MAX_VEL_SAMPLES)
  415. {
  416. m_avgVelCount++;
  417. }
  418. else
  419. {
  420. // we have enough samples to know if we're stuck
  421. float avgVel = 0.0f;
  422. for( int t=0; t<m_avgVelCount; ++t )
  423. avgVel += m_avgVel[t];
  424. avgVel /= m_avgVelCount;
  425. // cannot make this velocity too high, or bots will get "stuck" when going down ladders
  426. //(IsUsingLadder()) ? 10.0f : 20.0f;
  427. float stuckVel = ( IsRunning() ) ? 10.0f : 5.0f;
  428. if (avgVel < stuckVel)
  429. {
  430. // we are stuck - note when and where we initially become stuck
  431. m_stuckTimestamp = gpGlobals->curtime;
  432. m_stuckSpot = GetAbsOrigin();
  433. m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
  434. PrintIfWatched( "STUCK\n" );
  435. if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0 && UTIL_GetListenServerHost() )
  436. {
  437. CBasePlayer *localPlayer = UTIL_GetListenServerHost();
  438. CSingleUserRecipientFilter filter( localPlayer );
  439. EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
  440. }
  441. m_isStuck = true;
  442. }
  443. }
  444. }
  445. // always need to track this
  446. m_lastOrigin = GetAbsOrigin();
  447. }
  448. //--------------------------------------------------------------------------------------------------------------
  449. /**
  450. * Check if we need to jump due to height change
  451. */
  452. bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
  453. {
  454. // Don't try to jump if in the air.
  455. if( !(GetFlags() & FL_ONGROUND) )
  456. {
  457. return false;
  458. }
  459. float dz = ground - GetFeetZ();
  460. if (dz > StepHeight && !onlyJumpDown)
  461. {
  462. // dont restrict jump time when going up
  463. if (Jump( MUST_JUMP ))
  464. {
  465. return true;
  466. }
  467. }
  468. else if (!IsUsingLadder() && dz < -JumpHeight)
  469. {
  470. if (Jump( mustJump ))
  471. {
  472. return true;
  473. }
  474. }
  475. return false;
  476. }
  477. //--------------------------------------------------------------------------------------------------------------
  478. /**
  479. * Find "simple" ground height, treating current nav area as part of the floor
  480. */
  481. bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
  482. {
  483. if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
  484. {
  485. // our current nav area also serves as a ground polygon
  486. if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
  487. *height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
  488. return true;
  489. }
  490. return false;
  491. }
  492. //--------------------------------------------------------------------------------------------------------------
  493. /**
  494. * Get our current radio chatter place
  495. */
  496. Place CCSBot::GetPlace( void ) const
  497. {
  498. if (m_lastKnownArea)
  499. return m_lastKnownArea->GetPlace();
  500. return UNDEFINED_PLACE;
  501. }
  502. //--------------------------------------------------------------------------------------------------------------
  503. /**
  504. * Move towards position, independant of view angle
  505. */
  506. void CCSBot::MoveTowardsPosition( const Vector &pos )
  507. {
  508. Vector myOrigin = GetCentroid( this );
  509. //
  510. // Jump up on ledges
  511. // Because we may not be able to get to our goal position and enter the next
  512. // area because our extent collides with a nearby vertical ledge, make sure
  513. // we look far enough ahead to avoid this situation.
  514. // Can't look too far ahead, or bots will try to jump up slopes.
  515. //
  516. // NOTE: We need to do this frequently to catch edges at the right time
  517. // @todo Look ahead *along path* instead of straight line
  518. //
  519. if ( (m_lastKnownArea == NULL || !( m_lastKnownArea->GetAttributes() & ( NAV_MESH_NO_JUMP | NAV_MESH_STAIRS ) ) ) &&
  520. !IsOnLadder())
  521. {
  522. float ground;
  523. Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
  524. aheadRay.NormalizeInPlace();
  525. // look far ahead to allow us to smoothly jump over gaps, ledges, etc
  526. // only jump if ground is flat at lookahead spot to avoid jumping up slopes
  527. bool jumped = false;
  528. if (IsRunning())
  529. {
  530. const float farLookAheadRange = 80.0f; // 60
  531. Vector normal;
  532. Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
  533. stepAhead.z += HalfHumanHeight;
  534. if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
  535. {
  536. if (normal.z > 0.9f)
  537. jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
  538. }
  539. }
  540. if (!jumped)
  541. {
  542. // close up jumping
  543. const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
  544. Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
  545. stepAhead.z += HalfHumanHeight;
  546. if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
  547. {
  548. jumped = DiscontinuityJump( ground );
  549. }
  550. }
  551. if (!jumped)
  552. {
  553. // about to fall gap-jumping
  554. const float lookAheadRange = 10.0f;
  555. Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
  556. stepAhead.z += HalfHumanHeight;
  557. if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
  558. {
  559. jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
  560. }
  561. }
  562. }
  563. // compute our current forward and lateral vectors
  564. float angle = EyeAngles().y;
  565. Vector2D dir( BotCOS(angle), BotSIN(angle) );
  566. Vector2D lat( -dir.y, dir.x );
  567. // compute unit vector to goal position
  568. Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
  569. to.NormalizeInPlace();
  570. // move towards the position independant of our view direction
  571. float toProj = to.x * dir.x + to.y * dir.y;
  572. float latProj = to.x * lat.x + to.y * lat.y;
  573. const float c = 0.25f; // 0.5
  574. if (toProj > c)
  575. MoveForward();
  576. else if (toProj < -c)
  577. MoveBackward();
  578. // if we are avoiding someone via strafing, don't override
  579. if (m_avoid != NULL)
  580. return;
  581. if (latProj >= c)
  582. StrafeLeft();
  583. else if (latProj <= -c)
  584. StrafeRight();
  585. }
  586. //--------------------------------------------------------------------------------------------------------------
  587. /**
  588. * Move away from position, independant of view angle
  589. */
  590. void CCSBot::MoveAwayFromPosition( const Vector &pos )
  591. {
  592. // compute our current forward and lateral vectors
  593. float angle = EyeAngles().y;
  594. Vector2D dir( BotCOS(angle), BotSIN(angle) );
  595. Vector2D lat( -dir.y, dir.x );
  596. // compute unit vector to goal position
  597. Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
  598. to.NormalizeInPlace();
  599. // move away from the position independant of our view direction
  600. float toProj = to.x * dir.x + to.y * dir.y;
  601. float latProj = to.x * lat.x + to.y * lat.y;
  602. const float c = 0.5f;
  603. if (toProj > c)
  604. MoveBackward();
  605. else if (toProj < -c)
  606. MoveForward();
  607. if (latProj >= c)
  608. StrafeRight();
  609. else if (latProj <= -c)
  610. StrafeLeft();
  611. }
  612. //--------------------------------------------------------------------------------------------------------------
  613. /**
  614. * Strafe (sidestep) away from position, independant of view angle
  615. */
  616. void CCSBot::StrafeAwayFromPosition( const Vector &pos )
  617. {
  618. // compute our current forward and lateral vectors
  619. float angle = EyeAngles().y;
  620. Vector2D dir( BotCOS(angle), BotSIN(angle) );
  621. Vector2D lat( -dir.y, dir.x );
  622. // compute unit vector to goal position
  623. Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
  624. to.NormalizeInPlace();
  625. float latProj = to.x * lat.x + to.y * lat.y;
  626. if (latProj >= 0.0f)
  627. StrafeRight();
  628. else
  629. StrafeLeft();
  630. }
  631. //--------------------------------------------------------------------------------------------------------------
  632. /**
  633. * For getting un-stuck
  634. */
  635. void CCSBot::Wiggle( void )
  636. {
  637. if (IsCrouching())
  638. {
  639. return;
  640. }
  641. // for wiggling
  642. if (m_wiggleTimer.IsElapsed())
  643. {
  644. m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
  645. m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
  646. }
  647. Vector forward, right;
  648. EyeVectors( &forward, &right );
  649. const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
  650. float ground;
  651. switch( m_wiggleDirection )
  652. {
  653. case LEFT:
  654. {
  655. // don't move left if we will fall
  656. Vector pos = GetAbsOrigin() - (lookAheadRange * right);
  657. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  658. {
  659. if (GetAbsOrigin().z - ground < StepHeight)
  660. {
  661. StrafeLeft();
  662. }
  663. }
  664. break;
  665. }
  666. case RIGHT:
  667. {
  668. // don't move right if we will fall
  669. Vector pos = GetAbsOrigin() + (lookAheadRange * right);
  670. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  671. {
  672. if (GetAbsOrigin().z - ground < StepHeight)
  673. {
  674. StrafeRight();
  675. }
  676. }
  677. break;
  678. }
  679. case FORWARD:
  680. {
  681. // don't move forward if we will fall
  682. Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
  683. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  684. {
  685. if (GetAbsOrigin().z - ground < StepHeight)
  686. {
  687. MoveForward();
  688. }
  689. }
  690. break;
  691. }
  692. case BACKWARD:
  693. {
  694. // don't move backward if we will fall
  695. Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
  696. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  697. {
  698. if (GetAbsOrigin().z - ground < StepHeight)
  699. {
  700. MoveBackward();
  701. }
  702. }
  703. break;
  704. }
  705. }
  706. if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
  707. {
  708. if (Jump())
  709. {
  710. m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
  711. }
  712. }
  713. }
  714. //--------------------------------------------------------------------------------------------------------------
  715. /**
  716. * Determine approach points from eye position and approach areas of current area
  717. */
  718. void CCSBot::ComputeApproachPoints( void )
  719. {
  720. m_approachPointCount = 0;
  721. if (m_lastKnownArea == NULL)
  722. {
  723. return;
  724. }
  725. // assume we're crouching for now
  726. Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
  727. Vector ap;
  728. float halfWidth;
  729. for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
  730. {
  731. const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
  732. if (info->here.area == NULL || info->prev.area == NULL)
  733. {
  734. continue;
  735. }
  736. // compute approach point (approach area is "info->here")
  737. if (info->prevToHereHow <= GO_WEST)
  738. {
  739. info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
  740. ap.z = info->here.area->GetZ( ap );
  741. }
  742. else
  743. {
  744. // use the area's center as an approach point
  745. ap = info->here.area->GetCenter();
  746. }
  747. // "bend" our line of sight around corners until we can see the approach point
  748. Vector bendPoint;
  749. if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
  750. {
  751. // put point on the ground
  752. if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
  753. {
  754. bendPoint.z = ap.z;
  755. }
  756. m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
  757. m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
  758. ++m_approachPointCount;
  759. }
  760. }
  761. }
  762. //--------------------------------------------------------------------------------------------------------------
  763. void CCSBot::DrawApproachPoints( void ) const
  764. {
  765. for( int i=0; i<m_approachPointCount; ++i )
  766. {
  767. if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
  768. NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
  769. else
  770. NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
  771. }
  772. }
  773. //--------------------------------------------------------------------------------------------------------------
  774. /**
  775. * Find the approach point that is nearest to our current path, ahead of us
  776. */
  777. bool CCSBot::FindApproachPointNearestPath( Vector *pos )
  778. {
  779. if (!HasPath())
  780. return false;
  781. // make sure approach points are accurate
  782. ComputeApproachPoints();
  783. if (m_approachPointCount == 0)
  784. return false;
  785. Vector target = Vector( 0, 0, 0 ), close;
  786. float targetRangeSq = 0.0f;
  787. bool found = false;
  788. int start = m_pathIndex;
  789. int end = m_pathLength;
  790. //
  791. // We dont want the strictly closest point, but the farthest approach point
  792. // from us that is near our path
  793. //
  794. const float nearPathSq = 10000.0f; // (100)
  795. for( int i=0; i<m_approachPointCount; ++i )
  796. {
  797. if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
  798. continue;
  799. float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
  800. if (rangeSq > nearPathSq)
  801. continue;
  802. if (rangeSq > targetRangeSq)
  803. {
  804. target = close;
  805. targetRangeSq = rangeSq;
  806. found = true;
  807. }
  808. }
  809. if (found)
  810. {
  811. *pos = target + Vector( 0, 0, HalfHumanHeight );
  812. return true;
  813. }
  814. return false;
  815. }
  816. //--------------------------------------------------------------------------------------------------------------
  817. /**
  818. * Return true if we are at the/an enemy spawn right now
  819. */
  820. bool CCSBot::IsAtEnemySpawn( void ) const
  821. {
  822. CBaseEntity *spot;
  823. const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
  824. // check if we are at any of the enemy's spawn points
  825. for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
  826. {
  827. CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
  828. if (area && GetLastKnownArea() == area)
  829. {
  830. return true;
  831. }
  832. }
  833. return false;
  834. }