summaryrefslogtreecommitdiff
path: root/game/server/NextBot/Player
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/NextBot/Player')
-rw-r--r--game/server/NextBot/Player/NextBotPlayer.cpp22
-rw-r--r--game/server/NextBot/Player/NextBotPlayer.h910
-rw-r--r--game/server/NextBot/Player/NextBotPlayerBody.cpp881
-rw-r--r--game/server/NextBot/Player/NextBotPlayerBody.h153
-rw-r--r--game/server/NextBot/Player/NextBotPlayerLocomotion.cpp826
-rw-r--r--game/server/NextBot/Player/NextBotPlayerLocomotion.h223
6 files changed, 3015 insertions, 0 deletions
diff --git a/game/server/NextBot/Player/NextBotPlayer.cpp b/game/server/NextBot/Player/NextBotPlayer.cpp
new file mode 100644
index 0000000..a2121f7
--- /dev/null
+++ b/game/server/NextBot/Player/NextBotPlayer.cpp
@@ -0,0 +1,22 @@
+// NextBotPlayer.cpp
+// A CBasePlayer bot based on the NextBot technology
+// Author: Michael Booth, November 2005
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+
+#include "nav_mesh.h"
+
+#include "NextBot.h"
+#include "NextBotPlayer.h"
+
+#include "in_buttons.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar NextBotPlayerStop( "nb_player_stop", "0", FCVAR_CHEAT, "Stop all NextBotPlayers from updating" );
+ConVar NextBotPlayerWalk( "nb_player_walk", "0", FCVAR_CHEAT, "Force bots to walk" );
+ConVar NextBotPlayerCrouch( "nb_player_crouch", "0", FCVAR_CHEAT, "Force bots to crouch" );
+ConVar NextBotPlayerMove( "nb_player_move", "1", FCVAR_CHEAT, "Prevents bots from moving" );
+
diff --git a/game/server/NextBot/Player/NextBotPlayer.h b/game/server/NextBot/Player/NextBotPlayer.h
new file mode 100644
index 0000000..2032c72
--- /dev/null
+++ b/game/server/NextBot/Player/NextBotPlayer.h
@@ -0,0 +1,910 @@
+// NextBotPlayer.h
+// A CBasePlayer bot based on the NextBot technology
+// Author: Michael Booth, November 2005
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#ifndef _NEXT_BOT_PLAYER_H_
+#define _NEXT_BOT_PLAYER_H_
+
+#include "cbase.h"
+#include "gameinterface.h"
+
+#include "NextBot.h"
+#include "Path/NextBotPathFollow.h"
+//#include "NextBotPlayerBody.h"
+#include "NextBotBehavior.h"
+
+#include "in_buttons.h"
+
+extern ConVar NextBotPlayerStop;
+extern ConVar NextBotPlayerWalk;
+extern ConVar NextBotPlayerCrouch;
+extern ConVar NextBotPlayerMove;
+
+
+
+//--------------------------------------------------------------------------------------------------
+/**
+ * Instantiate a NextBot derived from CBasePlayer and spawn it into the environment.
+ * Assumes class T is derived from CBasePlayer, and has the following method that
+ * creates a new entity of type T and returns it:
+ *
+ * static CBasePlayer *T::AllocatePlayerEntity( edict_t *pEdict, const char *playerName )
+ *
+ */
+template < typename T >
+T * NextBotCreatePlayerBot( const char *name, bool bReportFakeClient = true )
+{
+ /*
+ if ( UTIL_ClientsInGame() >= gpGlobals->maxClients )
+ {
+ Msg( "CreatePlayerBot: Failed - server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients );
+ return NULL;
+ }
+ */
+
+ // This is a "back door" for allocating a custom player bot entity when
+ // the engine calls ClientPutInServer (from CreateFakeClient)
+ ClientPutInServerOverride( T::AllocatePlayerEntity );
+
+ // create the bot and spawn it into the environment
+ edict_t *botEdict = engine->CreateFakeClientEx( name, bReportFakeClient );
+
+ // close the "back door"
+ ClientPutInServerOverride( NULL );
+
+ if ( botEdict == NULL )
+ {
+ Msg( "CreatePlayerBot: Unable to create bot %s - CreateFakeClient() returned NULL.\n", name );
+ 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( "CreatePlayerBot: Could not Instance() from the bot edict.\n" );
+ return NULL;
+ }
+
+ bot->SetPlayerName( name );
+
+ // flag this as a fakeclient (bot)
+ bot->ClearFlags();
+ bot->AddFlag( FL_CLIENT | FL_FAKECLIENT );
+
+ return bot;
+}
+
+
+//--------------------------------------------------------------------------------------------------
+/**
+ * Interface to access player input buttons.
+ * Unless a duration is given, each button is released at the start of the next frame.
+ * The release methods allow releasing a button before its duration has elapsed.
+ */
+class INextBotPlayerInput
+{
+public:
+ virtual void PressFireButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseFireButton( void ) = 0;
+
+ virtual void PressAltFireButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseAltFireButton( void ) = 0;
+
+ virtual void PressMeleeButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseMeleeButton( void ) = 0;
+
+ virtual void PressSpecialFireButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseSpecialFireButton( void ) = 0;
+
+ virtual void PressUseButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseUseButton( void ) = 0;
+
+ virtual void PressReloadButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseReloadButton( void ) = 0;
+
+ virtual void PressForwardButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseForwardButton( void ) = 0;
+
+ virtual void PressBackwardButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseBackwardButton( void ) = 0;
+
+ virtual void PressLeftButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseLeftButton( void ) = 0;
+
+ virtual void PressRightButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseRightButton( void ) = 0;
+
+ virtual void PressJumpButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseJumpButton( void ) = 0;
+
+ virtual void PressCrouchButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseCrouchButton( void ) = 0;
+
+ virtual void PressWalkButton( float duration = -1.0f ) = 0;
+ virtual void ReleaseWalkButton( void ) = 0;
+
+ virtual void SetButtonScale( float forward, float right ) = 0;
+};
+
+
+//--------------------------------------------------------------------------------------------------
+/**
+ * Drive a CBasePlayer-derived entity via NextBot logic
+ */
+template < typename PlayerType >
+class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerInput
+{
+public:
+ DECLARE_CLASS( NextBotPlayer, PlayerType );
+
+ NextBotPlayer( void );
+ virtual ~NextBotPlayer();
+
+ virtual void Spawn( void );
+
+ virtual void SetSpawnPoint( CBaseEntity *spawnPoint ); // define place in environment where bot will (re)spawn
+ virtual CBaseEntity *EntSelectSpawnPoint( void );
+
+ virtual void PhysicsSimulate( void );
+
+ virtual bool IsNetClient( void ) const { return false; } // Bots should return FALSE for this, they can't receive NET messages
+ virtual bool IsFakeClient( void ) const { return true; }
+ virtual bool IsBot( void ) const { return true; }
+ virtual INextBot *MyNextBotPointer( void ) { return this; }
+
+ // this is valid because the templatized PlayerType must be derived from CBasePlayer, which is derived from CBaseCombatCharacter
+ virtual CBaseCombatCharacter *GetEntity( void ) const { return ( PlayerType * )this; }
+
+ virtual bool IsRemovedOnReset( void ) const { return false; } // remove this bot when the NextBot manager calls Reset
+
+ virtual bool IsDormantWhenDead( void ) const { return true; } // should this player-bot continue to update itself when dead (respawn logic, etc)
+
+ // allocate a bot and bind it to the edict
+ static CBasePlayer *AllocatePlayerEntity( edict_t *edict, const char *playerName );
+
+ //------------------------------------------------------------------------
+ // utility methods
+ float GetDistanceBetween( CBaseEntity *other ) const; // return distance between us and the given entity
+ bool IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const; // return true if distance between is less than the given value
+ bool IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const; // return true if distance between is greater than the given value
+
+ float GetDistanceBetween( const Vector &target ) const; // return distance between us and the given entity
+ bool IsDistanceBetweenLessThan( const Vector &target, float range ) const; // return true if distance between is less than the given value
+ bool IsDistanceBetweenGreaterThan( const Vector &target, float range ) const; // return true if distance between is greater than the given value
+
+ //------------------------------------------------------------------------
+ // INextBotPlayerInput
+ virtual void PressFireButton( float duration = -1.0f );
+ virtual void ReleaseFireButton( void );
+
+ virtual void PressAltFireButton( float duration = -1.0f );
+ virtual void ReleaseAltFireButton( void );
+
+ virtual void PressMeleeButton( float duration = -1.0f );
+ virtual void ReleaseMeleeButton( void );
+
+ virtual void PressSpecialFireButton( float duration = -1.0f );
+ virtual void ReleaseSpecialFireButton( void );
+
+ virtual void PressUseButton( float duration = -1.0f );
+ virtual void ReleaseUseButton( void );
+
+ virtual void PressReloadButton( float duration = -1.0f );
+ virtual void ReleaseReloadButton( void );
+
+ virtual void PressForwardButton( float duration = -1.0f );
+ virtual void ReleaseForwardButton( void );
+
+ virtual void PressBackwardButton( float duration = -1.0f );
+ virtual void ReleaseBackwardButton( void );
+
+ virtual void PressLeftButton( float duration = -1.0f );
+ virtual void ReleaseLeftButton( void );
+
+ virtual void PressRightButton( float duration = -1.0f );
+ virtual void ReleaseRightButton( void );
+
+ virtual void PressJumpButton( float duration = -1.0f );
+ virtual void ReleaseJumpButton( void );
+
+ virtual void PressCrouchButton( float duration = -1.0f );
+ virtual void ReleaseCrouchButton( void );
+
+ virtual void PressWalkButton( float duration = -1.0f );
+ virtual void ReleaseWalkButton( void );
+
+ virtual void SetButtonScale( float forward, float right );
+
+ //------------------------------------------------------------------------
+ // Event hooks into NextBot system
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual void HandleAnimEvent( animevent_t *event );
+ virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
+ virtual void Touch( CBaseEntity *other );
+ virtual void Weapon_Equip( CBaseCombatWeapon *weapon ); // for OnPickUp
+ virtual void Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity ); // for OnDrop
+ virtual void OnMainActivityComplete( Activity newActivity, Activity oldActivity );
+ virtual void OnMainActivityInterrupted( Activity newActivity, Activity oldActivity );
+ //------------------------------------------------------------------------
+
+ bool IsAbleToAutoCenterOnLadders( void ) const;
+
+ virtual void AvoidPlayers( CUserCmd *pCmd ) { } // some game types allow players to pass through each other, this method pushes them apart
+
+public:
+ // begin INextBot ------------------------------------------------------------------------------------------------------------------
+ virtual void Update( void ); // (EXTEND) update internal state
+
+protected:
+ int m_inputButtons; // this is still needed to guarantee each button press is captured at least once
+ int m_prevInputButtons;
+ CountdownTimer m_fireButtonTimer;
+ CountdownTimer m_meleeButtonTimer;
+ CountdownTimer m_specialFireButtonTimer;
+ CountdownTimer m_useButtonTimer;
+ CountdownTimer m_reloadButtonTimer;
+ CountdownTimer m_forwardButtonTimer;
+ CountdownTimer m_backwardButtonTimer;
+ CountdownTimer m_leftButtonTimer;
+ CountdownTimer m_rightButtonTimer;
+ CountdownTimer m_jumpButtonTimer;
+ CountdownTimer m_crouchButtonTimer;
+ CountdownTimer m_walkButtonTimer;
+ CountdownTimer m_buttonScaleTimer;
+ IntervalTimer m_burningTimer; // how long since we were last burning
+ float m_forwardScale;
+ float m_rightScale;
+ CHandle< CBaseEntity > m_spawnPointEntity;
+};
+
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::SetSpawnPoint( CBaseEntity *spawnPoint )
+{
+ m_spawnPointEntity = spawnPoint;
+}
+
+template < typename PlayerType >
+inline CBaseEntity *NextBotPlayer< PlayerType >::EntSelectSpawnPoint( void )
+{
+ if ( m_spawnPointEntity != NULL )
+ return m_spawnPointEntity;
+
+ return BaseClass::EntSelectSpawnPoint();
+}
+
+template < typename PlayerType >
+inline float NextBotPlayer< PlayerType >::GetDistanceBetween( CBaseEntity *other ) const
+{
+ return (this->GetAbsOrigin() - other->GetAbsOrigin()).Length();
+}
+
+template < typename PlayerType >
+inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const
+{
+ return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthLessThan( range );
+}
+
+template < typename PlayerType >
+inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const
+{
+ return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthGreaterThan( range );
+}
+
+template < typename PlayerType >
+inline float NextBotPlayer< PlayerType >::GetDistanceBetween( const Vector &target ) const
+{
+ return (this->GetAbsOrigin() - target).Length();
+}
+
+template < typename PlayerType >
+inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( const Vector &target, float range ) const
+{
+ return (this->GetAbsOrigin() - target).IsLengthLessThan( range );
+}
+
+template < typename PlayerType >
+inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( const Vector &target, float range ) const
+{
+ return (this->GetAbsOrigin() - target).IsLengthGreaterThan( range );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressFireButton( float duration )
+{
+ m_inputButtons |= IN_ATTACK;
+ m_fireButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseFireButton( void )
+{
+ m_inputButtons &= ~IN_ATTACK;
+ m_fireButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressAltFireButton( float duration )
+{
+ PressMeleeButton( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseAltFireButton( void )
+{
+ ReleaseMeleeButton();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressMeleeButton( float duration )
+{
+ m_inputButtons |= IN_ATTACK2;
+ m_meleeButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseMeleeButton( void )
+{
+ m_inputButtons &= ~IN_ATTACK2;
+ m_meleeButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressSpecialFireButton( float duration )
+{
+ m_inputButtons |= IN_ATTACK3;
+ m_specialFireButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseSpecialFireButton( void )
+{
+ m_inputButtons &= ~IN_ATTACK3;
+ m_specialFireButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressUseButton( float duration )
+{
+ m_inputButtons |= IN_USE;
+ m_useButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseUseButton( void )
+{
+ m_inputButtons &= ~IN_USE;
+ m_useButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressReloadButton( float duration )
+{
+ m_inputButtons |= IN_RELOAD;
+ m_reloadButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseReloadButton( void )
+{
+ m_inputButtons &= ~IN_RELOAD;
+ m_reloadButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressJumpButton( float duration )
+{
+ m_inputButtons |= IN_JUMP;
+ m_jumpButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseJumpButton( void )
+{
+ m_inputButtons &= ~IN_JUMP;
+ m_jumpButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressCrouchButton( float duration )
+{
+ m_inputButtons |= IN_DUCK;
+ m_crouchButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseCrouchButton( void )
+{
+ m_inputButtons &= ~IN_DUCK;
+ m_crouchButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressWalkButton( float duration )
+{
+ m_inputButtons |= IN_SPEED;
+ m_walkButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseWalkButton( void )
+{
+ m_inputButtons &= ~IN_SPEED;
+ m_walkButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressForwardButton( float duration )
+{
+ m_inputButtons |= IN_FORWARD;
+ m_forwardButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseForwardButton( void )
+{
+ m_inputButtons &= ~IN_FORWARD;
+ m_forwardButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressBackwardButton( float duration )
+{
+ m_inputButtons |= IN_BACK;
+ m_backwardButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseBackwardButton( void )
+{
+ m_inputButtons &= ~IN_BACK;
+ m_backwardButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressLeftButton( float duration )
+{
+ m_inputButtons |= IN_MOVELEFT;
+ m_leftButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseLeftButton( void )
+{
+ m_inputButtons &= ~IN_MOVELEFT;
+ m_leftButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PressRightButton( float duration )
+{
+ m_inputButtons |= IN_MOVERIGHT;
+ m_rightButtonTimer.Start( duration );
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::ReleaseRightButton( void )
+{
+ m_inputButtons &= ~IN_MOVERIGHT;
+ m_rightButtonTimer.Invalidate();
+}
+
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::SetButtonScale( float forward, float right )
+{
+ m_forwardScale = forward;
+ m_rightScale = right;
+ m_buttonScaleTimer.Start( 0.01 );
+}
+
+
+
+//-----------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline NextBotPlayer< PlayerType >::NextBotPlayer( void )
+{
+ m_prevInputButtons = 0;
+ m_inputButtons = 0;
+ m_burningTimer.Invalidate();
+ m_spawnPointEntity = NULL;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline NextBotPlayer< PlayerType >::~NextBotPlayer()
+{
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::Spawn( void )
+{
+ engine->SetFakeClientConVarValue( this->edict(), "cl_autohelp", "0" );
+
+ m_prevInputButtons = m_inputButtons = 0;
+ m_fireButtonTimer.Invalidate();
+ m_meleeButtonTimer.Invalidate();
+ m_specialFireButtonTimer.Invalidate();
+ m_useButtonTimer.Invalidate();
+ m_reloadButtonTimer.Invalidate();
+ m_forwardButtonTimer.Invalidate();
+ m_backwardButtonTimer.Invalidate();
+ m_leftButtonTimer.Invalidate();
+ m_rightButtonTimer.Invalidate();
+ m_jumpButtonTimer.Invalidate();
+ m_crouchButtonTimer.Invalidate();
+ m_walkButtonTimer.Invalidate();
+ m_buttonScaleTimer.Invalidate();
+ m_forwardScale = m_rightScale = 0.04;
+ m_burningTimer.Invalidate();
+
+ // reset first, because Spawn() may access various interfaces
+ INextBot::Reset();
+
+ BaseClass::Spawn();
+}
+
+
+
+//-----------------------------------------------------------------------------------------------------
+inline void _NextBot_BuildUserCommand( CUserCmd *cmd, const QAngle &viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
+{
+ Q_memset( cmd, 0, sizeof( CUserCmd ) );
+
+ 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 );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
+{
+ VPROF( "NextBotPlayer::PhysicsSimulate" );
+
+ // Make sure not to simulate this guy twice per frame
+ if ( PlayerType::m_nSimulationTick == gpGlobals->tickcount )
+ {
+ return;
+ }
+
+ if ( engine->IsPaused() )
+ {
+ // We're paused - don't add new commands
+ PlayerType::PhysicsSimulate();
+ return;
+ }
+
+ if ( ( IsDormantWhenDead() && PlayerType::m_lifeState == LIFE_DEAD ) || NextBotStop.GetBool() )
+ {
+ // death animation complete - nothing left to do except let PhysicsSimulate run PreThink etc
+ PlayerType::PhysicsSimulate();
+ return;
+ }
+
+ int inputButtons;
+ //
+ // Update bot behavior
+ //
+ if ( BeginUpdate() )
+ {
+ Update();
+
+ // build button bits
+ if ( !m_fireButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_ATTACK;
+
+ if ( !m_meleeButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_ATTACK2;
+
+ if ( !m_specialFireButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_ATTACK3;
+
+ if ( !m_useButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_USE;
+
+ if ( !m_reloadButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_RELOAD;
+
+ if ( !m_forwardButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_FORWARD;
+
+ if ( !m_backwardButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_BACK;
+
+ if ( !m_leftButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_MOVELEFT;
+
+ if ( !m_rightButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_MOVERIGHT;
+
+ if ( !m_jumpButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_JUMP;
+
+ if ( !m_crouchButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_DUCK;
+
+ if ( !m_walkButtonTimer.IsElapsed() )
+ m_inputButtons |= IN_SPEED;
+
+ m_prevInputButtons = m_inputButtons;
+ inputButtons = m_inputButtons;
+
+ EndUpdate();
+ }
+ else
+ {
+ // HACK: Smooth out body animations
+ GetBodyInterface()->Update();
+
+ // keep buttons pressed between Update() calls (m_prevInputButtons),
+ // and include any button presses that occurred this tick (m_inputButtons).
+ inputButtons = m_prevInputButtons | m_inputButtons;
+ }
+
+ //
+ // Convert NextBot locomotion and posture into
+ // player commands
+ //
+ IBody *body = GetBodyInterface();
+ ILocomotion *mover = GetLocomotionInterface();
+
+ if ( body->IsActualPosture( IBody::CROUCH ) )
+ {
+ inputButtons |= IN_DUCK;
+ }
+
+ float forwardSpeed = 0.0f;
+ float strafeSpeed = 0.0f;
+ float verticalSpeed = ( m_inputButtons & IN_JUMP ) ? mover->GetRunSpeed() : 0.0f;
+
+ if ( inputButtons & IN_FORWARD )
+ {
+ forwardSpeed = mover->GetRunSpeed();
+ }
+ else if ( inputButtons & IN_BACK )
+ {
+ forwardSpeed = -mover->GetRunSpeed();
+ }
+
+ if ( inputButtons & IN_MOVELEFT )
+ {
+ strafeSpeed = -mover->GetRunSpeed();
+ }
+ else if ( inputButtons & IN_MOVERIGHT )
+ {
+ strafeSpeed = mover->GetRunSpeed();
+ }
+
+ if ( NextBotPlayerWalk.GetBool() )
+ {
+ inputButtons |= IN_SPEED;
+ }
+
+ if ( NextBotPlayerCrouch.GetBool() )
+ {
+ inputButtons |= IN_DUCK;
+ }
+
+ if ( !m_buttonScaleTimer.IsElapsed() )
+ {
+ forwardSpeed = mover->GetRunSpeed() * m_forwardScale;
+ strafeSpeed = mover->GetRunSpeed() * m_rightScale;
+ }
+
+ if ( !NextBotPlayerMove.GetBool() )
+ {
+ inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP );
+ forwardSpeed = 0.0f;
+ strafeSpeed = 0.0f;
+ verticalSpeed = 0.0f;
+ }
+
+ QAngle angles = this->EyeAngles();
+
+#ifdef TERROR
+ if ( IsStunned() )
+ {
+ inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK );
+ }
+
+ // "Look" in the direction we're climbing/stumbling etc. We can't do anything anyway, and it
+ // keeps motion extraction working.
+ if ( IsRenderYawOverridden() && IsMotionControlledXY( GetMainActivity() ) )
+ {
+ angles[YAW] = GetOverriddenRenderYaw();
+ }
+#endif
+
+ // construct a "command" to move the player
+ CUserCmd userCmd;
+ _NextBot_BuildUserCommand( &userCmd, angles, forwardSpeed, strafeSpeed, verticalSpeed, inputButtons, 0 );
+
+ AvoidPlayers( &userCmd );
+
+ // allocate a new command and add it to the player's list of command to process
+ this->ProcessUsercmds( &userCmd, 1, 1, 0, false );
+
+ m_inputButtons = 0;
+
+ // actually execute player commands and do player physics
+ PlayerType::PhysicsSimulate();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
+{
+ // propagate into NextBot responders
+ INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
+
+ BaseClass::OnNavAreaChanged( enteredArea, leftArea );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::Touch( CBaseEntity *other )
+{
+ if ( ShouldTouch( other ) )
+ {
+ // propagate touch into NextBot event responders
+ trace_t result;
+ result = this->GetTouchTrace();
+ OnContact( other, &result );
+ }
+
+ BaseClass::Touch( other );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::Weapon_Equip( CBaseCombatWeapon *weapon )
+{
+#ifdef TERROR
+ // TODO: Reimplement GetDroppingPlayer() into GetLastOwner()
+ OnPickUp( weapon, weapon->GetDroppingPlayer() );
+#else
+ OnPickUp( weapon, NULL );
+#endif
+
+ BaseClass::Weapon_Equip( weapon );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity )
+{
+ OnDrop( weapon );
+
+ BaseClass::Weapon_Drop( weapon, target, velocity );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::OnMainActivityComplete( Activity newActivity, Activity oldActivity )
+{
+#ifdef TERROR
+ BaseClass::OnMainActivityComplete( newActivity, oldActivity );
+#endif
+ OnAnimationActivityComplete( oldActivity );
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::OnMainActivityInterrupted( Activity newActivity, Activity oldActivity )
+{
+#ifdef TERROR
+ BaseClass::OnMainActivityInterrupted( newActivity, oldActivity );
+#endif
+ OnAnimationActivityInterrupted( oldActivity );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::Update( void )
+{
+ // don't spend CPU updating if this Survivor is dead
+ if ( ( this->IsAlive() || !IsDormantWhenDead() ) && !NextBotPlayerStop.GetBool() )
+ {
+ INextBot::Update();
+ }
+}
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline bool NextBotPlayer< PlayerType >::IsAbleToAutoCenterOnLadders( void ) const
+{
+ const ILocomotion *locomotion = GetLocomotionInterface();
+ return locomotion && locomotion->IsAbleToAutoCenterOnLadder();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline int NextBotPlayer< PlayerType >::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamageType() & DMG_BURN )
+ {
+ if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
+ {
+ // emit ignite event periodically as long as we are burning
+ OnIgnite();
+ m_burningTimer.Start();
+ }
+ }
+
+ // propagate event to components
+ OnInjured( info );
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline int NextBotPlayer< PlayerType >::OnTakeDamage_Dying( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamageType() & DMG_BURN )
+ {
+ if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
+ {
+ // emit ignite event periodically as long as we are burning
+ OnIgnite();
+ m_burningTimer.Start();
+ }
+ }
+
+ // propagate event to components
+ OnInjured( info );
+
+ return BaseClass::OnTakeDamage_Dying( info );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::Event_Killed( const CTakeDamageInfo &info )
+{
+ // propagate event to my components
+ OnKilled( info );
+
+ BaseClass::Event_Killed( info );
+}
+
+
+
+//----------------------------------------------------------------------------------------------------------
+template < typename PlayerType >
+inline void NextBotPlayer< PlayerType >::HandleAnimEvent( animevent_t *event )
+{
+ // propagate event to components
+ OnAnimationEvent( event );
+
+ BaseClass::HandleAnimEvent( event );
+}
+
+
+#endif // _NEXT_BOT_PLAYER_H_
diff --git a/game/server/NextBot/Player/NextBotPlayerBody.cpp b/game/server/NextBot/Player/NextBotPlayerBody.cpp
new file mode 100644
index 0000000..246b6fa
--- /dev/null
+++ b/game/server/NextBot/Player/NextBotPlayerBody.cpp
@@ -0,0 +1,881 @@
+// NextBotPlayerBody.cpp
+// Implementation of Body interface for CBasePlayer-derived classes
+// Author: Michael Booth, October 2006
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+
+#include "NextBot.h"
+#include "NextBotPlayerBody.h"
+#include "NextBotPlayer.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+ConVar nb_saccade_time( "nb_saccade_time", "0.1", FCVAR_CHEAT );
+ConVar nb_saccade_speed( "nb_saccade_speed", "1000", FCVAR_CHEAT );
+ConVar nb_head_aim_steady_max_rate( "nb_head_aim_steady_max_rate", "100", FCVAR_CHEAT );
+ConVar nb_head_aim_settle_duration( "nb_head_aim_settle_duration", "0.3", FCVAR_CHEAT );
+ConVar nb_head_aim_resettle_angle( "nb_head_aim_resettle_angle", "100", FCVAR_CHEAT, "After rotating through this angle, the bot pauses to 'recenter' its virtual mouse on its virtual mousepad" );
+ConVar nb_head_aim_resettle_time( "nb_head_aim_resettle_time", "0.3", FCVAR_CHEAT, "How long the bot pauses to 'recenter' its virtual mouse on its virtual mousepad" );
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * A useful reply for IBody::AimHeadTowards. When the
+ * head is aiming on target, press the fire button.
+ */
+void PressFireButtonReply::OnSuccess( INextBot *bot )
+{
+ INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
+ if ( playerInput )
+ {
+ playerInput->PressFireButton();
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * A useful reply for IBody::AimHeadTowards. When the
+ * head is aiming on target, press the alternate fire button.
+ */
+void PressAltFireButtonReply::OnSuccess( INextBot *bot )
+{
+ INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
+ if ( playerInput )
+ {
+ playerInput->PressMeleeButton();
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * A useful reply for IBody::AimHeadTowards. When the
+ * head is aiming on target, press the jump button.
+ */
+void PressJumpButtonReply::OnSuccess( INextBot *bot )
+{
+ INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
+ if ( playerInput )
+ {
+ playerInput->PressJumpButton();
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------
+//-----------------------------------------------------------------------------------------------
+PlayerBody::PlayerBody( INextBot *bot ) : IBody( bot )
+{
+ m_player = static_cast< CBasePlayer * >( bot->GetEntity() );
+}
+
+
+//-----------------------------------------------------------------------------------------------
+PlayerBody::~PlayerBody()
+{
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * reset to initial state
+ */
+void PlayerBody::Reset( void )
+{
+ m_posture = STAND;
+
+ m_lookAtPos = vec3_origin;
+ m_lookAtSubject = NULL;
+ m_lookAtReplyWhenAimed = NULL;
+ m_lookAtVelocity = vec3_origin;
+ m_lookAtExpireTimer.Invalidate();
+
+ m_lookAtPriority = BORING;
+ m_lookAtExpireTimer.Invalidate();
+ m_lookAtDurationTimer.Invalidate();
+ m_isSightedIn = false;
+ m_hasBeenSightedIn = false;
+ m_headSteadyTimer.Invalidate();
+ m_priorAngles = vec3_angle;
+ m_anchorRepositionTimer.Invalidate();
+ m_anchorForward = vec3_origin;
+}
+
+ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Update internal state.
+ * Do this every tick to keep head aims smooth and accurate
+ */
+void PlayerBody::Upkeep( void )
+{
+ // If mimicking the player, don't modify the view angles.
+ static ConVarRef bot_mimic( "bot_mimic" );
+ if ( bot_mimic.IsValid() && bot_mimic.GetBool() )
+ return;
+
+ const float deltaT = gpGlobals->frametime;
+ if ( deltaT < 0.00001f )
+ {
+ return;
+ }
+
+ CBasePlayer *player = ( CBasePlayer * )GetBot()->GetEntity();
+
+ // get current view angles
+ QAngle currentAngles = player->EyeAngles() + player->GetPunchAngle();
+
+ // track when our head is "steady"
+ bool isSteady = true;
+
+ float actualPitchRate = AngleDiff( currentAngles.x, m_priorAngles.x );
+ if ( abs( actualPitchRate ) > nb_head_aim_steady_max_rate.GetFloat() * deltaT )
+ {
+ isSteady = false;
+ }
+ else
+ {
+ float actualYawRate = AngleDiff( currentAngles.y, m_priorAngles.y );
+
+ if ( abs( actualYawRate ) > nb_head_aim_steady_max_rate.GetFloat() * deltaT )
+ {
+ isSteady = false;
+ }
+ }
+
+ if ( isSteady )
+ {
+ if ( !m_headSteadyTimer.HasStarted() )
+ {
+ m_headSteadyTimer.Start();
+ }
+ }
+ else
+ {
+ m_headSteadyTimer.Invalidate();
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ if ( IsHeadSteady() )
+ {
+ const float maxTime = 3.0f;
+ float t = GetHeadSteadyDuration() / maxTime;
+ t = clamp( t, 0.f, 1.0f );
+ NDebugOverlay::Circle( player->EyePosition(), t * 10.0f, 0, 255, 0, 255, true, 2.0f * deltaT );
+ }
+ }
+
+ m_priorAngles = currentAngles;
+
+
+ // if our current look-at has expired, don't change our aim further
+ if ( m_hasBeenSightedIn && m_lookAtExpireTimer.IsElapsed() )
+ {
+ return;
+ }
+
+ // simulate limited range of mouse movements
+ // compute the angle change from "center"
+ const Vector &forward = GetViewVector();
+ float deltaAngle = RAD2DEG( acos( DotProduct( forward, m_anchorForward ) ) );
+ if ( deltaAngle > nb_head_aim_resettle_angle.GetFloat() )
+ {
+ // time to recenter our 'virtual mouse'
+ m_anchorRepositionTimer.Start( RandomFloat( 0.9f, 1.1f ) * nb_head_aim_resettle_time.GetFloat() );
+ m_anchorForward = forward;
+ return;
+ }
+
+ // if we're currently recentering our "virtual mouse", wait
+ if ( m_anchorRepositionTimer.HasStarted() && !m_anchorRepositionTimer.IsElapsed() )
+ {
+ return;
+ }
+ m_anchorRepositionTimer.Invalidate();
+
+
+ // if we have a subject, update lookat point
+ CBaseEntity *subject = m_lookAtSubject;
+ if ( subject )
+ {
+ if ( m_lookAtTrackingTimer.IsElapsed() )
+ {
+ // update subject tracking by periodically estimating linear aim velocity, allowing for "slop" between updates
+ Vector desiredLookAtPos;
+
+ if ( subject->MyCombatCharacterPointer() )
+ {
+ desiredLookAtPos = GetBot()->GetIntentionInterface()->SelectTargetPoint( GetBot(), subject->MyCombatCharacterPointer() );
+ }
+ else
+ {
+ desiredLookAtPos = subject->WorldSpaceCenter();
+ }
+
+ desiredLookAtPos += GetHeadAimSubjectLeadTime() * subject->GetAbsVelocity();
+
+ Vector errorVector = desiredLookAtPos - m_lookAtPos;
+ float error = errorVector.NormalizeInPlace();
+
+ float trackingInterval = GetHeadAimTrackingInterval();
+ if ( trackingInterval < deltaT )
+ {
+ trackingInterval = deltaT;
+ }
+
+ float errorVel = error / trackingInterval;
+
+ m_lookAtVelocity = ( errorVel * errorVector ) + subject->GetAbsVelocity();
+
+ m_lookAtTrackingTimer.Start( RandomFloat( 0.8f, 1.2f ) * trackingInterval );
+ }
+
+ m_lookAtPos += deltaT * m_lookAtVelocity;
+ }
+
+
+ // aim view towards last look at point
+ Vector to = m_lookAtPos - GetEyePosition();
+ to.NormalizeInPlace();
+
+ QAngle desiredAngles;
+ VectorAngles( to, desiredAngles );
+
+ QAngle angles;
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ NDebugOverlay::Line( GetEyePosition(), GetEyePosition() + 100.0f * forward, 255, 255, 0, false, 2.0f * deltaT );
+
+ float thickness = isSteady ? 2.0f : 3.0f;
+ int r = m_isSightedIn ? 255 : 0;
+ int g = subject ? 255 : 0;
+ NDebugOverlay::HorzArrow( GetEyePosition(), m_lookAtPos, thickness, r, g, 255, 255, false, 2.0f * deltaT );
+ }
+
+
+ const float onTargetTolerance = 0.98f;
+ float dot = DotProduct( forward, to );
+ if ( dot > onTargetTolerance )
+ {
+ // on target
+ m_isSightedIn = true;
+
+ if ( !m_hasBeenSightedIn )
+ {
+ m_hasBeenSightedIn = true;
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At SIGHTED IN\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName() );
+ }
+ }
+
+ if ( m_lookAtReplyWhenAimed )
+ {
+ m_lookAtReplyWhenAimed->OnSuccess( GetBot() );
+ m_lookAtReplyWhenAimed = NULL;
+ }
+ }
+ else
+ {
+ // off target
+ m_isSightedIn = false;
+ }
+
+
+ // rotate view at a rate proportional to how far we have to turn
+ // max rate if we need to turn around
+ // want first derivative continuity of rate as our aim hits to avoid pop
+ float approachRate = GetMaxHeadAngularVelocity();
+
+ const float easeOut = 0.7f;
+ if ( dot > easeOut )
+ {
+ float t = RemapVal( dot, easeOut, 1.0f, 1.0f, 0.02f );
+ const float halfPI = 1.57f;
+ approachRate *= sin( halfPI * t );
+ }
+
+ const float easeInTime = 0.25f;
+ if ( m_lookAtDurationTimer.GetElapsedTime() < easeInTime )
+ {
+ approachRate *= m_lookAtDurationTimer.GetElapsedTime() / easeInTime;
+ }
+
+ angles.y = ApproachAngle( desiredAngles.y, currentAngles.y, approachRate * deltaT );
+ angles.x = ApproachAngle( desiredAngles.x, currentAngles.x, 0.5f * approachRate * deltaT );
+ angles.z = 0.0f;
+
+ // back out "punch angle"
+ angles -= player->GetPunchAngle();
+
+ angles.x = AngleNormalize( angles.x );
+ angles.y = AngleNormalize( angles.y );
+
+ player->SnapEyeAngles( angles );
+}
+
+
+//-----------------------------------------------------------------------------------------------
+bool PlayerBody::SetPosition( const Vector &pos )
+{
+ m_player->SetAbsOrigin( pos );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return the eye position of the bot in world coordinates
+ */
+const Vector &PlayerBody::GetEyePosition( void ) const
+{
+ m_eyePos = m_player->EyePosition();
+ return m_eyePos;
+}
+
+
+CBaseEntity *PlayerBody::GetEntity( void )
+{
+ return m_player;
+}
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return the view unit direction vector in world coordinates
+ */
+const Vector &PlayerBody::GetViewVector( void ) const
+{
+ m_player->EyeVectors( &m_viewVector );
+ return m_viewVector;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Aim the bot's head towards the given goal
+ */
+void PlayerBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
+{
+ if ( duration <= 0.0f )
+ {
+ duration = 0.1f;
+ }
+
+ // don't spaz our aim around
+ if ( m_lookAtPriority == priority )
+ {
+ if ( !IsHeadSteady() || GetHeadSteadyDuration() < nb_head_aim_settle_duration.GetFloat() )
+ {
+ // we're still finishing a look-at at the same priority
+ if ( replyWhenAimed )
+ {
+ replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - previous aim not %s\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName(),
+ reason,
+ IsHeadSteady() ? "settled long enough" : "head-steady" );
+ }
+ return;
+ }
+ }
+
+ // don't short-circuit if "sighted in" to avoid rapid view jitter
+ if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
+ {
+ // higher priority lookat still ongoing
+ if ( replyWhenAimed )
+ {
+ replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - higher priority aim in progress\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName(),
+ reason );
+ }
+ return;
+ }
+
+ if ( m_lookAtReplyWhenAimed )
+ {
+ // in-process aim was interrupted
+ m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
+ }
+
+ m_lookAtReplyWhenAimed = replyWhenAimed;
+ m_lookAtExpireTimer.Start( duration );
+
+ // if given the same point, just update priority
+ const float epsilon = 1.0f;
+ if ( ( m_lookAtPos - lookAtPos ).IsLengthLessThan( epsilon ) )
+ {
+ m_lookAtPriority = priority;
+ return;
+ }
+
+ // new look-at point
+
+ m_lookAtPos = lookAtPos;
+ m_lookAtSubject = NULL;
+
+ m_lookAtPriority = priority;
+ m_lookAtDurationTimer.Start();
+
+ // do NOT clear this here, or continuous calls to AimHeadTowards will keep IsHeadAimingOnTarget returning false all of the time
+ // m_isSightedIn = false;
+
+ m_hasBeenSightedIn = false;
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ NDebugOverlay::Cross3D( lookAtPos, 2.0f, 255, 255, 100, true, 2.0f * duration );
+
+ const char *priName = "";
+ switch( priority )
+ {
+ case BORING: priName = "BORING"; break;
+ case INTERESTING: priName = "INTERESTING"; break;
+ case IMPORTANT: priName = "IMPORTANT"; break;
+ case CRITICAL: priName = "CRITICAL"; break;
+ }
+
+ ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At ( %g, %g, %g ) for %3.2f s, Pri = %s, Reason = %s\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName(),
+ lookAtPos.x, lookAtPos.y, lookAtPos.z,
+ duration,
+ priName,
+ ( reason ) ? reason : "" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Aim the bot's head towards the given goal
+ */
+void PlayerBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
+{
+ if ( duration <= 0.0f )
+ {
+ duration = 0.1f;
+ }
+
+ if ( subject == NULL )
+ {
+ return;
+ }
+
+ // don't spaz our aim around
+ if ( m_lookAtPriority == priority )
+ {
+ if ( !IsHeadSteady() || GetHeadSteadyDuration() < nb_head_aim_settle_duration.GetFloat() )
+ {
+ // we're still finishing a look-at at the same priority
+ if ( replyWhenAimed )
+ {
+ replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - previous aim not %s\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName(),
+ reason,
+ IsHeadSteady() ? "head-steady" : "settled long enough" );
+ }
+ return;
+ }
+ }
+
+ // don't short-circuit if "sighted in" to avoid rapid view jitter
+ if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
+ {
+ // higher priority lookat still ongoing
+ if ( replyWhenAimed )
+ {
+ replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At '%s' rejected - higher priority aim in progress\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName(),
+ reason );
+ }
+ return;
+ }
+
+ if ( m_lookAtReplyWhenAimed )
+ {
+ // in-process aim was interrupted
+ m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
+ }
+
+ m_lookAtReplyWhenAimed = replyWhenAimed;
+ m_lookAtExpireTimer.Start( duration );
+
+ // if given the same subject, just update priority
+ if ( subject == m_lookAtSubject )
+ {
+ m_lookAtPriority = priority;
+ return;
+ }
+
+ // new subject
+ m_lookAtSubject = subject;
+
+#ifdef REFACTOR_FOR_CLIENT_SIDE_EYE_TRACKING
+ CBasePlayer *pMyPlayer = static_cast< CBasePlayer * >( GetEntity() );
+ if ( subject->IsPlayer() )
+ {
+ // looking at a player, look at their eye position
+ TerrorPlayer *pMyTarget = ToTerrorPlayer( subject );
+ m_lookAtPos = subject->EyePosition();
+ if(pMyPlayer)
+ {
+ pMyPlayer->SetLookatPlayer( pMyTarget );
+ }
+ }
+ else
+ {
+ // not looking at a player
+ m_lookAtPos = subject->WorldSpaceCenter();
+ if(pMyPlayer)
+ {
+ pMyPlayer->SetLookatPlayer( NULL );
+ }
+ }
+#endif
+
+ m_lookAtPriority = priority;
+ m_lookAtDurationTimer.Start();
+
+ // do NOT clear this here, or continuous calls to AimHeadTowards will keep IsHeadAimingOnTarget returning false all of the time
+ // m_isSightedIn = false;
+
+ m_hasBeenSightedIn = false;
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
+ {
+ NDebugOverlay::Cross3D( m_lookAtPos, 2.0f, 100, 100, 100, true, duration );
+
+ const char *priName = "";
+ switch( priority )
+ {
+ case BORING: priName = "BORING"; break;
+ case INTERESTING: priName = "INTERESTING"; break;
+ case IMPORTANT: priName = "IMPORTANT"; break;
+ case CRITICAL: priName = "CRITICAL"; break;
+ }
+
+ ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At subject %s for %3.2f s, Pri = %s, Reason = %s\n",
+ gpGlobals->curtime,
+ m_player->GetPlayerName(),
+ subject->GetClassname(),
+ duration,
+ priName,
+ ( reason ) ? reason : "" );
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if head is not rapidly turning to look somewhere else
+ */
+bool PlayerBody::IsHeadSteady( void ) const
+{
+ return m_headSteadyTimer.HasStarted();
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return the duration that the bot's head has been on-target
+ */
+float PlayerBody::GetHeadSteadyDuration( void ) const
+{
+ // return ( IsHeadAimingOnTarget() ) ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
+ return m_headSteadyTimer.HasStarted() ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+// Clear out currently pending replyWhenAimed callback
+void PlayerBody::ClearPendingAimReply( void )
+{
+ m_lookAtReplyWhenAimed = NULL;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+float PlayerBody::GetMaxHeadAngularVelocity( void ) const
+{
+ return nb_saccade_speed.GetFloat();
+}
+
+
+//-----------------------------------------------------------------------------------------------
+bool PlayerBody::StartActivity( Activity act, unsigned int flags )
+{
+ // player animation state is controlled on the client
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return currently animating activity
+ */
+Activity PlayerBody::GetActivity( void ) const
+{
+ return ACT_INVALID;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if currently animating activity matches the given one
+ */
+bool PlayerBody::IsActivity( Activity act ) const
+{
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if currently animating activity has any of the given flags
+ */
+bool PlayerBody::HasActivityType( unsigned int flags ) const
+{
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Request a posture change
+ */
+void PlayerBody::SetDesiredPosture( PostureType posture )
+{
+ m_posture = posture;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Get posture body is trying to assume
+ */
+IBody::PostureType PlayerBody::GetDesiredPosture( void ) const
+{
+ return m_posture;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if body is trying to assume this posture
+ */
+bool PlayerBody::IsDesiredPosture( PostureType posture ) const
+{
+ return ( posture == m_posture );
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if body's actual posture matches its desired posture
+ */
+bool PlayerBody::IsInDesiredPosture( void ) const
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return body's current actual posture
+ */
+IBody::PostureType PlayerBody::GetActualPosture( void ) const
+{
+ return m_posture;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if body is actually in the given posture
+ */
+bool PlayerBody::IsActualPosture( PostureType posture ) const
+{
+ return ( posture == m_posture );
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if body's current posture allows it to move around the world
+ */
+bool PlayerBody::IsPostureMobile( void ) const
+{
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if body's posture is in the process of changing to new posture
+ */
+bool PlayerBody::IsPostureChanging( void ) const
+{
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Arousal level change
+ */
+void PlayerBody::SetArousal( ArousalType arousal )
+{
+ m_arousal = arousal;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Get arousal level
+ */
+IBody::ArousalType PlayerBody::GetArousal( void ) const
+{
+ return m_arousal;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return true if body is at this arousal level
+ */
+bool PlayerBody::IsArousal( ArousalType arousal ) const
+{
+ return ( arousal == m_arousal );
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Width of bot's collision hull in XY plane
+ */
+float PlayerBody::GetHullWidth( void ) const
+{
+ return VEC_HULL_MAX_SCALED( m_player ).x - VEC_HULL_MIN_SCALED( m_player ).x;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Height of bot's current collision hull based on posture
+ */
+float PlayerBody::GetHullHeight( void ) const
+{
+ if ( m_posture == CROUCH )
+ {
+ return GetCrouchHullHeight();
+ }
+
+ return GetStandHullHeight();
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Height of bot's collision hull when standing
+ */
+float PlayerBody::GetStandHullHeight( void ) const
+{
+ return VEC_HULL_MAX_SCALED( m_player ).z - VEC_HULL_MIN_SCALED( m_player ).z;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Height of bot's collision hull when crouched
+ */
+float PlayerBody::GetCrouchHullHeight( void ) const
+{
+ return VEC_DUCK_HULL_MAX_SCALED( m_player ).z - VEC_DUCK_HULL_MIN_SCALED( m_player ).z;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return current collision hull minimums based on actual body posture
+ */
+const Vector &PlayerBody::GetHullMins( void ) const
+{
+ if ( m_posture == CROUCH )
+ {
+ m_hullMins = VEC_DUCK_HULL_MIN_SCALED( m_player );
+ }
+ else
+ {
+ m_hullMins = VEC_HULL_MIN_SCALED( m_player );
+ }
+
+ return m_hullMins;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return current collision hull maximums based on actual body posture
+ */
+const Vector &PlayerBody::GetHullMaxs( void ) const
+{
+ if ( m_posture == CROUCH )
+ {
+ m_hullMaxs = VEC_DUCK_HULL_MAX_SCALED( m_player );
+ }
+ else
+ {
+ m_hullMaxs = VEC_HULL_MAX_SCALED( m_player );
+ }
+
+ return m_hullMaxs;
+}
+
+
+//-----------------------------------------------------------------------------------------------
+/**
+ * Return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
+ */
+unsigned int PlayerBody::GetSolidMask( void ) const
+{
+ return ( m_player ) ? m_player->PlayerSolidMask() : MASK_PLAYERSOLID;
+}
+
+
+
+
+
diff --git a/game/server/NextBot/Player/NextBotPlayerBody.h b/game/server/NextBot/Player/NextBotPlayerBody.h
new file mode 100644
index 0000000..eda3643
--- /dev/null
+++ b/game/server/NextBot/Player/NextBotPlayerBody.h
@@ -0,0 +1,153 @@
+// NextBotPlayerBody.h
+// Control and information about the bot's body state (posture, animation state, etc)
+// Author: Michael Booth, October 2006
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#ifndef _NEXT_BOT_PLAYER_BODY_H_
+#define _NEXT_BOT_PLAYER_BODY_H_
+
+#include "NextBotBodyInterface.h"
+
+
+//----------------------------------------------------------------------------------------------------------------
+/**
+ * A useful reply for IBody::AimHeadTowards. When the
+ * head is aiming on target, press the fire button.
+ */
+class PressFireButtonReply : public INextBotReply
+{
+public:
+ virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
+};
+
+
+//----------------------------------------------------------------------------------------------------------------
+/**
+ * A useful reply for IBody::AimHeadTowards. When the
+ * head is aiming on target, press the alt-fire button.
+ */
+class PressAltFireButtonReply : public INextBotReply
+{
+public:
+ virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
+};
+
+
+//----------------------------------------------------------------------------------------------------------------
+/**
+ * A useful reply for IBody::AimHeadTowards. When the
+ * head is aiming on target, press the jump button.
+ */
+class PressJumpButtonReply : public INextBotReply
+{
+public:
+ virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
+};
+
+
+//----------------------------------------------------------------------------------------------------------------
+/**
+ * The interface for control and information about the bot's body state (posture, animation state, etc)
+ */
+class PlayerBody : public IBody
+{
+public:
+ PlayerBody( INextBot *bot );
+ virtual ~PlayerBody();
+
+ virtual void Reset( void ); // reset to initial state
+ virtual void Upkeep( void ); // lightweight update guaranteed to occur every server tick
+
+ virtual bool SetPosition( const Vector &pos );
+
+ virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
+ virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
+
+ virtual void AimHeadTowards( const Vector &lookAtPos,
+ LookAtPriorityType priority = BORING,
+ float duration = 0.0f,
+ INextBotReply *replyWhenAimed = NULL,
+ const char *reason = NULL ); // aim the bot's head towards the given goal
+
+ virtual void AimHeadTowards( CBaseEntity *subject,
+ LookAtPriorityType priority = BORING,
+ float duration = 0.0f,
+ INextBotReply *replyWhenAimed = NULL,
+ const char *reason = NULL ); // continually aim the bot's head towards the given subject
+
+ virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
+ virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
+ virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has been on-target
+ virtual void ClearPendingAimReply( void ); // clear out currently pending replyWhenAimed callback
+
+ virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
+
+ virtual bool StartActivity( Activity act, unsigned int flags );
+ virtual Activity GetActivity( void ) const; // return currently animating activity
+ virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
+ virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
+
+ virtual void SetDesiredPosture( PostureType posture ); // request a posture change
+ virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
+ virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
+ virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
+
+ virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
+ virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
+
+ virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
+ virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
+
+ virtual void SetArousal( ArousalType arousal ); // arousal level change
+ virtual ArousalType GetArousal( void ) const; // get arousal level
+ virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
+
+ virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
+ virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
+ virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
+ virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
+ virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
+ virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
+
+ virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
+
+ virtual CBaseEntity *GetEntity( void ); // get the entity
+private:
+ CBasePlayer *m_player;
+
+ PostureType m_posture;
+ ArousalType m_arousal;
+
+ mutable Vector m_eyePos; // for use with GetEyePosition() ONLY
+ mutable Vector m_viewVector; // for use with GetViewVector() ONLY
+ mutable Vector m_hullMins; // for use with GetHullMins() ONLY
+ mutable Vector m_hullMaxs; // for use with GetHullMaxs() ONLY
+
+ Vector m_lookAtPos; // if m_lookAtSubject is non-NULL, it continually overwrites this position with its own
+ EHANDLE m_lookAtSubject;
+ Vector m_lookAtVelocity; // world velocity of lookat point, for tracking moving subjects
+ CountdownTimer m_lookAtTrackingTimer;
+
+ LookAtPriorityType m_lookAtPriority;
+ CountdownTimer m_lookAtExpireTimer; // how long until this lookat expired
+ IntervalTimer m_lookAtDurationTimer; // how long have we been looking at this target
+ INextBotReply *m_lookAtReplyWhenAimed;
+ bool m_isSightedIn; // true if we are looking at our last lookat target
+ bool m_hasBeenSightedIn; // true if we have hit the current lookat target
+
+ IntervalTimer m_headSteadyTimer;
+ QAngle m_priorAngles; // last update's head angles
+ QAngle m_desiredAngles;
+
+ CountdownTimer m_anchorRepositionTimer; // the time is takes us to recenter our virtual mouse
+ Vector m_anchorForward;
+};
+
+inline bool PlayerBody::IsHeadAimingOnTarget( void ) const
+{
+ // TODO: Calling this immediately after AimHeadTowards will always return false until next Upkeep() (MSB)
+ return m_isSightedIn;
+}
+
+
+#endif // _NEXT_BOT_PLAYER_BODY_H_
diff --git a/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp b/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
new file mode 100644
index 0000000..ea30ee7
--- /dev/null
+++ b/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
@@ -0,0 +1,826 @@
+// NextBotPlayerLocomotion.cpp
+// Implementation of Locomotion interface for CBasePlayer-derived classes
+// Author: Michael Booth, November 2005
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+#include "nav_mesh.h"
+#include "in_buttons.h"
+#include "NextBot.h"
+#include "NextBotUtil.h"
+#include "NextBotPlayer.h"
+#include "NextBotPlayerLocomotion.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar NextBotPlayerMoveDirect( "nb_player_move_direct", "0" );
+
+//-----------------------------------------------------------------------------------------------------
+PlayerLocomotion::PlayerLocomotion( INextBot *bot ) : ILocomotion( bot )
+{
+ m_player = NULL;
+ Reset();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Reset locomotor to initial state
+ */
+void PlayerLocomotion::Reset( void )
+{
+ m_player = static_cast< CBasePlayer * >( GetBot()->GetEntity() );
+
+ m_isJumping = false;
+ m_isClimbingUpToLedge = false;
+ m_isJumpingAcrossGap = false;
+ m_hasLeftTheGround = false;
+ m_desiredSpeed = 0.0f;
+
+
+ m_ladderState = NO_LADDER;
+ m_ladderInfo = NULL;
+ m_ladderDismountGoal = NULL;
+ m_ladderTimer.Invalidate();
+
+ m_minSpeedLimit = 0.0f;
+ m_maxSpeedLimit = 9999999.9f;
+
+ BaseClass::Reset();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::TraverseLadder( void )
+{
+ switch( m_ladderState )
+ {
+ case APPROACHING_ASCENDING_LADDER:
+ m_ladderState = ApproachAscendingLadder();
+ return true;
+
+ case APPROACHING_DESCENDING_LADDER:
+ m_ladderState = ApproachDescendingLadder();
+ return true;
+
+ case ASCENDING_LADDER:
+ m_ladderState = AscendLadder();
+ return true;
+
+ case DESCENDING_LADDER:
+ m_ladderState = DescendLadder();
+ return true;
+
+ case DISMOUNTING_LADDER_TOP:
+ m_ladderState = DismountLadderTop();
+ return true;
+
+ case DISMOUNTING_LADDER_BOTTOM:
+ m_ladderState = DismountLadderBottom();
+ return true;
+
+ case NO_LADDER:
+ default:
+ m_ladderInfo = NULL;
+
+ if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
+ {
+ // on ladder and don't want to be
+ GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
+ }
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * We're close, but not yet on, this ladder - approach it
+ */
+PlayerLocomotion::LadderState PlayerLocomotion::ApproachAscendingLadder( void )
+{
+ if ( m_ladderInfo == NULL )
+ {
+ return NO_LADDER;
+ }
+
+ // sanity check - are we already at the end of this ladder?
+ if ( GetFeet().z >= m_ladderInfo->m_top.z - GetStepHeight() )
+ {
+ m_ladderTimer.Start( 2.0f );
+ return DISMOUNTING_LADDER_TOP;
+ }
+
+ // sanity check - are we too far below this ladder to reach it?
+ if ( GetFeet().z <= m_ladderInfo->m_bottom.z - GetMaxJumpHeight() )
+ {
+ return NO_LADDER;
+ }
+
+ FaceTowards( m_ladderInfo->m_bottom );
+
+ // it is important to approach precisely, so use a very large weight to wash out all other Approaches
+ Approach( m_ladderInfo->m_bottom, 9999999.9f );
+
+ if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
+ {
+ // we're on the ladder
+ return ASCENDING_LADDER;
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach ascending ladder", 0.1f, 255, 255, 255, 255 );
+ }
+
+ return APPROACHING_ASCENDING_LADDER;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+PlayerLocomotion::LadderState PlayerLocomotion::ApproachDescendingLadder( void )
+{
+ if ( m_ladderInfo == NULL )
+ {
+ return NO_LADDER;
+ }
+
+ // sanity check - are we already at the end of this ladder?
+ if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetMaxJumpHeight() )
+ {
+ m_ladderTimer.Start( 2.0f );
+ return DISMOUNTING_LADDER_BOTTOM;
+ }
+
+ Vector mountPoint = m_ladderInfo->m_top + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth() * m_ladderInfo->GetNormal();
+ Vector to = mountPoint - GetFeet();
+ to.z = 0.0f;
+
+ float mountRange = to.NormalizeInPlace();
+ Vector moveGoal;
+
+ const float veryClose = 10.0f;
+ if ( mountRange < veryClose )
+ {
+ // we're right at the ladder - just keep moving forward until we grab it
+ const Vector &forward = GetMotionVector();
+ moveGoal = GetFeet() + 100.0f * forward;
+ }
+ else
+ {
+ if ( DotProduct( to, m_ladderInfo->GetNormal() ) < 0.0f )
+ {
+ // approaching front of downward ladder
+ // ##
+ // ->+ ##
+ // | ##
+ // | ##
+ // | ##
+ // <-+ ##
+ // ######
+ //
+ moveGoal = m_ladderInfo->m_top - 100.0f * m_ladderInfo->GetNormal();
+ }
+ else
+ {
+ // approaching back of downward ladder
+ //
+ // ->+
+ // ##|
+ // ##|
+ // ##+-->
+ // ######
+ //
+ moveGoal = m_ladderInfo->m_top + 100.0f * m_ladderInfo->GetNormal();
+ }
+ }
+
+ FaceTowards( moveGoal );
+
+ // it is important to approach precisely, so use a very large weight to wash out all other Approaches
+ Approach( moveGoal, 9999999.9f );
+
+ if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
+ {
+ // we're on the ladder
+ return DESCENDING_LADDER;
+ }
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach descending ladder", 0.1f, 255, 255, 255, 255 );
+ }
+
+ return APPROACHING_DESCENDING_LADDER;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+PlayerLocomotion::LadderState PlayerLocomotion::AscendLadder( void )
+{
+ if ( m_ladderInfo == NULL )
+ {
+ return NO_LADDER;
+ }
+
+ if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
+ {
+ // slipped off ladder
+ m_ladderInfo = NULL;
+ return NO_LADDER;
+ }
+
+ if ( GetFeet().z >= m_ladderInfo->m_top.z )
+ {
+ // reached top of ladder
+ m_ladderTimer.Start( 2.0f );
+ return DISMOUNTING_LADDER_TOP;
+ }
+
+ // climb up this ladder - look up
+ Vector goal = GetFeet() + 100.0f * ( -m_ladderInfo->GetNormal() + Vector( 0, 0, 2 ) );
+
+ GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
+
+ // it is important to approach precisely, so use a very large weight to wash out all other Approaches
+ Approach( goal, 9999999.9f );
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Ascend", 0.1f, 255, 255, 255, 255 );
+ }
+
+ return ASCENDING_LADDER;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+PlayerLocomotion::LadderState PlayerLocomotion::DescendLadder( void )
+{
+ if ( m_ladderInfo == NULL )
+ {
+ return NO_LADDER;
+ }
+
+ if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
+ {
+ // slipped off ladder
+ m_ladderInfo = NULL;
+ return NO_LADDER;
+ }
+
+ if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetBot()->GetLocomotionInterface()->GetStepHeight() )
+ {
+ // reached bottom of ladder
+ m_ladderTimer.Start( 2.0f );
+ return DISMOUNTING_LADDER_BOTTOM;
+ }
+
+ // climb down this ladder - look down
+ Vector goal = GetFeet() + 100.0f * ( m_ladderInfo->GetNormal() + Vector( 0, 0, -2 ) );
+
+ GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
+
+ // it is important to approach precisely, so use a very large weight to wash out all other Approaches
+ Approach( goal, 9999999.9f );
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Descend", 0.1f, 255, 255, 255, 255 );
+ }
+
+ return DESCENDING_LADDER;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderTop( void )
+{
+ if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
+ {
+ m_ladderInfo = NULL;
+ return NO_LADDER;
+ }
+
+ IBody *body = GetBot()->GetBodyInterface();
+ Vector toGoal = m_ladderDismountGoal->GetCenter() - GetFeet();
+ toGoal.z = 0.0f;
+ float range = toGoal.NormalizeInPlace();
+ toGoal.z = 1.0f;
+
+ body->AimHeadTowards( body->GetEyePosition() + 100.0f * toGoal, IBody::MANDATORY, 0.1f, NULL, "Ladder dismount" );
+
+ // it is important to approach precisely, so use a very large weight to wash out all other Approaches
+ Approach( GetFeet() + 100.0f * toGoal, 9999999.9f );
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Dismount top", 0.1f, 255, 255, 255, 255 );
+ NDebugOverlay::HorzArrow( GetFeet(), m_ladderDismountGoal->GetCenter(), 5.0f, 255, 255, 0, 255, true, 0.1f );
+ }
+
+ // test 2D vector here in case nav area is under the geometry a bit
+ const float tolerance = 10.0f;
+ if ( GetBot()->GetEntity()->GetLastKnownArea() == m_ladderDismountGoal && range < tolerance )
+ {
+ // reached dismount goal
+ m_ladderInfo = NULL;
+ return NO_LADDER;
+ }
+
+ return DISMOUNTING_LADDER_TOP;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderBottom( void )
+{
+ if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
+ {
+ m_ladderInfo = NULL;
+ return NO_LADDER;
+ }
+
+ if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
+ {
+ // near the bottom - just let go
+ GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
+ m_ladderInfo = NULL;
+ }
+
+ return NO_LADDER;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Update internal state
+ */
+void PlayerLocomotion::Update( void )
+{
+ if ( TraverseLadder() )
+ {
+ return BaseClass::Update();
+ }
+
+ if ( m_isJumpingAcrossGap || m_isClimbingUpToLedge )
+ {
+ // force a run
+ SetMinimumSpeedLimit( GetRunSpeed() );
+
+ Vector toLanding = m_landingGoal - GetFeet();
+ toLanding.z = 0.0f;
+ toLanding.NormalizeInPlace();
+
+ if ( m_hasLeftTheGround )
+ {
+ // face into the jump/climb
+ GetBot()->GetBodyInterface()->AimHeadTowards( GetBot()->GetEntity()->EyePosition() + 100.0 * toLanding, IBody::MANDATORY, 0.25f, NULL, "Facing impending jump/climb" );
+
+ if ( IsOnGround() )
+ {
+ // back on the ground - jump is complete
+ m_isClimbingUpToLedge = false;
+ m_isJumpingAcrossGap = false;
+ SetMinimumSpeedLimit( 0.0f );
+ }
+ }
+ else
+ {
+ // haven't left the ground yet - just starting the jump
+
+ if ( !IsClimbingOrJumping() )
+ {
+ Jump();
+ }
+
+ Vector vel = GetBot()->GetEntity()->GetAbsVelocity();
+
+ if ( m_isJumpingAcrossGap )
+ {
+ // cheat and max our velocity in case we were stopped at the edge of this gap
+ vel.x = GetRunSpeed() * toLanding.x;
+ vel.y = GetRunSpeed() * toLanding.y;
+ // leave vel.z unchanged
+ }
+
+ GetBot()->GetEntity()->SetAbsVelocity( vel );
+
+ if ( !IsOnGround() )
+ {
+ // jump has begun
+ m_hasLeftTheGround = true;
+ }
+ }
+
+
+ Approach( m_landingGoal );
+ }
+
+ BaseClass::Update();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+void PlayerLocomotion::AdjustPosture( const Vector &moveGoal )
+{
+ // This function has no effect if we're not standing or crouching
+ IBody *body = GetBot()->GetBodyInterface();
+ if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
+ return;
+
+ // not all games have auto-crouch, so don't assume it here
+ BaseClass::AdjustPosture( moveGoal );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Build a user command to move this player towards the goal position
+ */
+void PlayerLocomotion::Approach( const Vector &pos, float goalWeight )
+{
+ VPROF_BUDGET( "PlayerLocomotion::Approach", "NextBot" );
+
+ BaseClass::Approach( pos );
+
+ AdjustPosture( pos );
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::Line( GetFeet(), pos, 255, 255, 0, true, 0.1f );
+ }
+
+ INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
+
+ if ( !playerButtons )
+ {
+ DevMsg( "PlayerLocomotion::Approach: No INextBotPlayerInput\n " );
+ return;
+ }
+
+ Vector forward3D;
+ m_player->EyeVectors( &forward3D );
+
+ Vector2D forward( forward3D.x, forward3D.y );
+ forward.NormalizeInPlace();
+
+ Vector2D right( forward.y, -forward.x );
+
+ // compute unit vector to goal position
+ Vector2D to = ( pos - GetFeet() ).AsVector2D();
+ float goalDistance = to.NormalizeInPlace();
+
+ float ahead = to.Dot( forward );
+ float side = to.Dot( right );
+
+#ifdef NEED_TO_INTEGRATE_MOTION_CONTROLLED_CODE_FROM_L4D_PLAYERS
+ // If we're climbing ledges, we need to stay crouched to prevent player movement code from messing
+ // with our origin.
+ CTerrorPlayer *player = ToTerrorPlayer(m_player);
+ if ( player && player->IsMotionControlledZ( player->GetMainActivity() ) )
+ {
+ playerButtons->PressCrouchButton();
+ return;
+ }
+#endif
+
+ if ( m_player->IsOnLadder() && IsUsingLadder() && ( m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER ) )
+ {
+ // we are on a ladder and WANT to be on a ladder.
+ playerButtons->PressForwardButton();
+
+ // Stay in center of ladder. The gamemovement will autocenter us in most cases, but this is needed in case it doesn't.
+ if ( m_ladderInfo )
+ {
+ Vector posOnLadder;
+ CalcClosestPointOnLine( GetFeet(), m_ladderInfo->m_bottom, m_ladderInfo->m_top, posOnLadder );
+
+ Vector alongLadder = m_ladderInfo->m_top - m_ladderInfo->m_bottom;
+ alongLadder.NormalizeInPlace();
+
+ Vector rightLadder = CrossProduct( alongLadder, m_ladderInfo->GetNormal() );
+
+ Vector away = GetFeet() - posOnLadder;
+
+ // we only want error in plane of ladder
+ float error = DotProduct( away, rightLadder );
+ away.NormalizeInPlace();
+
+ const float tolerance = 5.0f + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth();
+ if ( error > tolerance )
+ {
+ if ( DotProduct( away, rightLadder ) > 0.0f )
+ {
+ playerButtons->PressLeftButton();
+ }
+ else
+ {
+ playerButtons->PressRightButton();
+ }
+ }
+ }
+ }
+ else
+ {
+ const float epsilon = 0.25f;
+ if ( NextBotPlayerMoveDirect.GetBool() )
+ {
+ if ( goalDistance > epsilon )
+ {
+ playerButtons->SetButtonScale( ahead, side );
+ }
+ }
+
+ if ( ahead > epsilon )
+ {
+ playerButtons->PressForwardButton();
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 0, 255, 0, 255, true, 0.1f );
+ }
+ }
+ else if ( ahead < -epsilon )
+ {
+ playerButtons->PressBackwardButton();
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 255, 0, 0, 255, true, 0.1f );
+ }
+ }
+
+ if ( side <= -epsilon )
+ {
+ playerButtons->PressLeftButton();
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 255, 0, 255, 255, true, 0.1f );
+ }
+ }
+ else if ( side >= epsilon )
+ {
+ playerButtons->PressRightButton();
+
+ if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
+ {
+ NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 0, 255, 255, 255, true, 0.1f );
+ }
+ }
+ }
+
+ if ( !IsRunning() )
+ {
+ playerButtons->PressWalkButton();
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Move the bot to the precise given position immediately,
+ */
+void PlayerLocomotion::DriveTo( const Vector &pos )
+{
+ BaseClass::DriveTo( pos );
+
+ Approach( pos );
+}
+
+
+//----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const
+{
+ // don't jump unless we have to
+ const PathFollower *path = GetBot()->GetCurrentPath();
+ if ( path )
+ {
+ const float watchForClimbRange = 75.0f;
+ if ( !path->IsDiscontinuityAhead( GetBot(), Path::CLIMB_UP, watchForClimbRange ) )
+ {
+ // we are not planning on climbing
+
+ // always allow climbing over movable obstacles
+ if ( obstacle && !const_cast< CBaseEntity * >( obstacle )->IsWorld() )
+ {
+ IPhysicsObject *physics = obstacle->VPhysicsGetObject();
+ if ( physics && physics->IsMoveable() )
+ {
+ // movable physics object - climb over it
+ return true;
+ }
+ }
+
+ if ( !GetBot()->GetLocomotionInterface()->IsStuck() )
+ {
+ // we're not stuck - don't try to jump up yet
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle )
+{
+ if ( !IsClimbPossible( GetBot(), obstacle ) )
+ {
+ return false;
+ }
+
+ Jump();
+
+ m_isClimbingUpToLedge = true;
+ m_landingGoal = landingGoal;
+ m_hasLeftTheGround = false;
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+void PlayerLocomotion::JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward )
+{
+ Jump();
+
+ // face forward
+ GetBot()->GetBodyInterface()->AimHeadTowards( landingGoal, IBody::MANDATORY, 1.0f, NULL, "Looking forward while jumping a gap" );
+
+ m_isJumpingAcrossGap = true;
+ m_landingGoal = landingGoal;
+ m_hasLeftTheGround = false;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+void PlayerLocomotion::Jump( void )
+{
+ m_isJumping = true;
+ m_jumpTimer.Start( 0.5f );
+
+ INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
+ if ( playerButtons )
+ {
+ playerButtons->PressJumpButton();
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::IsClimbingOrJumping( void ) const
+{
+ if ( !m_isJumping )
+ return false;
+
+ if ( m_jumpTimer.IsElapsed() && IsOnGround() )
+ {
+ m_isJumping = false;
+ return false;
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::IsClimbingUpToLedge( void ) const
+{
+ return m_isClimbingUpToLedge;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::IsJumpingAcrossGap( void ) const
+{
+ return m_isJumpingAcrossGap;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Return true if standing on something
+ */
+bool PlayerLocomotion::IsOnGround( void ) const
+{
+ return (m_player->GetGroundEntity() != NULL);
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Return the current ground entity or NULL if not on the ground
+ */
+CBaseEntity *PlayerLocomotion::GetGround( void ) const
+{
+ return m_player->GetGroundEntity();
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Surface normal of the ground we are in contact with
+ */
+const Vector &PlayerLocomotion::GetGroundNormal( void ) const
+{
+ static Vector up( 0, 0, 1.0f );
+ return up;
+
+ // TODO: Integrate movehelper_server for this: return m_player->GetGroundNormal();
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Climb the given ladder to the top and dismount
+ */
+void PlayerLocomotion::ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
+{
+ // look up and push forward
+// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, 1.0f ) - ladder->GetNormal() );
+// Approach( goal );
+// FaceTowards( goal );
+
+ m_ladderState = APPROACHING_ASCENDING_LADDER;
+ m_ladderInfo = ladder;
+ m_ladderDismountGoal = dismountGoal;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Descend the given ladder to the bottom and dismount
+ */
+void PlayerLocomotion::DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
+{
+ // look down and push forward
+// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, -1.0f ) - ladder->GetNormal() );
+// Approach( goal );
+// FaceTowards( goal );
+
+ m_ladderState = APPROACHING_DESCENDING_LADDER;
+ m_ladderInfo = ladder;
+ m_ladderDismountGoal = dismountGoal;
+}
+
+
+//----------------------------------------------------------------------------------------------------
+bool PlayerLocomotion::IsUsingLadder( void ) const
+{
+ return ( m_ladderState != NO_LADDER );
+}
+
+
+//----------------------------------------------------------------------------------------------------
+/**
+ * Rotate body to face towards "target"
+ */
+void PlayerLocomotion::FaceTowards( const Vector &target )
+{
+ // player body follows view direction
+ Vector look( target.x, target.y, GetBot()->GetEntity()->EyePosition().z );
+
+ GetBot()->GetBodyInterface()->AimHeadTowards( look, IBody::BORING, 0.1f, NULL, "Body facing" );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+* Return position of "feet" - point below centroid of bot at feet level
+*/
+const Vector &PlayerLocomotion::GetFeet( void ) const
+{
+ return m_player->GetAbsOrigin();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Return current world space velocity
+ */
+const Vector &PlayerLocomotion::GetVelocity( void ) const
+{
+ return m_player->GetAbsVelocity();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+float PlayerLocomotion::GetRunSpeed( void ) const
+{
+ return m_player->MaxSpeed();
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+float PlayerLocomotion::GetWalkSpeed( void ) const
+{
+ return 0.5f * m_player->MaxSpeed();
+}
+
diff --git a/game/server/NextBot/Player/NextBotPlayerLocomotion.h b/game/server/NextBot/Player/NextBotPlayerLocomotion.h
new file mode 100644
index 0000000..5da7260
--- /dev/null
+++ b/game/server/NextBot/Player/NextBotPlayerLocomotion.h
@@ -0,0 +1,223 @@
+// NextBotPlayerLocomotion.h
+// Locomotor for CBasePlayer derived bots
+// Author: Michael Booth, November 2005
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#ifndef _NEXT_BOT_PLAYER_LOCOMOTION_H_
+#define _NEXT_BOT_PLAYER_LOCOMOTION_H_
+
+#include "NextBot.h"
+#include "NextBotLocomotionInterface.h"
+#include "Path/NextBotPathFollow.h"
+
+class CBasePlayer;
+
+//--------------------------------------------------------------------------------------------------
+/**
+ * Basic player locomotion implementation
+ */
+class PlayerLocomotion : public ILocomotion
+{
+public:
+ DECLARE_CLASS( PlayerLocomotion, ILocomotion );
+
+ PlayerLocomotion( INextBot *bot );
+ virtual ~PlayerLocomotion() { }
+
+ virtual void Reset( void ); // reset to initial state
+ virtual void Update( void ); // update internal state
+
+ virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
+ virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
+
+ //
+ // ILocomotion modifiers
+ //
+ virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
+ virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
+ virtual void Jump( void ); // initiate a simple undirected jump in the air
+ virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
+ virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
+ virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
+
+ virtual void Run( void ); // set desired movement speed to running
+ virtual void Walk( void ); // set desired movement speed to walking
+ virtual void Stop( void ); // set desired movement speed to stopped
+ virtual bool IsRunning( void ) const;
+ virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
+ virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
+ virtual void SetMinimumSpeedLimit( float limit ); // speed cannot drop below this
+ virtual void SetMaximumSpeedLimit( float limit ); // speed cannot rise above this
+
+ virtual bool IsOnGround( void ) const; // return true if standing on something
+ virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
+ virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
+
+ virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
+ virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
+ virtual bool IsUsingLadder( void ) const;
+ virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
+ virtual bool IsAbleToAutoCenterOnLadder( void ) const;
+
+ virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
+
+ virtual void SetDesiredLean( const QAngle &lean ) { }
+ virtual const QAngle &GetDesiredLean( void ) const { static QAngle junk; return junk; }
+
+ //
+ // ILocomotion information
+ //
+ virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of bot at feet level
+
+ virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
+ virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
+ virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
+
+ virtual float GetRunSpeed( void ) const; // get maximum running speed
+ virtual float GetWalkSpeed( void ) const; // get maximum walking speed
+
+ virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
+ virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
+
+ virtual const Vector &GetVelocity( void ) const; // return current world space velocity
+
+protected:
+ virtual void AdjustPosture( const Vector &moveGoal );
+
+private:
+ CBasePlayer *m_player; // the player we are locomoting
+
+ mutable bool m_isJumping;
+ CountdownTimer m_jumpTimer;
+
+ bool m_isClimbingUpToLedge;
+ bool m_isJumpingAcrossGap;
+ Vector m_landingGoal;
+ bool m_hasLeftTheGround;
+
+ float m_desiredSpeed;
+ float m_minSpeedLimit;
+ float m_maxSpeedLimit;
+
+ bool TraverseLadder( void ); // when climbing/descending a ladder
+
+ enum LadderState
+ {
+ NO_LADDER, // not using a ladder
+ APPROACHING_ASCENDING_LADDER,
+ APPROACHING_DESCENDING_LADDER,
+ ASCENDING_LADDER,
+ DESCENDING_LADDER,
+ DISMOUNTING_LADDER_TOP,
+ DISMOUNTING_LADDER_BOTTOM,
+ };
+
+ LadderState m_ladderState;
+ LadderState ApproachAscendingLadder( void );
+ LadderState ApproachDescendingLadder( void );
+ LadderState AscendLadder( void );
+ LadderState DescendLadder( void );
+ LadderState DismountLadderTop( void );
+ LadderState DismountLadderBottom( void );
+
+ const CNavLadder *m_ladderInfo;
+ const CNavArea *m_ladderDismountGoal;
+ CountdownTimer m_ladderTimer; // a "give up" timer if things go awry
+
+ bool IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const;
+};
+
+
+inline float PlayerLocomotion::GetStepHeight( void ) const
+{
+ return 18.0f;
+}
+
+
+inline float PlayerLocomotion::GetMaxJumpHeight( void ) const
+{
+ return 57.0f;
+}
+
+
+inline float PlayerLocomotion::GetDeathDropHeight( void ) const
+{
+ return 200.0f;
+}
+
+
+inline float PlayerLocomotion::GetMaxAcceleration( void ) const
+{
+ return 100.0f;
+}
+
+inline float PlayerLocomotion::GetMaxDeceleration( void ) const
+{
+ return 200.0f;
+}
+
+inline void PlayerLocomotion::Run( void )
+{
+ m_desiredSpeed = GetRunSpeed();
+}
+
+inline void PlayerLocomotion::Walk( void )
+{
+ m_desiredSpeed = GetWalkSpeed();
+}
+
+inline void PlayerLocomotion::Stop( void )
+{
+ m_desiredSpeed = 0.0f;
+}
+
+inline bool PlayerLocomotion::IsRunning( void ) const
+{
+ return true;
+}
+
+inline void PlayerLocomotion::SetDesiredSpeed( float speed )
+{
+ m_desiredSpeed = speed;
+}
+
+inline float PlayerLocomotion::GetDesiredSpeed( void ) const
+{
+ return clamp( m_desiredSpeed, m_minSpeedLimit, m_maxSpeedLimit );
+}
+
+inline void PlayerLocomotion::SetMinimumSpeedLimit( float limit )
+{
+ m_minSpeedLimit = limit;
+}
+
+inline void PlayerLocomotion::SetMaximumSpeedLimit( float limit )
+{
+ m_maxSpeedLimit = limit;
+}
+
+inline bool PlayerLocomotion::IsAbleToAutoCenterOnLadder( void ) const
+{
+ return IsUsingLadder() && (m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER);
+}
+
+inline bool PlayerLocomotion::IsAscendingOrDescendingLadder( void ) const
+{
+ switch( m_ladderState )
+ {
+ case ASCENDING_LADDER:
+ case DESCENDING_LADDER:
+ case DISMOUNTING_LADDER_TOP:
+ case DISMOUNTING_LADDER_BOTTOM:
+ return true;
+ default:
+ // Explicitly handle the default so that clang knows not to warn us.
+ // warning: enumeration values 'NO_LADDER', 'APPROACHING_ASCENDING_LADDER', and 'APPROACHING_DESCENDING_LADDER' not handled in switch [-Wswitch-enum]
+ break;
+ }
+
+ return false;
+}
+
+
+#endif // _NEXT_BOT_PLAYER_LOCOMOTION_H_