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.
533 lines
14 KiB
533 lines
14 KiB
// NextBotInterface.cpp
|
|
// Implentation of system methods for NextBot interface
|
|
// Author: Michael Booth, May 2006
|
|
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "props.h"
|
|
#include "fmtstr.h"
|
|
#include "team.h"
|
|
|
|
#include "NextBotInterface.h"
|
|
#include "NextBotBodyInterface.h"
|
|
#include "NextBotManager.h"
|
|
|
|
#include "tier0/vprof.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// development only, off by default for 360
|
|
ConVar NextBotDebugHistory( "nb_debug_history", IsX360() ? "0" : "1", FCVAR_CHEAT, "If true, each bot keeps a history of debug output in memory" );
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
INextBot::INextBot( void ) : m_debugHistory( MAX_NEXTBOT_DEBUG_HISTORY, 0 ) // CUtlVector: grow to max length, alloc 0 initially
|
|
{
|
|
m_tickLastUpdate = -999;
|
|
m_id = -1;
|
|
m_componentList = NULL;
|
|
m_debugDisplayLine = 0;
|
|
|
|
m_immobileTimer.Invalidate();
|
|
m_immobileCheckTimer.Invalidate();
|
|
m_immobileAnchor = vec3_origin;
|
|
|
|
m_currentPath = NULL;
|
|
|
|
// register with the manager
|
|
m_id = TheNextBots().Register( this );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
INextBot::~INextBot()
|
|
{
|
|
ResetDebugHistory();
|
|
|
|
// tell the manager we're gone
|
|
TheNextBots().UnRegister( this );
|
|
|
|
if ( m_baseLocomotion )
|
|
delete m_baseLocomotion;
|
|
|
|
if ( m_baseBody )
|
|
delete m_baseBody;
|
|
|
|
if ( m_baseIntention )
|
|
delete m_baseIntention;
|
|
|
|
if ( m_baseVision )
|
|
delete m_baseVision;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
void INextBot::Reset( void )
|
|
{
|
|
m_tickLastUpdate = -999;
|
|
m_debugType = 0;
|
|
m_debugDisplayLine = 0;
|
|
|
|
m_immobileTimer.Invalidate();
|
|
m_immobileCheckTimer.Invalidate();
|
|
m_immobileAnchor = vec3_origin;
|
|
|
|
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
|
|
{
|
|
comp->Reset();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
void INextBot::ResetDebugHistory( void )
|
|
{
|
|
for ( int i=0; i<m_debugHistory.Count(); ++i )
|
|
{
|
|
delete m_debugHistory[i];
|
|
}
|
|
|
|
m_debugHistory.RemoveAll();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::BeginUpdate()
|
|
{
|
|
if ( TheNextBots().ShouldUpdate( this ) )
|
|
{
|
|
TheNextBots().NotifyBeginUpdate( this );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
void INextBot::EndUpdate( void )
|
|
{
|
|
TheNextBots().NotifyEndUpdate( this );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
void INextBot::Update( void )
|
|
{
|
|
VPROF_BUDGET( "INextBot::Update", "NextBot" );
|
|
m_debugDisplayLine = 0;
|
|
|
|
if ( IsDebugging( NEXTBOT_DEBUG_ALL ) )
|
|
{
|
|
CFmtStr msg;
|
|
DisplayDebugText( msg.sprintf( "#%d", GetEntity()->entindex() ) );
|
|
}
|
|
|
|
UpdateImmobileStatus();
|
|
|
|
// update all components
|
|
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
|
|
{
|
|
if ( comp->ComputeUpdateInterval() )
|
|
{
|
|
comp->Update();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
void INextBot::Upkeep( void )
|
|
{
|
|
// do upkeep for all components
|
|
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
|
|
{
|
|
comp->Upkeep();
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::SetPosition( const Vector &pos )
|
|
{
|
|
IBody *body = GetBodyInterface();
|
|
if (body)
|
|
{
|
|
return body->SetPosition( pos );
|
|
}
|
|
|
|
// fall back to setting raw entity position
|
|
GetEntity()->SetAbsOrigin( pos );
|
|
return true;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
const Vector &INextBot::GetPosition( void ) const
|
|
{
|
|
return const_cast< INextBot * >( this )->GetEntity()->GetAbsOrigin();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if given actor is our enemy
|
|
*/
|
|
bool INextBot::IsEnemy( const CBaseEntity *them ) const
|
|
{
|
|
if ( them == NULL )
|
|
return false;
|
|
|
|
// this is not strictly correct, as spectators are not enemies
|
|
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() != them->GetTeamNumber();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if given actor is our friend
|
|
*/
|
|
bool INextBot::IsFriend( const CBaseEntity *them ) const
|
|
{
|
|
if ( them == NULL )
|
|
return false;
|
|
|
|
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() == them->GetTeamNumber();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if 'them' is actually me
|
|
*/
|
|
bool INextBot::IsSelf( const CBaseEntity *them ) const
|
|
{
|
|
if ( them == NULL )
|
|
return false;
|
|
|
|
return const_cast< INextBot * >( this )->GetEntity()->entindex() == them->entindex();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Components call this to register themselves with the bot that contains them
|
|
*/
|
|
void INextBot::RegisterComponent( INextBotComponent *comp )
|
|
{
|
|
// add to head of singly linked list
|
|
comp->m_nextComponent = m_componentList;
|
|
m_componentList = comp;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::IsRangeLessThan( CBaseEntity *subject, float range ) const
|
|
{
|
|
Vector botPos;
|
|
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
|
if ( !bot || !subject )
|
|
return 0.0f;
|
|
|
|
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
|
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
|
return computedRange < range;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::IsRangeLessThan( const Vector &pos, float range ) const
|
|
{
|
|
Vector to = pos - GetPosition();
|
|
return to.IsLengthLessThan( range );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::IsRangeGreaterThan( CBaseEntity *subject, float range ) const
|
|
{
|
|
Vector botPos;
|
|
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
|
if ( !bot || !subject )
|
|
return 0.0f;
|
|
|
|
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
|
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
|
return computedRange > range;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::IsRangeGreaterThan( const Vector &pos, float range ) const
|
|
{
|
|
Vector to = pos - GetPosition();
|
|
return to.IsLengthGreaterThan( range );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
float INextBot::GetRangeTo( CBaseEntity *subject ) const
|
|
{
|
|
Vector botPos;
|
|
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
|
if ( !bot || !subject )
|
|
return 0.0f;
|
|
|
|
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
|
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
|
return computedRange;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
float INextBot::GetRangeTo( const Vector &pos ) const
|
|
{
|
|
Vector to = pos - GetPosition();
|
|
return to.Length();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
float INextBot::GetRangeSquaredTo( CBaseEntity *subject ) const
|
|
{
|
|
Vector botPos;
|
|
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
|
|
if ( !bot || !subject )
|
|
return 0.0f;
|
|
|
|
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
|
|
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
|
|
return computedRange * computedRange;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
float INextBot::GetRangeSquaredTo( const Vector &pos ) const
|
|
{
|
|
Vector to = pos - GetPosition();
|
|
return to.LengthSqr();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
bool INextBot::IsDebugging( unsigned int type ) const
|
|
{
|
|
if ( TheNextBots().IsDebugging( type ) )
|
|
{
|
|
return TheNextBots().IsDebugFilterMatch( this );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return the name of this bot for debugging purposes
|
|
*/
|
|
const char *INextBot::GetDebugIdentifier( void ) const
|
|
{
|
|
const int nameSize = 256;
|
|
static char name[ nameSize ];
|
|
|
|
Q_snprintf( name, nameSize, "%s(#%d)", const_cast< INextBot * >( this )->GetEntity()->GetClassname(), const_cast< INextBot * >( this )->GetEntity()->entindex() );
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we match the given debug symbol
|
|
*/
|
|
bool INextBot::IsDebugFilterMatch( const char *name ) const
|
|
{
|
|
// compare debug identifier
|
|
if ( !Q_strnicmp( name, GetDebugIdentifier(), Q_strlen( name ) ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// compare team name
|
|
CTeam *team = GetEntity()->GetTeam();
|
|
if ( team && !Q_strnicmp( name, team->GetName(), Q_strlen( name ) ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* There are some things we never want to climb on
|
|
*/
|
|
bool INextBot::IsAbleToClimbOnto( const CBaseEntity *object ) const
|
|
{
|
|
if ( object == NULL || !const_cast<CBaseEntity *>(object)->IsAIWalkable() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// never climb onto doors
|
|
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "prop_door*" ) || FClassnameIs( const_cast< CBaseEntity * >( object ), "func_door*" ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// ok to climb on this object
|
|
return true;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Can we break this object
|
|
*/
|
|
bool INextBot::IsAbleToBreak( const CBaseEntity *object ) const
|
|
{
|
|
if ( object && object->m_takedamage == DAMAGE_YES )
|
|
{
|
|
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable" ) &&
|
|
object->GetHealth() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable_surf" ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( dynamic_cast< const CBreakableProp * >( object ) != NULL )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------
|
|
void INextBot::DisplayDebugText( const char *text ) const
|
|
{
|
|
const_cast< INextBot * >( this )->GetEntity()->EntityText( m_debugDisplayLine++, text, 0.1 );
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void INextBot::DebugConColorMsg( NextBotDebugType debugType, const Color &color, const char *fmt, ... )
|
|
{
|
|
bool isDataFormatted = false;
|
|
|
|
va_list argptr;
|
|
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
|
|
|
|
if ( developer.GetBool() && IsDebugging( debugType ) )
|
|
{
|
|
va_start(argptr, fmt);
|
|
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
|
|
va_end(argptr);
|
|
isDataFormatted = true;
|
|
|
|
ConColorMsg( color, "%s", data );
|
|
}
|
|
|
|
if ( !NextBotDebugHistory.GetBool() )
|
|
{
|
|
if ( m_debugHistory.Count() )
|
|
{
|
|
ResetDebugHistory();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Don't bother with event data - it's spammy enough to overshadow everything else.
|
|
if ( debugType == NEXTBOT_EVENTS )
|
|
return;
|
|
|
|
if ( !isDataFormatted )
|
|
{
|
|
va_start(argptr, fmt);
|
|
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
|
|
va_end(argptr);
|
|
isDataFormatted = true;
|
|
}
|
|
|
|
int lastLine = m_debugHistory.Count() - 1;
|
|
if ( lastLine >= 0 )
|
|
{
|
|
NextBotDebugLineType *line = m_debugHistory[lastLine];
|
|
if ( line->debugType == debugType && V_strstr( line->data, "\n" ) == NULL )
|
|
{
|
|
// append onto previous line
|
|
V_strncat( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Prune out an old line if needed, keeping a pointer to re-use the memory
|
|
NextBotDebugLineType *line = NULL;
|
|
if ( m_debugHistory.Count() == MAX_NEXTBOT_DEBUG_HISTORY )
|
|
{
|
|
line = m_debugHistory[0];
|
|
m_debugHistory.Remove( 0 );
|
|
}
|
|
|
|
// Add to debug history
|
|
if ( !line )
|
|
{
|
|
line = new NextBotDebugLineType;
|
|
}
|
|
line->debugType = debugType;
|
|
V_strncpy( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
|
|
m_debugHistory.AddToTail( line );
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
// build a vector of debug history of the given types
|
|
void INextBot::GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const
|
|
{
|
|
if ( !lines )
|
|
return;
|
|
|
|
lines->RemoveAll();
|
|
|
|
for ( int i=0; i<m_debugHistory.Count(); ++i )
|
|
{
|
|
NextBotDebugLineType *line = m_debugHistory[i];
|
|
if ( line->debugType & type )
|
|
{
|
|
lines->AddToTail( line );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------
|
|
void INextBot::UpdateImmobileStatus( void )
|
|
{
|
|
if ( m_immobileCheckTimer.IsElapsed() )
|
|
{
|
|
m_immobileCheckTimer.Start( 1.0f );
|
|
|
|
// if we haven't moved farther than this in 1 second, we're immobile
|
|
if ( ( GetEntity()->GetAbsOrigin() - m_immobileAnchor ).IsLengthGreaterThan( GetImmobileSpeedThreshold() ) )
|
|
{
|
|
// moved far enough, not immobile
|
|
m_immobileAnchor = GetEntity()->GetAbsOrigin();
|
|
m_immobileTimer.Invalidate();
|
|
}
|
|
else
|
|
{
|
|
// haven't escaped our anchor - we are immobile
|
|
if ( !m_immobileTimer.HasStarted() )
|
|
{
|
|
m_immobileTimer.Start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|