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
963 lines
26 KiB
//========= 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; 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; |
|
} |
|
|
|
|