diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/cstrike/bot/bot.h | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/cstrike/bot/bot.h')
| -rw-r--r-- | game/shared/cstrike/bot/bot.h | 1053 |
1 files changed, 1053 insertions, 0 deletions
diff --git a/game/shared/cstrike/bot/bot.h b/game/shared/cstrike/bot/bot.h new file mode 100644 index 0000000..3067c2c --- /dev/null +++ b/game/shared/cstrike/bot/bot.h @@ -0,0 +1,1053 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// +// Author: Michael S. Booth ([email protected]), 2003 +// +// NOTE: The CS Bot code uses Doxygen-style comments. If you run Doxygen over this code, it will +// auto-generate documentation. Visit www.doxygen.org to download the system for free. +// + +#ifndef BOT_H +#define BOT_H + +#include "cbase.h" +#include "in_buttons.h" +#include "movehelper_server.h" +#include "mathlib/mathlib.h" + +#include "bot_manager.h" +#include "bot_util.h" +#include "bot_constants.h" +#include "nav_mesh.h" +#include "gameinterface.h" +#include "weapon_csbase.h" +#include "shared_util.h" +#include "util.h" +#include "shareddefs.h" + +#include "tier0/vprof.h" + +class BotProfile; + + +extern bool AreBotsAllowed(); + + +//-------------------------------------------------------------------------------------------------------- +// BOTPORT: Convert everything to assume "origin" means "feet" + +// +// Utility function to get "centroid" or center of player or player equivalent +// +inline Vector GetCentroid( const CBaseEntity *player ) +{ + Vector centroid = player->GetAbsOrigin(); + + const Vector &mins = player->WorldAlignMins(); + const Vector &maxs = player->WorldAlignMaxs(); + + centroid.z += (maxs.z - mins.z)/2.0f; + + //centroid.z += HalfHumanHeight; + + return centroid; +} + + +CBasePlayer* ClientPutInServerOverride_Bot( edict_t *pEdict, const char *playername ); + +/// @todo Remove this nasty hack - CreateFakeClient() calls CBot::Spawn, which needs the profile +extern const BotProfile *g_botInitProfile; +extern int g_botInitTeam; +extern int g_nClientPutInServerOverrides; + +//-------------------------------------------------------------------------------------------------------- +template < class T > T * CreateBot( const BotProfile *profile, int team ) +{ + if ( !AreBotsAllowed() ) + return NULL; + + if ( UTIL_ClientsInGame() >= gpGlobals->maxClients ) + { + CONSOLE_ECHO( "Unable to create bot: Server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients ); + return NULL; + } + + // set the bot's name + char botName[64]; + UTIL_ConstructBotNetName( botName, 64, profile ); + + // This is a backdoor we use so when the engine calls ClientPutInServer (from CreateFakeClient), + // expecting the game to make an entity for the fake client, we can make our special bot class + // instead of a CCSPlayer. + g_nClientPutInServerOverrides = 0; + ClientPutInServerOverride( ClientPutInServerOverride_Bot ); + + // get an edict for the bot + // NOTE: This will ultimately invoke CBot::Spawn(), so set the profile now + g_botInitProfile = profile; + g_botInitTeam = team; + edict_t *botEdict = engine->CreateFakeClient( botName ); + + ClientPutInServerOverride( NULL ); + Assert( g_nClientPutInServerOverrides == 1 ); + + + if ( botEdict == NULL ) + { + CONSOLE_ECHO( "Unable to create bot: CreateFakeClient() returned null.\n" ); + return NULL; + } + + + // create an instance of the bot's class and bind it to the edict + T *bot = dynamic_cast< T * >( CBaseEntity::Instance( botEdict ) ); + + if ( bot == NULL ) + { + Assert( false ); + Error( "Could not allocate and bind entity to bot edict.\n" ); + return NULL; + } + + bot->ClearFlags(); + bot->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + return bot; +} + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- +/** + * The base bot class from which bots for specific games are derived + * A template is needed here because the CBot class must be derived from CBasePlayer, + * but also may need to be derived from a more specific player class, such as CCSPlayer + */ +template < class PlayerType > +class CBot : public PlayerType +{ +public: + DECLARE_CLASS( CBot, PlayerType ); + + CBot( void ); ///< constructor initializes all values to zero + virtual ~CBot(); + virtual bool Initialize( const BotProfile *profile, int team ); ///< (EXTEND) prepare bot for action + + unsigned int GetID( void ) const { return m_id; } ///< return bot's unique ID + + virtual bool IsBot( void ) const { return true; } + virtual bool IsNetClient( void ) const { return false; } // Bots should return FALSE for this, they can't receive NET messages + + virtual void Spawn( void ); ///< (EXTEND) spawn the bot into the game + + virtual void Upkeep( void ) = 0; ///< lightweight maintenance, invoked frequently + virtual void Update( void ) = 0; ///< heavyweight algorithms, invoked less often + + + virtual void Run( void ); + virtual void Walk( void ); + virtual bool IsRunning( void ) const { return m_isRunning; } + + virtual void Crouch( void ); + virtual void StandUp( void ); + bool IsCrouching( void ) const { return m_isCrouching; } + + void PushPostureContext( void ); ///< push the current posture context onto the top of the stack + void PopPostureContext( void ); ///< restore the posture context to the next context on the stack + + virtual void MoveForward( void ); + virtual void MoveBackward( void ); + virtual void StrafeLeft( void ); + virtual void StrafeRight( void ); + + #define MUST_JUMP true + virtual bool Jump( bool mustJump = false ); ///< returns true if jump was started + bool IsJumping( void ); ///< returns true if we are in the midst of a jump + float GetJumpTimestamp( void ) const { return m_jumpTimestamp; } ///< return time last jump began + + virtual void ClearMovement( void ); ///< zero any MoveForward(), Jump(), etc + + const Vector &GetViewVector( void ); ///< return the actual view direction + + + //------------------------------------------------------------------------------------ + // Weapon interface + // + virtual void UseEnvironment( void ); + virtual void PrimaryAttack( void ); + virtual void ClearPrimaryAttack( void ); + virtual void TogglePrimaryAttack( void ); + virtual void SecondaryAttack( void ); + virtual void Reload( void ); + + float GetActiveWeaponAmmoRatio( void ) const; ///< returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) + bool IsActiveWeaponClipEmpty( void ) const; ///< return true if active weapon has any empty clip + bool IsActiveWeaponOutOfAmmo( void ) const; ///< return true if active weapon has no ammo at all + bool IsActiveWeaponRecoilHigh( void ) const; ///< return true if active weapon's bullet spray has become large and inaccurate + bool IsUsingScope( void ); ///< return true if looking thru weapon's scope + + + //------------------------------------------------------------------------------------ + // Event hooks + // + + /// invoked when injured by something (EXTEND) - returns the amount of damage inflicted + virtual int OnTakeDamage( const CTakeDamageInfo &info ) + { + return PlayerType::OnTakeDamage( info ); + } + + /// invoked when killed (EXTEND) + virtual void Event_Killed( const CTakeDamageInfo &info ) + { + PlayerType::Event_Killed( info ); + } + + bool IsEnemy( CBaseEntity *ent ) const; ///< returns TRUE if given entity is our enemy + int GetEnemiesRemaining( void ) const; ///< return number of enemies left alive + int GetFriendsRemaining( void ) const; ///< return number of friends left alive + + bool IsPlayerFacingMe( CBasePlayer *enemy ) const; ///< return true if player is facing towards us + bool IsPlayerLookingAtMe( CBasePlayer *enemy, float cosTolerance = 0.9f ) const; ///< returns true if other player is pointing right at us + bool IsLookingAtPosition( const Vector &pos, float angleTolerance = 20.0f ) const; ///< returns true if looking (roughly) at given position + + bool IsLocalPlayerWatchingMe( void ) const; ///< return true if local player is observing this bot + + void PrintIfWatched( PRINTF_FORMAT_STRING const char *format, ... ) const; ///< output message to console if we are being watched by the local player + + virtual void UpdatePlayer( void ); ///< update player physics, movement, weapon firing commands, etc + virtual void BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse ); + virtual void SetModel( const char *modelName ); + + int Save( CSave &save ) const { return 0; } + int Restore( CRestore &restore ) const { return 0; } + virtual void Think( void ) { } + + const BotProfile *GetProfile( void ) const { return m_profile; } ///< return our personality profile + + virtual bool ClientCommand( const CCommand &args ); ///< Do a "client command" - useful for invoking menu choices, etc. + virtual int Cmd_Argc( void ); ///< Returns the number of tokens in the command string + virtual char *Cmd_Argv( int argc ); ///< Retrieves a specified token + +private: + CUtlVector< char * > m_args; + +protected: + const BotProfile *m_profile; ///< the "personality" profile of this bot + +private: + friend class CBotManager; + + unsigned int m_id; ///< unique bot ID + + CUserCmd m_userCmd; + bool m_isRunning; ///< run/walk mode + bool m_isCrouching; ///< true if crouching (ducking) + float m_forwardSpeed; + float m_strafeSpeed; + float m_verticalSpeed; + int m_buttonFlags; ///< bitfield of movement buttons + + float m_jumpTimestamp; ///< time when we last began a jump + + Vector m_viewForward; ///< forward view direction (only valid when GetViewVector() is used) + + /// the PostureContext represents the current settings of walking and crouching + struct PostureContext + { + bool isRunning; + bool isCrouching; + }; + enum { MAX_POSTURE_STACK = 8 }; + PostureContext m_postureStack[ MAX_POSTURE_STACK ]; + int m_postureStackIndex; ///< index of top of stack + + void ResetCommand( void ); + //byte ThrottledMsec( void ) const; + +protected: + virtual float GetMoveSpeed( void ); ///< returns current movement speed (for walk/run) +}; + + +//----------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------------------------- +// +// Inlines +// + +//-------------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::SetModel( const char *modelName ) +{ + BaseClass::SetModel( modelName ); +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline float CBot<T>::GetMoveSpeed( void ) +{ + return this->MaxSpeed(); +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::Run( void ) +{ + m_isRunning = true; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::Walk( void ) +{ + m_isRunning = false; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsActiveWeaponRecoilHigh( void ) const +{ + const QAngle &angles = const_cast< CBot<T> * >( this )->GetPunchAngle(); + const float highRecoil = -1.5f; + return (angles.x < highRecoil); +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::PushPostureContext( void ) +{ + if (m_postureStackIndex == MAX_POSTURE_STACK) + { + PrintIfWatched( "PushPostureContext() overflow error!\n" ); + return; + } + + m_postureStack[ m_postureStackIndex ].isRunning = m_isRunning; + m_postureStack[ m_postureStackIndex ].isCrouching = m_isCrouching; + ++m_postureStackIndex; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline void CBot<T>::PopPostureContext( void ) +{ + if (m_postureStackIndex == 0) + { + PrintIfWatched( "PopPostureContext() underflow error!\n" ); + m_isRunning = true; + m_isCrouching = false; + return; + } + + --m_postureStackIndex; + m_isRunning = m_postureStack[ m_postureStackIndex ].isRunning; + m_isCrouching = m_postureStack[ m_postureStackIndex ].isCrouching; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsPlayerFacingMe( CBasePlayer *other ) const +{ + Vector toOther = other->GetAbsOrigin() - this->GetAbsOrigin(); + + Vector otherForward; + AngleVectors( other->EyeAngles() + other->GetPunchAngle(), &otherForward ); + + if (DotProduct( otherForward, toOther ) < 0.0f) + return true; + + return false; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsPlayerLookingAtMe( CBasePlayer *other, float cosTolerance ) const +{ + Vector toOther = other->GetAbsOrigin() - this->GetAbsOrigin(); + toOther.NormalizeInPlace(); + + Vector otherForward; + AngleVectors( other->EyeAngles() + other->GetPunchAngle(), &otherForward ); + + // other player must be pointing nearly right at us to be "looking at" us + if (DotProduct( otherForward, toOther ) < -cosTolerance) + return true; + + return false; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline const Vector &CBot<T>::GetViewVector( void ) +{ + AngleVectors( this->EyeAngles() + this->GetPunchAngle(), &m_viewForward ); + return m_viewForward; +} + +//----------------------------------------------------------------------------------------------------------- +template < class T > +inline bool CBot<T>::IsLookingAtPosition( const Vector &pos, float angleTolerance ) const +{ + // forced to do this since many methods in CBaseEntity are not const, but should be + CBot< T > *me = const_cast< CBot< T > * >( this ); + + Vector to = pos - me->EyePosition(); + + QAngle idealAngles; + VectorAngles( to, idealAngles ); + + QAngle viewAngles = me->EyeAngles(); + + float deltaYaw = AngleNormalize( idealAngles.y - viewAngles.y ); + float deltaPitch = AngleNormalize( idealAngles.x - viewAngles.x ); + + if (fabs( deltaYaw ) < angleTolerance && abs( deltaPitch ) < angleTolerance) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline CBot< PlayerType >::CBot( void ) +{ + // the profile will be attached after this instance is constructed + m_profile = NULL; + + // assign this bot a unique ID + static unsigned int nextID = 1; + + // wraparound (highly unlikely) + if (nextID == 0) + ++nextID; + + m_id = nextID; + ++nextID; + + m_postureStackIndex = 0; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline CBot< PlayerType >::~CBot( void ) +{ +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Prepare bot for action + */ +template < class PlayerType > +inline bool CBot< PlayerType >::Initialize( const BotProfile *profile, int team ) +{ + m_profile = profile; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::Spawn( void ) +{ + // initialize the bot (thus setting its profile) + if (m_profile == NULL) + Initialize( g_botInitProfile, g_botInitTeam ); + + // let the base class set some things up + PlayerType::Spawn(); + + // Make sure everyone knows we are a bot + this->AddFlag( FL_CLIENT | FL_FAKECLIENT ); + + // Bots use their own thinking mechanism + this->SetThink( NULL ); + + m_isRunning = true; + m_isCrouching = false; + m_postureStackIndex = 0; + + m_jumpTimestamp = 0.0f; + + // Command interface variable initialization + ResetCommand(); +} + + +/* +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::BotThink( void ) +{ +float g_flBotFullThinkInterval = 1.0 / 15.0; // full AI at lower frequency (was 10 in GoldSrc) + + + Upkeep(); + + if (gpGlobals->curtime >= m_flNextFullBotThink) + { + m_flNextFullBotThink = gpGlobals->curtime + g_flBotFullThinkInterval; + + ResetCommand(); + Update(); + } + + UpdatePlayer(); +} +*/ + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::MoveForward( void ) +{ + m_forwardSpeed = GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_FORWARD ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_BACK ); +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::MoveBackward( void ) +{ + m_forwardSpeed = -GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_BACK ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_FORWARD ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::StrafeLeft( void ) +{ + m_strafeSpeed = -GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_MOVELEFT ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_MOVERIGHT ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::StrafeRight( void ) +{ + m_strafeSpeed = GetMoveSpeed(); + SETBITS( m_buttonFlags, IN_MOVERIGHT ); + + // make mutually exclusive + CLEARBITS( m_buttonFlags, IN_MOVELEFT ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline bool CBot< PlayerType >::Jump( bool mustJump ) +{ + if (IsJumping() || IsCrouching()) + return false; + + if (!mustJump) + { + const float minJumpInterval = 0.9f; // 1.5f; + if (gpGlobals->curtime - m_jumpTimestamp < minJumpInterval) + return false; + } + + // still need sanity check for jumping frequency + const float sanityInterval = 0.3f; + if (gpGlobals->curtime - m_jumpTimestamp < sanityInterval) + return false; + + // jump + SETBITS( m_buttonFlags, IN_JUMP ); + m_jumpTimestamp = gpGlobals->curtime; + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Zero any MoveForward(), Jump(), etc + */ +template < class PlayerType > +void CBot< PlayerType >::ClearMovement( void ) +{ + m_forwardSpeed = 0.0; + m_strafeSpeed = 0.0; + m_verticalSpeed = 100.0; // stay at the top of water, so we don't drown. TODO: swim logic + m_buttonFlags &= ~(IN_FORWARD | IN_BACK | IN_LEFT | IN_RIGHT | IN_JUMP); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns true if we are in the midst of a jump + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsJumping( void ) +{ + // if long time after last jump, we can't be jumping + if (gpGlobals->curtime - m_jumpTimestamp > 3.0f) + return false; + + // if we just jumped, we're still jumping + if (gpGlobals->curtime - m_jumpTimestamp < 0.9f) // 1.0f + return true; + + // a little after our jump, we're jumping until we hit the ground + if (FBitSet( this->GetFlags(), FL_ONGROUND )) + return false; + + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::Crouch( void ) +{ + m_isCrouching = true; +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::StandUp( void ) +{ + m_isCrouching = false; +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::UseEnvironment( void ) +{ + SETBITS( m_buttonFlags, IN_USE ); +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::PrimaryAttack( void ) +{ + SETBITS( m_buttonFlags, IN_ATTACK ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::ClearPrimaryAttack( void ) +{ + CLEARBITS( m_buttonFlags, IN_ATTACK ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::TogglePrimaryAttack( void ) +{ + if (FBitSet( m_buttonFlags, IN_ATTACK )) + { + CLEARBITS( m_buttonFlags, IN_ATTACK ); + } + else + { + SETBITS( m_buttonFlags, IN_ATTACK ); + } +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::SecondaryAttack( void ) +{ + SETBITS( m_buttonFlags, IN_ATTACK2 ); +} + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::Reload( void ) +{ + SETBITS( m_buttonFlags, IN_RELOAD ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns ratio of ammo left to max ammo (1 = full clip, 0 = empty) + */ +template < class PlayerType > +inline float CBot< PlayerType >::GetActiveWeaponAmmoRatio( void ) const +{ + CWeaponCSBase *weapon = this->GetActiveCSWeapon(); + + if (weapon == NULL) + return 0.0f; + + // weapons with no ammo are always full + if (weapon->Clip1() < 0) + return 1.0f; + + return (float)weapon->Clip1() / (float)weapon->GetMaxClip1(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if active weapon has an empty clip + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsActiveWeaponClipEmpty( void ) const +{ + CWeaponCSBase *gun = this->GetActiveCSWeapon(); + + if (gun && gun->Clip1() == 0) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if active weapon has no ammo at all + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsActiveWeaponOutOfAmmo( void ) const +{ + CWeaponCSBase *weapon = this->GetActiveCSWeapon(); + + if (weapon == NULL) + return true; + + return !weapon->HasAnyAmmo(); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if looking thru weapon's scope + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsUsingScope( void ) +{ + // if our field of view is less than 90, we're looking thru a scope (maybe only true for CS...) + if (this->GetFOV() < this->GetDefaultFOV()) + return true; + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Fill in a CUserCmd with our data + */ +template < class PlayerType > +inline void CBot< PlayerType >::BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse ) +{ + Q_memset( &cmd, 0, sizeof( cmd ) ); + cmd.command_number = gpGlobals->tickcount; + cmd.forwardmove = forwardmove; + cmd.sidemove = sidemove; + cmd.upmove = upmove; + cmd.buttons = buttons; + cmd.impulse = impulse; + + VectorCopy( viewangles, cmd.viewangles ); + cmd.random_seed = random->RandomInt( 0, 0x7fffffff ); +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Update player physics, movement, weapon firing commands, etc + */ +template < class PlayerType > +inline void CBot< PlayerType >::UpdatePlayer( void ) +{ + if (m_isCrouching) + { + SETBITS( m_buttonFlags, IN_DUCK ); + } + else if (!m_isRunning) + { + SETBITS( m_buttonFlags, IN_SPEED ); + } + + if ( this->IsEFlagSet(EFL_BOT_FROZEN) ) + { + m_buttonFlags = 0; // Freeze. + m_forwardSpeed = 0; + m_strafeSpeed = 0; + m_verticalSpeed = 0; + } + + // Fill in a CUserCmd with our data + this->BuildUserCmd( m_userCmd, this->EyeAngles(), m_forwardSpeed, m_strafeSpeed, m_verticalSpeed, m_buttonFlags, 0 ); + + // Save off the CUserCmd to execute later + this->ProcessUsercmds( &m_userCmd, 1, 1, 0, false ); +} + + +//-------------------------------------------------------------------------------------------------------------- +template < class PlayerType > +inline void CBot< PlayerType >::ResetCommand( void ) +{ + m_forwardSpeed = 0.0; + m_strafeSpeed = 0.0; + m_verticalSpeed = 100.0; // stay at the top of water, so we don't drown. TODO: swim logic + m_buttonFlags = 0; +} + + +//-------------------------------------------------------------------------------------------------------------- +/* +template < class PlayerType > +inline byte CBot< PlayerType >::ThrottledMsec( void ) const +{ + int iNewMsec; + + // Estimate Msec to use for this command based on time passed from the previous command + iNewMsec = (int)( (gpGlobals->curtime - m_flPreviousCommandTime) * 1000 ); + if (iNewMsec > 255) // Doh, bots are going to be slower than they should if this happens. + iNewMsec = 255; // Upgrade that CPU or use less bots! + + return (byte)iNewMsec; +} +*/ + +//-------------------------------------------------------------------------------------------------------------- +/** + * Do a "client command" - useful for invoking menu choices, etc. + */ +template < class PlayerType > +inline bool CBot< PlayerType >::ClientCommand( const CCommand &args ) +{ + // Remove old args + int i; + for ( i=0; i<m_args.Count(); ++i ) + { + delete[] m_args[i]; + } + m_args.RemoveAll(); + + // parse individual args + const char *cmd = args.GetCommandString(); + while (1) + { + // skip whitespace up to a /n + while (*cmd && *cmd <= ' ' && *cmd != '\n') + { + cmd++; + } + + if (*cmd == '\n') + { // a newline seperates commands in the buffer + cmd++; + break; + } + + if (!*cmd) + break; + + cmd = SharedParse (cmd); + if (!cmd) + break; + + m_args.AddToTail( CloneString( SharedGetToken() ) ); + } + + // and pass to the base class + return PlayerType::ClientCommand( args ); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns the number of tokens in the command string + */ +template < class PlayerType > +inline int CBot< PlayerType >::Cmd_Argc() +{ + return m_args.Count(); +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Retrieves a specified token + */ +template < class PlayerType > +inline char * CBot< PlayerType >::Cmd_Argv( int argc ) +{ + if ( argc < 0 || argc >= m_args.Count() ) + return NULL; + return m_args[argc]; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Returns TRUE if given entity is our enemy + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsEnemy( CBaseEntity *ent ) const +{ + // only Players (real and AI) can be enemies + if (!ent->IsPlayer()) + return false; + + // corpses are no threat + if (!ent->IsAlive()) + return false; + + CBasePlayer *player = static_cast<CBasePlayer *>( ent ); + + // if they are on our team, they are our friends + if (player->GetTeamNumber() == this->GetTeamNumber()) + return false; + + // yep, we hate 'em + return true; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of enemies left alive + */ +template < class PlayerType > +inline int CBot< PlayerType >::GetEnemiesRemaining( void ) const +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (!IsEnemy( player )) + continue; + + if (!player->IsAlive()) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return number of friends left alive + */ +template < class PlayerType > +inline int CBot< PlayerType >::GetFriendsRemaining( void ) const +{ + int count = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; ++i ) + { + CBaseEntity *player = UTIL_PlayerByIndex( i ); + + if (player == NULL) + continue; + + if (IsEnemy( player )) + continue; + + if (!player->IsAlive()) + continue; + + if (player == static_cast<CBaseEntity *>( const_cast<CBot *>( this ) )) + continue; + + count++; + } + + return count; +} + +//-------------------------------------------------------------------------------------------------------------- +/** + * Return true if the local player is currently in observer mode watching this bot. + */ +template < class PlayerType > +inline bool CBot< PlayerType >::IsLocalPlayerWatchingMe( void ) const +{ + if ( engine->IsDedicatedServer() ) + return false; + + CBasePlayer *player = UTIL_GetListenServerHost(); + if ( player == NULL ) + return false; + + if ( cv_bot_debug_target.GetInt() > 0 ) + { + return this->entindex() == cv_bot_debug_target.GetInt(); + } + + if ( player->IsObserver() || !player->IsAlive() ) + { + if ( const_cast< CBot< PlayerType > * >(this) == player->GetObserverTarget() ) + { + switch( player->GetObserverMode() ) + { + case OBS_MODE_IN_EYE: + case OBS_MODE_CHASE: + return true; + } + } + } + + return false; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Output message to console if we are being watched by the local player + */ +template < class PlayerType > +inline void CBot< PlayerType >::PrintIfWatched( PRINTF_FORMAT_STRING const char *format, ... ) const +{ + if (cv_bot_debug.GetInt() == 0) + { + return; + } + + if ((IsLocalPlayerWatchingMe() && (cv_bot_debug.GetInt() == 1 || cv_bot_debug.GetInt() == 3)) || + (cv_bot_debug.GetInt() == 2 || cv_bot_debug.GetInt() == 4)) + { + va_list varg; + char buffer[ CBotManager::MAX_DBG_MSG_SIZE ]; + const char *name = const_cast< CBot< PlayerType > * >( this )->GetPlayerName(); + + va_start( varg, format ); + vsprintf( buffer, format, varg ); + va_end( varg ); + + // prefix the console message with the bot's name (this can be NULL if bot was just added) + ClientPrint( UTIL_GetListenServerHost(), + HUD_PRINTCONSOLE, + UTIL_VarArgs( "%s: %s", + (name) ? name : "(NULL netname)", buffer ) ); + + TheBots->AddDebugMessage( buffer ); + } +} + +//----------------------------------------------------------------------------------------------------------- +//----------------------------------------------------------------------------------------------------------- + +extern void InstallBotControl( void ); +extern void RemoveBotControl( void ); +extern void Bot_ServerCommand( void ); +extern void Bot_RegisterCvars( void ); + +extern bool IsSpotOccupied( CBaseEntity *me, const Vector &pos ); // if a player is at the given spot, return true +extern const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange = 1000.0f, bool isSniper = false, bool useNearest = false ); +extern const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper = false ); +extern const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange = 1000.0f, int avoidTeam = 0 ); + + +#endif // BOT_H |