// 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; ientindex() ) ); } 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(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; idebugType & 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(); } } } }