//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// // Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 #include "cbase.h" #include "cs_bot.h" #include "obstacle_pushaway.h" #include "fmtstr.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" const float NearBreakableCheckDist = 20.0f; const float FarBreakableCheckDist = 300.0f; #define DEBUG_BREAKABLES 0 #define DEBUG_DOORS 0 //-------------------------------------------------------------------------------------------------------------- #if DEBUG_BREAKABLES static void DrawOutlinedQuad( const Vector &p1, const Vector &p2, const Vector &p3, const Vector &p4, int r, int g, int b, float duration ) { NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration ); NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration ); NDebugOverlay::Line( p1, p2, r, g, b, false, duration ); NDebugOverlay::Line( p2, p3, r, g, b, false, duration ); NDebugOverlay::Line( p3, p4, r, g, b, false, duration ); NDebugOverlay::Line( p4, p1, r, g, b, false, duration ); } ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" ); #endif // DEBUG_BREAKABLES //-------------------------------------------------------------------------------------------------------------- CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator ) { CBaseEntity *entity = NULL; Ray_t ray; ray.Init( start, end, mins, maxs ); partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator ); if ( enumerator->m_nAlreadyHit > 0 ) { entity = enumerator->m_AlreadyHit[0]; } #if DEBUG_BREAKABLES if ( entity ) { DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() ); } else { DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 ); } #endif // DEBUG_BREAKABLES return entity; } //-------------------------------------------------------------------------------------------------------------- /** * Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one. */ CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck ) { Vector goal; int pathIndex = FindPathPoint( distance, &goal, NULL ); bool isDegeneratePath = ( pathIndex == m_pathLength ); if ( isDegeneratePath ) { goal = m_goalPosition; } goal.z += HalfHumanHeight; Vector mins, maxs; mins = Vector( 0, 0, -HalfHumanWidth ); maxs = Vector( 0, 0, HalfHumanHeight ); if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck ) { mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth ); maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight ); } CBaseEntity *entity = NULL; if ( isDegeneratePath ) { entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator ); #if DEBUG_BREAKABLES if ( entity ) { NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() ); } #endif // DEBUG_BREAKABLES } else { int startIndex = MAX( 0, m_pathIndex ); float distanceLeft = distance; // HACK: start with an index one lower than normal, so we can trace from the bot's location to the // start of the path nodes. for( int i=startIndex-1; im_top; } else if ( m_path[i].how == GO_LADDER_UP ) { start = m_path[i].ladder->m_top; } else if ( m_path[i+1].how == GO_LADDER_DOWN ) { // Need two checks. First we'll check along the ladder start = m_path[i].pos; end = m_path[i+1].ladder->m_bottom; } else if ( m_path[i].how == GO_LADDER_DOWN ) { start = m_path[i].ladder->m_bottom; } } float segmentLength = (start - end).Length(); if ( distanceLeft - segmentLength < 0 ) { // scale our segment back so we don't look too far Vector direction = end - start; direction.NormalizeInPlace(); end = start + direction * distanceLeft; } entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator ); if ( entity ) { #if DEBUG_BREAKABLES NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() ); #endif // DEBUG_BREAKABLES break; } if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder break; distanceLeft -= segmentLength; if ( distanceLeft < 0 ) break; if ( i != startIndex - 1 && m_path[i+1].ladder ) { // Now we'll check from the ladder out to the endpoint start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top; end = m_path[i+1].pos; entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator ); if ( entity ) { #if DEBUG_BREAKABLES NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() ); #endif // DEBUG_BREAKABLES break; } } } } if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) ) return NULL; return entity; } //-------------------------------------------------------------------------------------------------------------- void CCSBot::PushawayTouch( CBaseEntity *pOther ) { #if DEBUG_BREAKABLES NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f ); #endif // DEBUG_BREAKABLES // if we're not stuck or crouched, we don't care if ( !m_isStuck && !IsCrouching() ) return; // See if it's breakable CBaseEntity *props[1]; CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) ); enumerator.EnumElement( pOther ); if ( enumerator.m_nAlreadyHit == 1 ) { // it's breakable - try to shoot it. SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true ); } } //-------------------------------------------------------------------------------------------------------------- /** * Check for breakable physics props and other breakable entities. We do this here instead of catching them * in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also, * looking ahead like this lets us anticipate when we'll need to break something, and do it before being * stopped by it. */ void CCSBot::BreakablesCheck( void ) { #if DEBUG_BREAKABLES /* // Debug code to visually mark all breakables near us { Ray_t ray; Vector origin = WorldSpaceCenter(); Vector mins( -400, -400, -400 ); Vector maxs( 400, 400, 400 ); ray.Init( origin, origin, mins, maxs ); CBaseEntity *props[40]; CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) ); partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator ); for ( int i=0; im_takedamage == DAMAGE_YES ) { CFmtStr msg; const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth ); if ( prop->m_iHealth > 200 ) { NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f ); prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 ); } else { NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f ); prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 ); } } } } */ #endif // DEBUG_BREAKABLES if ( IsAttacking() ) { // make sure we aren't running into a breakable trying to knife an enemy if ( IsUsingKnife() && m_enemy != NULL ) { CBaseEntity *breakables[1]; CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) ); CBaseEntity *breakable = NULL; Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth ); Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight ); breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator ); if ( breakable ) { #if DEBUG_BREAKABLES NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() ); #endif // DEBUG_BREAKABLES // look at it (chances are we'll already be looking at it, since it's between us and our enemy) SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true ); // 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) PrimaryAttack(); } } return; } if ( !HasPath() ) return; bool isNear = true; // Check just in front of us on the path CBaseEntity *breakables[4]; CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) ); CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true ); // If we don't have an object right in front of us, check a ways out if ( !breakable ) { breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false ); isNear = false; } // Try to shoot a breakable we know about if ( breakable ) { // look at it SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true ); } // break it if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack ) { if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) ) { EquipBestWeapon( MUST_EQUIP ); } else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime ) { bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f ); if ( !shouldShoot ) { CBaseEntity *breakables[1]; CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) ); // compute the unit vector along our view Vector aimDir = GetViewVector(); // trace the potential bullet's path trace_t result; UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result ); if ( result.DidHitNonWorldEntity() ) { LOSbreakable.EnumElement( result.m_pEnt ); if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[0] == breakable ) { shouldShoot = true; } } } shouldShoot = shouldShoot && !IsFriendInLineOfFire(); if ( shouldShoot ) { PrimaryAttack(); } } } } //-------------------------------------------------------------------------------------------------------------- /** * Check for doors that need +use to open. */ void CCSBot::DoorCheck( void ) { if ( IsAttacking() && !IsUsingKnife() ) { // If we're attacking with a gun or nade, don't bother with doors. If we're trying to // knife someone, we might need to open a door. m_isOpeningDoor = false; return; } if ( !HasPath() ) return; // Find any doors that need a +use to open just in front of us along the path. CBaseEntity *doors[4]; CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) ); CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false ); if ( door ) { if ( !IsLookingAtSpot( PRIORITY_HIGH ) ) { if ( !IsOpeningDoor() ) { OpenDoor( door ); } } } } //-------------------------------------------------------------------------------------------------------------- /** * Reset the stuck-checker. */ void CCSBot::ResetStuckMonitor( void ) { if (m_isStuck) { if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost()) { CBasePlayer *localPlayer = UTIL_GetListenServerHost(); CSingleUserRecipientFilter filter( localPlayer ); EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" ); } } m_isStuck = false; m_stuckTimestamp = 0.0f; m_stuckJumpTimer.Invalidate(); m_avgVelIndex = 0; m_avgVelCount = 0; m_areaEnteredTimestamp = gpGlobals->curtime; } //-------------------------------------------------------------------------------------------------------------- /** * Test if we have become stuck */ void CCSBot::StuckCheck( void ) { if (m_isStuck) { // we are stuck - see if we have moved far enough to be considered unstuck Vector delta = GetAbsOrigin() - m_stuckSpot; const float unstuckRange = 75.0f; if (delta.IsLengthGreaterThan( unstuckRange )) { // we are no longer stuck ResetStuckMonitor(); PrintIfWatched( "UN-STUCK\n" ); } } else { // check if we are stuck // compute average velocity over a short period (for stuck check) Vector vel = GetAbsOrigin() - m_lastOrigin; // if we are jumping, ignore Z if (IsJumping()) vel.z = 0.0f; // cannot be Length2D, or will break ladder movement (they are only Z) float moveDist = vel.Length(); float deltaT = g_BotUpdateInterval; m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT; if (m_avgVelIndex == MAX_VEL_SAMPLES) m_avgVelIndex = 0; if (m_avgVelCount < MAX_VEL_SAMPLES) { m_avgVelCount++; } else { // we have enough samples to know if we're stuck float avgVel = 0.0f; for( int t=0; tcurtime; m_stuckSpot = GetAbsOrigin(); m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0 PrintIfWatched( "STUCK\n" ); if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0.0f && UTIL_GetListenServerHost()) { CBasePlayer *localPlayer = UTIL_GetListenServerHost(); CSingleUserRecipientFilter filter( localPlayer ); EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" ); } m_isStuck = true; } } } // always need to track this m_lastOrigin = GetAbsOrigin(); } //-------------------------------------------------------------------------------------------------------------- /** * Check if we need to jump due to height change */ bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump ) { // Don't try to jump if in the air. if( !(GetFlags() & FL_ONGROUND) ) { return false; } float dz = ground - GetFeetZ(); if (dz > StepHeight && !onlyJumpDown) { // dont restrict jump time when going up if (Jump( MUST_JUMP )) { return true; } } else if (!IsUsingLadder() && dz < -JumpHeight) { if (Jump( mustJump )) { return true; } } return false; } //-------------------------------------------------------------------------------------------------------------- /** * Find "simple" ground height, treating current nav area as part of the floor */ bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal ) { if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal )) { // our current nav area also serves as a ground polygon if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos )) *height = MAX( (*height), m_lastKnownArea->GetZ( pos ) ); return true; } return false; } //-------------------------------------------------------------------------------------------------------------- /** * Get our current radio chatter place */ Place CCSBot::GetPlace( void ) const { if (m_lastKnownArea) return m_lastKnownArea->GetPlace(); return UNDEFINED_PLACE; } //-------------------------------------------------------------------------------------------------------------- /** * Move towards position, independant of view angle */ void CCSBot::MoveTowardsPosition( const Vector &pos ) { Vector myOrigin = GetCentroid( this ); // // Jump up on ledges // Because we may not be able to get to our goal position and enter the next // area because our extent collides with a nearby vertical ledge, make sure // we look far enough ahead to avoid this situation. // Can't look too far ahead, or bots will try to jump up slopes. // // NOTE: We need to do this frequently to catch edges at the right time // @todo Look ahead *along path* instead of straight line // if ((m_lastKnownArea == NULL || !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP)) && !IsOnLadder()) { float ground; Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 ); aheadRay.NormalizeInPlace(); // look far ahead to allow us to smoothly jump over gaps, ledges, etc // only jump if ground is flat at lookahead spot to avoid jumping up slopes bool jumped = false; if (IsRunning()) { const float farLookAheadRange = 80.0f; // 60 Vector normal; Vector stepAhead = myOrigin + farLookAheadRange * aheadRay; stepAhead.z += HalfHumanHeight; if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal )) { if (normal.z > 0.9f) jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN ); } } if (!jumped) { // close up jumping const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls Vector stepAhead = myOrigin + lookAheadRange * aheadRay; stepAhead.z += HalfHumanHeight; if (GetSimpleGroundHeightWithFloor( stepAhead, &ground )) { jumped = DiscontinuityJump( ground ); } } if (!jumped) { // about to fall gap-jumping const float lookAheadRange = 10.0f; Vector stepAhead = myOrigin + lookAheadRange * aheadRay; stepAhead.z += HalfHumanHeight; if (GetSimpleGroundHeightWithFloor( stepAhead, &ground )) { jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP ); } } } // compute our current forward and lateral vectors float angle = EyeAngles().y; Vector2D dir( BotCOS(angle), BotSIN(angle) ); Vector2D lat( -dir.y, dir.x ); // compute unit vector to goal position Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y ); to.NormalizeInPlace(); // move towards the position independant of our view direction float toProj = to.x * dir.x + to.y * dir.y; float latProj = to.x * lat.x + to.y * lat.y; const float c = 0.25f; // 0.5 if (toProj > c) MoveForward(); else if (toProj < -c) MoveBackward(); // if we are avoiding someone via strafing, don't override if (m_avoid != NULL) return; if (latProj >= c) StrafeLeft(); else if (latProj <= -c) StrafeRight(); } //-------------------------------------------------------------------------------------------------------------- /** * Move away from position, independant of view angle */ void CCSBot::MoveAwayFromPosition( const Vector &pos ) { // compute our current forward and lateral vectors float angle = EyeAngles().y; Vector2D dir( BotCOS(angle), BotSIN(angle) ); Vector2D lat( -dir.y, dir.x ); // compute unit vector to goal position Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y ); to.NormalizeInPlace(); // move away from the position independant of our view direction float toProj = to.x * dir.x + to.y * dir.y; float latProj = to.x * lat.x + to.y * lat.y; const float c = 0.5f; if (toProj > c) MoveBackward(); else if (toProj < -c) MoveForward(); if (latProj >= c) StrafeRight(); else if (latProj <= -c) StrafeLeft(); } //-------------------------------------------------------------------------------------------------------------- /** * Strafe (sidestep) away from position, independant of view angle */ void CCSBot::StrafeAwayFromPosition( const Vector &pos ) { // compute our current forward and lateral vectors float angle = EyeAngles().y; Vector2D dir( BotCOS(angle), BotSIN(angle) ); Vector2D lat( -dir.y, dir.x ); // compute unit vector to goal position Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y ); to.NormalizeInPlace(); float latProj = to.x * lat.x + to.y * lat.y; if (latProj >= 0.0f) StrafeRight(); else StrafeLeft(); } //-------------------------------------------------------------------------------------------------------------- /** * For getting un-stuck */ void CCSBot::Wiggle( void ) { if (IsCrouching()) { return; } // for wiggling if (m_wiggleTimer.IsElapsed()) { m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 ); m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5 } Vector forward, right; EyeVectors( &forward, &right ); const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f; float ground; switch( m_wiggleDirection ) { case LEFT: { // don't move left if we will fall Vector pos = GetAbsOrigin() - (lookAheadRange * right); if (GetSimpleGroundHeightWithFloor( pos, &ground )) { if (GetAbsOrigin().z - ground < StepHeight) { StrafeLeft(); } } break; } case RIGHT: { // don't move right if we will fall Vector pos = GetAbsOrigin() + (lookAheadRange * right); if (GetSimpleGroundHeightWithFloor( pos, &ground )) { if (GetAbsOrigin().z - ground < StepHeight) { StrafeRight(); } } break; } case FORWARD: { // don't move forward if we will fall Vector pos = GetAbsOrigin() + (lookAheadRange * forward); if (GetSimpleGroundHeightWithFloor( pos, &ground )) { if (GetAbsOrigin().z - ground < StepHeight) { MoveForward(); } } break; } case BACKWARD: { // don't move backward if we will fall Vector pos = GetAbsOrigin() - (lookAheadRange * forward); if (GetSimpleGroundHeightWithFloor( pos, &ground )) { if (GetAbsOrigin().z - ground < StepHeight) { MoveBackward(); } } break; } } if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP)) { if (Jump()) { m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) ); } } } //-------------------------------------------------------------------------------------------------------------- /** * Determine approach points from eye position and approach areas of current area */ void CCSBot::ComputeApproachPoints( void ) { m_approachPointCount = 0; if (m_lastKnownArea == NULL) { return; } // assume we're crouching for now Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position Vector ap; float halfWidth; for( int i=0; iGetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i ) { const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i ); if (info->here.area == NULL || info->prev.area == NULL) { continue; } // compute approach point (approach area is "info->here") if (info->prevToHereHow <= GO_WEST) { info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth ); ap.z = info->here.area->GetZ( ap ); } else { // use the area's center as an approach point ap = info->here.area->GetCenter(); } // "bend" our line of sight around corners until we can see the approach point Vector bendPoint; if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint )) { // put point on the ground if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false) { bendPoint.z = ap.z; } m_approachPoint[ m_approachPointCount ].m_pos = bendPoint; m_approachPoint[ m_approachPointCount ].m_area = info->here.area; ++m_approachPointCount; } } } //-------------------------------------------------------------------------------------------------------------- void CCSBot::DrawApproachPoints( void ) const { for( int i=0; iGetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) )) NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f ); else NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f ); } } //-------------------------------------------------------------------------------------------------------------- /** * Find the approach point that is nearest to our current path, ahead of us */ bool CCSBot::FindApproachPointNearestPath( Vector *pos ) { if (!HasPath()) return false; // make sure approach points are accurate ComputeApproachPoints(); if (m_approachPointCount == 0) return false; Vector target = Vector( 0, 0, 0 ), close; float targetRangeSq = 0.0f; bool found = false; int start = m_pathIndex; int end = m_pathLength; // // We dont want the strictly closest point, but the farthest approach point // from us that is near our path // const float nearPathSq = 10000.0f; // (100) for( int i=0; i nearPathSq) continue; if (rangeSq > targetRangeSq) { target = close; targetRangeSq = rangeSq; found = true; } } if (found) { *pos = target + Vector( 0, 0, HalfHumanHeight ); return true; } return false; } //-------------------------------------------------------------------------------------------------------------- /** * Return true if we are at the/an enemy spawn right now */ bool CCSBot::IsAtEnemySpawn( void ) const { CBaseEntity *spot; const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist"; // check if we are at any of the enemy's spawn points for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) ) { CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() ); if (area && GetLastKnownArea() == area) { return true; } } return false; }