diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/NextBot/Player | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/NextBot/Player')
| -rw-r--r-- | game/server/NextBot/Player/NextBotPlayer.cpp | 22 | ||||
| -rw-r--r-- | game/server/NextBot/Player/NextBotPlayer.h | 910 | ||||
| -rw-r--r-- | game/server/NextBot/Player/NextBotPlayerBody.cpp | 881 | ||||
| -rw-r--r-- | game/server/NextBot/Player/NextBotPlayerBody.h | 153 | ||||
| -rw-r--r-- | game/server/NextBot/Player/NextBotPlayerLocomotion.cpp | 826 | ||||
| -rw-r--r-- | game/server/NextBot/Player/NextBotPlayerLocomotion.h | 223 |
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_ |