summaryrefslogtreecommitdiff
path: root/game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/NextBot/Player/NextBotPlayerLocomotion.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'game/server/NextBot/Player/NextBotPlayerLocomotion.cpp')
-rw-r--r--game/server/NextBot/Player/NextBotPlayerLocomotion.cpp826
1 files changed, 826 insertions, 0 deletions
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();
+}
+