// NextBotPathFollow.cpp
// Path following
// Author: Michael Booth, April 2005
//========= Copyright Valve Corporation, All rights reserved. ============//

#include "cbase.h"

#include "BasePropDoor.h"

#include "nav_mesh.h"
#include "NextBot.h"
#include "NextBotPathFollow.h"
#include "NextBotUtil.h"

#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotVisionInterface.h"

#include "tier0/vprof.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

ConVar NextBotSpeedLookAheadRange( "nb_speed_look_ahead_range", "150", FCVAR_CHEAT );
ConVar NextBotGoalLookAheadRange( "nb_goal_look_ahead_range", "50", FCVAR_CHEAT );
ConVar NextBotLadderAlignRange( "nb_ladder_align_range", "50", FCVAR_CHEAT );

ConVar NextBotAllowAvoiding( "nb_allow_avoiding", "1", FCVAR_CHEAT );
ConVar NextBotAllowClimbing( "nb_allow_climbing", "1", FCVAR_CHEAT );
ConVar NextBotAllowGapJumping( "nb_allow_gap_jumping", "1", FCVAR_CHEAT );

ConVar NextBotDebugClimbing( "nb_debug_climbing", "0", FCVAR_CHEAT );


//--------------------------------------------------------------------------------------------------------------
/**
 * Constructor
 */
PathFollower::PathFollower( void )
{
	m_goal = NULL;
	m_didAvoidCheck = false;

	m_avoidTimer.Invalidate();
	m_waitTimer.Invalidate();
	m_hindrance = NULL;

	m_minLookAheadRange = -1.0f;

	// was 10.0f for L4D - need a better solution here (MSB 5/15/09)
	m_goalTolerance = 25.0f;
}


//--------------------------------------------------------------------------------------------------------------
class CDetachPath
{
public:
	CDetachPath( PathFollower *path )
	{
		m_path = path;
	}

	bool operator() ( INextBot *bot )
	{
		bot->NotifyPathDestruction( m_path );
		return true;
	}

	PathFollower *m_path;
};

