diff options
Diffstat (limited to 'game/server/cstrike/bot/cs_bot_nav.cpp')
| -rw-r--r-- | game/server/cstrike/bot/cs_bot_nav.cpp | 963 |
1 files changed, 963 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot_nav.cpp b/game/server/cstrike/bot/cs_bot_nav.cpp new file mode 100644 index 0000000..8406101 --- /dev/null +++ b/game/server/cstrike/bot/cs_bot_nav.cpp @@ -0,0 +1,963 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Author: Michael S. Booth ([email protected]), 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; i<m_pathLength-1; ++i ) + { + Vector start, end; + if ( i == startIndex - 1 ) + { + start = GetAbsOrigin(); + end = m_path[i+1].pos; + } + else + { + start = m_path[i].pos; + end = m_path[i+1].pos; + + if ( m_path[i+1].how == GO_LADDER_UP ) + { + // Need two checks. First we'll check along the ladder + start = m_path[i].pos; + end = m_path[i+1].ladder->m_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; i<enumerator.m_nAlreadyHit; ++i ) + { + CBaseEntity *prop = props[i]; + if ( prop && prop->m_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; t<m_avgVelCount; ++t ) + avgVel += m_avgVel[t]; + + avgVel /= m_avgVelCount; + + // cannot make this velocity too high, or bots will get "stuck" when going down ladders + float stuckVel = (IsUsingLadder()) ? 10.0f : 20.0f; + + if (avgVel < stuckVel) + { + // we are stuck - note when and where we initially become stuck + m_stuckTimestamp = gpGlobals->curtime; + 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; i<m_lastKnownArea->GetApproachInfoCount() && 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; i<m_approachPointCount; ++i ) + { + if (TheCSBots()->GetElapsedRoundTime() >= 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<m_approachPointCount; ++i ) + { + if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false) + continue; + + float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr(); + if (rangeSq > 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; +} + |