//========= Copyright Valve Corporation, All rights reserved. ============// // simple_bot.cpp // A simple bot // Michael Booth, February 2009 #include "cbase.h" #include "simple_bot.h" #include "nav_mesh.h" //----------------------------------------------------------------------------------------------------- // Command to add a Simple Bot where your crosshairs are aiming //----------------------------------------------------------------------------------------------------- CON_COMMAND_F( simple_bot_add, "Add a simple bot.", FCVAR_CHEAT ) { CBasePlayer *player = UTIL_GetCommandClient(); if ( !player ) { return; } Vector forward; player->EyeVectors( &forward ); trace_t result; UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, player, COLLISION_GROUP_NONE, &result ); if ( !result.DidHit() ) { return; } CSimpleBot *bot = static_cast< CSimpleBot * >( CreateEntityByName( "simple_bot" ) ); if ( bot ) { Vector forward = player->GetAbsOrigin() - result.endpos; forward.z = 0.0f; forward.NormalizeInPlace(); QAngle angles; VectorAngles( forward, angles ); bot->SetAbsAngles( angles ); bot->SetAbsOrigin( result.endpos + Vector( 0, 0, 10.0f ) ); DispatchSpawn( bot ); } } //----------------------------------------------------------------------------------------------------- // The Simple Bot //----------------------------------------------------------------------------------------------------- LINK_ENTITY_TO_CLASS( simple_bot, CSimpleBot ); #ifndef TF_DLL PRECACHE_REGISTER( simple_bot ); #endif //----------------------------------------------------------------------------------------------------- CSimpleBot::CSimpleBot() { ALLOCATE_INTENTION_INTERFACE( CSimpleBot ); m_locomotor = new NextBotGroundLocomotion( this ); } //----------------------------------------------------------------------------------------------------- CSimpleBot::~CSimpleBot() { DEALLOCATE_INTENTION_INTERFACE; if ( m_locomotor ) delete m_locomotor; } //----------------------------------------------------------------------------------------------------- void CSimpleBot::Precache() { BaseClass::Precache(); #ifndef DOTA_DLL PrecacheModel( "models/humans/group01/female_01.mdl" ); #endif } //----------------------------------------------------------------------------------------------------- void CSimpleBot::Spawn( void ) { BaseClass::Spawn(); #ifndef DOTA_DLL SetModel( "models/humans/group01/female_01.mdl" ); #endif } //--------------------------------------------------------------------------------------------- // The Simple Bot behaviors //--------------------------------------------------------------------------------------------- /** * For use with TheNavMesh->ForAllAreas() * Find the Nth area in the sequence */ class SelectNthAreaFunctor { public: SelectNthAreaFunctor( int count ) { m_count = count; m_area = NULL; } bool operator() ( CNavArea *area ) { m_area = area; return ( m_count-- > 0 ); } int m_count; CNavArea *m_area; }; //--------------------------------------------------------------------------------------------- /** * This action causes the bot to pick a random nav area in the mesh and move to it, then * pick another, etc. * Actions usually each have their own .cpp/.h file and are organized into folders since there * are often many of them. For this example, we're keeping everything to a single .cpp/.h file. */ class CSimpleBotRoam : public Action< CSimpleBot > { public: //---------------------------------------------------------------------------------- // OnStart is called once when the Action first becomes active virtual ActionResult< CSimpleBot > OnStart( CSimpleBot *me, Action< CSimpleBot > *priorAction ) { // smooth out the bot's path following by moving toward a point farther down the path m_path.SetMinLookAheadDistance( 300.0f ); return Continue(); } //---------------------------------------------------------------------------------- // Update is called repeatedly (usually once per server frame) while the Action is active virtual ActionResult< CSimpleBot > Update( CSimpleBot *me, float interval ) { if ( m_path.IsValid() && !m_timer.IsElapsed() ) { // PathFollower::Update() moves the bot along the path using the bot's ILocomotion and IBody interfaces m_path.Update( me ); } else { SelectNthAreaFunctor pick( RandomInt( 0, TheNavMesh->GetNavAreaCount() - 1 ) ); TheNavMesh->ForAllAreas( pick ); if ( pick.m_area ) { CSimpleBotPathCost cost( me ); m_path.Compute( me, pick.m_area->GetCenter(), cost ); } // follow this path for a random duration (or until we reach the end) m_timer.Start( RandomFloat( 5.0f, 10.0f ) ); } return Continue(); } //---------------------------------------------------------------------------------- // this is an event handler - many more are available (see declaration of Action< Actor > in NextBotBehavior.h) virtual EventDesiredResult< CSimpleBot > OnStuck( CSimpleBot *me ) { // we are stuck trying to follow the current path - invalidate it so a new one is chosen m_path.Invalidate(); return TryContinue(); } virtual const char *GetName( void ) const { return "Roam"; } // return name of this action private: PathFollower m_path; CountdownTimer m_timer; }; //--------------------------------------------------------------------------------------------- /** * Instantiate the bot's Intention interface and start the initial Action (CSimpleBotRoam in this case) */ IMPLEMENT_INTENTION_INTERFACE( CSimpleBot, CSimpleBotRoam )