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.

963 lines
26 KiB

  1. //========= Copyright 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. #if DEBUG_BREAKABLES
  201. /*
  202. // Debug code to visually mark all breakables near us
  203. {
  204. Ray_t ray;
  205. Vector origin = WorldSpaceCenter();
  206. Vector mins( -400, -400, -400 );
  207. Vector maxs( 400, 400, 400 );
  208. ray.Init( origin, origin, mins, maxs );
  209. CBaseEntity *props[40];
  210. CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
  211. partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
  212. for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
  213. {
  214. CBaseEntity *prop = props[i];
  215. if ( prop && prop->m_takedamage == DAMAGE_YES )
  216. {
  217. CFmtStr msg;
  218. const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
  219. if ( prop->m_iHealth > 200 )
  220. {
  221. NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
  222. prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
  223. }
  224. else
  225. {
  226. NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
  227. prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
  228. }
  229. }
  230. }
  231. }
  232. */
  233. #endif // DEBUG_BREAKABLES
  234. if ( IsAttacking() )
  235. {
  236. // make sure we aren't running into a breakable trying to knife an enemy
  237. if ( IsUsingKnife() && m_enemy != NULL )
  238. {
  239. CBaseEntity *breakables[1];
  240. CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
  241. CBaseEntity *breakable = NULL;
  242. Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
  243. Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
  244. breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
  245. if ( breakable )
  246. {
  247. #if DEBUG_BREAKABLES
  248. NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
  249. #endif // DEBUG_BREAKABLES
  250. // look at it (chances are we'll already be looking at it, since it's between us and our enemy)
  251. SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
  252. // 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)
  253. PrimaryAttack();
  254. }
  255. }
  256. return;
  257. }
  258. if ( !HasPath() )
  259. return;
  260. bool isNear = true;
  261. // Check just in front of us on the path
  262. CBaseEntity *breakables[4];
  263. CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
  264. CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
  265. // If we don't have an object right in front of us, check a ways out
  266. if ( !breakable )
  267. {
  268. breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
  269. isNear = false;
  270. }
  271. // Try to shoot a breakable we know about
  272. if ( breakable )
  273. {
  274. // look at it
  275. SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
  276. }
  277. // break it
  278. if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
  279. {
  280. if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
  281. {
  282. EquipBestWeapon( MUST_EQUIP );
  283. }
  284. else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
  285. {
  286. bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
  287. if ( !shouldShoot )
  288. {
  289. CBaseEntity *breakables[1];
  290. CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
  291. // compute the unit vector along our view
  292. Vector aimDir = GetViewVector();
  293. // trace the potential bullet's path
  294. trace_t result;
  295. UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  296. if ( result.DidHitNonWorldEntity() )
  297. {
  298. LOSbreakable.EnumElement( result.m_pEnt );
  299. if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[0] == breakable )
  300. {
  301. shouldShoot = true;
  302. }
  303. }
  304. }
  305. shouldShoot = shouldShoot && !IsFriendInLineOfFire();
  306. if ( shouldShoot )
  307. {
  308. PrimaryAttack();
  309. }
  310. }
  311. }
  312. }
  313. //--------------------------------------------------------------------------------------------------------------
  314. /**
  315. * Check for doors that need +use to open.
  316. */
  317. void CCSBot::DoorCheck( void )
  318. {
  319. if ( IsAttacking() && !IsUsingKnife() )
  320. {
  321. // If we're attacking with a gun or nade, don't bother with doors. If we're trying to
  322. // knife someone, we might need to open a door.
  323. m_isOpeningDoor = false;
  324. return;
  325. }
  326. if ( !HasPath() )
  327. return;
  328. // Find any doors that need a +use to open just in front of us along the path.
  329. CBaseEntity *doors[4];
  330. CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
  331. CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
  332. if ( door )
  333. {
  334. if ( !IsLookingAtSpot( PRIORITY_HIGH ) )
  335. {
  336. if ( !IsOpeningDoor() )
  337. {
  338. OpenDoor( door );
  339. }
  340. }
  341. }
  342. }
  343. //--------------------------------------------------------------------------------------------------------------
  344. /**
  345. * Reset the stuck-checker.
  346. */
  347. void CCSBot::ResetStuckMonitor( void )
  348. {
  349. if (m_isStuck)
  350. {
  351. if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
  352. {
  353. CBasePlayer *localPlayer = UTIL_GetListenServerHost();
  354. CSingleUserRecipientFilter filter( localPlayer );
  355. EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
  356. }
  357. }
  358. m_isStuck = false;
  359. m_stuckTimestamp = 0.0f;
  360. m_stuckJumpTimer.Invalidate();
  361. m_avgVelIndex = 0;
  362. m_avgVelCount = 0;
  363. m_areaEnteredTimestamp = gpGlobals->curtime;
  364. }
  365. //--------------------------------------------------------------------------------------------------------------
  366. /**
  367. * Test if we have become stuck
  368. */
  369. void CCSBot::StuckCheck( void )
  370. {
  371. if (m_isStuck)
  372. {
  373. // we are stuck - see if we have moved far enough to be considered unstuck
  374. Vector delta = GetAbsOrigin() - m_stuckSpot;
  375. const float unstuckRange = 75.0f;
  376. if (delta.IsLengthGreaterThan( unstuckRange ))
  377. {
  378. // we are no longer stuck
  379. ResetStuckMonitor();
  380. PrintIfWatched( "UN-STUCK\n" );
  381. }
  382. }
  383. else
  384. {
  385. // check if we are stuck
  386. // compute average velocity over a short period (for stuck check)
  387. Vector vel = GetAbsOrigin() - m_lastOrigin;
  388. // if we are jumping, ignore Z
  389. if (IsJumping())
  390. vel.z = 0.0f;
  391. // cannot be Length2D, or will break ladder movement (they are only Z)
  392. float moveDist = vel.Length();
  393. float deltaT = g_BotUpdateInterval;
  394. m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
  395. if (m_avgVelIndex == MAX_VEL_SAMPLES)
  396. m_avgVelIndex = 0;
  397. if (m_avgVelCount < MAX_VEL_SAMPLES)
  398. {
  399. m_avgVelCount++;
  400. }
  401. else
  402. {
  403. // we have enough samples to know if we're stuck
  404. float avgVel = 0.0f;
  405. for( int t=0; t<m_avgVelCount; ++t )
  406. avgVel += m_avgVel[t];
  407. avgVel /= m_avgVelCount;
  408. // cannot make this velocity too high, or bots will get "stuck" when going down ladders
  409. float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f;
  410. if (avgVel < stuckVel)
  411. {
  412. // we are stuck - note when and where we initially become stuck
  413. m_stuckTimestamp = gpGlobals->curtime;
  414. m_stuckSpot = GetAbsOrigin();
  415. m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
  416. PrintIfWatched( "STUCK\n" );
  417. if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0.0f && UTIL_GetListenServerHost())
  418. {
  419. CBasePlayer *localPlayer = UTIL_GetListenServerHost();
  420. CSingleUserRecipientFilter filter( localPlayer );
  421. EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
  422. }
  423. m_isStuck = true;
  424. }
  425. }
  426. }
  427. // always need to track this
  428. m_lastOrigin = GetAbsOrigin();
  429. }
  430. //--------------------------------------------------------------------------------------------------------------
  431. /**
  432. * Check if we need to jump due to height change
  433. */
  434. bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
  435. {
  436. // Don't try to jump if in the air.
  437. if( !(GetFlags() & FL_ONGROUND) )
  438. {
  439. return false;
  440. }
  441. float dz = ground - GetFeetZ();
  442. if (dz > StepHeight && !onlyJumpDown)
  443. {
  444. // dont restrict jump time when going up
  445. if (Jump( MUST_JUMP ))
  446. {
  447. return true;
  448. }
  449. }
  450. else if (!IsUsingLadder() && dz < -JumpHeight)
  451. {
  452. if (Jump( mustJump ))
  453. {
  454. return true;
  455. }
  456. }
  457. return false;
  458. }
  459. //--------------------------------------------------------------------------------------------------------------
  460. /**
  461. * Find "simple" ground height, treating current nav area as part of the floor
  462. */
  463. bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
  464. {
  465. if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
  466. {
  467. // our current nav area also serves as a ground polygon
  468. if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
  469. *height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
  470. return true;
  471. }
  472. return false;
  473. }
  474. //--------------------------------------------------------------------------------------------------------------
  475. /**
  476. * Get our current radio chatter place
  477. */
  478. Place CCSBot::GetPlace( void ) const
  479. {
  480. if (m_lastKnownArea)
  481. return m_lastKnownArea->GetPlace();
  482. return UNDEFINED_PLACE;
  483. }
  484. //--------------------------------------------------------------------------------------------------------------
  485. /**
  486. * Move towards position, independant of view angle
  487. */
  488. void CCSBot::MoveTowardsPosition( const Vector &pos )
  489. {
  490. Vector myOrigin = GetCentroid( this );
  491. //
  492. // Jump up on ledges
  493. // Because we may not be able to get to our goal position and enter the next
  494. // area because our extent collides with a nearby vertical ledge, make sure
  495. // we look far enough ahead to avoid this situation.
  496. // Can't look too far ahead, or bots will try to jump up slopes.
  497. //
  498. // NOTE: We need to do this frequently to catch edges at the right time
  499. // @todo Look ahead *along path* instead of straight line
  500. //
  501. if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP)) &&
  502. !IsOnLadder())
  503. {
  504. float ground;
  505. Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
  506. aheadRay.NormalizeInPlace();
  507. // look far ahead to allow us to smoothly jump over gaps, ledges, etc
  508. // only jump if ground is flat at lookahead spot to avoid jumping up slopes
  509. bool jumped = false;
  510. if (IsRunning())
  511. {
  512. const float farLookAheadRange = 80.0f; // 60
  513. Vector normal;
  514. Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
  515. stepAhead.z += HalfHumanHeight;
  516. if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
  517. {
  518. if (normal.z > 0.9f)
  519. jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
  520. }
  521. }
  522. if (!jumped)
  523. {
  524. // close up jumping
  525. const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
  526. Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
  527. stepAhead.z += HalfHumanHeight;
  528. if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
  529. {
  530. jumped = DiscontinuityJump( ground );
  531. }
  532. }
  533. if (!jumped)
  534. {
  535. // about to fall gap-jumping
  536. const float lookAheadRange = 10.0f;
  537. Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
  538. stepAhead.z += HalfHumanHeight;
  539. if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
  540. {
  541. jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
  542. }
  543. }
  544. }
  545. // compute our current forward and lateral vectors
  546. float angle = EyeAngles().y;
  547. Vector2D dir( BotCOS(angle), BotSIN(angle) );
  548. Vector2D lat( -dir.y, dir.x );
  549. // compute unit vector to goal position
  550. Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
  551. to.NormalizeInPlace();
  552. // move towards the position independant of our view direction
  553. float toProj = to.x * dir.x + to.y * dir.y;
  554. float latProj = to.x * lat.x + to.y * lat.y;
  555. const float c = 0.25f; // 0.5
  556. if (toProj > c)
  557. MoveForward();
  558. else if (toProj < -c)
  559. MoveBackward();
  560. // if we are avoiding someone via strafing, don't override
  561. if (m_avoid != NULL)
  562. return;
  563. if (latProj >= c)
  564. StrafeLeft();
  565. else if (latProj <= -c)
  566. StrafeRight();
  567. }
  568. //--------------------------------------------------------------------------------------------------------------
  569. /**
  570. * Move away from position, independant of view angle
  571. */
  572. void CCSBot::MoveAwayFromPosition( const Vector &pos )
  573. {
  574. // compute our current forward and lateral vectors
  575. float angle = EyeAngles().y;
  576. Vector2D dir( BotCOS(angle), BotSIN(angle) );
  577. Vector2D lat( -dir.y, dir.x );
  578. // compute unit vector to goal position
  579. Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
  580. to.NormalizeInPlace();
  581. // move away from the position independant of our view direction
  582. float toProj = to.x * dir.x + to.y * dir.y;
  583. float latProj = to.x * lat.x + to.y * lat.y;
  584. const float c = 0.5f;
  585. if (toProj > c)
  586. MoveBackward();
  587. else if (toProj < -c)
  588. MoveForward();
  589. if (latProj >= c)
  590. StrafeRight();
  591. else if (latProj <= -c)
  592. StrafeLeft();
  593. }
  594. //--------------------------------------------------------------------------------------------------------------
  595. /**
  596. * Strafe (sidestep) away from position, independant of view angle
  597. */
  598. void CCSBot::StrafeAwayFromPosition( const Vector &pos )
  599. {
  600. // compute our current forward and lateral vectors
  601. float angle = EyeAngles().y;
  602. Vector2D dir( BotCOS(angle), BotSIN(angle) );
  603. Vector2D lat( -dir.y, dir.x );
  604. // compute unit vector to goal position
  605. Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
  606. to.NormalizeInPlace();
  607. float latProj = to.x * lat.x + to.y * lat.y;
  608. if (latProj >= 0.0f)
  609. StrafeRight();
  610. else
  611. StrafeLeft();
  612. }
  613. //--------------------------------------------------------------------------------------------------------------
  614. /**
  615. * For getting un-stuck
  616. */
  617. void CCSBot::Wiggle( void )
  618. {
  619. if (IsCrouching())
  620. {
  621. return;
  622. }
  623. // for wiggling
  624. if (m_wiggleTimer.IsElapsed())
  625. {
  626. m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
  627. m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
  628. }
  629. Vector forward, right;
  630. EyeVectors( &forward, &right );
  631. const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
  632. float ground;
  633. switch( m_wiggleDirection )
  634. {
  635. case LEFT:
  636. {
  637. // don't move left if we will fall
  638. Vector pos = GetAbsOrigin() - (lookAheadRange * right);
  639. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  640. {
  641. if (GetAbsOrigin().z - ground < StepHeight)
  642. {
  643. StrafeLeft();
  644. }
  645. }
  646. break;
  647. }
  648. case RIGHT:
  649. {
  650. // don't move right if we will fall
  651. Vector pos = GetAbsOrigin() + (lookAheadRange * right);
  652. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  653. {
  654. if (GetAbsOrigin().z - ground < StepHeight)
  655. {
  656. StrafeRight();
  657. }
  658. }
  659. break;
  660. }
  661. case FORWARD:
  662. {
  663. // don't move forward if we will fall
  664. Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
  665. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  666. {
  667. if (GetAbsOrigin().z - ground < StepHeight)
  668. {
  669. MoveForward();
  670. }
  671. }
  672. break;
  673. }
  674. case BACKWARD:
  675. {
  676. // don't move backward if we will fall
  677. Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
  678. if (GetSimpleGroundHeightWithFloor( pos, &ground ))
  679. {
  680. if (GetAbsOrigin().z - ground < StepHeight)
  681. {
  682. MoveBackward();
  683. }
  684. }
  685. break;
  686. }
  687. }
  688. if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
  689. {
  690. if (Jump())
  691. {
  692. m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
  693. }
  694. }
  695. }
  696. //--------------------------------------------------------------------------------------------------------------
  697. /**
  698. * Determine approach points from eye position and approach areas of current area
  699. */
  700. void CCSBot::ComputeApproachPoints( void )
  701. {
  702. m_approachPointCount = 0;
  703. if (m_lastKnownArea == NULL)
  704. {
  705. return;
  706. }
  707. // assume we're crouching for now
  708. Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
  709. Vector ap;
  710. float halfWidth;
  711. for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
  712. {
  713. const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
  714. if (info->here.area == NULL || info->prev.area == NULL)
  715. {
  716. continue;
  717. }
  718. // compute approach point (approach area is "info->here")
  719. if (info->prevToHereHow <= GO_WEST)
  720. {
  721. info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
  722. ap.z = info->here.area->GetZ( ap );
  723. }
  724. else
  725. {
  726. // use the area's center as an approach point
  727. ap = info->here.area->GetCenter();
  728. }
  729. // "bend" our line of sight around corners until we can see the approach point
  730. Vector bendPoint;
  731. if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
  732. {
  733. // put point on the ground
  734. if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
  735. {
  736. bendPoint.z = ap.z;
  737. }
  738. m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
  739. m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
  740. ++m_approachPointCount;
  741. }
  742. }
  743. }
  744. //--------------------------------------------------------------------------------------------------------------
  745. void CCSBot::DrawApproachPoints( void ) const
  746. {
  747. for( int i=0; i<m_approachPointCount; ++i )
  748. {
  749. if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
  750. NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
  751. else
  752. NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
  753. }
  754. }
  755. //--------------------------------------------------------------------------------------------------------------
  756. /**
  757. * Find the approach point that is nearest to our current path, ahead of us
  758. */
  759. bool CCSBot::FindApproachPointNearestPath( Vector *pos )
  760. {
  761. if (!HasPath())
  762. return false;
  763. // make sure approach points are accurate
  764. ComputeApproachPoints();
  765. if (m_approachPointCount == 0)
  766. return false;
  767. Vector target = Vector( 0, 0, 0 ), close;
  768. float targetRangeSq = 0.0f;
  769. bool found = false;
  770. int start = m_pathIndex;
  771. int end = m_pathLength;
  772. //
  773. // We dont want the strictly closest point, but the farthest approach point
  774. // from us that is near our path
  775. //
  776. const float nearPathSq = 10000.0f; // (100)
  777. for( int i=0; i<m_approachPointCount; ++i )
  778. {
  779. if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
  780. continue;
  781. float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
  782. if (rangeSq > nearPathSq)
  783. continue;
  784. if (rangeSq > targetRangeSq)
  785. {
  786. target = close;
  787. targetRangeSq = rangeSq;
  788. found = true;
  789. }
  790. }
  791. if (found)
  792. {
  793. *pos = target + Vector( 0, 0, HalfHumanHeight );
  794. return true;
  795. }
  796. return false;
  797. }
  798. //--------------------------------------------------------------------------------------------------------------
  799. /**
  800. * Return true if we are at the/an enemy spawn right now
  801. */
  802. bool CCSBot::IsAtEnemySpawn( void ) const
  803. {
  804. CBaseEntity *spot;
  805. const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
  806. // check if we are at any of the enemy's spawn points
  807. for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
  808. {
  809. CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
  810. if (area && GetLastKnownArea() == area)
  811. {
  812. return true;
  813. }
  814. }
  815. return false;
  816. }