summaryrefslogtreecommitdiff
path: root/game/shared/cstrike/bot
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/cstrike/bot')
-rw-r--r--game/shared/cstrike/bot/bot.cpp109
-rw-r--r--game/shared/cstrike/bot/bot.h1053
-rw-r--r--game/shared/cstrike/bot/bot_constants.h40
-rw-r--r--game/shared/cstrike/bot/bot_hide.cpp490
-rw-r--r--game/shared/cstrike/bot/bot_manager.cpp402
-rw-r--r--game/shared/cstrike/bot/bot_manager.h195
-rw-r--r--game/shared/cstrike/bot/bot_profile.cpp704
-rw-r--r--game/shared/cstrike/bot/bot_profile.h251
-rw-r--r--game/shared/cstrike/bot/bot_util.cpp604
-rw-r--r--game/shared/cstrike/bot/bot_util.h167
-rw-r--r--game/shared/cstrike/bot/improv_locomotor.h57
-rw-r--r--game/shared/cstrike/bot/nav_path.cpp1208
-rw-r--r--game/shared/cstrike/bot/nav_path.h246
-rw-r--r--game/shared/cstrike/bot/shared_util.cpp207
-rw-r--r--game/shared/cstrike/bot/shared_util.h83
15 files changed, 5816 insertions, 0 deletions
diff --git a/game/shared/cstrike/bot/bot.cpp b/game/shared/cstrike/bot/bot.cpp
new file mode 100644
index 0000000..4b17ddf
--- /dev/null
+++ b/game/shared/cstrike/bot/bot.cpp
@@ -0,0 +1,109 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), Leon Hartwig, 2003
+
+#include "cbase.h"
+#include "basegrenade_shared.h"
+
+#include "bot.h"
+#include "bot_util.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+/// @todo Remove this nasty hack - CreateFakeClient() calls CBot::Spawn, which needs the profile and team
+const BotProfile *g_botInitProfile = NULL;
+int g_botInitTeam = 0;
+
+//
+// NOTE: Because CBot had to be templatized, the code was moved into bot.h
+//
+
+
+//--------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------------
+
+ActiveGrenade::ActiveGrenade( CBaseGrenade *grenadeEntity )
+{
+ m_entity = grenadeEntity;
+ m_detonationPosition = grenadeEntity->GetAbsOrigin();
+ m_dieTimestamp = 0.0f;
+ m_radius = HEGrenadeRadius;
+
+ m_isSmoke = FStrEq( grenadeEntity->GetClassname(), "smokegrenade_projectile" );
+ if ( m_isSmoke )
+ {
+ m_radius = SmokeGrenadeRadius;
+ }
+
+ m_isFlashbang = FStrEq( grenadeEntity->GetClassname(), "flashbang_projectile" );
+ if ( m_isFlashbang )
+ {
+ m_radius = FlashbangGrenadeRadius;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called when the grenade in the world goes away
+ */
+void ActiveGrenade::OnEntityGone( void )
+{
+ if (m_isSmoke)
+ {
+ // smoke lingers after grenade is gone
+ const float smokeLingerTime = 4.0f;
+ m_dieTimestamp = gpGlobals->curtime + smokeLingerTime;
+ }
+
+ m_entity = NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void ActiveGrenade::Update( void )
+{
+ if (m_entity != NULL)
+ {
+ m_detonationPosition = m_entity->GetAbsOrigin();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this grenade is valid
+ */
+bool ActiveGrenade::IsValid( void ) const
+{
+ if ( m_isSmoke )
+ {
+ if ( m_entity == NULL && gpGlobals->curtime > m_dieTimestamp )
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if ( m_entity == NULL )
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+const Vector &ActiveGrenade::GetPosition( void ) const
+{
+ // smoke grenades can vanish before the smoke itself does - refer to the detonation position
+ if (m_entity == NULL)
+ return GetDetonationPosition();
+
+ return m_entity->GetAbsOrigin();
+}
+
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
diff --git a/game/shared/cstrike/bot/bot_constants.h b/game/shared/cstrike/bot/bot_constants.h
new file mode 100644
index 0000000..94f5090
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_constants.h
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Matthew D. Campbell ([email protected]), 2003
+
+#ifndef BOT_CONSTANTS_H
+#define BOT_CONSTANTS_H
+
+/// version number is MAJOR.MINOR
+#define BOT_VERSION_MAJOR 1
+#define BOT_VERSION_MINOR 50
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Difficulty levels
+ */
+enum BotDifficultyType
+{
+ BOT_EASY = 0,
+ BOT_NORMAL = 1,
+ BOT_HARD = 2,
+ BOT_EXPERT = 3,
+
+ NUM_DIFFICULTY_LEVELS
+};
+
+#ifdef DEFINE_DIFFICULTY_NAMES
+ const char *BotDifficultyName[] =
+ {
+ "EASY", "NORMAL", "HARD", "EXPERT", NULL
+ };
+#else
+ extern const char *BotDifficultyName[];
+#endif
+
+#endif // BOT_CONSTANTS_H
diff --git a/game/shared/cstrike/bot/bot_hide.cpp b/game/shared/cstrike/bot/bot_hide.cpp
new file mode 100644
index 0000000..9a2b02c
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_hide.cpp
@@ -0,0 +1,490 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// bot_hide.cpp
+// Mechanisms for using Hiding Spots in the Navigation Mesh
+// Author: Michael Booth, 2003-2004
+
+#include "cbase.h"
+#include "bot.h"
+#include "cs_nav_pathfind.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * If a player is at the given spot, return true
+ */
+bool IsSpotOccupied( CBaseEntity *me, const Vector &pos )
+{
+ const float closeRange = 75.0f; // 50
+
+ // is there a player in this spot
+ float range;
+ CBasePlayer *player = UTIL_GetClosestPlayer( pos, &range );
+
+ if (player != me)
+ {
+ if (player && range < closeRange)
+ return true;
+ }
+
+ // is there is a hostage in this spot
+ // BOTPORT: Implement hostage manager
+ /*
+ if (g_pHostages)
+ {
+ CHostage *hostage = g_pHostages->GetClosestHostage( *pos, &range );
+ if (hostage && hostage != me && range < closeRange)
+ return true;
+ }
+ */
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+class CollectHidingSpotsFunctor
+{
+public:
+ CollectHidingSpotsFunctor( CBaseEntity *me, const Vector &origin, float range, int flags, Place place = UNDEFINED_PLACE ) : m_origin( origin )
+ {
+ m_me = me;
+ m_count = 0;
+ m_range = range;
+ m_flags = (unsigned char)flags;
+ m_place = place;
+ m_totalWeight = 0;
+ }
+
+ enum { MAX_SPOTS = 256 };
+
+ bool operator() ( CNavArea *area )
+ {
+ // if a place is specified, only consider hiding spots from areas in that place
+ if (m_place != UNDEFINED_PLACE && area->GetPlace() != m_place)
+ return true;
+
+ // collect all the hiding spots in this area
+ const HidingSpotVector *pSpots = area->GetHidingSpots();
+
+ FOR_EACH_VEC( (*pSpots), it )
+ {
+ const HidingSpot *spot = (*pSpots)[ it ];
+
+ // if we've filled up, stop searching
+ if (m_count == MAX_SPOTS)
+ {
+ return false;
+ }
+
+ // make sure hiding spot is in range
+ if (m_range > 0.0f)
+ {
+ if ((spot->GetPosition() - m_origin).IsLengthGreaterThan( m_range ))
+ {
+ continue;
+ }
+ }
+
+ // if a Player is using this hiding spot, don't consider it
+ if (IsSpotOccupied( m_me, spot->GetPosition() ))
+ {
+ // player is in hiding spot
+ /// @todo Check if player is moving or sitting still
+ continue;
+ }
+
+ if (spot->GetArea() && (spot->GetArea()->GetAttributes() & NAV_MESH_DONT_HIDE))
+ {
+ // the area has been marked as DONT_HIDE since the last analysis, so let's ignore it
+ continue;
+ }
+
+ // only collect hiding spots with matching flags
+ if (m_flags & spot->GetFlags())
+ {
+ m_hidingSpot[ m_count ] = &spot->GetPosition();
+ m_hidingSpotWeight[ m_count ] = m_totalWeight;
+
+ // if it's an 'avoid' area, give it a low weight
+ if ( spot->GetArea() && ( spot->GetArea()->GetAttributes() & NAV_MESH_AVOID ) )
+ {
+ m_totalWeight += 1;
+ }
+ else
+ {
+ m_totalWeight += 2;
+ }
+
+ ++m_count;
+ }
+ }
+
+ return (m_count < MAX_SPOTS);
+ }
+
+ /**
+ * Remove the spot at index "i"
+ */
+ void RemoveSpot( int i )
+ {
+ if (m_count == 0)
+ return;
+
+ for( int j=i+1; j<m_count; ++j )
+ m_hidingSpot[j-1] = m_hidingSpot[j];
+
+ --m_count;
+ }
+
+
+ int GetRandomHidingSpot( void )
+ {
+ int weight = RandomInt( 0, m_totalWeight-1 );
+ for ( int i=0; i<m_count-1; ++i )
+ {
+ // if the next spot's starting weight is over the target weight, this spot is the one
+ if ( m_hidingSpotWeight[i+1] >= weight )
+ {
+ return i;
+ }
+ }
+
+ // if we didn't find any, it's the last one
+ return m_count - 1;
+ }
+
+ CBaseEntity *m_me;
+ const Vector &m_origin;
+ float m_range;
+
+ const Vector *m_hidingSpot[ MAX_SPOTS ];
+ int m_hidingSpotWeight[ MAX_SPOTS ];
+ int m_totalWeight;
+ int m_count;
+
+ unsigned char m_flags;
+
+ Place m_place;
+};
+
+/**
+ * Do a breadth-first search to find a nearby hiding spot and return it.
+ * Don't pick a hiding spot that a Player is currently occupying.
+ * @todo Clean up this mess
+ */
+const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange, bool isSniper, bool useNearest )
+{
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( pos );
+ if (startArea == NULL)
+ return NULL;
+
+ // collect set of nearby hiding spots
+ if (isSniper)
+ {
+ CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IDEAL_SNIPER_SPOT );
+ SearchSurroundingAreas( startArea, pos, collector, maxRange );
+
+ if (collector.m_count)
+ {
+ int which = collector.GetRandomHidingSpot();
+ return collector.m_hidingSpot[ which ];
+ }
+ else
+ {
+ // no ideal sniping spots, look for "good" sniping spots
+ CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::GOOD_SNIPER_SPOT );
+ SearchSurroundingAreas( startArea, pos, collector, maxRange );
+
+ if (collector.m_count)
+ {
+ int which = collector.GetRandomHidingSpot();
+ return collector.m_hidingSpot[ which ];
+ }
+
+ // no sniping spots at all.. fall through and pick a normal hiding spot
+ }
+ }
+
+ // collect hiding spots with decent "cover"
+ CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IN_COVER );
+ SearchSurroundingAreas( startArea, pos, collector, maxRange );
+
+ if (collector.m_count == 0)
+ {
+ // no hiding spots at all - if we're not a sniper, try to find a sniper spot to use instead
+ if (!isSniper)
+ {
+ return FindNearbyHidingSpot( me, pos, maxRange, true, useNearest );
+ }
+
+ return NULL;
+ }
+
+ if (useNearest)
+ {
+ // return closest hiding spot
+ const Vector *closest = NULL;
+ float closeRangeSq = 9999999999.9f;
+ for( int i=0; i<collector.m_count; ++i )
+ {
+ float rangeSq = (*collector.m_hidingSpot[i] - pos).LengthSqr();
+ if (rangeSq < closeRangeSq)
+ {
+ closeRangeSq = rangeSq;
+ closest = collector.m_hidingSpot[i];
+ }
+ }
+
+ return closest;
+ }
+
+ // select a hiding spot at random
+ int which = collector.GetRandomHidingSpot();
+ return collector.m_hidingSpot[ which ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Select a random hiding spot among the nav areas that are tagged with the given place
+ */
+const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper )
+{
+ // collect set of nearby hiding spots
+ if (isSniper)
+ {
+ CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IDEAL_SNIPER_SPOT, place );
+ TheNavMesh->ForAllAreas( collector );
+
+ if (collector.m_count)
+ {
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+ }
+ else
+ {
+ // no ideal sniping spots, look for "good" sniping spots
+ CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::GOOD_SNIPER_SPOT, place );
+ TheNavMesh->ForAllAreas( collector );
+
+ if (collector.m_count)
+ {
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+ }
+
+ // no sniping spots at all.. fall through and pick a normal hiding spot
+ }
+ }
+
+ // collect hiding spots with decent "cover"
+ CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IN_COVER, place );
+ TheNavMesh->ForAllAreas( collector );
+
+ if (collector.m_count == 0)
+ return NULL;
+
+ // select a hiding spot at random
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------------
+/**
+ * Select a nearby retreat spot.
+ * Don't pick a hiding spot that a Player is currently occupying.
+ * If "avoidTeam" is nonzero, avoid getting close to members of that team.
+ */
+const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange, int avoidTeam )
+{
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( start );
+ if (startArea == NULL)
+ return NULL;
+
+ // collect hiding spots with decent "cover"
+ CollectHidingSpotsFunctor collector( me, start, maxRange, HidingSpot::IN_COVER );
+ SearchSurroundingAreas( startArea, start, collector, maxRange );
+
+ if (collector.m_count == 0)
+ return NULL;
+
+ // find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover
+ for( int i=0; i<collector.m_count; ++i )
+ {
+ // check if we would have to cross a line of fire to reach this hiding spot
+ if (IsCrossingLineOfFire( start, *collector.m_hidingSpot[i], me ))
+ {
+ collector.RemoveSpot( i );
+
+ // back up a step, so iteration won't skip a spot
+ --i;
+
+ continue;
+ }
+
+ // check if there is someone on the avoidTeam near this hiding spot
+ if (avoidTeam)
+ {
+ float range;
+ if (UTIL_GetClosestPlayer( *collector.m_hidingSpot[i], avoidTeam, &range ))
+ {
+ const float dangerRange = 150.0f;
+ if (range < dangerRange)
+ {
+ // there is an avoidable player too near this spot - remove it
+ collector.RemoveSpot( i );
+
+ // back up a step, so iteration won't skip a spot
+ --i;
+
+ continue;
+ }
+ }
+ }
+ }
+
+ if (collector.m_count <= 0)
+ return NULL;
+
+ // all remaining spots are ok - pick one at random
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------------
+/**
+ * Functor to collect all hiding spots in range that we can reach before the enemy arrives.
+ * NOTE: This only works for the initial rush.
+ */
+class CollectArriveFirstSpotsFunctor
+{
+public:
+ CollectArriveFirstSpotsFunctor( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float range, int flags ) : m_searchOrigin( searchOrigin )
+ {
+ m_me = me;
+ m_count = 0;
+ m_range = range;
+ m_flags = (unsigned char)flags;
+ m_enemyArriveTime = enemyArriveTime;
+ }
+
+ enum { MAX_SPOTS = 256 };
+
+ bool operator() ( CNavArea *area )
+ {
+ // collect all the hiding spots in this area
+ const HidingSpotVector *pSpots = area->GetHidingSpots();
+
+ FOR_EACH_VEC( (*pSpots), it )
+ {
+ const HidingSpot *spot = (*pSpots)[ it ];
+
+ // make sure hiding spot is in range
+ if (m_range > 0.0f)
+ {
+ if ((spot->GetPosition() - m_searchOrigin).IsLengthGreaterThan( m_range ))
+ {
+ continue;
+ }
+ }
+
+ // if a Player is using this hiding spot, don't consider it
+ if (IsSpotOccupied( m_me, spot->GetPosition() ))
+ {
+ // player is in hiding spot
+ /// @todo Check if player is moving or sitting still
+ continue;
+ }
+
+ // only collect hiding spots with matching flags
+ if (!(m_flags & spot->GetFlags()))
+ {
+ continue;
+ }
+
+ // only collect this hiding spot if we can reach it before the enemy arrives
+ // NOTE: This assumes the area is fairly small and the difference of moving to the corner vs the center is small
+ const float settleTime = 1.0f;
+ if (spot->GetArea()->GetEarliestOccupyTime( m_me->GetTeamNumber() ) + settleTime < m_enemyArriveTime)
+ {
+ m_hidingSpot[ m_count++ ] = spot;
+ }
+ }
+
+ // if we've filled up, stop searching
+ if (m_count == MAX_SPOTS)
+ return false;
+
+ return true;
+ }
+
+ CBaseEntity *m_me;
+ const Vector &m_searchOrigin;
+
+ float m_range;
+ float m_enemyArriveTime;
+ unsigned char m_flags;
+
+ const HidingSpot *m_hidingSpot[ MAX_SPOTS ];
+ int m_count;
+};
+
+
+/**
+ * Select a hiding spot that we can reach before the enemy arrives.
+ * NOTE: This only works for the initial rush.
+ */
+const HidingSpot *FindInitialEncounterSpot( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float maxRange, bool isSniper )
+{
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( searchOrigin );
+ if (startArea == NULL)
+ return NULL;
+
+ // collect set of nearby hiding spots
+ if (isSniper)
+ {
+ CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IDEAL_SNIPER_SPOT );
+ SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
+
+ if (collector.m_count)
+ {
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+ }
+ else
+ {
+ // no ideal sniping spots, look for "good" sniping spots
+ CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::GOOD_SNIPER_SPOT );
+ SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
+
+ if (collector.m_count)
+ {
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+ }
+
+ // no sniping spots at all.. fall through and pick a normal hiding spot
+ }
+ }
+
+ // collect hiding spots with decent "cover"
+ CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IN_COVER | HidingSpot::EXPOSED );
+ SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange );
+
+ if (collector.m_count == 0)
+ return NULL;
+
+ // select a hiding spot at random
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_hidingSpot[ which ];
+}
+
diff --git a/game/shared/cstrike/bot/bot_manager.cpp b/game/shared/cstrike/bot/bot_manager.cpp
new file mode 100644
index 0000000..ce15823
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_manager.cpp
@@ -0,0 +1,402 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+
+#include "bot.h"
+#include "bot_manager.h"
+#include "nav_area.h"
+#include "bot_util.h"
+#include "basegrenade_shared.h"
+
+#include "cs_bot.h"
+
+#include "tier0/vprof.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+float g_BotUpkeepInterval = 0.0f;
+float g_BotUpdateInterval = 0.0f;
+
+
+//--------------------------------------------------------------------------------------------------------------
+CBotManager::CBotManager()
+{
+ InitBotTrig();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+CBotManager::~CBotManager()
+{
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when the round is restarting
+ */
+void CBotManager::RestartRound( void )
+{
+ DestroyAllGrenades();
+ ClearDebugMessages();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked at the start of each frame
+ */
+void CBotManager::StartFrame( void )
+{
+ VPROF_BUDGET( "CBotManager::StartFrame", VPROF_BUDGETGROUP_NPCS );
+
+ ValidateActiveGrenades();
+
+ // debug smoke grenade visualization
+ if (cv_bot_debug.GetInt() == 5)
+ {
+ Vector edge, lastEdge;
+
+ FOR_EACH_LL( m_activeGrenadeList, it )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+
+ const Vector &pos = ag->GetDetonationPosition();
+
+ UTIL_DrawBeamPoints( pos, pos + Vector( 0, 0, 50 ), 1, 255, 100, 0 );
+
+ lastEdge = Vector( ag->GetRadius() + pos.x, pos.y, pos.z );
+ float angle;
+ for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
+ {
+ edge.x = ag->GetRadius() * BotCOS( angle ) + pos.x;
+ edge.y = pos.y;
+ edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z;
+
+ UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
+
+ lastEdge = edge;
+ }
+
+ lastEdge = Vector( pos.x, ag->GetRadius() + pos.y, pos.z );
+ for( angle=0.0f; angle <= 180.0f; angle += 22.5f )
+ {
+ edge.x = pos.x;
+ edge.y = ag->GetRadius() * BotCOS( angle ) + pos.y;
+ edge.z = ag->GetRadius() * BotSIN( angle ) + pos.z;
+
+ UTIL_DrawBeamPoints( edge, lastEdge, 1, 255, 50, 0 );
+
+ lastEdge = edge;
+ }
+ }
+ }
+
+ // set frame duration
+ g_BotUpkeepInterval = m_frameTimer.GetElapsedTime();
+ m_frameTimer.Start();
+
+ g_BotUpdateInterval = (g_BotUpdateSkipCount+1) * g_BotUpkeepInterval;
+
+ //
+ // Process each active bot
+ //
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (!player)
+ continue;
+
+ // Hack for now so the temp bot code works. The temp bots are very useful for debugging
+ // because they can be setup to mimic the player's usercmds.
+ if (player->IsBot() && IsEntityValid( player ) )
+ {
+ // EVIL: Messes up vtables
+ //CBot< CBasePlayer > *bot = static_cast< CBot< CBasePlayer > * >( player );
+ CCSBot *bot = dynamic_cast< CCSBot * >( player );
+
+ if ( bot )
+ {
+ bot->Upkeep();
+
+ if (((gpGlobals->tickcount + bot->entindex()) % g_BotUpdateSkipCount) == 0)
+ {
+ bot->ResetCommand();
+ bot->Update();
+ }
+
+ bot->UpdatePlayer();
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add an active grenade to the bot's awareness
+ */
+void CBotManager::AddGrenade( CBaseGrenade *grenade )
+{
+ ActiveGrenade *ag = new ActiveGrenade( grenade );
+ m_activeGrenadeList.AddToTail( ag );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The grenade entity in the world is going away
+ */
+void CBotManager::RemoveGrenade( CBaseGrenade *grenade )
+{
+ FOR_EACH_LL( m_activeGrenadeList, it )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+
+ if (ag->IsEntity( grenade ))
+ {
+ ag->OnEntityGone();
+ return;
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The grenade entity has changed its radius
+ */
+void CBotManager::SetGrenadeRadius( CBaseGrenade *grenade, float radius )
+{
+ FOR_EACH_LL( m_activeGrenadeList, it )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+
+ if (ag->IsEntity( grenade ))
+ {
+ ag->SetRadius( radius );
+ return;
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Destroy any invalid active grenades
+ */
+void CBotManager::ValidateActiveGrenades( void )
+{
+ int it = m_activeGrenadeList.Head();
+
+ while( it != m_activeGrenadeList.InvalidIndex() )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+
+ int current = it;
+ it = m_activeGrenadeList.Next( it );
+
+ // lazy validation
+ if (!ag->IsValid())
+ {
+ m_activeGrenadeList.Remove( current );
+ delete ag;
+ continue;
+ }
+ else
+ {
+ ag->Update();
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CBotManager::DestroyAllGrenades( void )
+{
+ m_activeGrenadeList.PurgeAndDeleteElements();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if position is inside a smoke cloud
+ */
+bool CBotManager::IsInsideSmokeCloud( const Vector *pos )
+{
+ int it = m_activeGrenadeList.Head();
+
+ while( it != m_activeGrenadeList.InvalidIndex() )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+
+ int current = it;
+ it = m_activeGrenadeList.Next( it );
+
+ // lazy validation
+ if (!ag->IsValid())
+ {
+ m_activeGrenadeList.Remove( current );
+ delete ag;
+ continue;
+ }
+
+ if (ag->IsSmoke())
+ {
+ const Vector &smokeOrigin = ag->GetDetonationPosition();
+
+ if ((smokeOrigin - *pos).IsLengthLessThan( ag->GetRadius() ))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if line intersects smoke volume
+ * Determine the length of the line of sight covered by each smoke cloud,
+ * and sum them (overlap is additive for obstruction).
+ * If the overlap exceeds the threshold, the bot can't see through.
+ */
+bool CBotManager::IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat )
+{
+ VPROF_BUDGET( "CBotManager::IsLineBlockedBySmoke", VPROF_BUDGETGROUP_NPCS );
+
+ float totalSmokedLength = 0.0f; // distance along line of sight covered by smoke
+
+ // compute unit vector and length of line of sight segment
+ Vector sightDir = to - from;
+ float sightLength = sightDir.NormalizeInPlace();
+
+ FOR_EACH_LL( m_activeGrenadeList, it )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+ const float smokeRadiusSq = ag->GetRadius() * ag->GetRadius() * grenadeBloat * grenadeBloat;
+
+ if (ag->IsSmoke())
+ {
+ const Vector &smokeOrigin = ag->GetDetonationPosition();
+
+ Vector toGrenade = smokeOrigin - from;
+
+ float alongDist = DotProduct( toGrenade, sightDir );
+
+ // compute closest point to grenade along line of sight ray
+ Vector close;
+
+ // constrain closest point to line segment
+ if (alongDist < 0.0f)
+ close = from;
+ else if (alongDist >= sightLength)
+ close = to;
+ else
+ close = from + sightDir * alongDist;
+
+ // if closest point is within smoke radius, the line overlaps the smoke cloud
+ Vector toClose = close - smokeOrigin;
+ float lengthSq = toClose.LengthSqr();
+
+ if (lengthSq < smokeRadiusSq)
+ {
+ // some portion of the ray intersects the cloud
+
+ float fromSq = toGrenade.LengthSqr();
+ float toSq = (smokeOrigin - to).LengthSqr();
+
+ if (fromSq < smokeRadiusSq)
+ {
+ if (toSq < smokeRadiusSq)
+ {
+ // both 'from' and 'to' lie within the cloud
+ // entire length is smoked
+ totalSmokedLength += (to - from).Length();
+ }
+ else
+ {
+ // 'from' is inside the cloud, 'to' is outside
+ // compute half of total smoked length as if ray crosses entire cloud chord
+ float halfSmokedLength = (float)sqrt( smokeRadiusSq - lengthSq );
+
+ if (alongDist > 0.0f)
+ {
+ // ray goes thru 'close'
+ totalSmokedLength += halfSmokedLength + (close - from).Length();
+ }
+ else
+ {
+ // ray starts after 'close'
+ totalSmokedLength += halfSmokedLength - (close - from).Length();
+ }
+
+ }
+ }
+ else if (toSq < smokeRadiusSq)
+ {
+ // 'from' is outside the cloud, 'to' is inside
+ // compute half of total smoked length as if ray crosses entire cloud chord
+ float halfSmokedLength = (float)sqrt( smokeRadiusSq - lengthSq );
+
+ Vector v = to - smokeOrigin;
+ if (DotProduct( v, sightDir ) > 0.0f)
+ {
+ // ray goes thru 'close'
+ totalSmokedLength += halfSmokedLength + (close - to).Length();
+ }
+ else
+ {
+ // ray ends before 'close'
+ totalSmokedLength += halfSmokedLength - (close - to).Length();
+ }
+ }
+ else
+ {
+ // 'from' and 'to' lie outside of the cloud - the line of sight completely crosses it
+ // determine the length of the chord that crosses the cloud
+ float smokedLength = 2.0f * (float)sqrt( smokeRadiusSq - lengthSq );
+
+ totalSmokedLength += smokedLength;
+ }
+ }
+ }
+ }
+
+ // define how much smoke a bot can see thru
+ const float maxSmokedLength = 0.7f * SmokeGrenadeRadius;
+
+ // return true if the total length of smoke-covered line-of-sight is too much
+ return (totalSmokedLength > maxSmokedLength);
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+void CBotManager::ClearDebugMessages( void )
+{
+ m_debugMessageCount = 0;
+ m_currentDebugMessage = -1;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Add a new debug message to the message history
+ */
+void CBotManager::AddDebugMessage( const char *msg )
+{
+ if (++m_currentDebugMessage >= MAX_DBG_MSGS)
+ {
+ m_currentDebugMessage = 0;
+ }
+
+ if (m_debugMessageCount < MAX_DBG_MSGS)
+ {
+ ++m_debugMessageCount;
+ }
+
+ Q_strncpy( m_debugMessage[ m_currentDebugMessage ].m_string, msg, MAX_DBG_MSG_SIZE );
+ m_debugMessage[ m_currentDebugMessage ].m_age.Start();
+}
diff --git a/game/shared/cstrike/bot/bot_manager.h b/game/shared/cstrike/bot/bot_manager.h
new file mode 100644
index 0000000..b7d2bd1
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_manager.h
@@ -0,0 +1,195 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#ifndef BASE_CONTROL_H
+#define BASE_CONTROL_H
+
+#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
+
+extern float g_BotUpkeepInterval; ///< duration between bot upkeeps
+extern float g_BotUpdateInterval; ///< duration between bot updates
+const int g_BotUpdateSkipCount = 2; ///< number of upkeep periods to skip update
+
+class CNavArea;
+
+/// TODO: move CS-specific defines into CSBot files
+enum
+{
+ SmokeGrenadeRadius = 155,
+ FlashbangGrenadeRadius = 115,
+ HEGrenadeRadius = 115,
+};
+
+//--------------------------------------------------------------------------------------------------------------
+class CBaseGrenade;
+
+/**
+ * An ActiveGrenade is a representation of a grenade in the world
+ * NOTE: Currently only used for smoke grenade line-of-sight testing
+ * @todo Use system allow bots to avoid HE and Flashbangs
+ */
+class ActiveGrenade
+{
+public:
+ ActiveGrenade( CBaseGrenade *grenadeEntity );
+
+ void OnEntityGone( void ); ///< called when the grenade in the world goes away
+ void Update( void ); ///< called every frame
+ bool IsValid( void ) const ; ///< return true if this grenade is valid
+
+ bool IsEntity( CBaseGrenade *grenade ) const { return (grenade == m_entity) ? true : false; }
+ CBaseGrenade *GetEntity( void ) const { return m_entity; }
+
+ const Vector &GetDetonationPosition( void ) const { return m_detonationPosition; }
+ const Vector &GetPosition( void ) const;
+ bool IsSmoke( void ) const { return m_isSmoke; }
+ bool IsFlashbang( void ) const { return m_isFlashbang; }
+ CBaseGrenade *GetGrenade( void ) { return m_entity; }
+ float GetRadius( void ) const { return m_radius; }
+ void SetRadius( float radius ) { m_radius = radius; }
+
+private:
+ CBaseGrenade *m_entity; ///< the entity
+ Vector m_detonationPosition; ///< the location where the grenade detonated (smoke)
+ float m_dieTimestamp; ///< time this should go away after m_entity is NULL
+ bool m_isSmoke; ///< true if this is a smoke grenade
+ bool m_isFlashbang; ///< true if this is a flashbang grenade
+ float m_radius;
+};
+
+typedef CUtlLinkedList<ActiveGrenade *> ActiveGrenadeList;
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * This class manages all active bots, propagating events to them and updating them.
+ */
+class CBotManager
+{
+public:
+ CBotManager();
+ virtual ~CBotManager();
+
+ CBasePlayer *AllocateAndBindBotEntity( edict_t *ed ); ///< allocate the appropriate entity for the bot and bind it to the given edict
+ virtual CBasePlayer *AllocateBotEntity( void ) = 0; ///< factory method to allocate the appropriate entity for the bot
+
+ virtual void ClientDisconnect( CBaseEntity *entity ) = 0;
+ virtual bool ClientCommand( CBasePlayer *player, const CCommand &args ) = 0;
+
+ virtual void ServerActivate( void ) = 0;
+ virtual void ServerDeactivate( void ) = 0;
+ virtual bool ServerCommand( const char * pcmd ) = 0;
+
+ virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins
+ virtual void StartFrame( void ); ///< (EXTEND) called each frame
+
+ virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const = 0; ///< return priority of player (0 = max pri)
+
+
+ void AddGrenade( CBaseGrenade *grenade ); ///< add an active grenade to the bot's awareness
+ void RemoveGrenade( CBaseGrenade *grenade ); ///< the grenade entity in the world is going away
+ void SetGrenadeRadius( CBaseGrenade *grenade, float radius ); ///< the radius of the grenade entity (or associated smoke cloud)
+ void ValidateActiveGrenades( void ); ///< destroy any invalid active grenades
+ void DestroyAllGrenades( void );
+ bool IsLineBlockedBySmoke( const Vector &from, const Vector &to, float grenadeBloat = 1.0f ); ///< return true if line intersects smoke volume, with grenade radius increased by the grenadeBloat factor
+ bool IsInsideSmokeCloud( const Vector *pos ); ///< return true if position is inside a smoke cloud
+
+ //
+ // Invoke functor on all active grenades.
+ // If any functor call return false, return false. Otherwise, return true.
+ //
+ template < typename T >
+ bool ForEachGrenade( T &func )
+ {
+ int it = m_activeGrenadeList.Head();
+
+ while( it != m_activeGrenadeList.InvalidIndex() )
+ {
+ ActiveGrenade *ag = m_activeGrenadeList[ it ];
+
+ int current = it;
+ it = m_activeGrenadeList.Next( it );
+
+ // lazy validation
+ if (!ag->IsValid())
+ {
+ m_activeGrenadeList.Remove( current );
+ delete ag;
+ continue;
+ }
+ else
+ {
+ if (func( ag ) == false)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ enum { MAX_DBG_MSG_SIZE = 1024 };
+ struct DebugMessage
+ {
+ char m_string[ MAX_DBG_MSG_SIZE ];
+ IntervalTimer m_age;
+ };
+
+ // debug message history -------------------------------------------------------------------------------
+ int GetDebugMessageCount( void ) const; ///< get number of debug messages in history
+ const DebugMessage *GetDebugMessage( int which = 0 ) const; ///< return the debug message emitted by the bot (0 = most recent)
+ void ClearDebugMessages( void );
+ void AddDebugMessage( const char *msg );
+
+
+private:
+ ActiveGrenadeList m_activeGrenadeList;///< the list of active grenades the bots are aware of
+
+ enum { MAX_DBG_MSGS = 6 };
+ DebugMessage m_debugMessage[ MAX_DBG_MSGS ]; ///< debug message history
+ int m_debugMessageCount;
+ int m_currentDebugMessage;
+
+ IntervalTimer m_frameTimer; ///< for measuring each frame's duration
+};
+
+
+inline CBasePlayer *CBotManager::AllocateAndBindBotEntity( edict_t *ed )
+{
+ CBasePlayer::s_PlayerEdict = ed;
+ return AllocateBotEntity();
+}
+
+inline int CBotManager::GetDebugMessageCount( void ) const
+{
+ return m_debugMessageCount;
+}
+
+inline const CBotManager::DebugMessage *CBotManager::GetDebugMessage( int which ) const
+{
+ if (which >= m_debugMessageCount)
+ return NULL;
+
+ int i = m_currentDebugMessage - which;
+ if (i < 0)
+ i += MAX_DBG_MSGS;
+
+ return &m_debugMessage[ i ];
+}
+
+
+
+
+
+// global singleton to create and control bots
+extern CBotManager *TheBots;
+
+
+#endif
diff --git a/game/shared/cstrike/bot/bot_profile.cpp b/game/shared/cstrike/bot/bot_profile.cpp
new file mode 100644
index 0000000..13da6b0
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_profile.cpp
@@ -0,0 +1,704 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+
+#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
+
+#define DEFINE_DIFFICULTY_NAMES
+#include "bot_profile.h"
+#include "shared_util.h"
+
+#include "bot.h"
+#include "bot_util.h"
+#include "cs_bot.h" // BOTPORT: Remove this CS dependency
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+BotProfileManager *TheBotProfiles = NULL;
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Generates a filename-decorated skin name
+ */
+static const char * GetDecoratedSkinName( const char *name, const char *filename )
+{
+ const int BufLen = _MAX_PATH + 64;
+ static char buf[BufLen];
+ Q_snprintf( buf, sizeof( buf ), "%s/%s", filename, name );
+ return buf;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+const char* BotProfile::GetWeaponPreferenceAsString( int i ) const
+{
+ if ( i < 0 || i >= m_weaponPreferenceCount )
+ return NULL;
+
+ return WeaponIDToAlias( m_weaponPreference[ i ] );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this profile has a primary weapon preference
+ */
+bool BotProfile::HasPrimaryPreference( void ) const
+{
+ for( int i=0; i<m_weaponPreferenceCount; ++i )
+ {
+ if (IsPrimaryWeapon( m_weaponPreference[i] ))
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this profile has a pistol weapon preference
+ */
+bool BotProfile::HasPistolPreference( void ) const
+{
+ for( int i=0; i<m_weaponPreferenceCount; ++i )
+ if (IsSecondaryWeapon( m_weaponPreference[i] ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this profile is valid for the specified team
+ */
+bool BotProfile::IsValidForTeam( int team ) const
+{
+ return ( team == TEAM_UNASSIGNED || m_teams == TEAM_UNASSIGNED || team == m_teams );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Return true if this profile inherits from the specified template
+*/
+bool BotProfile::InheritsFrom( const char *name ) const
+{
+ if ( WildcardMatch( name, GetName() ) )
+ return true;
+
+ for ( int i=0; i<m_templates.Count(); ++i )
+ {
+ const BotProfile *queryTemplate = m_templates[i];
+ if ( queryTemplate->InheritsFrom( name ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Constructor
+ */
+BotProfileManager::BotProfileManager( void )
+{
+ m_nextSkin = 0;
+ for (int i=0; i<NumCustomSkins; ++i)
+ {
+ m_skins[i] = NULL;
+ m_skinFilenames[i] = NULL;
+ m_skinModelnames[i] = NULL;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load the bot profile database
+ */
+void BotProfileManager::Init( const char *filename, unsigned int *checksum )
+{
+ FileHandle_t file = filesystem->Open( filename, "r" );
+
+ if (!file)
+ {
+ if ( true ) // UTIL_IsGame( "czero" ) )
+ {
+ CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename );
+ }
+ return;
+ }
+
+ int dataLength = filesystem->Size( filename );
+ char *dataPointer = new char[ dataLength ];
+ int dataReadLength = filesystem->Read( dataPointer, dataLength, file );
+ filesystem->Close( file );
+ if ( dataReadLength > 0 )
+ {
+ // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
+ // return fewer bytes than we were expecting.
+ dataPointer[ dataReadLength - 1 ] = 0;
+ }
+
+ const char *dataFile = dataPointer;
+
+ // compute simple checksum
+ if (checksum)
+ {
+ *checksum = 0; // ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength );
+ }
+
+ BotProfile defaultProfile;
+
+ //
+ // Parse the BotProfile.db into BotProfile instances
+ //
+ while( true )
+ {
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ break;
+
+ char *token = SharedGetToken();
+
+ bool isDefault = (!stricmp( token, "Default" ));
+ bool isTemplate = (!stricmp( token, "Template" ));
+ bool isCustomSkin = (!stricmp( token, "Skin" ));
+
+ if ( isCustomSkin )
+ {
+ const int BufLen = 64;
+ char skinName[BufLen];
+
+ // get skin name
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ Q_snprintf( skinName, sizeof( skinName ), "%s", token );
+
+ // get attribute name
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ if (stricmp( "Model", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ // eat '='
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ if (strcmp( "=", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ // get attribute value
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+
+ const char *decoratedName = GetDecoratedSkinName( skinName, filename );
+ bool skinExists = GetCustomSkinIndex( decoratedName ) > 0;
+ if ( m_nextSkin < NumCustomSkins && !skinExists )
+ {
+ // decorate the name
+ m_skins[ m_nextSkin ] = CloneString( decoratedName );
+
+ // construct the model filename
+ m_skinModelnames[ m_nextSkin ] = CloneString( token );
+ m_skinFilenames[ m_nextSkin ] = new char[ strlen(token)*2 + strlen("models/player//.mdl") + 1 ];
+ Q_snprintf( m_skinFilenames[ m_nextSkin ], sizeof( m_skinFilenames[ m_nextSkin ] ), "models/player/%s/%s.mdl", token, token );
+ ++m_nextSkin;
+ }
+
+ // eat 'End'
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ if (strcmp( "End", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc.
+ }
+
+ // encountered a new profile
+ BotProfile *profile;
+
+ if (isDefault)
+ {
+ profile = &defaultProfile;
+ }
+ else
+ {
+ profile = new BotProfile;
+
+ // always inherit from Default
+ *profile = defaultProfile;
+ }
+
+ // do inheritance in order of appearance
+ if (!isTemplate && !isDefault)
+ {
+ const BotProfile *inherit = NULL;
+
+ // template names are separated by "+"
+ while(true)
+ {
+ char *c = strchr( token, '+' );
+ if (c)
+ *c = '\000';
+
+ // find the given template name
+ FOR_EACH_LL( m_templateList, it )
+ {
+ BotProfile *profile = m_templateList[ it ];
+ if (!stricmp( profile->GetName(), token ))
+ {
+ inherit = profile;
+ break;
+ }
+ }
+
+ if (inherit == NULL)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token );
+ delete [] dataPointer;
+ return;
+ }
+
+ // inherit the data
+ profile->Inherit( inherit, &defaultProfile );
+
+ if (c == NULL)
+ break;
+
+ token = c+1;
+ }
+ }
+
+
+ // get name of this profile
+ if (!isDefault)
+ {
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ profile->m_name = CloneString( SharedGetToken() );
+
+ /**
+ * HACK HACK
+ * Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
+ * preference towards silencers based on his name.
+ */
+ if ( profile->m_name[0] % 2 )
+ {
+ profile->m_prefersSilencer = true;
+ }
+ }
+
+ // read attributes for this profile
+ bool isFirstWeaponPref = true;
+ while( true )
+ {
+ // get next token
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+
+ // check for End delimiter
+ if (!stricmp( token, "End" ))
+ break;
+
+ // found attribute name - keep it
+ char attributeName[64];
+ strcpy( attributeName, token );
+
+ // eat '='
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ token = SharedGetToken();
+ if (strcmp( "=", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ // get attribute value
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+
+ // store value in appropriate attribute
+ if (!stricmp( "Aggression", attributeName ))
+ {
+ profile->m_aggression = (float)atof(token) / 100.0f;
+ }
+ else if (!stricmp( "Skill", attributeName ))
+ {
+ profile->m_skill = (float)atof(token) / 100.0f;
+ }
+ else if (!stricmp( "Skin", attributeName ))
+ {
+ profile->m_skin = atoi(token);
+ if ( profile->m_skin == 0 )
+ {
+ // atoi() failed - try to look up a custom skin by name
+ profile->m_skin = GetCustomSkinIndex( token, filename );
+ }
+ }
+ else if (!stricmp( "Teamwork", attributeName ))
+ {
+ profile->m_teamwork = (float)atof(token) / 100.0f;
+ }
+ else if (!stricmp( "Cost", attributeName ))
+ {
+ profile->m_cost = atoi(token);
+ }
+ else if (!stricmp( "VoicePitch", attributeName ))
+ {
+ profile->m_voicePitch = atoi(token);
+ }
+ else if (!stricmp( "VoiceBank", attributeName ))
+ {
+ profile->m_voiceBank = FindVoiceBankIndex( token );
+ }
+ else if (!stricmp( "WeaponPreference", attributeName ))
+ {
+ // weapon preferences override parent prefs
+ if (isFirstWeaponPref)
+ {
+ isFirstWeaponPref = false;
+ profile->m_weaponPreferenceCount = 0;
+ }
+
+ if (!stricmp( token, "none" ))
+ {
+ profile->m_weaponPreferenceCount = 0;
+ }
+ else
+ {
+ if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
+ {
+ profile->m_weaponPreference[ profile->m_weaponPreferenceCount++ ] = AliasToWeaponID( token );
+ }
+ }
+ }
+ else if (!stricmp( "ReactionTime", attributeName ))
+ {
+ profile->m_reactionTime = (float)atof(token);
+
+#ifndef GAMEUI_EXPORTS
+ // subtract off latency due to "think" update rate.
+ // In GameUI, we don't really care.
+ //profile->m_reactionTime -= g_BotUpdateInterval;
+#endif
+
+ }
+ else if (!stricmp( "AttackDelay", attributeName ))
+ {
+ profile->m_attackDelay = (float)atof(token);
+ }
+ else if (!stricmp( "Difficulty", attributeName ))
+ {
+ // override inheritance
+ profile->m_difficultyFlags = 0;
+
+ // parse bit flags
+ while(true)
+ {
+ char *c = strchr( token, '+' );
+ if (c)
+ *c = '\000';
+
+ for( int i=0; i<NUM_DIFFICULTY_LEVELS; ++i )
+ if (!stricmp( BotDifficultyName[i], token ))
+ profile->m_difficultyFlags |= (1 << i);
+
+ if (c == NULL)
+ break;
+
+ token = c+1;
+ }
+ }
+ else if (!stricmp( "Team", attributeName ))
+ {
+ if ( !stricmp( token, "T" ) )
+ {
+ profile->m_teams = TEAM_TERRORIST;
+ }
+ else if ( !stricmp( token, "CT" ) )
+ {
+ profile->m_teams = TEAM_CT;
+ }
+ else
+ {
+ profile->m_teams = TEAM_UNASSIGNED;
+ }
+ }
+ else
+ {
+ CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName );
+ }
+ }
+
+ if (!isDefault)
+ {
+ if (isTemplate)
+ {
+ // add to template list
+ m_templateList.AddToTail( profile );
+ }
+ else
+ {
+ // add profile to the master list
+ m_profileList.AddToTail( profile );
+ }
+ }
+ }
+
+ delete [] dataPointer;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+BotProfileManager::~BotProfileManager( void )
+{
+ Reset();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Free all bot profiles
+ */
+void BotProfileManager::Reset( void )
+{
+ m_profileList.PurgeAndDeleteElements();
+ m_templateList.PurgeAndDeleteElements();
+
+ int i;
+
+ for (i=0; i<NumCustomSkins; ++i)
+ {
+ if ( m_skins[i] )
+ {
+ delete[] m_skins[i];
+ m_skins[i] = NULL;
+ }
+ if ( m_skinFilenames[i] )
+ {
+ delete[] m_skinFilenames[i];
+ m_skinFilenames[i] = NULL;
+ }
+ if ( m_skinModelnames[i] )
+ {
+ delete[] m_skinModelnames[i];
+ m_skinModelnames[i] = NULL;
+ }
+ }
+
+ for ( i=0; i<m_voiceBanks.Count(); ++i )
+ {
+ delete[] m_voiceBanks[i];
+ }
+ m_voiceBanks.RemoveAll();
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns custom skin name at a particular index
+ */
+const char * BotProfileManager::GetCustomSkin( int index )
+{
+ if ( index < FirstCustomSkin || index > LastCustomSkin )
+ {
+ return NULL;
+ }
+
+ return m_skins[ index - FirstCustomSkin ];
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns custom skin filename at a particular index
+ */
+const char * BotProfileManager::GetCustomSkinFname( int index )
+{
+ if ( index < FirstCustomSkin || index > LastCustomSkin )
+ {
+ return NULL;
+ }
+
+ return m_skinFilenames[ index - FirstCustomSkin ];
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns custom skin modelname at a particular index
+ */
+const char * BotProfileManager::GetCustomSkinModelname( int index )
+{
+ if ( index < FirstCustomSkin || index > LastCustomSkin )
+ {
+ return NULL;
+ }
+
+ return m_skinModelnames[ index - FirstCustomSkin ];
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
+ */
+int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename )
+{
+ const char * skinName = name;
+ if ( filename )
+ {
+ skinName = GetDecoratedSkinName( name, filename );
+ }
+
+ for (int i=0; i<NumCustomSkins; ++i)
+ {
+ if ( m_skins[i] )
+ {
+ if ( !stricmp( skinName, m_skins[i] ) )
+ {
+ return FirstCustomSkin + i;
+ }
+ }
+ }
+ return 0;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * return index of the (custom) bot phrase db, inserting it if needed
+ */
+int BotProfileManager::FindVoiceBankIndex( const char *filename )
+{
+ int index = 0;
+
+ for ( int i=0; i<m_voiceBanks.Count(); ++i )
+ {
+ if ( !stricmp( filename, m_voiceBanks[i] ) )
+ {
+ return index;
+ }
+ }
+
+ m_voiceBanks.AddToTail( CloneString( filename ) );
+ return index;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return random unused profile that matches the given difficulty level
+ */
+const BotProfile *BotProfileManager::GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType ) const
+{
+ // count up valid profiles
+ CUtlVector< const BotProfile * > profiles;
+ FOR_EACH_LL( m_profileList, it )
+ {
+ const BotProfile *profile = m_profileList[ it ];
+
+ // Match difficulty
+ if ( !profile->IsDifficulty( difficulty ) )
+ continue;
+
+ // Prevent duplicate names
+ if ( UTIL_IsNameTaken( profile->GetName() ) )
+ continue;
+
+ // Match team choice
+ if ( !profile->IsValidForTeam( team ) )
+ continue;
+
+ // Match desired weapon
+ if ( weaponType != WEAPONTYPE_UNKNOWN )
+ {
+ if ( !profile->GetWeaponPreferenceCount() )
+ continue;
+
+ if ( weaponType != WeaponClassFromWeaponID( (CSWeaponID)profile->GetWeaponPreference( 0 ) ) )
+ continue;
+ }
+
+ profiles.AddToTail( profile );
+ }
+
+ if ( !profiles.Count() )
+ return NULL;
+
+ // select one at random
+ int which = RandomInt( 0, profiles.Count()-1 );
+ return profiles[which];
+}
+
diff --git a/game/shared/cstrike/bot/bot_profile.h b/game/shared/cstrike/bot/bot_profile.h
new file mode 100644
index 0000000..172380a
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_profile.h
@@ -0,0 +1,251 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#ifndef _BOT_PROFILE_H_
+#define _BOT_PROFILE_H_
+
+#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
+
+#include "bot_constants.h"
+#include "bot_util.h"
+#include "cs_weapon_parse.h"
+
+enum
+{
+ FirstCustomSkin = 100,
+ NumCustomSkins = 100,
+ LastCustomSkin = FirstCustomSkin + NumCustomSkins - 1,
+};
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * A BotProfile describes the "personality" of a given bot
+ */
+class BotProfile
+{
+public:
+ BotProfile( void )
+ {
+ m_name = NULL;
+ m_aggression = 0.0f;
+ m_skill = 0.0f;
+ m_teamwork = 0.0f;
+ m_weaponPreferenceCount = 0;
+ m_cost = 0;
+ m_skin = 0;
+ m_difficultyFlags = 0;
+ m_voicePitch = 100;
+ m_reactionTime = 0.3f;
+ m_attackDelay = 0.0f;
+ m_teams = TEAM_UNASSIGNED;
+ m_voiceBank = 0;
+ m_prefersSilencer = false;
+ }
+
+ ~BotProfile( void )
+ {
+ if ( m_name )
+ delete [] m_name;
+ }
+
+ const char *GetName( void ) const { return m_name; } ///< return bot's name
+ float GetAggression( void ) const { return m_aggression; }
+ float GetSkill( void ) const { return m_skill; }
+ float GetTeamwork( void ) const { return m_teamwork; }
+
+ CSWeaponID GetWeaponPreference( int i ) const { return m_weaponPreference[ i ]; }
+ const char *GetWeaponPreferenceAsString( int i ) const;
+ int GetWeaponPreferenceCount( void ) const { return m_weaponPreferenceCount; }
+ bool HasPrimaryPreference( void ) const; ///< return true if this profile has a primary weapon preference
+ bool HasPistolPreference( void ) const; ///< return true if this profile has a pistol weapon preference
+
+ int GetCost( void ) const { return m_cost; }
+ int GetSkin( void ) const { return m_skin; }
+ bool IsDifficulty( BotDifficultyType diff ) const; ///< return true if this profile can be used for the given difficulty level
+ int GetVoicePitch( void ) const { return m_voicePitch; }
+ float GetReactionTime( void ) const { return m_reactionTime; }
+ float GetAttackDelay( void ) const { return m_attackDelay; }
+ int GetVoiceBank() const { return m_voiceBank; }
+
+ bool IsValidForTeam( int team ) const;
+
+ bool PrefersSilencer() const { return m_prefersSilencer; }
+
+ bool InheritsFrom( const char *name ) const;
+
+private:
+ friend class BotProfileManager; ///< for loading profiles
+
+ void Inherit( const BotProfile *parent, const BotProfile *baseline ); ///< copy values from parent if they differ from baseline
+
+ char *m_name; ///< the bot's name
+ float m_aggression; ///< percentage: 0 = coward, 1 = berserker
+ float m_skill; ///< percentage: 0 = terrible, 1 = expert
+ float m_teamwork; ///< percentage: 0 = rogue, 1 = complete obeyance to team, lots of comm
+
+ enum { MAX_WEAPON_PREFS = 16 };
+ CSWeaponID m_weaponPreference[ MAX_WEAPON_PREFS ]; ///< which weapons this bot likes to use, in order of priority
+ int m_weaponPreferenceCount;
+
+ int m_cost; ///< reputation point cost for career mode
+ int m_skin; ///< "skin" index
+ unsigned char m_difficultyFlags; ///< bits set correspond to difficulty levels this is valid for
+ int m_voicePitch; ///< the pitch shift for bot chatter (100 = normal)
+ float m_reactionTime; //< our reaction time in seconds
+ float m_attackDelay; ///< time in seconds from when we notice an enemy to when we open fire
+ int m_teams; ///< teams for which this profile is valid
+
+ bool m_prefersSilencer; ///< does the bot prefer to use silencers?
+
+ int m_voiceBank; ///< Index of the BotChatter.db voice bank this profile uses (0 is the default)
+
+ CUtlVector< const BotProfile * > m_templates; ///< List of templates we inherit from
+};
+typedef CUtlLinkedList<BotProfile *> BotProfileList;
+
+
+inline bool BotProfile::IsDifficulty( BotDifficultyType diff ) const
+{
+ return (m_difficultyFlags & (1 << diff)) ? true : false;
+}
+
+/**
+ * Copy in data from parent if it differs from the baseline
+ */
+inline void BotProfile::Inherit( const BotProfile *parent, const BotProfile *baseline )
+{
+ if (parent->m_aggression != baseline->m_aggression)
+ m_aggression = parent->m_aggression;
+
+ if (parent->m_skill != baseline->m_skill)
+ m_skill = parent->m_skill;
+
+ if (parent->m_teamwork != baseline->m_teamwork)
+ m_teamwork = parent->m_teamwork;
+
+ if (parent->m_weaponPreferenceCount != baseline->m_weaponPreferenceCount)
+ {
+ m_weaponPreferenceCount = parent->m_weaponPreferenceCount;
+ for( int i=0; i<parent->m_weaponPreferenceCount; ++i )
+ m_weaponPreference[i] = parent->m_weaponPreference[i];
+ }
+
+ if (parent->m_cost != baseline->m_cost)
+ m_cost = parent->m_cost;
+
+ if (parent->m_skin != baseline->m_skin)
+ m_skin = parent->m_skin;
+
+ if (parent->m_difficultyFlags != baseline->m_difficultyFlags)
+ m_difficultyFlags = parent->m_difficultyFlags;
+
+ if (parent->m_voicePitch != baseline->m_voicePitch)
+ m_voicePitch = parent->m_voicePitch;
+
+ if (parent->m_reactionTime != baseline->m_reactionTime)
+ m_reactionTime = parent->m_reactionTime;
+
+ if (parent->m_attackDelay != baseline->m_attackDelay)
+ m_attackDelay = parent->m_attackDelay;
+
+ if (parent->m_teams != baseline->m_teams)
+ m_teams = parent->m_teams;
+
+ if (parent->m_voiceBank != baseline->m_voiceBank)
+ m_voiceBank = parent->m_voiceBank;
+
+ m_templates.AddToTail( parent );
+}
+
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * The BotProfileManager defines the interface to accessing BotProfiles
+ */
+class BotProfileManager
+{
+public:
+ BotProfileManager( void );
+ ~BotProfileManager( void );
+
+ void Init( const char *filename, unsigned int *checksum = NULL );
+ void Reset( void );
+
+ /// given a name, return a profile
+ const BotProfile *GetProfile( const char *name, int team ) const
+ {
+ FOR_EACH_LL( m_profileList, it )
+ {
+ BotProfile *profile = m_profileList[ it ];
+
+ if ( !stricmp( name, profile->GetName() ) && profile->IsValidForTeam( team ) )
+ return profile;
+ }
+
+ return NULL;
+ }
+
+ /// given a template name and difficulty, return a profile
+ const BotProfile *GetProfileMatchingTemplate( const char *profileName, int team, BotDifficultyType difficulty ) const
+ {
+ FOR_EACH_LL( m_profileList, it )
+ {
+ BotProfile *profile = m_profileList[ it ];
+
+ if ( !profile->InheritsFrom( profileName ) )
+ continue;
+
+ if ( !profile->IsValidForTeam( team ) )
+ continue;
+
+ if ( !profile->IsDifficulty( difficulty ) )
+ continue;
+
+ if ( UTIL_IsNameTaken( profile->GetName() ) )
+ continue;
+
+ return profile;
+ }
+
+ return NULL;
+ }
+
+ const BotProfileList *GetProfileList( void ) const { return &m_profileList; } ///< return list of all profiles
+
+ const BotProfile *GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType ) const; ///< return random unused profile that matches the given difficulty level
+
+ const char * GetCustomSkin( int index ); ///< Returns custom skin name at a particular index
+ const char * GetCustomSkinModelname( int index ); ///< Returns custom skin modelname at a particular index
+ const char * GetCustomSkinFname( int index ); ///< Returns custom skin filename at a particular index
+ int GetCustomSkinIndex( const char *name, const char *filename = NULL ); ///< Looks up a custom skin index by name
+
+ typedef CUtlVector<char *> VoiceBankList;
+ const VoiceBankList *GetVoiceBanks( void ) const { return &m_voiceBanks; }
+ int FindVoiceBankIndex( const char *filename ); ///< return index of the (custom) bot phrase db, inserting it if needed
+
+protected:
+ BotProfileList m_profileList; ///< the list of all bot profiles
+ BotProfileList m_templateList; ///< the list of all bot templates
+
+ VoiceBankList m_voiceBanks;
+
+ char *m_skins[ NumCustomSkins ]; ///< Custom skin names
+ char *m_skinModelnames[ NumCustomSkins ]; ///< Custom skin modelnames
+ char *m_skinFilenames[ NumCustomSkins ]; ///< Custom skin filenames
+ int m_nextSkin; ///< Next custom skin to allocate
+};
+
+/// the global singleton for accessing BotProfiles
+extern BotProfileManager *TheBotProfiles;
+
+
+#endif // _BOT_PROFILE_H_
diff --git a/game/shared/cstrike/bot/bot_util.cpp b/game/shared/cstrike/bot/bot_util.cpp
new file mode 100644
index 0000000..bd10ff2
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_util.cpp
@@ -0,0 +1,604 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_shareddefs.h"
+#include "engine/IEngineSound.h"
+#include "KeyValues.h"
+
+#include "bot.h"
+#include "bot_util.h"
+#include "bot_profile.h"
+
+#include "cs_bot.h"
+#include <ctype.h>
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static int s_iBeamSprite = 0;
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if given name is already in use by another player
+ */
+bool UTIL_IsNameTaken( const char *name, bool ignoreHumans )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (player->IsPlayer() && player->IsBot())
+ {
+ // bots can have prefixes so we need to check the name
+ // against the profile name instead.
+ CCSBot *bot = dynamic_cast<CCSBot *>(player);
+ if ( bot && bot->GetProfile()->GetName() && FStrEq(name, bot->GetProfile()->GetName()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!ignoreHumans)
+ {
+ if (FStrEq( name, player->GetPlayerName() ))
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+int UTIL_ClientsInGame( void )
+{
+ int count = 0;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBaseEntity *player = UTIL_PlayerByIndex( i );
+
+ if (player == NULL)
+ continue;
+
+ count++;
+ }
+
+ return count;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the number of non-bots on the given team
+ */
+int UTIL_HumansOnTeam( int teamID, bool isAlive )
+{
+ int count = 0;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBaseEntity *entity = UTIL_PlayerByIndex( i );
+
+ if ( entity == NULL )
+ continue;
+
+ CBasePlayer *player = static_cast<CBasePlayer *>( entity );
+
+ if (player->IsBot())
+ continue;
+
+ if (player->GetTeamNumber() != teamID)
+ continue;
+
+ if (isAlive && !player->IsAlive())
+ continue;
+
+ count++;
+ }
+
+ return count;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+int UTIL_BotsInGame( void )
+{
+ int count = 0;
+
+ for (int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex( i ));
+
+ if ( player == NULL )
+ continue;
+
+ if ( !player->IsBot() )
+ continue;
+
+ count++;
+ }
+
+ return count;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Kick a bot from the given team. If no bot exists on the team, return false.
+ */
+bool UTIL_KickBotFromTeam( int kickTeam )
+{
+ int i;
+
+ // try to kick a dead bot first
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (!player->IsBot())
+ continue;
+
+ if (!player->IsAlive() && player->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
+
+ return true;
+ }
+ }
+
+ // no dead bots, kick any bot on the given team
+ for ( i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (!player->IsBot())
+ continue;
+
+ if (player->GetTeamNumber() == kickTeam)
+ {
+ // its a bot on the right team - kick it
+ engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if all of the members of the given team are bots
+ */
+bool UTIL_IsTeamAllBots( int team )
+{
+ int botCount = 0;
+
+ for( int i=1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ // skip players on other teams
+ if (player->GetTeamNumber() != team)
+ continue;
+
+ // if not a bot, fail the test
+ if (!player->IsBot())
+ return false;
+
+ // is a bot on given team
+ ++botCount;
+ }
+
+ // if team is empty, there are no bots
+ return (botCount) ? true : false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest active player to the given position.
+ * If 'distance' is non-NULL, the distance to the closest player is returned in it.
+ */
+extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance )
+{
+ CBasePlayer *closePlayer = NULL;
+ float closeDistSq = 999999999999.9f;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (!IsEntityValid( player ))
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ Vector playerOrigin = GetCentroid( player );
+ float distSq = (playerOrigin - pos).LengthSqr();
+ if (distSq < closeDistSq)
+ {
+ closeDistSq = distSq;
+ closePlayer = static_cast<CBasePlayer *>( player );
+ }
+ }
+
+ if (distance)
+ *distance = (float)sqrt( closeDistSq );
+
+ return closePlayer;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest active player on the given team to the given position.
+ * If 'distance' is non-NULL, the distance to the closest player is returned in it.
+ */
+extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance )
+{
+ CBasePlayer *closePlayer = NULL;
+ float closeDistSq = 999999999999.9f;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (!IsEntityValid( player ))
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ if (player->GetTeamNumber() != team)
+ continue;
+
+ Vector playerOrigin = GetCentroid( player );
+ float distSq = (playerOrigin - pos).LengthSqr();
+ if (distSq < closeDistSq)
+ {
+ closeDistSq = distSq;
+ closePlayer = static_cast<CBasePlayer *>( player );
+ }
+ }
+
+ if (distance)
+ *distance = (float)sqrt( closeDistSq );
+
+ return closePlayer;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+// Takes the bot pointer and constructs the net name using the current bot name prefix.
+void UTIL_ConstructBotNetName( char *name, int nameLength, const BotProfile *profile )
+{
+ if (profile == NULL)
+ {
+ name[0] = 0;
+ return;
+ }
+
+ // if there is no bot prefix just use the profile name.
+ if ((cv_bot_prefix.GetString() == NULL) || (strlen(cv_bot_prefix.GetString()) == 0))
+ {
+ Q_strncpy( name, profile->GetName(), nameLength );
+ return;
+ }
+
+ // find the highest difficulty
+ const char *diffStr = BotDifficultyName[0];
+ for ( int i=BOT_EXPERT; i>0; --i )
+ {
+ if ( profile->IsDifficulty( (BotDifficultyType)i ) )
+ {
+ diffStr = BotDifficultyName[i];
+ break;
+ }
+ }
+
+ const char *weaponStr = NULL;
+ if ( profile->GetWeaponPreferenceCount() )
+ {
+ weaponStr = profile->GetWeaponPreferenceAsString( 0 );
+
+ const char *translatedAlias = GetTranslatedWeaponAlias( weaponStr );
+
+ char wpnName[128];
+ Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
+ WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
+ if ( hWpnInfo != GetInvalidWeaponInfoHandle() )
+ {
+ CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
+ if ( pWeaponInfo )
+ {
+ CSWeaponType weaponType = pWeaponInfo->m_WeaponType;
+ weaponStr = WeaponClassAsString( weaponType );
+ }
+ }
+ }
+ if ( !weaponStr )
+ {
+ weaponStr = "";
+ }
+
+ char skillStr[16];
+ Q_snprintf( skillStr, sizeof( skillStr ), "%.0f", profile->GetSkill()*100 );
+
+ char temp[MAX_PLAYER_NAME_LENGTH*2];
+ char prefix[MAX_PLAYER_NAME_LENGTH*2];
+ Q_strncpy( temp, cv_bot_prefix.GetString(), sizeof( temp ) );
+ Q_StrSubst( temp, "<difficulty>", diffStr, prefix, sizeof( prefix ) );
+ Q_StrSubst( prefix, "<weaponclass>", weaponStr, temp, sizeof( temp ) );
+ Q_StrSubst( temp, "<skill>", skillStr, prefix, sizeof( prefix ) );
+ Q_snprintf( name, nameLength, "%s %s", prefix, profile->GetName() );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if anyone on the given team can see the given spot
+ */
+bool UTIL_IsVisibleToTeam( const Vector &spot, int team )
+{
+ for( int i = 1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
+
+ if (player == NULL)
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ if (player->GetTeamNumber() != team)
+ continue;
+
+ trace_t result;
+ UTIL_TraceLine( player->EyePosition(), spot, CONTENTS_SOLID, player, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction == 1.0f)
+ return true;
+ }
+
+ return false;
+}
+
+
+//------------------------------------------------------------------------------------------------------------
+void UTIL_DrawBeamFromEnt( int i, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
+{
+/* BOTPORT: What is the replacement for MESSAGE_BEGIN?
+ MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecEnd ); // vecEnd = origin???
+ WRITE_BYTE( TE_BEAMENTPOINT );
+ WRITE_SHORT( i );
+ WRITE_COORD( vecEnd.x );
+ WRITE_COORD( vecEnd.y );
+ WRITE_COORD( vecEnd.z );
+ WRITE_SHORT( s_iBeamSprite );
+ WRITE_BYTE( 0 ); // startframe
+ WRITE_BYTE( 0 ); // framerate
+ WRITE_BYTE( iLifetime ); // life
+ WRITE_BYTE( 10 ); // width
+ WRITE_BYTE( 0 ); // noise
+ WRITE_BYTE( bRed ); // r, g, b
+ WRITE_BYTE( bGreen ); // r, g, b
+ WRITE_BYTE( bBlue ); // r, g, b
+ WRITE_BYTE( 255 ); // brightness
+ WRITE_BYTE( 0 ); // speed
+ MESSAGE_END();
+ */
+}
+
+
+//------------------------------------------------------------------------------------------------------------
+void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
+{
+ NDebugOverlay::Line( vecStart, vecEnd, bRed, bGreen, bBlue, true, 0.1f );
+
+ /*
+ MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecStart );
+ WRITE_BYTE( TE_BEAMPOINTS );
+ WRITE_COORD( vecStart.x );
+ WRITE_COORD( vecStart.y );
+ WRITE_COORD( vecStart.z );
+ WRITE_COORD( vecEnd.x );
+ WRITE_COORD( vecEnd.y );
+ WRITE_COORD( vecEnd.z );
+ WRITE_SHORT( s_iBeamSprite );
+ WRITE_BYTE( 0 ); // startframe
+ WRITE_BYTE( 0 ); // framerate
+ WRITE_BYTE( iLifetime ); // life
+ WRITE_BYTE( 10 ); // width
+ WRITE_BYTE( 0 ); // noise
+ WRITE_BYTE( bRed ); // r, g, b
+ WRITE_BYTE( bGreen ); // r, g, b
+ WRITE_BYTE( bBlue ); // r, g, b
+ WRITE_BYTE( 255 ); // brightness
+ WRITE_BYTE( 0 ); // speed
+ MESSAGE_END();
+ */
+}
+
+
+//------------------------------------------------------------------------------------------------------------
+void CONSOLE_ECHO( const char * pszMsg, ... )
+{
+ va_list argptr;
+ static char szStr[1024];
+
+ va_start( argptr, pszMsg );
+ vsprintf( szStr, pszMsg, argptr );
+ va_end( argptr );
+
+ Msg( "%s", szStr );
+}
+
+
+//------------------------------------------------------------------------------------------------------------
+void BotPrecache( void )
+{
+ s_iBeamSprite = CBaseEntity::PrecacheModel( "sprites/smoke.spr" );
+}
+
+//------------------------------------------------------------------------------------------------------------
+#define COS_TABLE_SIZE 256
+static float cosTable[ COS_TABLE_SIZE ];
+
+void InitBotTrig( void )
+{
+ for( int i=0; i<COS_TABLE_SIZE; ++i )
+ {
+ float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1));
+ cosTable[i] = (float)cos( angle );
+ }
+}
+
+float BotCOS( float angle )
+{
+ angle = AngleNormalizePositive( angle );
+ int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
+ return cosTable[i];
+}
+
+float BotSIN( float angle )
+{
+ angle = AngleNormalizePositive( angle - 90 );
+ int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
+ return cosTable[i];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Send a "hint" message to all players, dead or alive.
+ */
+void HintMessageToAllPlayers( const char *message )
+{
+ hudtextparms_t textParms;
+
+ textParms.x = -1.0f;
+ textParms.y = -1.0f;
+ textParms.fadeinTime = 1.0f;
+ textParms.fadeoutTime = 5.0f;
+ textParms.holdTime = 5.0f;
+ textParms.fxTime = 0.0f;
+ textParms.r1 = 100;
+ textParms.g1 = 255;
+ textParms.b1 = 100;
+ textParms.r2 = 255;
+ textParms.g2 = 255;
+ textParms.b2 = 255;
+ textParms.effect = 0;
+ textParms.channel = 0;
+
+ UTIL_HudMessageAll( textParms, message );
+}
+
+//--------------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if moving from "start" to "finish" will cross a player's line of fire.
+ * The path from "start" to "finish" is assumed to be a straight line.
+ * "start" and "finish" are assumed to be points on the ground.
+ */
+bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore, int ignoreTeam )
+{
+ for ( int p=1; p <= gpGlobals->maxClients; ++p )
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( p ) );
+
+ if (!IsEntityValid( player ))
+ continue;
+
+ if (player == ignore)
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ if (ignoreTeam && player->GetTeamNumber() == ignoreTeam)
+ continue;
+
+ // compute player's unit aiming vector
+ Vector viewForward;
+ AngleVectors( player->EyeAngles() + player->GetPunchAngle(), &viewForward );
+
+ const float longRange = 5000.0f;
+ Vector playerOrigin = GetCentroid( player );
+ Vector playerTarget = playerOrigin + longRange * viewForward;
+
+ Vector result( 0, 0, 0 );
+ if (IsIntersecting2D( start, finish, playerOrigin, playerTarget, &result ))
+ {
+ // simple check to see if intersection lies in the Z range of the path
+ float loZ, hiZ;
+
+ if (start.z < finish.z)
+ {
+ loZ = start.z;
+ hiZ = finish.z;
+ }
+ else
+ {
+ loZ = finish.z;
+ hiZ = start.z;
+ }
+
+ if (result.z >= loZ && result.z <= hiZ + HumanHeight)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Performs a simple case-insensitive string comparison, honoring trailing * wildcards
+*/
+bool WildcardMatch( const char *query, const char *test )
+{
+ if ( !query || !test )
+ return false;
+
+ while ( *test && *query )
+ {
+ char nameChar = *test;
+ char queryChar = *query;
+ if ( tolower(nameChar) != tolower(queryChar) ) // case-insensitive
+ break;
+ ++test;
+ ++query;
+ }
+
+ if ( *query == 0 && *test == 0 )
+ return true;
+
+ // Support trailing *
+ if ( *query == '*' )
+ return true;
+
+ return false;
+}
+
+
+
diff --git a/game/shared/cstrike/bot/bot_util.h b/game/shared/cstrike/bot/bot_util.h
new file mode 100644
index 0000000..572b1db
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_util.h
@@ -0,0 +1,167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef BOT_UTIL_H
+#define BOT_UTIL_H
+
+
+#include "convar.h"
+#include "util.h"
+
+//--------------------------------------------------------------------------------------------------------------
+enum PriorityType
+{
+ PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_UNINTERRUPTABLE
+};
+
+
+extern ConVar cv_bot_traceview;
+extern ConVar cv_bot_stop;
+extern ConVar cv_bot_show_nav;
+extern ConVar cv_bot_walk;
+extern ConVar cv_bot_difficulty;
+extern ConVar cv_bot_debug;
+extern ConVar cv_bot_debug_target;
+extern ConVar cv_bot_quota;
+extern ConVar cv_bot_quota_mode;
+extern ConVar cv_bot_prefix;
+extern ConVar cv_bot_allow_rogues;
+extern ConVar cv_bot_allow_pistols;
+extern ConVar cv_bot_allow_shotguns;
+extern ConVar cv_bot_allow_sub_machine_guns;
+extern ConVar cv_bot_allow_rifles;
+extern ConVar cv_bot_allow_machine_guns;
+extern ConVar cv_bot_allow_grenades;
+extern ConVar cv_bot_allow_snipers;
+extern ConVar cv_bot_allow_shield;
+extern ConVar cv_bot_join_team;
+extern ConVar cv_bot_join_after_player;
+extern ConVar cv_bot_auto_vacate;
+extern ConVar cv_bot_zombie;
+extern ConVar cv_bot_defer_to_human;
+extern ConVar cv_bot_chatter;
+extern ConVar cv_bot_profile_db;
+extern ConVar cv_bot_dont_shoot;
+extern ConVar cv_bot_eco_limit;
+extern ConVar cv_bot_auto_follow;
+extern ConVar cv_bot_flipout;
+
+#define RAD_TO_DEG( deg ) ((deg) * 180.0 / M_PI)
+#define DEG_TO_RAD( rad ) ((rad) * M_PI / 180.0)
+
+#define SIGN( num ) (((num) < 0) ? -1 : 1)
+#define ABS( num ) (SIGN(num) * (num))
+
+
+#define CREATE_FAKE_CLIENT ( *g_engfuncs.pfnCreateFakeClient )
+#define GET_USERINFO ( *g_engfuncs.pfnGetInfoKeyBuffer )
+#define SET_KEY_VALUE ( *g_engfuncs.pfnSetKeyValue )
+#define SET_CLIENT_KEY_VALUE ( *g_engfuncs.pfnSetClientKeyValue )
+
+class BotProfile;
+
+extern void BotPrecache( void );
+extern int UTIL_ClientsInGame( void );
+
+extern bool UTIL_IsNameTaken( const char *name, bool ignoreHumans = false ); ///< return true if given name is already in use by another player
+
+#define IS_ALIVE true
+extern int UTIL_HumansOnTeam( int teamID, bool isAlive = false );
+
+extern int UTIL_BotsInGame( void );
+extern bool UTIL_IsTeamAllBots( int team );
+extern void UTIL_DrawBeamFromEnt( int iIndex, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue );
+extern void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue );
+extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance = NULL );
+extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance = NULL );
+extern bool UTIL_KickBotFromTeam( int kickTeam ); ///< kick a bot from the given team. If no bot exists on the team, return false.
+
+extern bool UTIL_IsVisibleToTeam( const Vector &spot, int team ); ///< return true if anyone on the given team can see the given spot
+
+/// return true if moving from "start" to "finish" will cross a player's line of fire.
+extern bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore = NULL, int ignoreTeam = 0 );
+
+extern void UTIL_ConstructBotNetName(char *name, int nameLength, const BotProfile *bot); ///< constructs a complete name including prefix
+
+/**
+ * Echos text to the console, and prints it on the client's screen. This is NOT tied to the developer cvar.
+ * If you are adding debugging output in cstrike, use UTIL_DPrintf() (debug.h) instead.
+ */
+extern void CONSOLE_ECHO( PRINTF_FORMAT_STRING const char * pszMsg, ... );
+
+extern void InitBotTrig( void );
+extern float BotCOS( float angle );
+extern float BotSIN( float angle );
+
+extern void HintMessageToAllPlayers( const char *message );
+
+bool WildcardMatch( const char *query, const char *test ); ///< Performs a simple case-insensitive string comparison, honoring trailing * wildcards
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the given entity is valid
+ */
+inline bool IsEntityValid( CBaseEntity *entity )
+{
+ if (entity == NULL)
+ return false;
+
+ if (FNullEnt( entity->edict() ))
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Given two line segments: startA to endA, and startB to endB, return true if they intesect
+ * and put the intersection point in "result".
+ * Note that this computes the intersection of the 2D (x,y) projection of the line segments.
+ */
+inline bool IsIntersecting2D( const Vector &startA, const Vector &endA,
+ const Vector &startB, const Vector &endB,
+ Vector *result = NULL )
+{
+ float denom = (endA.x - startA.x) * (endB.y - startB.y) - (endA.y - startA.y) * (endB.x - startB.x);
+ if (denom == 0.0f)
+ {
+ // parallel
+ return false;
+ }
+
+ float numS = (startA.y - startB.y) * (endB.x - startB.x) - (startA.x - startB.x) * (endB.y - startB.y);
+ if (numS == 0.0f)
+ {
+ // coincident
+ return true;
+ }
+
+ float numT = (startA.y - startB.y) * (endA.x - startA.x) - (startA.x - startB.x) * (endA.y - startA.y);
+
+ float s = numS / denom;
+ if (s < 0.0f || s > 1.0f)
+ {
+ // intersection is not within line segment of startA to endA
+ return false;
+ }
+
+ float t = numT / denom;
+ if (t < 0.0f || t > 1.0f)
+ {
+ // intersection is not within line segment of startB to endB
+ return false;
+ }
+
+ // compute intesection point
+ if (result)
+ *result = startA + s * (endA - startA);
+
+ return true;
+}
+
+
+#endif
diff --git a/game/shared/cstrike/bot/improv_locomotor.h b/game/shared/cstrike/bot/improv_locomotor.h
new file mode 100644
index 0000000..1705461
--- /dev/null
+++ b/game/shared/cstrike/bot/improv_locomotor.h
@@ -0,0 +1,57 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// improv_locomotor.h
+// Interface for moving Improvs along computed paths
+// Author: Michael Booth, July 2004
+
+#ifndef _IMPROV_LOCOMOTOR_H_
+#define _IMPROV_LOCOMOTOR_H_
+
+// TODO: Remove duplicate methods from CImprov, and update CImprov to use this class
+
+/**
+ * A locomotor owns the movement of an Improv
+ */
+class CImprovLocomotor
+{
+public:
+ virtual const Vector &GetCentroid( void ) const = 0;
+ virtual const Vector &GetFeet( void ) const = 0; ///< return position of "feet" - point below centroid of improv at feet level
+ virtual const Vector &GetEyes( void ) const = 0;
+ virtual float GetMoveAngle( void ) const = 0; ///< return direction of movement
+
+ virtual CNavArea *GetLastKnownArea( void ) const = 0;
+ virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ) = 0; ///< find "simple" ground height, treating current nav area as part of the floor
+
+ virtual void Crouch( void ) = 0;
+ virtual void StandUp( void ) = 0; ///< "un-crouch"
+ virtual bool IsCrouching( void ) const = 0;
+
+ virtual void Jump( void ) = 0; ///< initiate a jump
+ virtual bool IsJumping( void ) const = 0;
+
+ virtual void Run( void ) = 0; ///< set movement speed to running
+ virtual void Walk( void ) = 0; ///< set movement speed to walking
+ virtual bool IsRunning( void ) const = 0;
+
+ virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ) = 0; ///< invoked when a ladder is encountered while following a path
+ virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT ) = 0; ///< traverse given ladder
+ virtual bool IsUsingLadder( void ) const = 0;
+
+ enum MoveToFailureType
+ {
+ FAIL_INVALID_PATH,
+ FAIL_STUCK,
+ FAIL_FELL_OFF,
+ };
+ virtual void TrackPath( const Vector &pathGoal, float deltaT ) = 0; ///< move along path by following "pathGoal"
+ virtual void OnMoveToSuccess( const Vector &goal ) { } ///< invoked when an improv reaches its MoveTo goal
+ virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ) { } ///< invoked when an improv fails to reach a MoveTo goal
+};
+
+#endif // _IMPROV_LOCOMOTOR_H_
diff --git a/game/shared/cstrike/bot/nav_path.cpp b/game/shared/cstrike/bot/nav_path.cpp
new file mode 100644
index 0000000..92285bc
--- /dev/null
+++ b/game/shared/cstrike/bot/nav_path.cpp
@@ -0,0 +1,1208 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_path.cpp
+// Encapsulation of a path through space
+// Author: Michael S. Booth ([email protected]), November 2003
+
+#include "cbase.h"
+#include "cs_gamerules.h"
+#include "cs_player.h"
+
+#include "nav_mesh.h"
+#include "nav_path.h"
+#include "bot_util.h"
+#include "improv_locomotor.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#ifdef _WIN32
+#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
+#endif
+
+
+#define DrawLine( from, to, duration, red, green, blue ) NDebugOverlay::Line( from, to, red, green, blue, true, 0.1f )
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Determine actual path positions
+ */
+bool CNavPath::ComputePathPositions( void )
+{
+ if (m_segmentCount == 0)
+ return false;
+
+ // start in first area's center
+ m_path[0].pos = m_path[0].area->GetCenter();
+ m_path[0].ladder = NULL;
+ m_path[0].how = NUM_TRAVERSE_TYPES;
+
+ for( int i=1; i<m_segmentCount; ++i )
+ {
+ const PathSegment *from = &m_path[ i-1 ];
+ PathSegment *to = &m_path[ i ];
+
+ if (to->how <= GO_WEST) // walk along the floor to the next area
+ {
+ to->ladder = NULL;
+
+ // compute next point, keeping path as straight as possible
+ from->area->ComputeClosestPointInPortal( to->area, (NavDirType)to->how, from->pos, &to->pos );
+
+ // move goal position into the goal area a bit
+ const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
+ AddDirectionVector( &to->pos, (NavDirType)to->how, stepInDist );
+
+ // we need to walk out of "from" area, so keep Z where we can reach it
+ to->pos.z = from->area->GetZ( to->pos );
+
+ // if this is a "jump down" connection, we must insert an additional point on the path
+ if (to->area->IsConnected( from->area, NUM_DIRECTIONS ) == false)
+ {
+ // this is a "jump down" link
+
+ // compute direction of path just prior to "jump down"
+ Vector2D dir;
+ DirectionToVector2D( (NavDirType)to->how, &dir );
+
+ // shift top of "jump down" out a bit to "get over the ledge"
+ const float pushDist = 25.0f;
+ to->pos.x += pushDist * dir.x;
+ to->pos.y += pushDist * dir.y;
+
+ // insert a duplicate node to represent the bottom of the fall
+ if (m_segmentCount < MAX_PATH_SEGMENTS-1)
+ {
+ // copy nodes down
+ for( int j=m_segmentCount; j>i; --j )
+ m_path[j] = m_path[j-1];
+
+ // path is one node longer
+ ++m_segmentCount;
+
+ // move index ahead into the new node we just duplicated
+ ++i;
+
+ m_path[i].pos.x = to->pos.x + pushDist * dir.x;
+ m_path[i].pos.y = to->pos.y + pushDist * dir.y;
+
+ // put this one at the bottom of the fall
+ m_path[i].pos.z = to->area->GetZ( m_path[i].pos );
+ }
+ }
+ }
+ else if (to->how == GO_LADDER_UP) // to get to next area, must go up a ladder
+ {
+ // find our ladder
+ const NavLadderConnectList *list = from->area->GetLadderList( CSNavLadder::LADDER_UP );
+ int it;
+ for( it = list->Head(); it != list->InvalidIndex(); it = list->Next(it))
+ {
+ CSNavLadder *ladder = (*list)[ it ].ladder;
+
+ // can't use "behind" area when ascending...
+ if (ladder->m_topForwardArea == to->area ||
+ ladder->m_topLeftArea == to->area ||
+ ladder->m_topRightArea == to->area)
+ {
+ to->ladder = ladder;
+ to->pos = ladder->m_bottom + ladder->GetNormal() * 2.0f * HalfHumanWidth;
+ break;
+ }
+ }
+
+ if (it == list->InvalidIndex())
+ {
+ //PrintIfWatched( "ERROR: Can't find ladder in path\n" );
+ return false;
+ }
+ }
+ else if (to->how == GO_LADDER_DOWN) // to get to next area, must go down a ladder
+ {
+ // find our ladder
+ const NavLadderConnectList *list = from->area->GetLadderList( CSNavLadder::LADDER_DOWN );
+ int it;
+ for( it = list->Head(); it != list->InvalidIndex(); it = list->Next(it))
+ {
+ CSNavLadder *ladder = (*list)[ it ].ladder;
+
+ if (ladder->m_bottomArea == to->area)
+ {
+ to->ladder = ladder;
+ to->pos = ladder->m_top;
+ to->pos = ladder->m_top - ladder->GetNormal() * 2.0f * HalfHumanWidth;
+ break;
+ }
+ }
+
+ if (it == list->InvalidIndex())
+ {
+ //PrintIfWatched( "ERROR: Can't find ladder in path\n" );
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if position is at the end of the path
+ */
+bool CNavPath::IsAtEnd( const Vector &pos ) const
+{
+ if (!IsValid())
+ return false;
+
+ const float epsilon = 20.0f;
+ return (pos - GetEndpoint()).IsLengthLessThan( epsilon );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return length of path from start to finish
+ */
+float CNavPath::GetLength( void ) const
+{
+ float length = 0.0f;
+ for( int i=1; i<GetSegmentCount(); ++i )
+ {
+ length += (m_path[i].pos - m_path[i-1].pos).Length();
+ }
+
+ return length;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
+ * @todo Be careful of returning "positions" along one-way drops, ladders, etc.
+ */
+bool CNavPath::GetPointAlongPath( float distAlong, Vector *pointOnPath ) const
+{
+ if (!IsValid() || pointOnPath == NULL)
+ return false;
+
+ if (distAlong <= 0.0f)
+ {
+ *pointOnPath = m_path[0].pos;
+ return true;
+ }
+
+ float lengthSoFar = 0.0f;
+ float segmentLength;
+ Vector dir;
+ for( int i=1; i<GetSegmentCount(); ++i )
+ {
+ dir = m_path[i].pos - m_path[i-1].pos;
+ segmentLength = dir.Length();
+
+ if (segmentLength + lengthSoFar >= distAlong)
+ {
+ // desired point is on this segment of the path
+ float delta = distAlong - lengthSoFar;
+ float t = delta / segmentLength;
+
+ *pointOnPath = m_path[i].pos + t * dir;
+
+ return true;
+ }
+
+ lengthSoFar += segmentLength;
+ }
+
+ *pointOnPath = m_path[ GetSegmentCount()-1 ].pos;
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the node index closest to the given distance along the path without going over - returns (-1) if error
+ */
+int CNavPath::GetSegmentIndexAlongPath( float distAlong ) const
+{
+ if (!IsValid())
+ return -1;
+
+ if (distAlong <= 0.0f)
+ {
+ return 0;
+ }
+
+ float lengthSoFar = 0.0f;
+ Vector dir;
+ for( int i=1; i<GetSegmentCount(); ++i )
+ {
+ lengthSoFar += (m_path[i].pos - m_path[i-1].pos).Length();
+
+ if (lengthSoFar > distAlong)
+ {
+ return i-1;
+ }
+ }
+
+ return GetSegmentCount()-1;
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute closest point on path to given point
+ * NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
+ */
+bool CNavPath::FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const
+{
+ if (!IsValid() || close == NULL)
+ return false;
+
+ Vector along, toWorldPos;
+ Vector pos;
+ const Vector *from, *to;
+ float length;
+ float closeLength;
+ float closeDistSq = 9999999999.9;
+ float distSq;
+
+ for( int i=startIndex; i<=endIndex; ++i )
+ {
+ from = &m_path[i-1].pos;
+ to = &m_path[i].pos;
+
+ // compute ray along this path segment
+ along = *to - *from;
+
+ // make it a unit vector along the path
+ length = along.NormalizeInPlace();
+
+ // compute vector from start of segment to our point
+ toWorldPos = *worldPos - *from;
+
+ // find distance of closest point on ray
+ closeLength = DotProduct( toWorldPos, along );
+
+ // constrain point to be on path segment
+ if (closeLength <= 0.0f)
+ pos = *from;
+ else if (closeLength >= length)
+ pos = *to;
+ else
+ pos = *from + closeLength * along;
+
+ distSq = (pos - *worldPos).LengthSqr();
+
+ // keep the closest point so far
+ if (distSq < closeDistSq)
+ {
+ closeDistSq = distSq;
+ *close = pos;
+ }
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Build trivial path when start and goal are in the same nav area
+ */
+bool CNavPath::BuildTrivialPath( const Vector &start, const Vector &goal )
+{
+ m_segmentCount = 0;
+
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( start );
+ if (startArea == NULL)
+ return false;
+
+ CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal );
+ if (goalArea == NULL)
+ return false;
+
+ m_segmentCount = 2;
+
+ m_path[0].area = startArea;
+ m_path[0].pos.x = start.x;
+ m_path[0].pos.y = start.y;
+ m_path[0].pos.z = startArea->GetZ( start );
+ m_path[0].ladder = NULL;
+ m_path[0].how = NUM_TRAVERSE_TYPES;
+
+ m_path[1].area = goalArea;
+ m_path[1].pos.x = goal.x;
+ m_path[1].pos.y = goal.y;
+ m_path[1].pos.z = goalArea->GetZ( goal );
+ m_path[1].ladder = NULL;
+ m_path[1].how = NUM_TRAVERSE_TYPES;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Draw the path for debugging.
+ */
+void CNavPath::Draw( const Vector &color )
+{
+ if (!IsValid())
+ return;
+
+ for( int i=1; i<m_segmentCount; ++i )
+ {
+ DrawLine( m_path[i-1].pos + Vector( 0, 0, HalfHumanHeight ),
+ m_path[i].pos + Vector( 0, 0, HalfHumanHeight ), 2, 255 * color.x, 255 * color.y, 255 * color.z );
+ }
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Check line of sight from 'anchor' node on path to subsequent nodes until
+ * we find a node that can't been seen from 'anchor'.
+ */
+int CNavPath::FindNextOccludedNode( int anchor )
+{
+ for( int i=anchor+1; i<m_segmentCount; ++i )
+ {
+ // don't remove ladder nodes
+ if (m_path[i].ladder)
+ return i;
+
+ if (!IsWalkableTraceLineClear( m_path[ anchor ].pos, m_path[ i ].pos ))
+ {
+ // cant see this node from anchor node
+ return i;
+ }
+
+ Vector anchorPlusHalf = m_path[ anchor ].pos + Vector( 0, 0, HalfHumanHeight );
+ Vector iPlusHalf = m_path[ i ].pos +Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( anchorPlusHalf, iPlusHalf) )
+ {
+ // cant see this node from anchor node
+ return i;
+ }
+
+ Vector anchorPlusFull = m_path[ anchor ].pos + Vector( 0, 0, HumanHeight );
+ Vector iPlusFull = m_path[ i ].pos + Vector( 0, 0, HumanHeight );
+ if (!IsWalkableTraceLineClear( anchorPlusFull, iPlusFull ))
+ {
+ // cant see this node from anchor node
+ return i;
+ }
+ }
+
+ return m_segmentCount;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Smooth out path, removing redundant nodes
+ */
+void CNavPath::Optimize( void )
+{
+// DONT USE THIS: Optimizing the path results in cutting thru obstacles
+return;
+
+ if (m_segmentCount < 3)
+ return;
+
+ int anchor = 0;
+
+ while( anchor < m_segmentCount )
+ {
+ int occluded = FindNextOccludedNode( anchor );
+ int nextAnchor = occluded-1;
+
+ if (nextAnchor > anchor)
+ {
+ // remove redundant nodes between anchor and nextAnchor
+ int removeCount = nextAnchor - anchor - 1;
+ if (removeCount > 0)
+ {
+ for( int i=nextAnchor; i<m_segmentCount; ++i )
+ {
+ m_path[i-removeCount] = m_path[i];
+ }
+ m_segmentCount -= removeCount;
+ }
+ }
+
+ ++anchor;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------------------
+
+/**
+ * Constructor
+ */
+CNavPathFollower::CNavPathFollower( void )
+{
+ m_improv = NULL;
+ m_path = NULL;
+
+ m_segmentIndex = 0;
+ m_isLadderStarted = false;
+
+ m_isDebug = false;
+}
+
+void CNavPathFollower::Reset( void )
+{
+ m_segmentIndex = 1;
+ m_isLadderStarted = false;
+
+ m_stuckMonitor.Reset();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Move improv along path
+ */
+void CNavPathFollower::Update( float deltaT, bool avoidObstacles )
+{
+ if (m_path == NULL || m_path->IsValid() == false)
+ return;
+
+ const CNavPath::PathSegment *node = (*m_path)[ m_segmentIndex ];
+
+ if (node == NULL)
+ {
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_INVALID_PATH );
+ m_path->Invalidate();
+ return;
+ }
+
+ // handle ladders
+ /*
+ if (node->ladder)
+ {
+ const Vector *approachPos = NULL;
+ const Vector *departPos = NULL;
+
+ if (m_segmentIndex)
+ approachPos = &(*m_path)[ m_segmentIndex-1 ]->pos;
+
+ if (m_segmentIndex < m_path->GetSegmentCount()-1)
+ departPos = &(*m_path)[ m_segmentIndex+1 ]->pos;
+
+ if (!m_isLadderStarted)
+ {
+ // set up ladder movement
+ m_improv->StartLadder( node->ladder, node->how, approachPos, departPos );
+ m_isLadderStarted = true;
+ }
+
+ // move improv along ladder
+ if (m_improv->TraverseLadder( node->ladder, node->how, approachPos, departPos, deltaT ))
+ {
+ // completed ladder
+ ++m_segmentIndex;
+ }
+ return;
+ }
+ */
+
+ // reset ladder init flag
+ m_isLadderStarted = false;
+
+ //
+ // Check if we reached the end of the path
+ //
+ const float closeRange = 20.0f;
+ if ((m_improv->GetFeet() - node->pos).IsLengthLessThan( closeRange ))
+ {
+ ++m_segmentIndex;
+
+ if (m_segmentIndex >= m_path->GetSegmentCount())
+ {
+ m_improv->OnMoveToSuccess( m_path->GetEndpoint() );
+ m_path->Invalidate();
+ return;
+ }
+ }
+
+
+ m_goal = node->pos;
+
+ const float aheadRange = 300.0f;
+ m_segmentIndex = FindPathPoint( aheadRange, &m_goal, &m_behindIndex );
+ if (m_segmentIndex >= m_path->GetSegmentCount())
+ m_segmentIndex = m_path->GetSegmentCount()-1;
+
+
+ bool isApproachingJumpArea = false;
+
+ //
+ // Crouching
+ //
+ if (!m_improv->IsUsingLadder())
+ {
+ // because hostage crouching is not really supported by the engine,
+ // if we are standing in a crouch area, we must crouch to avoid collisions
+ if (m_improv->GetLastKnownArea() &&
+ m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_CROUCH &&
+ !(m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_JUMP))
+ {
+ m_improv->Crouch();
+ }
+
+ // if we are approaching a crouch area, crouch
+ // if there are no crouch areas coming up, stand
+ const float crouchRange = 50.0f;
+ bool didCrouch = false;
+ for( int i=m_segmentIndex; i<m_path->GetSegmentCount(); ++i )
+ {
+ const CNavArea *to = (*m_path)[i]->area;
+
+ // if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
+ if (to->GetAttributes() & NAV_MESH_JUMP)
+ {
+ isApproachingJumpArea = true;
+ break;
+ }
+
+ Vector close;
+ to->GetClosestPointOnArea( m_improv->GetCentroid(), &close );
+
+ if ((close - m_improv->GetFeet()).AsVector2D().IsLengthGreaterThan( crouchRange ))
+ break;
+
+ if (to->GetAttributes() & NAV_MESH_CROUCH)
+ {
+ m_improv->Crouch();
+ didCrouch = true;
+ break;
+ }
+
+ }
+
+ if (!didCrouch && !m_improv->IsJumping())
+ {
+ // no crouch areas coming up
+ m_improv->StandUp();
+ }
+
+ } // end crouching logic
+
+
+ if (m_isDebug)
+ {
+ m_path->Draw();
+ UTIL_DrawBeamPoints( m_improv->GetCentroid(), m_goal + Vector( 0, 0, StepHeight ), 1, 255, 0, 255 );
+ UTIL_DrawBeamPoints( m_goal + Vector( 0, 0, StepHeight ), m_improv->GetCentroid(), 1, 255, 0, 255 );
+ }
+
+ // check if improv becomes stuck
+ m_stuckMonitor.Update( m_improv );
+
+
+ // if improv has been stuck for too long, give up
+ const float giveUpTime = 2.0f;
+ if (m_stuckMonitor.GetDuration() > giveUpTime)
+ {
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_STUCK );
+ m_path->Invalidate();
+ return;
+ }
+
+
+ // if our goal is high above us, we must have fallen
+ if (m_goal.z - m_improv->GetFeet().z > JumpCrouchHeight)
+ {
+ const float closeRange = 75.0f;
+ Vector2D to( m_improv->GetFeet().x - m_goal.x, m_improv->GetFeet().y - m_goal.y );
+ if (to.IsLengthLessThan( closeRange ))
+ {
+ // we can't reach the goal position
+ // check if we can reach the next node, in case this was a "jump down" situation
+ const CNavPath::PathSegment *nextNode = (*m_path)[ m_behindIndex+1 ];
+ if (m_behindIndex >=0 && nextNode)
+ {
+ if (nextNode->pos.z - m_improv->GetFeet().z > JumpCrouchHeight)
+ {
+ // the next node is too high, too - we really did fall of the path
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_FELL_OFF );
+ m_path->Invalidate();
+ return;
+ }
+ }
+ else
+ {
+ // fell trying to get to the last node in the path
+ m_improv->OnMoveToFailure( m_path->GetEndpoint(), CImprovLocomotor::FAIL_FELL_OFF );
+ m_path->Invalidate();
+ return;
+ }
+ }
+ }
+
+
+ // avoid small obstacles
+ if (avoidObstacles && !isApproachingJumpArea && !m_improv->IsJumping() && m_segmentIndex < m_path->GetSegmentCount()-1)
+ {
+ FeelerReflexAdjustment( &m_goal );
+
+ // currently, this is only used for hostages, and their collision physics stinks
+ // do more feeler checks to avoid short obstacles
+ /*
+ const float inc = 0.25f;
+ for( float t = 0.5f; t < 1.0f; t += inc )
+ {
+ FeelerReflexAdjustment( &m_goal, t * StepHeight );
+ }
+ */
+
+ }
+
+ // move improv along path
+ m_improv->TrackPath( m_goal, deltaT );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest point to our current position on our current path
+ * If "local" is true, only check the portion of the path surrounding m_pathIndex.
+ */
+int CNavPathFollower::FindOurPositionOnPath( Vector *close, bool local ) const
+{
+ if (!m_path->IsValid())
+ return -1;
+
+ Vector along, toFeet;
+ Vector feet = m_improv->GetFeet();
+ Vector eyes = m_improv->GetEyes();
+ Vector pos;
+ const Vector *from, *to;
+ float length;
+ float closeLength;
+ float closeDistSq = 9999999999.9;
+ int closeIndex = -1;
+ float distSq;
+
+ int start, end;
+
+ if (local)
+ {
+ start = m_segmentIndex - 3;
+ if (start < 1)
+ start = 1;
+
+ end = m_segmentIndex + 3;
+ if (end > m_path->GetSegmentCount())
+ end = m_path->GetSegmentCount();
+ }
+ else
+ {
+ start = 1;
+ end = m_path->GetSegmentCount();
+ }
+
+ for( int i=start; i<end; ++i )
+ {
+ from = &(*m_path)[i-1]->pos;
+ to = &(*m_path)[i]->pos;
+
+ // compute ray along this path segment
+ along = *to - *from;
+
+ // make it a unit vector along the path
+ length = along.NormalizeInPlace();
+
+ // compute vector from start of segment to our point
+ toFeet = feet - *from;
+
+ // find distance of closest point on ray
+ closeLength = DotProduct( toFeet, along );
+
+ // constrain point to be on path segment
+ if (closeLength <= 0.0f)
+ pos = *from;
+ else if (closeLength >= length)
+ pos = *to;
+ else
+ pos = *from + closeLength * along;
+
+ distSq = (pos - feet).LengthSqr();
+
+ // keep the closest point so far
+ if (distSq < closeDistSq)
+ {
+ // don't use points we cant see
+ Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_DOORS | WALK_THRU_BREAKABLES ))
+ continue;
+
+ // don't use points we cant reach
+ //if (!IsStraightLinePathWalkable( &pos ))
+ // continue;
+
+ closeDistSq = distSq;
+ if (close)
+ *close = pos;
+ closeIndex = i-1;
+ }
+ }
+
+ return closeIndex;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Compute a point a fixed distance ahead along our path.
+ * Returns path index just after point.
+ */
+int CNavPathFollower::FindPathPoint( float aheadRange, Vector *point, int *prevIndex )
+{
+ // find path index just past aheadRange
+ int afterIndex;
+
+ // finds the closest point on local area of path, and returns the path index just prior to it
+ Vector close;
+ int startIndex = FindOurPositionOnPath( &close, true );
+
+ if (prevIndex)
+ *prevIndex = startIndex;
+
+ if (startIndex <= 0)
+ {
+ // went off the end of the path
+ // or next point in path is unwalkable (ie: jump-down)
+ // keep same point
+ return m_segmentIndex;
+ }
+
+ // if we are crouching, just follow the path exactly
+ if (m_improv->IsCrouching())
+ {
+ // we want to move to the immediately next point along the path from where we are now
+ int index = startIndex+1;
+ if (index >= m_path->GetSegmentCount())
+ index = m_path->GetSegmentCount()-1;
+
+ *point = (*m_path)[ index ]->pos;
+
+ // if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
+ // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
+ const float closeEpsilon = 20.0f; // 10
+ while ((*point - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
+ {
+ ++index;
+
+ if (index >= m_path->GetSegmentCount())
+ {
+ index = m_path->GetSegmentCount()-1;
+ break;
+ }
+
+ *point = (*m_path)[ index ]->pos;
+ }
+
+ return index;
+ }
+
+ // make sure we use a node a minimum distance ahead of us, to avoid wiggling
+ while (startIndex < m_path->GetSegmentCount()-1)
+ {
+ Vector pos = (*m_path)[ startIndex+1 ]->pos;
+
+ // we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
+ const float closeEpsilon = 20.0f;
+ if ((pos - close).AsVector2D().IsLengthLessThan( closeEpsilon ))
+ {
+ ++startIndex;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // if we hit a ladder or jump area, must stop (dont use ladder behind us)
+ if (startIndex > m_segmentIndex && startIndex < m_path->GetSegmentCount() &&
+ ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_MESH_JUMP))
+ {
+ *point = (*m_path)[ startIndex ]->pos;
+ return startIndex;
+ }
+
+ // we need the point just *ahead* of us
+ ++startIndex;
+ if (startIndex >= m_path->GetSegmentCount())
+ startIndex = m_path->GetSegmentCount()-1;
+
+ // if we hit a ladder or jump area, must stop
+ if (startIndex < m_path->GetSegmentCount() &&
+ ((*m_path)[ startIndex ]->ladder || (*m_path)[ startIndex ]->area->GetAttributes() & NAV_MESH_JUMP))
+ {
+ *point = (*m_path)[ startIndex ]->pos;
+ return startIndex;
+ }
+
+ // note direction of path segment we are standing on
+ Vector initDir = (*m_path)[ startIndex ]->pos - (*m_path)[ startIndex-1 ]->pos;
+ initDir.NormalizeInPlace();
+
+ Vector feet = m_improv->GetFeet();
+ Vector eyes = m_improv->GetEyes();
+ float rangeSoFar = 0;
+
+ // this flag is true if our ahead point is visible
+ bool visible = true;
+
+ Vector prevDir = initDir;
+
+ // step along the path until we pass aheadRange
+ bool isCorner = false;
+ int i;
+ for( i=startIndex; i<m_path->GetSegmentCount(); ++i )
+ {
+ Vector pos = (*m_path)[i]->pos;
+ Vector to = pos - (*m_path)[i-1]->pos;
+ Vector dir = to;
+ dir.NormalizeInPlace();
+
+ // don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
+ if (DotProduct( dir, initDir ) < 0.0f) // -0.25f
+ {
+ --i;
+ break;
+ }
+
+ // if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
+ if (DotProduct( dir, prevDir ) < 0.5f)
+ {
+ isCorner = true;
+ --i;
+ break;
+ }
+ prevDir = dir;
+
+ // don't use points we cant see
+ Vector probe = pos + Vector( 0, 0, HalfHumanHeight );
+ if (!IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ))
+ {
+ // presumably, the previous point is visible, so we will interpolate
+ visible = false;
+ break;
+ }
+
+ // if we encounter a ladder or jump area, we must stop
+ if (i < m_path->GetSegmentCount() &&
+ ((*m_path)[ i ]->ladder || (*m_path)[ i ]->area->GetAttributes() & NAV_MESH_JUMP))
+ break;
+
+ // Check straight-line path from our current position to this position
+ // Test for un-jumpable height change, or unrecoverable fall
+ //if (!IsStraightLinePathWalkable( &pos ))
+ //{
+ // --i;
+ // break;
+ //}
+
+ Vector along = (i == startIndex) ? (pos - feet) : (pos - (*m_path)[i-1]->pos);
+ rangeSoFar += along.Length2D();
+
+ // stop if we have gone farther than aheadRange
+ if (rangeSoFar >= aheadRange)
+ break;
+ }
+
+ if (i < startIndex)
+ afterIndex = startIndex;
+ else if (i < m_path->GetSegmentCount())
+ afterIndex = i;
+ else
+ afterIndex = m_path->GetSegmentCount()-1;
+
+
+ // compute point on the path at aheadRange
+ if (afterIndex == 0)
+ {
+ *point = (*m_path)[0]->pos;
+ }
+ else
+ {
+ // interpolate point along path segment
+ const Vector *afterPoint = &(*m_path)[ afterIndex ]->pos;
+ const Vector *beforePoint = &(*m_path)[ afterIndex-1 ]->pos;
+
+ Vector to = *afterPoint - *beforePoint;
+ float length = to.Length2D();
+
+ float t = 1.0f - ((rangeSoFar - aheadRange) / length);
+
+ if (t < 0.0f)
+ t = 0.0f;
+ else if (t > 1.0f)
+ t = 1.0f;
+
+ *point = *beforePoint + t * to;
+
+ // if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
+ if (!visible)
+ {
+ const float sightStepSize = 25.0f;
+ float dt = sightStepSize / length;
+
+ Vector probe = *point + Vector( 0, 0, HalfHumanHeight );
+ while( t > 0.0f && !IsWalkableTraceLineClear( eyes, probe, WALK_THRU_BREAKABLES ) )
+ {
+ t -= dt;
+ *point = *beforePoint + t * to;
+ }
+
+ if (t <= 0.0f)
+ *point = *beforePoint;
+ }
+ }
+
+ // if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
+ if (!isCorner)
+ {
+ const float epsilon = 50.0f;
+ Vector2D toPoint;
+ Vector2D centroid( m_improv->GetCentroid().x, m_improv->GetCentroid().y );
+
+ toPoint.x = point->x - centroid.x;
+ toPoint.y = point->y - centroid.y;
+
+ if (DotProduct2D( toPoint, initDir.AsVector2D() ) < 0.0f || toPoint.IsLengthLessThan( epsilon ))
+ {
+ int i;
+ for( i=startIndex; i<m_path->GetSegmentCount(); ++i )
+ {
+ toPoint.x = (*m_path)[i]->pos.x - centroid.x;
+ toPoint.y = (*m_path)[i]->pos.y - centroid.y;
+ if ((*m_path)[i]->ladder || (*m_path)[i]->area->GetAttributes() & NAV_MESH_JUMP || toPoint.IsLengthGreaterThan( epsilon ))
+ {
+ *point = (*m_path)[i]->pos;
+ startIndex = i;
+ break;
+ }
+ }
+
+ if (i == m_path->GetSegmentCount())
+ {
+ *point = m_path->GetEndpoint();
+ startIndex = m_path->GetSegmentCount()-1;
+ }
+ }
+ }
+
+ // m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
+ if (startIndex < m_path->GetSegmentCount())
+ return startIndex;
+
+ return m_path->GetSegmentCount()-1;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Do reflex avoidance movements if our "feelers" are touched
+ * @todo Parameterize feeler spacing
+ */
+void CNavPathFollower::FeelerReflexAdjustment( Vector *goalPosition, float height )
+{
+ // if we are in a "precise" area, do not do feeler adjustments
+ if (m_improv->GetLastKnownArea() && m_improv->GetLastKnownArea()->GetAttributes() & NAV_MESH_PRECISE)
+ return;
+
+ // use the direction towards the goal
+ Vector dir = *goalPosition - m_improv->GetFeet();
+ dir.z = 0.0f;
+ dir.NormalizeInPlace();
+
+ Vector lat( -dir.y, dir.x, 0.0f );
+
+ const float feelerOffset = (m_improv->IsCrouching()) ? 15.0f : 20.0f; // 15, 20
+ const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
+ const float feelerLengthWalk = 30.0f;
+
+ const float feelerHeight = (height > 0.0f) ? height : StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it
+
+ float feelerLength = (m_improv->IsRunning()) ? feelerLengthRun : feelerLengthWalk;
+
+ feelerLength = (m_improv->IsCrouching()) ? 20.0f : feelerLength;
+
+ //
+ // Feelers must follow floor slope
+ //
+ float ground;
+ Vector normal;
+ if (m_improv->GetSimpleGroundHeightWithFloor( m_improv->GetEyes(), &ground, &normal ) == false)
+ return;
+
+ // get forward vector along floor
+ dir = CrossProduct( lat, normal );
+
+ // correct the sideways vector
+ lat = CrossProduct( dir, normal );
+
+
+ Vector feet = m_improv->GetFeet();
+ feet.z += feelerHeight;
+
+ Vector from = feet + feelerOffset * lat;
+ Vector to = from + feelerLength * dir;
+
+ bool leftClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
+
+ // draw debug beams
+ if (m_isDebug)
+ {
+ if (leftClear)
+ UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
+ else
+ UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
+ }
+
+ from = feet - feelerOffset * lat;
+ to = from + feelerLength * dir;
+
+ bool rightClear = IsWalkableTraceLineClear( from, to, WALK_THRU_DOORS | WALK_THRU_BREAKABLES );
+
+ // draw debug beams
+ if (m_isDebug)
+ {
+ if (rightClear)
+ UTIL_DrawBeamPoints( from, to, 1, 0, 255, 0 );
+ else
+ UTIL_DrawBeamPoints( from, to, 1, 255, 0, 0 );
+ }
+
+
+
+ const float avoidRange = (m_improv->IsCrouching()) ? 150.0f : 300.0f;
+
+ if (!rightClear)
+ {
+ if (leftClear)
+ {
+ // right hit, left clear - veer left
+ *goalPosition = *goalPosition + avoidRange * lat;
+ //*goalPosition = m_improv->GetFeet() + avoidRange * lat;
+
+ //m_improv->StrafeLeft();
+ }
+ }
+ else if (!leftClear)
+ {
+ // right clear, left hit - veer right
+ *goalPosition = *goalPosition - avoidRange * lat;
+ //*goalPosition = m_improv->GetFeet() - avoidRange * lat;
+
+ //m_improv->StrafeRight();
+ }
+
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reset the stuck-checker.
+ */
+CStuckMonitor::CStuckMonitor( void )
+{
+ m_isStuck = false;
+ m_avgVelIndex = 0;
+ m_avgVelCount = 0;
+}
+
+/**
+ * Reset the stuck-checker.
+ */
+void CStuckMonitor::Reset( void )
+{
+ m_isStuck = false;
+ m_avgVelIndex = 0;
+ m_avgVelCount = 0;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Test if the improv has become stuck
+ */
+void CStuckMonitor::Update( CImprovLocomotor *improv )
+{
+ if (m_isStuck)
+ {
+ // improv is stuck - see if it has moved far enough to be considered unstuck
+ const float unstuckRange = 75.0f;
+ if ((improv->GetCentroid() - m_stuckSpot).IsLengthGreaterThan( unstuckRange ))
+ {
+ // no longer stuck
+ Reset();
+ //PrintIfWatched( "UN-STUCK\n" );
+ }
+ }
+ else
+ {
+ // check if improv has become stuck
+
+ // compute average velocity over a short period (for stuck check)
+ Vector vel = improv->GetCentroid() - m_lastCentroid;
+
+ // if we are jumping, ignore Z
+ //if (improv->IsJumping())
+ // vel.z = 0.0f;
+
+ // ignore Z unless we are on a ladder (which is only Z)
+ if (!improv->IsUsingLadder())
+ vel.z = 0.0f;
+
+ // cannot be Length2D, or will break ladder movement (they are only Z)
+ float moveDist = vel.Length();
+
+ float deltaT = gpGlobals->curtime - m_lastTime;
+ if (deltaT <= 0.0f)
+ return;
+
+ m_lastTime = gpGlobals->curtime;
+
+ // compute current velocity
+ m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
+
+ if (m_avgVelIndex == MAX_VEL_SAMPLES)
+ m_avgVelIndex = 0;
+
+ if (m_avgVelCount < MAX_VEL_SAMPLES)
+ {
+ ++m_avgVelCount;
+ }
+ else
+ {
+ // we have enough samples to know if we're stuck
+
+ float avgVel = 0.0f;
+ for( int t=0; t<m_avgVelCount; ++t )
+ avgVel += m_avgVel[t];
+
+ avgVel /= m_avgVelCount;
+
+ // cannot make this velocity too high, or actors will get "stuck" when going down ladders
+ float stuckVel = (improv->IsUsingLadder()) ? 10.0f : 20.0f;
+
+ if (avgVel < stuckVel)
+ {
+ // note when and where we initially become stuck
+ m_stuckTimer.Start();
+ m_stuckSpot = improv->GetCentroid();
+ m_isStuck = true;
+ }
+ }
+ }
+
+ // always need to track this
+ m_lastCentroid = improv->GetCentroid();
+}
+
diff --git a/game/shared/cstrike/bot/nav_path.h b/game/shared/cstrike/bot/nav_path.h
new file mode 100644
index 0000000..2542b84
--- /dev/null
+++ b/game/shared/cstrike/bot/nav_path.h
@@ -0,0 +1,246 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// nav_path.h
+// Navigation Path encapsulation
+// Author: Michael S. Booth ([email protected]), November 2003
+
+#ifndef _NAV_PATH_H_
+#define _NAV_PATH_H_
+
+#include "cs_nav_area.h"
+#include "bot_util.h"
+
+class CImprovLocomotor;
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * The CNavPath class encapsulates a path through space
+ */
+class CNavPath
+{
+public:
+ CNavPath( void )
+ {
+ m_segmentCount = 0;
+ }
+
+ struct PathSegment
+ {
+ CNavArea *area; ///< the area along the path
+ NavTraverseType how; ///< how to enter this area from the previous one
+ Vector pos; ///< our movement goal position at this point in the path
+ const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it
+ };
+
+ const PathSegment * operator[] ( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
+ const PathSegment *GetSegment( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
+ int GetSegmentCount( void ) const { return m_segmentCount; }
+ const Vector &GetEndpoint( void ) const { return m_path[ m_segmentCount-1 ].pos; }
+ bool IsAtEnd( const Vector &pos ) const; ///< return true if position is at the end of the path
+
+ float GetLength( void ) const; ///< return length of path from start to finish
+ bool GetPointAlongPath( float distAlong, Vector *pointOnPath ) const; ///< return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
+
+ /// return the node index closest to the given distance along the path without going over - returns (-1) if error
+ int GetSegmentIndexAlongPath( float distAlong ) const;
+
+ bool IsValid( void ) const { return (m_segmentCount > 0); }
+ void Invalidate( void ) { m_segmentCount = 0; }
+
+ void Draw( const Vector &color = Vector( 1.0f, 0.3f, 0 ) ); ///< draw the path for debugging
+
+ /// compute closest point on path to given point
+ bool FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const;
+
+ void Optimize( void );
+
+ /**
+ * Compute shortest path from 'start' to 'goal' via A* algorithm.
+ * If returns true, path was build to the goal position.
+ * If returns false, path may either be invalid (use IsValid() to check), or valid but
+ * doesn't reach all the way to the goal.
+ */
+ template< typename CostFunctor >
+ bool Compute( const Vector &start, const Vector &goal, CostFunctor &costFunc )
+ {
+ Invalidate();
+
+ CNavArea *startArea = TheNavMesh->GetNearestNavArea( start + Vector( 0.0f, 0.0f, 1.0f ) );
+ if (startArea == NULL)
+ {
+ return false;
+ }
+
+ CNavArea *goalArea = TheNavMesh->GetNavArea( goal );
+
+ // if we are already in the goal area, build trivial path
+ if (startArea == goalArea)
+ {
+ BuildTrivialPath( start, goal );
+ return true;
+ }
+
+ // make sure path end position is on the ground
+ Vector pathEndPosition = goal;
+ if (goalArea)
+ {
+ pathEndPosition.z = goalArea->GetZ( pathEndPosition );
+ }
+ else
+ {
+ TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
+ }
+
+ //
+ // Compute shortest path to goal
+ //
+ CNavArea *closestArea;
+ bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea );
+
+ //
+ // Build path by following parent links
+ //
+
+ // get count
+ int count = 0;
+ CNavArea *area;
+ for( area = closestArea; area; area = area->GetParent() )
+ {
+ ++count;
+ }
+
+ // save room for endpoint
+ if (count > MAX_PATH_SEGMENTS-1)
+ {
+ count = MAX_PATH_SEGMENTS-1;
+ }
+
+ if (count == 0)
+ {
+ return false;
+ }
+
+ if (count == 1)
+ {
+ BuildTrivialPath( start, goal );
+ return true;
+ }
+
+ // build path
+ m_segmentCount = count;
+ for( area = closestArea; count && area; area = area->GetParent() )
+ {
+ --count;
+ m_path[ count ].area = area;
+ m_path[ count ].how = area->GetParentHow();
+ }
+
+ // compute path positions
+ if (ComputePathPositions() == false)
+ {
+ //PrintIfWatched( "CNavPath::Compute: Error building path\n" );
+ Invalidate();
+ return false;
+ }
+
+ // append path end position
+ m_path[ m_segmentCount ].area = closestArea;
+ m_path[ m_segmentCount ].pos = pathEndPosition;
+ m_path[ m_segmentCount ].ladder = NULL;
+ m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
+ ++m_segmentCount;
+
+ return pathResult;
+ }
+
+private:
+ enum { MAX_PATH_SEGMENTS = 256 };
+ PathSegment m_path[ MAX_PATH_SEGMENTS ];
+ int m_segmentCount;
+
+ bool ComputePathPositions( void ); ///< determine actual path positions
+ bool BuildTrivialPath( const Vector &start, const Vector &goal ); ///< utility function for when start and goal are in the same area
+
+ int FindNextOccludedNode( int anchor ); ///< used by Optimize()
+};
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Monitor improv movement and determine if it becomes stuck
+ */
+class CStuckMonitor
+{
+public:
+ CStuckMonitor( void );
+
+ void Reset( void );
+ void Update( CImprovLocomotor *improv );
+ bool IsStuck( void ) const { return m_isStuck; }
+
+ float GetDuration( void ) const { return (m_isStuck) ? m_stuckTimer.GetElapsedTime() : 0.0f; }
+
+private:
+ bool m_isStuck; ///< if true, we are stuck
+ Vector m_stuckSpot; ///< the location where we became stuck
+ IntervalTimer m_stuckTimer; ///< how long we have been stuck
+
+ enum { MAX_VEL_SAMPLES = 5 };
+ float m_avgVel[ MAX_VEL_SAMPLES ];
+ int m_avgVelIndex;
+ int m_avgVelCount;
+ Vector m_lastCentroid;
+ float m_lastTime;
+};
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * The CNavPathFollower class implements path following behavior
+ */
+class CNavPathFollower
+{
+public:
+ CNavPathFollower( void );
+
+ void SetImprov( CImprovLocomotor *improv ) { m_improv = improv; }
+ void SetPath( CNavPath *path ) { m_path = path; }
+
+ void Reset( void );
+
+ #define DONT_AVOID_OBSTACLES false
+ void Update( float deltaT, bool avoidObstacles = true ); ///< move improv along path
+ void Debug( bool status ) { m_isDebug = status; } ///< turn debugging on/off
+
+ bool IsStuck( void ) const { return m_stuckMonitor.IsStuck(); } ///< return true if improv is stuck
+ void ResetStuck( void ) { m_stuckMonitor.Reset(); }
+ float GetStuckDuration( void ) const { return m_stuckMonitor.GetDuration(); } ///< return how long we've been stuck
+
+ void FeelerReflexAdjustment( Vector *goalPosition, float height = -1.0f ); ///< adjust goal position if "feelers" are touched
+
+private:
+ CImprovLocomotor *m_improv; ///< who is doing the path following
+
+ CNavPath *m_path; ///< the path being followed
+
+ int m_segmentIndex; ///< the point on the path the improv is moving towards
+ int m_behindIndex; ///< index of the node on the path just behind us
+ Vector m_goal; ///< last computed follow goal
+
+ bool m_isLadderStarted;
+
+ bool m_isDebug;
+
+ int FindOurPositionOnPath( Vector *close, bool local ) const; ///< return the closest point to our current position on current path
+ int FindPathPoint( float aheadRange, Vector *point, int *prevIndex ); ///< compute a point a fixed distance ahead along our path.
+
+ CStuckMonitor m_stuckMonitor;
+};
+
+
+
+#endif // _NAV_PATH_H_
+
diff --git a/game/shared/cstrike/bot/shared_util.cpp b/game/shared/cstrike/bot/shared_util.cpp
new file mode 100644
index 0000000..4d66845
--- /dev/null
+++ b/game/shared/cstrike/bot/shared_util.cpp
@@ -0,0 +1,207 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: dll-agnostic routines (no dll dependencies here)
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Matthew D. Campbell ([email protected]), 2003
+
+#include "cbase.h"
+
+#include <ctype.h>
+#include "shared_util.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static char s_shared_token[ 1500 ];
+static char s_shared_quote = '\"';
+
+//--------------------------------------------------------------------------------------------------------------
+char * SharedVarArgs(const char *format, ...)
+{
+ va_list argptr;
+ const int BufLen = 1024;
+ const int NumBuffers = 4;
+ static char string[NumBuffers][BufLen];
+ static int curstring = 0;
+
+ curstring = ( curstring + 1 ) % NumBuffers;
+
+ va_start (argptr, format);
+ V_vsprintf_safe( string[curstring], format, argptr );
+ va_end (argptr);
+
+ return string[curstring];
+}
+
+//--------------------------------------------------------------------------------------------------------------
+char * BufPrintf(char *buf, int& len, const char *fmt, ...)
+{
+ if (len <= 0)
+ return NULL;
+
+ va_list argptr;
+
+ va_start(argptr, fmt);
+ _vsnprintf(buf, len, fmt, argptr);
+ buf[ len - 1 ] = 0;
+ va_end(argptr);
+
+ len -= strlen(buf);
+ return buf + strlen(buf);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+wchar_t * BufWPrintf(wchar_t *buf, int& len, const wchar_t *fmt, ...)
+{
+ if (len <= 0)
+ return NULL;
+
+ va_list argptr;
+
+ va_start(argptr, fmt);
+#ifdef WIN32
+ _vsnwprintf(buf, len, fmt, argptr);
+#else
+ vswprintf( buf, len, fmt, argptr );
+#endif
+ buf[ len - 1 ] = 0;
+ va_end(argptr);
+
+ len -= wcslen(buf);
+ return buf + wcslen(buf);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+const wchar_t * NumAsWString( int val )
+{
+ const int BufLen = 16;
+ static wchar_t buf[BufLen];
+ int len = BufLen;
+ BufWPrintf( buf, len, L"%d", val );
+ return buf;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+const char * NumAsString( int val )
+{
+ const int BufLen = 16;
+ static char buf[BufLen];
+ int len = BufLen;
+ BufPrintf( buf, len, "%d", val );
+ return buf;
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns the token parsed by SharedParse()
+ */
+char *SharedGetToken( void )
+{
+ return s_shared_token;
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns the token parsed by SharedParse()
+ */
+void SharedSetQuoteChar( char c )
+{
+ s_shared_quote = c;
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Parse a token out of a string
+ */
+const char *SharedParse( const char *data )
+{
+ int c;
+ int len;
+
+ len = 0;
+ s_shared_token[0] = 0;
+
+ if (!data)
+ return NULL;
+
+// skip whitespace
+skipwhite:
+ while ( (c = *data) <= ' ')
+ {
+ if (c == 0)
+ return NULL; // end of file;
+ data++;
+ }
+
+// skip // comments
+ if (c=='/' && data[1] == '/')
+ {
+ while (*data && *data != '\n')
+ data++;
+ goto skipwhite;
+ }
+
+
+// handle quoted strings specially
+ if (c == s_shared_quote)
+ {
+ data++;
+ while (1)
+ {
+ c = *data++;
+ if (c==s_shared_quote || !c)
+ {
+ s_shared_token[len] = 0;
+ return data;
+ }
+ s_shared_token[len] = c;
+ len++;
+ }
+ }
+
+// parse single characters
+ if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' )
+ {
+ s_shared_token[len] = c;
+ len++;
+ s_shared_token[len] = 0;
+ return data+1;
+ }
+
+// parse a regular word
+ do
+ {
+ s_shared_token[len] = c;
+ data++;
+ len++;
+ c = *data;
+ if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' )
+ break;
+ } while (c>32);
+
+ s_shared_token[len] = 0;
+ return data;
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if additional data is waiting to be processed on this line
+ */
+bool SharedTokenWaiting( const char *buffer )
+{
+ const char *p;
+
+ p = buffer;
+ while ( *p && *p!='\n')
+ {
+ if ( !isspace( *p ) || isalnum( *p ) )
+ return true;
+
+ p++;
+ }
+
+ return false;
+}
diff --git a/game/shared/cstrike/bot/shared_util.h b/game/shared/cstrike/bot/shared_util.h
new file mode 100644
index 0000000..664dcd7
--- /dev/null
+++ b/game/shared/cstrike/bot/shared_util.h
@@ -0,0 +1,83 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: dll-agnostic routines (no dll dependencies here)
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Matthew D. Campbell ([email protected]), 2003
+
+#ifndef SHARED_UTIL_H
+#define SHARED_UTIL_H
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns the token parsed by SharedParse()
+ */
+char *SharedGetToken( void );
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Sets the character used to delimit quoted strings. Default is '\"'. Be sure to set it back when done.
+ */
+void SharedSetQuoteChar( char c );
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Parse a token out of a string
+ */
+const char *SharedParse( const char *data );
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if additional data is waiting to be processed on this line
+ */
+bool SharedTokenWaiting( const char *buffer );
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Simple utility function to allocate memory and duplicate a string
+ */
+inline char *CloneString( const char *str )
+{
+ char *cloneStr = new char [ strlen(str)+1 ];
+ strcpy( cloneStr, str );
+ return cloneStr;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * snprintf-alike that allows multiple prints into a buffer
+ */
+char * BufPrintf(char *buf, int& len, PRINTF_FORMAT_STRING const char *fmt, ...);
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * wide char version of BufPrintf
+ */
+wchar_t * BufWPrintf(wchar_t *buf, int& len, PRINTF_FORMAT_STRING const wchar_t *fmt, ...);
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * convenience function that prints an int into a static wchar_t*
+ */
+const wchar_t * NumAsWString( int val );
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * convenience function that prints an int into a static char*
+ */
+const char * NumAsString( int val );
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * convenience function that composes a string into a static char*
+ */
+char * SharedVarArgs(PRINTF_FORMAT_STRING const char *format, ...);
+
+#include "tier0/memdbgoff.h"
+
+#endif // SHARED_UTIL_H