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.
1054 lines
31 KiB
1054 lines
31 KiB
5 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
//
|
||
|
// Author: Michael S. Booth (mike@turtlerockstudios.com), 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
|