//--------------------------------------------------------------------------------------------------------------
PathFollower::~PathFollower()
{
	// allow bots to detach pointer to me
	CDetachPath detach( this );
	TheNextBots().ForEachBot( detach );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * When the path is invalidated, the follower is also reset
 */
void PathFollower::Invalidate( void )
{
	// extend
	Path::Invalidate();

	m_goal = NULL;

	m_avoidTimer.Invalidate();
	m_waitTimer.Invalidate();
	m_hindrance = NULL;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Invoked when the path is (re)computed (path is valid at the time of this call)
 */
void PathFollower::OnPathChanged( INextBot *bot, Path::ResultType result )
{
	// start from the beginning
	m_goal = FirstSegment();
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Adjust speed based on path curvature
 */
void PathFollower::AdjustSpeed( INextBot *bot )
{
	ILocomotion *mover = bot->GetLocomotionInterface();

	// if we're coming up on a gap jump, or we're in the air, use maximum speed
	if ( ( m_goal && m_goal->type == JUMP_OVER_GAP ) || !mover->IsOnGround() )
	{
		mover->SetDesiredSpeed( mover->GetRunSpeed() );
		return;
	}

	MoveCursorToClosestPosition( bot->GetPosition() );
	const Path::Data &data = GetCursorData();
	
	// speed based on curvature
	mover->SetDesiredSpeed( mover->GetRunSpeed() + fabs( data.curvature ) * ( mover->GetWalkSpeed() - mover->GetRunSpeed() ) );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if reached current goal along path
 * NOTE: Ladder goals are handled elsewhere
 */
bool PathFollower::IsAtGoal( INextBot *bot ) const
{
	VPROF_BUDGET( "PathFollower::IsAtGoal", "NextBot" );

	ILocomotion *mover = bot->GetLocomotionInterface();
	IBody *body = bot->GetBodyInterface();

	//
	// m_goal is the node we are moving toward along the path
	// current is the node just behind us
	//
	const Segment *current = PriorSegment( m_goal );
	Vector toGoal = m_goal->pos - mover->GetFeet();

// 	if ( m_goal->type == JUMP_OVER_GAP && !mover->IsOnGround() )
// 	{
// 		// jumping over a gap, don't skip ahead until we land
// 		return false;
// 	}

	if ( current == NULL )
	{
		// passed goal
		return true;
	}
	else if ( m_goal->type == DROP_DOWN )
	{
		// m_goal is the top of the drop-down, and the following segment is the landing point
		const Segment *landing = NextSegment( m_goal );

		if ( landing == NULL )
		{
			// passed goal or corrupt path
			return true;
		}		
		else
		{
			// did we reach the ground
			if ( mover->GetFeet().z - landing->pos.z < mover->GetStepHeight() )
			{
				// reached goal
				return true;
			}
		}
				
		/// @todo: it is possible to fall into a bad place and get stuck - should move back onto the path
		
	}
	else if ( m_goal->type == CLIMB_UP )
	{
		// once jump is started, assume it is successful, since
		// nav mesh may be substantially off from actual ground height at landing
		const Segment *landing = NextSegment( m_goal );

		if ( landing == NULL )
		{
			// passed goal or corrupt path
			return true;
		}		
		else if ( /*!mover->IsOnGround() && */ mover->GetFeet().z > m_goal->pos.z + mover->GetStepHeight() )
		{
			// we're off the ground, presumably climbing - assume we reached the goal
			return true;
		}
		/* This breaks infected climbing up holes in the ceiling - they can get within 2D range of m_goal before finding a ledge to climb up to
		else if ( mover->IsOnGround() )
		{
			// proximity check
			// Z delta can be anything, since we may be climbing over a tall fence, a physics prop, etc.
			const float rangeTolerance = 10.0f;
			if ( toGoal.AsVector2D().IsLengthLessThan( rangeTolerance ) )
			{
				// reached goal
				return true;
			}
		}
		*/
	}
	else
	{
		const Segment *next = NextSegment( m_goal );

		if ( next )
		{
			// because mover may be off the path, check if it crossed the plane of the goal
			// check against average of current and next forward vectors
			Vector2D dividingPlane;

			if ( current->ladder )
			{
				dividingPlane = m_goal->forward.AsVector2D();
			}
			else
			{
				dividingPlane = current->forward.AsVector2D() + m_goal->forward.AsVector2D();
			}

			if ( DotProduct2D( toGoal.AsVector2D(), dividingPlane ) < 0.0001f &&
				 abs( toGoal.z ) < body->GetStandHullHeight() )
			{	
				// only skip higher Z goal if next goal is directly reachable
				// can't use this for positions below us because we need to be able
				// to climb over random objects along our path that we can't actually
				// move *through*
				if ( toGoal.z < mover->GetStepHeight() && ( mover->IsPotentiallyTraversable( mover->GetFeet(), next->pos ) && !mover->HasPotentialGap( mover->GetFeet(), next->pos ) ) )
				{
					// passed goal
					return true;
				}
			}
		}

		// proximity check
		// Z delta can be anything, since we may be climbing over a tall fence, a physics prop, etc.
		if ( toGoal.AsVector2D().IsLengthLessThan( m_goalTolerance ) )
		{
			// reached goal
			return true;
		}
	}

	return false;	
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Move bot along ladder. Return true if ladder motion is in progress, false if complete.
 */
bool PathFollower::LadderUpdate( INextBot *bot )
{
	VPROF_BUDGET( "PathFollower::LadderUpdate", "NextBot" );

	ILocomotion *mover = bot->GetLocomotionInterface();
	IBody *body = bot->GetBodyInterface();
	
	if ( mover->IsUsingLadder() )
	{
		// wait for locomotor to finish traversing ladder
		return true;
	}

	if ( m_goal->ladder == NULL )
	{
		// Check if we have somehow ended up on a ladder, if so, and its a tall down-ladder we are expecting, jump the path ahead.
		// This happens for players, who run off ledges and the gamemovement sticks them onto ladders.  We only care about
		// tall down-ladders, because up ladders work without this, and short ladders aren't dangerous to miss and drop down
		// instead of climbing down.
		if ( bot->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
		{
			// 'current' is the segment we are on/just passed over
			const Segment *current = PriorSegment( m_goal );
			if ( current == NULL )
			{
				return false;
			}

			// Start with current, the segment we are currently traversing.  Skip the distance check for that segment, because
			// the pos is (hopefully) behind us.  And if it's a long path segment, it's already outside the climbLookAheadRange,
			// and thus it would prevent us looking at m_goal and further for imminent planned climbs.
			// 'current' is the segment we are on/just passed over
			const float ladderLookAheadRange = 50.0f;
			for( const Segment *s = current; s; s = NextSegment( s ) )
			{
				if ( s != current && ( s->pos - mover->GetFeet() ).AsVector2D().IsLengthGreaterThan( ladderLookAheadRange ) )
				{
					break;
				}

				// Only consider reasonably tall down ladders - if we don't grab onto a short ladder, it hopefully won't be a bad fall.
				if ( s->ladder != NULL && s->how == GO_LADDER_DOWN && s->ladder->m_length > mover->GetMaxJumpHeight() )
				{
					float destinationHeightDelta = s->pos.z - mover->GetFeet().z;
					if ( fabs(destinationHeightDelta) < mover->GetMaxJumpHeight() )
					{
						// Advance the goal, and fall through to the normal codepath.
						m_goal = s;
						break;
					}
				}
			}
		}

		if ( m_goal->ladder == NULL )
		{
			// no ladder to use
			return false;
		}
	}
	

	// start using the ladder
	const float mountRange = 25.0f;

	if ( m_goal->how == GO_LADDER_UP )
	{
		// check if we're off the ladder and at the top
		if ( !mover->IsUsingLadder() && mover->GetFeet().z > m_goal->ladder->m_top.z - mover->GetStepHeight() )
		{
			// we're up
			m_goal = NextSegment( m_goal );
			return false;
		}

		// approach the ladder
		Vector2D to = ( m_goal->ladder->m_bottom - mover->GetFeet() ).AsVector2D();

		body->AimHeadTowards( m_goal->ladder->m_top - 50.0f * m_goal->ladder->GetNormal() + Vector( 0, 0, body->GetCrouchHullHeight() ), 
							  IBody::CRITICAL, 
							  2.0f, 
							  NULL,
							  "Mounting upward ladder" );

		float range = to.NormalizeInPlace();
		if ( range < NextBotLadderAlignRange.GetFloat() )
		{
			// getting close - line up
			Vector2D ladderNormal2D = m_goal->ladder->GetNormal().AsVector2D();
			float dot = DotProduct2D( ladderNormal2D, to );

			const float cos5 = 0.9f;
			if ( dot < -cos5 )
			{
				// lined up - continue approach
				mover->Approach( m_goal->ladder->m_bottom );

				if ( range < mountRange )
				{
					// go up ladder
					mover->ClimbLadder( m_goal->ladder, m_goal->area );
				}				
			}
			else
			{
				// rotate around ladder and maintain distance from it
				Vector myPerp( -to.y, to.x, 0.0f );
				Vector2D ladderPerp2D( -ladderNormal2D.y, ladderNormal2D.x );

				Vector goal = m_goal->ladder->m_bottom;
				
				float alignRange = NextBotLadderAlignRange.GetFloat();
				
				if ( dot < 0.0f )
				{
					// we are on the correct side of the ladder
					// align range should drop off as we reach alignment
					alignRange = mountRange + (1.0f + dot) * (alignRange - mountRange);
				}
				
				goal.x -= alignRange * to.x;
				goal.y -= alignRange * to.y;				
				
				if ( DotProduct2D( to, ladderPerp2D ) < 0.0f )
				{
					goal += 10.0f * myPerp;
				}
				else
				{
					goal -= 10.0f * myPerp;
				}
				
				mover->Approach( goal );
			}
		}
		else
		{
			// approach the base of the ladder - use normal path following in case there are jumps/climbs on the way to the ladder
			return false;
		}
	}
	else	// go down ladder
	{
		// check if we fell off and are now below the ladder
		if ( mover->GetFeet().z < m_goal->ladder->m_bottom.z + mover->GetStepHeight() )
		{
			// we fell
			m_goal = NextSegment( m_goal );
		}
		else
		{
			// approach the ladder
			Vector mountPoint = m_goal->ladder->m_top + 0.5f * body->GetHullWidth() * m_goal->ladder->GetNormal();
			Vector2D to = ( mountPoint - mover->GetFeet() ).AsVector2D();

			if ( bot->IsDebugging( NEXTBOT_PATH ) )
			{
				const float size = 5.0f;	
				NDebugOverlay::Sphere( mountPoint, size, 255, 0, 255, true, 0.1f );			
			}

			body->AimHeadTowards( m_goal->ladder->m_bottom + 50.0f * m_goal->ladder->GetNormal() + Vector( 0, 0, body->GetCrouchHullHeight() ), 
								  IBody::CRITICAL, 
								  1.0f, 
								  NULL,
								  "Mounting downward ladder" );

			float range = to.NormalizeInPlace();

			// Approach the top of the ladder.  If we're already on the ladder, start descending.
			if ( range < mountRange || bot->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
			{
				// go down ladder
				mover->DescendLadder( m_goal->ladder, m_goal->area );

				// increment goal segment since locomotor will move us along the ladder
				m_goal = NextSegment( m_goal );
			}
			else
			{
				// approach the top of the ladder - use normal path following in case there are jumps/climbs on the way to the ladder
				return false;
			}
		}
	}

	return true;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Check if we have reached our current path goal and
 * iterate to next goal or finish the path
 */
bool PathFollower::CheckProgress( INextBot *bot )
{
	ILocomotion *mover = bot->GetLocomotionInterface();

	// skip nearby goal points that are redundant to smooth path following motion
	const Path::Segment *pSkipToGoal = NULL;
	if ( m_minLookAheadRange > 0.0f )
	{
		pSkipToGoal = m_goal;
		const Vector &myFeet = mover->GetFeet();
		while( pSkipToGoal && pSkipToGoal->type == ON_GROUND && mover->IsOnGround() )
		{
			if ( ( pSkipToGoal->pos - myFeet ).IsLengthLessThan( m_minLookAheadRange ) )
			{
				// goal is too close - step to next segment
				const Path::Segment *nextSegment = NextSegment( pSkipToGoal );

				if ( !nextSegment || nextSegment->type != ON_GROUND )
				{
					// can't skip ahead to next segment - head towards current goal
					break;
				}

				if ( nextSegment->pos.z > myFeet.z + mover->GetStepHeight() )
				{
					// going uphill or up stairs tends to cause problems if we skip ahead, so don't
					break;
				}

#ifdef DOTA_DLL
				if ( DotProduct( mover->GetMotionVector(), nextSegment->forward ) <= 0.1f )
				{
					// don't skip sharp turns
					break;
				}
#endif

				// can we reach the next path segment directly
				if ( mover->IsPotentiallyTraversable( myFeet, nextSegment->pos ) && !mover->HasPotentialGap( myFeet, nextSegment->pos ) )
				{
					pSkipToGoal = nextSegment;
				}
				else
				{
					// can't directly reach next segment - keep heading towards current goal
					break;
				}
			}
			else
			{
				// goal is farther than min lookahead
				break;
			}
		}

		// didn't find any goal to skip to
		if ( pSkipToGoal == m_goal )
		{
			pSkipToGoal = NULL;
		}
	}

	if ( IsAtGoal( bot ) )
	{
		// iterate to next segment of the path
		const Path::Segment *nextSegment = pSkipToGoal ? pSkipToGoal : NextSegment( m_goal );

		if ( nextSegment == NULL )
		{
			// must be on ground to complete the path
			if ( mover->IsOnGround() )
			{
				// the end of the path has been reached
				mover->GetBot()->OnMoveToSuccess( this );

				if ( bot->IsDebugging( NEXTBOT_PATH ) )
				{
					DevMsg( "PathFollower: OnMoveToSuccess\n" );
				}

				// don't invalidate if OnMoveToSuccess just recomputed a new path
				if ( GetAge() > 0.0f )
				{
					Invalidate();
				}

				return false;
			}
		}
		else
		{
			// keep moving
			m_goal = nextSegment;

			if ( bot->IsDebugging( NEXTBOT_PATH ) && !mover->IsPotentiallyTraversable( mover->GetFeet(), nextSegment->pos ) )
			{
				Warning( "PathFollower: path to my goal is blocked by something\n" );
				NDebugOverlay::Sphere( m_goal->pos, 5.f, 255, 0, 0, true, 3.f );
			}
		}
	}

	return true;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Move mover along path
 */
void PathFollower::Update( INextBot *bot )
{
	VPROF_BUDGET( "PathFollower::Update", "NextBotSpiky" );

	// track most recent path followed
	bot->SetCurrentPath( this );


	ILocomotion *mover = bot->GetLocomotionInterface();
	
	if ( !IsValid() || m_goal == NULL )
	{
		return;
	}

	if ( !m_waitTimer.IsElapsed() )
	{
		// still waiting
		//mover->ClearStuckStatus( "Waiting for blocker to move" );
		return;
	}

//	m_didAvoidCheck = false;


	if ( LadderUpdate( bot ) )
	{
		// we are traversing a ladder
		return;
	}


	// adjust speed based on path curvature
	AdjustSpeed( bot );

	if ( CheckProgress( bot ) == false )
	{
		// goal reached
		return;
	}

	// use the direction towards the goal as 'forward' direction
	Vector forward = m_goal->pos - mover->GetFeet();

	if ( m_goal->type == CLIMB_UP )
	{
		const Segment *next = NextSegment( m_goal );
		if ( next )
		{
			// use landing of climb up as forward to help ledge detection
			forward = next->pos - mover->GetFeet();
		}
	}

	forward.z = 0.0f;

	float goalRange = forward.NormalizeInPlace();

	Vector left( -forward.y, forward.x, 0.0f );

	if ( left.IsZero() )
	{
		// if left is zero, forward must also be - path follow failure
		mover->GetBot()->OnMoveToFailure( this, FAIL_STUCK );

		// don't invalidate if OnMoveToFailure just recomputed a new path
		if ( GetAge() > 0.0f )
		{
			Invalidate();
		}

		if ( bot->IsDebugging( NEXTBOT_PATH ) )
		{
			DevMsg( "PathFollower: OnMoveToFailure( FAIL_STUCK ) because forward and left are ZERO\n" );
		}

		return;
	}

	// unit vectors must follow floor slope
	const Vector &normal = mover->GetGroundNormal();

	// get forward vector along floor
	forward = CrossProduct( left, normal );

	// correct the sideways vector
	left = CrossProduct( normal, forward );

	if ( bot->IsDebugging( NEXTBOT_PATH ) )
	{
		float axisSize = 25.0f;
		NDebugOverlay::Line( mover->GetFeet(), mover->GetFeet() + axisSize * forward, 255, 0, 0, true, 0.1f );
		NDebugOverlay::Line( mover->GetFeet(), mover->GetFeet() + axisSize * normal, 0, 255, 0, true, 0.1f );
		NDebugOverlay::Line( mover->GetFeet(), mover->GetFeet() + axisSize * left, 0, 0, 255, true, 0.1f );
	}

	// climb up ledges
	if ( !Climbing( bot, m_goal, forward, left, goalRange ) )
	{
		// a failed climb could mean an invalid path
		if ( !IsValid() )
		{
			return;
		}

		// jump over gaps
		JumpOverGaps( bot, m_goal, forward, left, goalRange );
	}

	// event callbacks from the above climbs and jumps may invalidate the path
	if ( !IsValid() )
	{
		return;
	}
	
	// if our movement goal is high above us, we must have fallen
	CNavArea *myArea = bot->GetEntity()->GetLastKnownArea();
	bool isOnStairs = ( myArea && myArea->HasAttributes( NAV_MESH_STAIRS ) );

	// limit too high distance to reasonable value for bots that can climb very high
	float tooHighDistance = mover->GetMaxJumpHeight();

	if ( !m_goal->ladder && !mover->IsClimbingOrJumping() && !isOnStairs && m_goal->pos.z > mover->GetFeet().z + tooHighDistance )
	{
		const float closeRange = 25.0f; // 75.0f;
		Vector2D to( mover->GetFeet().x - m_goal->pos.x, mover->GetFeet().y - m_goal->pos.y );
		if ( mover->IsStuck() || to.IsLengthLessThan( closeRange ) )
		{
			// the goal is too high to reach

			// check if we can reach the next segment, in case this was a "jump down" situation
			const Path::Segment *next = NextSegment( m_goal );
			if ( mover->IsStuck() || !next || ( next->pos.z - mover->GetFeet().z > mover->GetMaxJumpHeight() ) || !mover->IsPotentiallyTraversable( mover->GetFeet(), next->pos ) )
			{
				// the next node is too high, too - we really did fall off the path
				mover->GetBot()->OnMoveToFailure( this, FAIL_FELL_OFF );

				// don't invalidate if OnMoveToFailure just recomputed a new path
				if ( GetAge() > 0.0f )
				{
					Invalidate();
				}

				if ( bot->IsDebugging( NEXTBOT_PATH ) )
				{
					DevMsg( "PathFollower: OnMoveToFailure( FAIL_FELL_OFF )\n" );
				}

				// reset stuck status since we're (likely) repathing anyways. otherwise, we could be stuck in a loop here and not move
				mover->ClearStuckStatus( "Fell off path" );

				return;
			}
		}
	}


	Vector goalPos = m_goal->pos;

	// avoid small obstacles
	forward = goalPos - mover->GetFeet();
	forward.z = 0.0f;
	float rangeToGoal = forward.NormalizeInPlace();

	left.x = -forward.y;
	left.y = forward.x;
	left.z = 0.0f;

	if ( true || m_goal != LastSegment() )	// think more about this - we often need to avoid to reach the final goal pos, too (MSB 5/15/09)
	{
		const float nearLedgeRange = 50.0f;
		if ( rangeToGoal > nearLedgeRange || ( m_goal && m_goal->type != CLIMB_UP ) )
		{
			goalPos = Avoid( bot, goalPos, forward, left );
		}
	}

	// face towards movement goal
	if ( mover->IsOnGround() )
	{	
		mover->FaceTowards( goalPos );
	}

	// move bot along path
	mover->Approach( goalPos );

	// Currently, Approach determines STAND or CROUCH. 
	// Override this if we're approaching a climb or a jump
	if ( m_goal && ( m_goal->type == CLIMB_UP || m_goal->type == JUMP_OVER_GAP ) )
	{
		bot->GetBodyInterface()->SetDesiredPosture( IBody::STAND );
	}

	if ( bot->IsDebugging( NEXTBOT_PATH ) )
	{
		const Segment *start = GetCurrentGoal();
		if ( start )
		{
			start = PriorSegment( start );
		}

		Draw( start );

		/*
		else
		{
		DrawInterpolated( 0.0f, GetLength() );
		}
		*/

		NDebugOverlay::Cross3D( goalPos, 5.0f, 150, 150, 255, true, 0.1f );
		NDebugOverlay::Line( bot->GetEntity()->WorldSpaceCenter(), goalPos, 255, 255, 0, true, 0.1f );
	}
}


//--------------------------------------------------------------------------------------------------------------
/**
 * If entity is returned, it is blocking us from continuing along our path
 */
CBaseEntity *PathFollower::FindBlocker( INextBot *bot )
{
	IIntention *think = bot->GetIntentionInterface();

	// if we don't care about hindrances, don't do the expensive tests
	if ( think->IsHindrance( bot, IS_ANY_HINDRANCE_POSSIBLE ) != ANSWER_YES )
		return NULL;

	ILocomotion *mover = bot->GetLocomotionInterface();
	IBody *body = bot->GetBodyInterface();

	trace_t result;
	NextBotTraceFilterOnlyActors filter( bot->GetEntity(), COLLISION_GROUP_NONE );

	const float size = body->GetHullWidth()/4.0f;	// keep this small to avoid lockups when groups of bots get close
	Vector blockerMins( -size, -size, mover->GetStepHeight() );
	Vector blockerMaxs( size, size, body->GetCrouchHullHeight() );

	Vector from = mover->GetFeet();
	float range = 0.0f;

	const float maxHindranceRangeAlong = 750.0f;

	// because our path goal may be far ahead of us if the way to there is unobstructed, we
	// need to start looking from the point of the path we are actually standing on
	MoveCursorToClosestPosition( mover->GetFeet() );

	for( const Segment *s = GetCursorData().segmentPrior; s && range < maxHindranceRangeAlong; s = NextSegment( s ) )
	{
		// trace along direction toward goal a minimum range, in case goal and hindrance are
		// very close, but goal is closer

		Vector traceForward = s->pos - from;
		float traceRange = traceForward.NormalizeInPlace();

		const float minTraceRange = 2.0f * body->GetHullWidth();
		if ( traceRange < minTraceRange )
		{
			traceRange = minTraceRange;
		}

		mover->TraceHull( from, from + traceRange * traceForward, blockerMins, blockerMaxs, body->GetSolidMask(), &filter, &result );

		if ( result.DidHitNonWorldEntity() )
		{
			// if blocker is close, they could be behind us - check
			Vector toBlocker = result.m_pEnt->GetAbsOrigin() - bot->GetLocomotionInterface()->GetFeet();

			Vector alongPath = s->pos - from;
			alongPath.z = 0.0f;

			if ( DotProduct( toBlocker, alongPath ) > 0.0f )
			{
				// ask the bot if this really is a hindrance
				if ( think->IsHindrance( bot, result.m_pEnt ) == ANSWER_YES )
				{
					if ( bot->IsDebugging( NEXTBOT_PATH ) )
					{
						NDebugOverlay::Circle( bot->GetLocomotionInterface()->GetFeet(), QAngle( -90.0f, 0, 0 ), 10.0f, 255, 0, 0, 255, true, 1.0f );
						NDebugOverlay::HorzArrow( bot->GetLocomotionInterface()->GetFeet(), result.m_pEnt->GetAbsOrigin(), 1.0f, 255, 0, 0, 255, true, 1.0f );
					}

					// we are blocked
					return result.m_pEnt;
				}
			}
		}

		from = s->pos;
		range += s->length;
	}

	return NULL;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Do reflex avoidance movements of very nearby obstacles.
 * Return adjusted goal.
 */
Vector PathFollower::Avoid( INextBot *bot, const Vector &goalPos, const Vector &forward, const Vector &left )
{
	VPROF_BUDGET( "PathFollower::Avoid", "NextBotExpensive" );

	if ( !NextBotAllowAvoiding.GetBool() )
	{
		return goalPos;
	}

	if ( !m_avoidTimer.IsElapsed() )
	{
		return goalPos;
	}
	
	// low frequency check until we actually hit something we need to avoid
	const float avoidInterval = 0.5f; // 1.0f;
	m_avoidTimer.Start( avoidInterval );

	ILocomotion *mover = bot->GetLocomotionInterface();

	if ( mover->IsClimbingOrJumping() || !mover->IsOnGround() )
	{
		return goalPos;
	}

	//
	// Check for potential blockers along our path and wait if we're blocked
	//
	m_hindrance = FindBlocker( bot );
	if ( m_hindrance != NULL )
	{
		// wait 
		m_waitTimer.Start( avoidInterval * RandomFloat( 1.0f, 2.0f ) );

		return mover->GetFeet();
	}


	// if we are in a "precise" area, do not use avoid volumes
	CNavArea *area = bot->GetEntity()->GetLastKnownArea();
	if ( area && ( area->GetAttributes() & NAV_MESH_PRECISE ) )
	{
		return goalPos;
	}

	m_didAvoidCheck = true;

	// we want to avoid other players, etc	
	trace_t result;
	NextBotTraceFilterOnlyActors filter( bot->GetEntity(), COLLISION_GROUP_NONE );

	IBody *body = bot->GetBodyInterface();
	unsigned int mask = body->GetSolidMask();

	const float size = body->GetHullWidth()/4.0f;
	const float offset = size + 2.0f;

	float range = mover->IsRunning() ? 50.0f : 30.0f;
	range *= bot->GetEntity()->GetModelScale();

	m_hullMin = Vector( -size, -size, mover->GetStepHeight()+0.1f );
	
	// only use crouch-high avoid volumes, since we'll just crouch if higher obstacles are near
	m_hullMax = Vector( size, size, body->GetCrouchHullHeight() );
	
	Vector nextStepHullMin( -size, -size, 2.0f * mover->GetStepHeight() + 0.1f );

	// avoid any open doors in our way
	CBasePropDoor *door = NULL;

	// check left side
	m_leftFrom = mover->GetFeet() + offset * left;
	m_leftTo = m_leftFrom + range * forward;

	m_isLeftClear = true;
	float leftAvoid = 0.0f;

	NextBotTraversableTraceFilter traverseFilter( bot );
	mover->TraceHull( m_leftFrom, m_leftTo, m_hullMin, m_hullMax, mask, &traverseFilter, &result );
	if ( result.fraction < 1.0f || result.startsolid )
	{
		// if this sensor is starting in a solid, set fraction to emulate being against a wall
		if ( result.startsolid )
		{
			result.fraction = 0.0f;
		}

		leftAvoid = clamp( 1.0f - result.fraction, 0.0f, 1.0f );

		m_isLeftClear = false;

		// track any doors we need to avoid
		if ( result.DidHitNonWorldEntity() )
		{
			door = dynamic_cast< CBasePropDoor * >( result.m_pEnt );
		}

		// check for steps
// 		float firstHit = result.fraction;
// 		mover->TraceHull( m_leftFrom, m_leftTo, nextStepHullMin, m_hullMax, mask, &filter, &result );
// 		if ( result.fraction <= firstHit ) //+ mover->GetStepHeight()/2.0f )
// 		{
// 			// it's not a step - we hit something
// 			m_isLeftClear = false;
// 		}
	}

	// check right side
	m_rightFrom = mover->GetFeet() - offset * left;
	m_rightTo = m_rightFrom + range * forward;

	m_isRightClear = true;
	float rightAvoid = 0.0f;

	mover->TraceHull( m_rightFrom, m_rightTo, m_hullMin, m_hullMax, mask, &traverseFilter, &result );
	if ( result.fraction < 1.0f || result.startsolid )
	{
		// if this sensor is starting in a solid, set fraction to emulate being against a wall
		if ( result.startsolid )
		{
			result.fraction = 0.0f;
		}

		rightAvoid = clamp( 1.0f - result.fraction, 0.0f, 1.0f );

		m_isRightClear = false;

		// track any doors we need to avoid
		if ( !door && result.DidHitNonWorldEntity() )
		{
			door = dynamic_cast< CBasePropDoor * >( result.m_pEnt );
		}

		// check for steps
// 		float firstHit = result.fraction;
// 		mover->TraceHull( m_rightFrom, m_rightTo, nextStepHullMin, m_hullMax, mask, &filter, &result );
// 		if ( result.fraction <= firstHit ) // + mover->GetStepHeight()/2.0f)
// 		{
// 			// it's not a step - we hit something
// 			m_isRightClear = false;
// 		}
	}

	Vector adjustedGoal = goalPos;

	// avoid doors directly in our way
	if ( door && !m_isLeftClear && !m_isRightClear )
	{
		Vector forward, right, up;
		AngleVectors( door->GetAbsAngles(), &forward, &right, &up );

		const float doorWidth = 100.0f;
		Vector doorEdge = door->GetAbsOrigin() - doorWidth * right;

		if ( bot->IsDebugging( NEXTBOT_PATH ) )
		{
			NDebugOverlay::Axis( door->GetAbsOrigin(), door->GetAbsAngles(), 20.0f, true, 10.0f );
			NDebugOverlay::Line( door->GetAbsOrigin(), doorEdge, 255, 255, 0, true, 10.0f );
		}

		// move around door
		adjustedGoal.x = doorEdge.x;
		adjustedGoal.y = doorEdge.y;

		// do avoid check again next frame
		m_avoidTimer.Invalidate();
	}
	else if ( !m_isLeftClear || !m_isRightClear )
	{
		// adjust goal to avoid small obstacle
		float avoidResult = 0.0f;
		if ( m_isLeftClear )
		{
			avoidResult = -rightAvoid;
		}
		else if (m_isRightClear)
		{
			avoidResult = leftAvoid;
		}
		else
		{
			// both left and right are blocked, avoid nearest
			const float equalTolerance = 0.01f;
			if ( fabs( rightAvoid - leftAvoid ) < equalTolerance )
			{
				// squarely against a wall, etc
				return adjustedGoal;
			} 
			else if ( rightAvoid > leftAvoid )
			{
				avoidResult = -rightAvoid;
			}
			else
			{
				avoidResult = leftAvoid;
			}
		}
		
		// adjust goal to avoid obstacle
		Vector avoidDir = 0.5f * forward - left * avoidResult;
		avoidDir.NormalizeInPlace();
		
		adjustedGoal = mover->GetFeet() + 100.0f * avoidDir;
		
		// do avoid check again next frame
		m_avoidTimer.Invalidate();
	}
	
	return adjustedGoal;
}


#ifdef EXPERIMENTAL_LEDGE_FINDER
//--------------------------------------------------------------------------------------------------------------
/**
 * Given a hull that defines the area of space that may contain a climbable ledge,
 * subdivide it until we find the ledge.
 */
bool PathFollower::FindClimbLedge( INextBot *bot, Vector startTracePos, Vector ledgeRegionMins, Vector ledgeRegionMaxs )
{
	float deltaZ = ledgeRegionMaxs.z - ledgeRegionMins.z;

	if ( deltaZ <= bot->GetLocomotionInterface()->GetStepHeight() )
	{
		// reached minimum subdivision limit - stop
		return false;
	}

	trace_t result;
	NextBotTraversableTraceFilter filter( bot, ILocomotion::IMMEDIATELY );

	mover->TraceHull( startTracePos, startTracePos,
					ledgeRegionMins, ledgeRegionMaxs, 
					bot->GetBodyInterface()->GetSolidMask(), &filter, &result );


	if ( result.DidHit() )
	{
		// volume is blocked - split into upper and lower volumes and try again
		float midZ = ( ledgeRegionMins.z + ledgeRegionMaxs.z ) / 2.0f;

		Vector upperLedgeRegionMins( ledgeRegionMins.x, ledgeRegionMins.y, midZ );
		Vector upperLedgeRegionMaxs = ledgeRegionMaxs;
		FindClimbLedge( bot, startTracePos, upperLedgeRegionMins, upperLedgeRegionMaxs );


		Vector lowerLedgeRegionMins = ledgeRegionMins;
		Vector lowerLedgeRegionMaxs( ledgeRegionMaxs.x, ledgeRegionMaxs.y, midZ );
		FindClimbLedge( bot, startTracePos, lowerLedgeRegionMins, lowerLedgeRegionMaxs );
	}
	else
	{
		// volume is clear, trace straight down to find ledge and keep lowest one we've found
		mover->TraceHull( startTracePos,
						startTracePos + Vector( 0, 0, -100.0f ),
						ledgeRegionMins, ledgeRegionMaxs, 
						bot->GetBodyInterface()->GetSolidMask(), &filter, &result );	
	}
}
#endif // _DEBUG


//--------------------------------------------------------------------------------------------------------------
/**
 * Climb up ledges
 */
bool PathFollower::Climbing( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &right, float goalRange )
{
	VPROF_BUDGET( "PathFollower::Climbing", "NextBot" );

	ILocomotion *mover = bot->GetLocomotionInterface();
	IBody *body = bot->GetBodyInterface();
	CNavArea *myArea = bot->GetEntity()->GetLastKnownArea();

	if ( !mover->IsAbleToClimb() || !NextBotAllowClimbing.GetBool() )
	{
		return false;
	}

	// use the 2D direction towards our goal
	Vector climbDirection = forward;
	climbDirection.z = 0.0f;
	climbDirection.NormalizeInPlace();

	// we can't have this as large as our hull width, or we'll find ledges ahead of us
	// that we will fall from when we climb up because our hull wont actually touch at the top.
	const float ledgeLookAheadRange = body->GetHullWidth() - 1;

	if ( mover->IsClimbingOrJumping() || mover->IsAscendingOrDescendingLadder() || !mover->IsOnGround() )
	{
		return false;
	}

	// can be in any posture when we climb

	if ( m_goal == NULL )
	{
		return false;
	}

	if ( TheNavMesh->IsAuthoritative() )
	{
		//
		// Trust what that nav mesh tells us.
		// No need for expensive ledge-finding for games with simpler geometry (like TF2)
		//
		if ( m_goal->type == CLIMB_UP )
		{
			const Segment *afterClimb = NextSegment( m_goal );
			if ( afterClimb && afterClimb->area )
			{
				// find closest point on climb-destination area
				Vector nearClimbGoal;
				afterClimb->area->GetClosestPointOnArea( mover->GetFeet(), &nearClimbGoal );

				climbDirection = nearClimbGoal - mover->GetFeet();
				climbDirection.z = 0.0f;
				climbDirection.NormalizeInPlace();

				if ( mover->ClimbUpToLedge( nearClimbGoal, climbDirection, NULL ) )
					return true;
			}
		}

		return false;
	}


	// If we're approaching a CLIMB_UP link, save off the height delta for it, and trust the nav *just* enough
	// to climb up to that ledge and only that ledge.  We keep as large a tolerance as possible, to trust
	// the nav as little as possible.  There's no valid way to have another CLIMB_UP link within crouch height,
	// because we can't actually fit in between the two areas, so one climb is invalid.
	float climbUpLedgeHeightDelta = -1.0f;
	const float climbUpLedgeTolerance = body->GetCrouchHullHeight();

	if ( m_goal->type == CLIMB_UP )
	{
		const Segment *afterClimb = NextSegment( m_goal );
		if ( afterClimb && afterClimb->area )
		{
			// find closest point on climb-destination area
			Vector nearClimbGoal;
			afterClimb->area->GetClosestPointOnArea( mover->GetFeet(), &nearClimbGoal );

			climbDirection = nearClimbGoal - mover->GetFeet();
			climbUpLedgeHeightDelta = climbDirection.z;
			climbDirection.z = 0.0f;
			climbDirection.NormalizeInPlace();
		}
	}

	// don't try to climb up stairs
	if ( m_goal->area->HasAttributes( NAV_MESH_STAIRS ) || ( myArea && myArea->HasAttributes( NAV_MESH_STAIRS ) ) )
	{
		if ( bot->IsDebugging( NEXTBOT_PATH ) )
		{
			NDebugOverlay::Cross3D( mover->GetFeet(), 5.0f, 0, 255, 255, true, 5.0f );
			DevMsg( "%3.2f: %s ON STAIRS\n", gpGlobals->curtime, bot->GetDebugIdentifier() );
		}
		return false;
	}

	// 'current' is the segment we are on/just passed over
	const Segment *current = PriorSegment( m_goal );
	if ( current == NULL )
	{
		return false;
	}

	// If path segment immediately ahead of us is not obstructed, don't try to climb.
	// This is required to try to avoid accidentally climbing onto valid high ledges when we really want to run UNDER them to our destination.
	// We need to check "immediate" traversability to pay attention to breakable objects in our way that we should climb over.
	// We also need to check traversability out to 2 * ledgeLookAheadRange in case our goal is just before a tricky ledge climb and once we pass the goal it will be too late.
	// When we're in a CLIMB_UP segment, allow us to look for ledges - we know the destination ledge height, and will only grab the correct ledge.
	Vector toGoal = m_goal->pos - mover->GetFeet();
	toGoal.NormalizeInPlace();

	if ( toGoal.z < mover->GetTraversableSlopeLimit() &&
		 !mover->IsStuck() && m_goal->type != CLIMB_UP &&
		 mover->IsPotentiallyTraversable( mover->GetFeet(), mover->GetFeet() + 2.0f * ledgeLookAheadRange * toGoal, ILocomotion::IMMEDIATELY ) )
	{
		return false;
	}


	// can't do this - we have to find the ledge to deal with breakable railings
#if 0
	// If our path requires a climb, do the climb.
	// This solves some issues where there are several possible climbable ledges at a given
	// location, and we need to know which ledge to climb - just use the preplanned path's choice.
	const Segment *ledge = NextSegment( m_goal );
	if ( m_goal->type == CLIMB_UP && ledge )
	{
		const float startClimbRange = body->GetHullWidth();
		if ( ( m_goal->pos - mover->GetFeet() ).IsLengthLessThan( startClimbRange ) )
		{
			mover->ClimbUpToLedge( ledge->pos, climbDirection );
			return true;
		}
	}
#endif 


	
	// Determine if we're approaching a planned climb.
	// Start with current, the segment we are currently traversing.  Skip the distance check for that segment, because
	// the pos is (hopefully) behind us.  And if it's a long path segment, it's already outside the climbLookAheadRange,
	// and thus it would prevent us looking at m_goal and further for imminent planned climbs.
	const float climbLookAheadRange = 150.0f;
	bool isPlannedClimbImminent = false;
	float plannedClimbZ = 0.0f;
	for( const Segment *s = current; s; s = NextSegment( s ) )
	{
		if ( s != current && ( s->pos - mover->GetFeet() ).AsVector2D().IsLengthGreaterThan( climbLookAheadRange ) )
		{
			break;
		}

		if ( s->type == CLIMB_UP )
		{
			isPlannedClimbImminent = true;

			const Segment *next = NextSegment( s );
			if ( next )
			{
				plannedClimbZ = next->pos.z;
			}
			break;
		}
	}

	unsigned int mask = body->GetSolidMask();
	trace_t result;
	NextBotTraversableTraceFilter filter( bot, ILocomotion::IMMEDIATELY );

	const float hullWidth = body->GetHullWidth();
	const float halfSize = hullWidth / 2.0f;
	const float minHullHeight = body->GetCrouchHullHeight();
	const float minLedgeHeight = mover->GetStepHeight() + 0.1f;

	Vector skipStepHeightHullMin( -halfSize, -halfSize, minLedgeHeight );

	// need to use minimum actual hull height here to catch porous fences and railings
	Vector skipStepHeightHullMax( halfSize, halfSize, minHullHeight + 0.1f );


	// Find the highest height we can stand at our current location.
	// Using the full width hull catches on small lips/ledges, so back up and try again.
	float ceilingFraction;

	// We can't use IsPotentiallyTraversable to test for ledges, because it's smaller Hull can cause the
	// next trace (trace the ceiling height forward) to start solid.
	//	mover->IsPotentiallyTraversable( mover->GetFeet(), mover->GetFeet() + Vector( 0, 0, mover->GetMaxJumpHeight() ), ILocomotion::IMMEDIATELY, &ceilingFraction );

	// Instead of IsPotentiallyTraversable, we back up the same distance and use a second upward trace
	// to see if that one finds a higher ceiling.  If so, we use that ceiling height, and use the
	// backed-up feet position for the ledge finding traces.
	Vector feet( mover->GetFeet() );
	Vector ceiling( feet + Vector( 0, 0, mover->GetMaxJumpHeight() ) );
	mover->TraceHull( feet, ceiling,
		skipStepHeightHullMin, skipStepHeightHullMax, mask, &filter, &result );
	ceilingFraction = result.fraction;
	bool isBackupTraceUsed = false;
	if ( ceilingFraction < 1.0f || result.startsolid )
	{
		trace_t backupTrace;
		const float backupDistance = hullWidth * 0.25f;	// The IsPotentiallyTraversable check this replaces uses a 1/4 hull width trace
		Vector backupFeet( feet - climbDirection * backupDistance );
		Vector backupCeiling( backupFeet + Vector( 0, 0, mover->GetMaxJumpHeight() ) );
		mover->TraceHull( backupFeet, backupCeiling,
			skipStepHeightHullMin, skipStepHeightHullMax, mask, &filter, &backupTrace );
		if ( !backupTrace.startsolid && backupTrace.fraction > ceilingFraction )
		{
			bot->DebugConColorMsg( NEXTBOT_PATH, Color( 255, 255, 255, 255 ), "%s backing up when looking for max ledge height\n", bot->GetDebugIdentifier() );
			result = backupTrace;
			ceilingFraction = result.fraction;
			feet = backupFeet;
			ceiling = backupCeiling;
			isBackupTraceUsed = true;
		}
	}

	float maxLedgeHeight = ceilingFraction * mover->GetMaxJumpHeight();

	if ( maxLedgeHeight <= mover->GetStepHeight() )
	{
		return false; // early out when we can't even climb StepHeight.
	}

	//
	// Check for ledge climbs over things in our way.
	// Even if we have a CLIMB_UP link in our path, we still need
	// to find the actual ledge by tracing the local geometry.
	//
	Vector climbHullMax( halfSize, halfSize, maxLedgeHeight );

	Vector ledgePos = feet;	// to be computed below

	mover->TraceHull( feet, 
					  feet + climbDirection * ledgeLookAheadRange, 
					  skipStepHeightHullMin, climbHullMax, mask, &filter, &result );

	if ( bot->IsDebugging( NEXTBOT_PATH ) && NextBotDebugClimbing.GetBool() )
	{
		// show ledge-finding hull as we move
		NDebugOverlay::SweptBox( feet, 
								 feet + climbDirection * ledgeLookAheadRange, 
								 skipStepHeightHullMin, climbHullMax, vec3_angle,
								 255, 100, 0, 255, 0.1f );
	}

	bool wasPotentialLedgeFound = result.DidHit() && !result.startsolid;
	// To test climbing up past small lips on walls, we can force the bot to run past the overhang and use the backup trace:
	// wasPotentialLedgeFound = wasPotentialLedgeFound && (result.fraction == 0 || isBackupTraceUsed);
	if ( wasPotentialLedgeFound )
	{
		VPROF_BUDGET( "PathFollower::Climbing( Search for ledge to climb )", "NextBot" );

		if ( bot->IsDebugging( NEXTBOT_PATH ) && NextBotDebugClimbing.GetBool() )
		{
			// show ledge-finding hull that found a ledge candidate 
			NDebugOverlay::SweptBox( feet, 
									 feet + climbDirection * ledgeLookAheadRange, 
									 skipStepHeightHullMin, climbHullMax, vec3_angle,
									 255, 100, 0, 100, 999.9f );

			// show primary climb direction
			NDebugOverlay::HorzArrow( feet, feet + 50.0f * climbDirection, 2.0f, 0, 255, 0, 255, true, 9999.9f );
		}

		// what are we climbing over?
		CBaseEntity *obstacle = result.m_pEnt;

		if ( !result.DidHitNonWorldEntity() || bot->IsAbleToClimbOnto( obstacle ) )
		{			
			if ( bot->IsDebugging( NEXTBOT_PATH ) )
			{
				DevMsg( "%3.2f: %s at potential ledge climb\n", gpGlobals->curtime, bot->GetDebugIdentifier() );
			}

			// the low hull sweep hit an obstacle - note how 'far in' this is
			float ledgeFrontWallDepth = ledgeLookAheadRange * result.fraction;

			float minLedgeDepth = body->GetHullWidth()/2.0f; // 5.0f;
			if ( m_goal->type == CLIMB_UP )
			{
				// Climbing up to a narrow nav area indicates a narrow ledge.  We need to reduce our minLedgeDepth
				// here or our path will say we should climb but we'll forever fail to find a wide enough ledge.
				const Segment *afterClimb = NextSegment( m_goal );
				if ( afterClimb && afterClimb->area )
				{
					Vector depthVector = climbDirection * minLedgeDepth;
					depthVector.z = 0;
					if ( fabs( depthVector.x ) > afterClimb->area->GetSizeX() )
					{
						depthVector.x = (depthVector.x > 0) ? afterClimb->area->GetSizeX() : -afterClimb->area->GetSizeX();
					}
					if ( fabs( depthVector.y ) > afterClimb->area->GetSizeY() )
					{
						depthVector.y = (depthVector.y > 0) ? afterClimb->area->GetSizeY() : -afterClimb->area->GetSizeY();
					}

					float areaDepth = depthVector.NormalizeInPlace();
					minLedgeDepth = MIN( minLedgeDepth, areaDepth );
				}
			}

			//
			// Find the ledge.  Start at the lowest jump we can make
			// and step up until we find the actual ledge.  
			//
			// The scan is limited to maxLedgeHeight in case our max 
			// jump/climb height is so tall the highest horizontal hull 
			// trace could be on the other side of the ceiling above us
			//

			float ledgeHeight = minLedgeHeight;
			const float ledgeHeightIncrement = 0.5f * mover->GetStepHeight();

			bool foundWall = false;
			bool foundLedge = false;
			
			// once we have found the ledge's front wall, we must look at least minLedgeDepth farther in to verify it is a ledge
			// NOTE: This *must* be ledgeLookAheadRange since ledges are compared against the initial trace which was ledgeLookAheadRange deep
			float ledgeTopLookAheadRange = ledgeLookAheadRange;

			// TODO: we also must look far enough ahead in case the ledge we actually find is "deeper" than the initial wall at the base

			Vector climbHullMin( -halfSize, -halfSize, 0.0f );
			Vector climbHullMax( halfSize, halfSize, minHullHeight );

			Vector wallPos;
			float wallDepth = 0.0f;

			bool isLastIteration = false;
			while( true )
			{				
				// trace forward to find the wall in front of us, or the empty space of the ledge above us
				mover->TraceHull( feet + Vector( 0, 0, ledgeHeight ), 
								feet + Vector( 0, 0, ledgeHeight ) + climbDirection * ledgeTopLookAheadRange, 
								climbHullMin, climbHullMax, mask, &filter, &result );

				float traceDepth = ledgeTopLookAheadRange * result.fraction;

				if ( !result.startsolid )
				{
					// if trace reached minLedgeDepth farther, this is a potential ledge
					if ( foundWall )
					{
						// TODO: test that potential ledge is flat enough to stand on
						if ( ( traceDepth - ledgeFrontWallDepth ) > minLedgeDepth )
						{
							bool isUsable = true;

							// initialize ledgePos from result of last trace
							ledgePos = result.endpos;

							// Find the actual ground level on the potential ledge
							// Only trace back down to the previous ledge height trace. 
							// The ledge can be no lower, or we would've found it in the last iteration.
							mover->TraceHull( ledgePos,
											ledgePos + Vector( 0, 0, -ledgeHeightIncrement ),
											climbHullMin, climbHullMax, mask, &filter, &result );

							ledgePos = result.endpos;

							// if the whole trace is in solid, we're out of luck, but
							// if the trace just started solid, 'ledgePos' should still be valid
							// since the trace left the solid and then hit.
							// if the trace hit nothing, the potential ledge is actually deeper in
							const float MinGroundNormal = 0.7f;	// players can't stand on ground steeper than 0.7
							if ( result.allsolid || !result.DidHit() || result.plane.normal.z < MinGroundNormal )
							{
								// not a usable ledge, try again
								isUsable = false;
							}
							else
							{
								if ( climbUpLedgeHeightDelta > 0.0f )
								{
									// if we're climbing to a specific ledge via a CLIMB_UP link, only climb to that ledge.
									// Do this only for the world (which includes static props) so we can still opportunistically
									// climb up onto breakable railings and physics props.
									if ( result.DidHitWorld() )
									{
										float potentialLedgeHeight = result.endpos.z - feet.z;
										if ( fabs(potentialLedgeHeight - climbUpLedgeHeightDelta) > climbUpLedgeTolerance )
										{
											isUsable = false;
										}
									}
								}
							}

							if ( isUsable )
							{
								// back up until we no longer are hitting the ledge to determine the
								// exact ledge edge position
								Vector validLedgePos = ledgePos; // save off a valid ledge pos
								const float edgeTolerance = 4.0f;
								const float maxBackUp = hullWidth;
								float backUpSoFar = edgeTolerance;
								Vector testPos = ledgePos;

								while( backUpSoFar < maxBackUp )
								{
									testPos -= edgeTolerance * climbDirection;
									backUpSoFar += edgeTolerance;

									mover->TraceHull( testPos,
													testPos + Vector( 0, 0, -ledgeHeightIncrement ),
													climbHullMin, climbHullMax, mask, &filter, &result );

									
									if ( bot->IsDebugging( NEXTBOT_PATH ) && NextBotDebugClimbing.GetBool() )
									{
										// show edge-finder hulls
										NDebugOverlay::SweptBox( testPos,
																 testPos + Vector( 0, 0, -mover->GetStepHeight() ), 
																 climbHullMin, climbHullMax, vec3_angle, 255, 0, 0, 255, 999.9f );
									}

									if ( result.DidHit() && result.plane.normal.z >= MinGroundNormal )
									{
										// we hit, this is closer to the actual ledge edge
										ledgePos = result.endpos;
									}
									else
									{
										// nothing but air or a steep slope below us, we have found the edge
										break;
									}
								}

								// we want ledgePos to be right on the edge itself, so move 
								// it ahead by half of the hull width
								ledgePos += climbDirection * halfSize;

								// Make sure this doesn't embed us in the far wall if the ledge is narrow, since we would
								// have backed up less than halfSize.
								Vector climbHullMinStep( climbHullMin ); // skip StepHeight for sloped ledges
								mover->TraceHull( validLedgePos,
									ledgePos,
									climbHullMinStep, climbHullMax, mask, &filter, &result );

								ledgePos = result.endpos;

								// Now since ledgePos + StepHeight is valid, trace down to find ground on sloped ledges.
								mover->TraceHull( ledgePos + Vector( 0, 0, StepHeight ),
									ledgePos,
									climbHullMin, climbHullMax, mask, &filter, &result );
								if ( !result.startsolid )
								{
									ledgePos = result.endpos;
								}
							}


/*** NOTE: While this saves us from climbing into a window below the window we want to get in,
 *** it also causes us to climb in midair high over crates sitting against walls we need to climb over.
							if ( isUsable && m_goal->type == CLIMB_UP )
							{
								// we can only accept ledges at least as high as our current CLIMB_UP destination
								// NOTE: Can't use plannedClimbZ here, since that could be 2 or 3 short climbs ahead
								const Segment *ledge = NextSegment( m_goal );

								if ( !ledge || ledgeHeight < ledge->pos.z - feet.z - mover->GetStepHeight() )
								{
									// this ledge is below the CLIMB_UP destination - can't use it
									isUsable = false;
								}
							}
*/


							if ( isUsable )
							{
								// found a usable ledge here
								foundLedge = true;
								break;
							}
						}
					}
					else if ( result.DidHit() )
					{
						// this iteration hit the wall under the ledge, 
						// meaning the next iteration that reaches far enough will be our ledge

						// Since we know that our desired route is likely blocked (via the 
						// IsTraversable check above) - any ledge we hit we must climb.

						// found a valid ledge wall
						foundWall = true;
						wallDepth = traceDepth;

						// make sure the subsequent traces are at least minLedgeDepth deeper than
						// the wall we just found, or all ledge checks will fail
						float minTraceDepth = traceDepth + minLedgeDepth + 0.1f;

						if ( ledgeTopLookAheadRange < minTraceDepth )
						{
							ledgeTopLookAheadRange = minTraceDepth;
						}

						if ( bot->IsDebugging( NEXTBOT_PATH ) )
						{
							DevMsg( "%3.2f: Climbing - found wall.\n", gpGlobals->curtime );
							if ( NextBotDebugClimbing.GetBool() )
							{
								NDebugOverlay::HorzArrow( result.endpos, result.endpos + 20.0f * result.plane.normal, 5.0f, 255, 100, 0, 255, true, 9999.9f );
							}
							wallPos = result.endpos;
						}
					}
					else if ( ledgeHeight > body->GetCrouchHullHeight() && !isPlannedClimbImminent )
					{
						// we haven't hit anything yet, and we're already above our heads - no obstacle
						if ( bot->IsDebugging( NEXTBOT_PATH ) )
						{
							DevMsg( "%3.2f: Climbing - skipping overhead climb we can walk/crawl under.\n", gpGlobals->curtime );
						}
						break;
					}
				}

				ledgeHeight += ledgeHeightIncrement;

				if ( ledgeHeight >= maxLedgeHeight )
				{
					if ( isLastIteration )
					{
						// tested at max height
						break;
					}

					// check one more time at max jump height
					isLastIteration = true;
					ledgeHeight = maxLedgeHeight;
				}
			}
			
			if ( foundLedge )
			{
				if ( bot->IsDebugging( NEXTBOT_PATH ) )
				{
					DevMsg( "%3.2f: STARTING LEDGE CLIMB UP\n", gpGlobals->curtime );

					if ( NextBotDebugClimbing.GetBool() ) 
					{
						NDebugOverlay::Cross3D( ledgePos, 10.0f, 0, 255, 0, true, 9999.9f );

						// display approximation of idealized ledge that has been found
						Vector side( -climbDirection.y, climbDirection.x, 0.0f );

						// this is an approximation, since AABB can hit at any angle
						Vector base = feet + halfSize * climbDirection;

						Vector wallBottomLeft = base + halfSize * side;
						Vector wallBottomRight = base - halfSize * side;
						Vector wallTopLeft = wallBottomLeft + Vector( 0, 0, ledgeHeight );
						Vector wallTopRight = wallBottomRight + Vector( 0, 0, ledgeHeight );

						NDebugOverlay::Triangle( wallBottomRight, wallBottomLeft, wallTopLeft, 255, 100, 0, 100, true, 9999.9f );
						NDebugOverlay::Triangle( wallBottomRight, wallTopLeft, wallTopRight, 255, 100, 0, 100, true, 9999.9f );

						Vector ledgeLeft = ledgePos + halfSize * side;
						Vector ledgeRight = ledgePos - halfSize * side;

						NDebugOverlay::Triangle( wallTopRight, wallTopLeft, ledgeLeft, 0, 100, 255, 100, true, 9999.9f );
						NDebugOverlay::Triangle( wallTopRight, ledgeLeft, ledgeRight, 0, 100, 255, 100, true, 9999.9f );
					}
				}

				if ( !mover->ClimbUpToLedge( ledgePos, climbDirection, obstacle ) )
				{
					// climb failed - build a new path in case we're now stuck
					//Invalidate();
					return false;
				}

				return true;
			}
			else if ( bot->IsDebugging( NEXTBOT_PATH ) )
			{
				DevMsg( "%3.2f: CANT FIND LEDGE TO CLIMB\n", gpGlobals->curtime );
			}
		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Jump over gaps
 */
bool PathFollower::JumpOverGaps( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &right, float goalRange )
{
	VPROF_BUDGET( "PathFollower::JumpOverGaps", "NextBot" );

	ILocomotion *mover = bot->GetLocomotionInterface();
	IBody *body = bot->GetBodyInterface();

	if ( !mover->IsAbleToJumpAcrossGaps() || !NextBotAllowGapJumping.GetBool() )
	{
		return false;
	}

	if ( mover->IsClimbingOrJumping() || mover->IsAscendingOrDescendingLadder() || !mover->IsOnGround() )
	{
		return false;
	}
	
	if ( !body->IsActualPosture( IBody::STAND ) )
	{
		// can't jump if we're not standing
		return false;
	}

	if ( m_goal == NULL )
	{
		return false;
	}

	trace_t result;
	NextBotTraversableTraceFilter filter( bot, ILocomotion::IMMEDIATELY );

	const float hullWidth = ( body ) ? body->GetHullWidth() : 1.0f;

	// 'current' is the segment we are on/just passed over
	const Segment *current = PriorSegment( m_goal );
	if ( current == NULL )
	{
		return false;
	}

	const float minGapJumpRange = 2.0f * hullWidth;

	const Segment *gap = NULL;

	if ( current->type == JUMP_OVER_GAP )
	{
		gap = current;
	}
	else
	{
		float searchRange = goalRange;
		for( const Segment *s = m_goal; s; s = NextSegment( s ) )
		{
			if ( searchRange > minGapJumpRange )
			{
				break;
			}

			if ( s->type == JUMP_OVER_GAP )
			{
				gap = s;
				break;
			}

			searchRange += s->length;
		}
	}

	if ( gap )
	{
		VPROF_BUDGET( "PathFollower::GapJumping", "NextBot" );

		float halfWidth = hullWidth/2.0f;

		if ( mover->IsGap( mover->GetFeet() + halfWidth * gap->forward, gap->forward ) )
		{
			// there is a gap to jump over
			const Segment *landing = NextSegment( gap );
			if ( landing )
			{
				mover->JumpAcrossGap( landing->pos, landing->forward );

				// if we're jumping over this gap, make sure our goal is the landing so we aim for it
				m_goal = landing;

				if ( bot->IsDebugging( NEXTBOT_PATH ) )
				{
					NDebugOverlay::Cross3D( m_goal->pos, 5.0f, 0, 255, 255, true, 5.0f );
					DevMsg( "%3.2f: GAP JUMP\n", gpGlobals->curtime );
				}
				return true;
			}
		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Draw the path for debugging
 */
void PathFollower::Draw( const Path::Segment *start ) const
{
	if ( m_goal == NULL )
		return;
		
	// show avoid volumes	
	if ( m_didAvoidCheck )
	{
		QAngle angles( 0, 0, 0 );
		
		if (m_isLeftClear)
			NDebugOverlay::SweptBox( m_leftFrom, m_leftTo, m_hullMin, m_hullMax, angles, 0, 255, 0, 255, 0.1f );
		else
			NDebugOverlay::SweptBox( m_leftFrom, m_leftTo, m_hullMin, m_hullMax, angles, 255, 0, 0, 255, 0.1f );
			
		if (m_isRightClear)
			NDebugOverlay::SweptBox( m_rightFrom, m_rightTo, m_hullMin, m_hullMax, angles, 0, 255, 0, 255, 0.1f );
		else
			NDebugOverlay::SweptBox( m_rightFrom, m_rightTo, m_hullMin, m_hullMax, angles, 255, 0, 0, 255, 0.1f );

		const_cast< PathFollower * >( this )->m_didAvoidCheck = false;
	}

	// highlight current goal segment
	if ( m_goal )
	{
		const float size = 5.0f;	
		NDebugOverlay::Sphere( m_goal->pos, size, 255, 255, 0, true, 0.1f );

		switch( m_goal->how )
		{
		case GO_NORTH:
		case GO_SOUTH:
			NDebugOverlay::Line( m_goal->m_portalCenter - Vector( m_goal->m_portalHalfWidth, 0, 0 ), m_goal->m_portalCenter + Vector( m_goal->m_portalHalfWidth, 0, 0 ), 255, 0, 255, true, 0.1f );
			break;

		default:
			NDebugOverlay::Line( m_goal->m_portalCenter - Vector( 0, m_goal->m_portalHalfWidth, 0 ), m_goal->m_portalCenter + Vector( 0, m_goal->m_portalHalfWidth, 0 ), 255, 0, 255, true, 0.1f );
			break;
		}

		// 'current' is the segment we are on/just passed over
		const Segment *current = PriorSegment( m_goal );
		if ( current )
		{
			NDebugOverlay::Line( current->pos, m_goal->pos, 255, 255, 0, true, 0.1f );
		}
	}

	// extend
	Path::Draw();
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if there is a the given discontinuity ahead in the path within the given range (-1 = entire remaining path)
 */
bool PathFollower::IsDiscontinuityAhead( INextBot *bot, Path::SegmentType type, float range ) const
{
	if ( m_goal )
	{
		const Path::Segment *current = PriorSegment( m_goal );
		if ( current && current->type == type )
		{
			// we're on the discontinuity now
			return true;
		}

		float rangeSoFar = ( m_goal->pos - bot->GetLocomotionInterface()->GetFeet() ).Length();

		for( const Segment *s = m_goal; s; s = NextSegment( s ) )
		{
			if ( rangeSoFar >= range )
			{
				break;
			}

			if ( s->type == type )
			{
				return true;
			}

			rangeSoFar += s->length;
		}
	}

	return false;
}