/*
*
*   This program is free software; you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation; either version 2 of the License, or (at
*   your option) any later version.
*
*   This program is distributed in the hope that it will be useful, but
*   WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*   General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with this program; if not, write to the Free Software Foundation,
*   Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*   In addition, as a special exception, the author gives permission to
*   link the code of this program with the Half-Life Game Engine ("HL
*   Engine") and Modified Game Libraries ("MODs") developed by Valve,
*   L.L.C ("Valve").  You must obey the GNU General Public License in all
*   respects for all of the code used other than the HL Engine and MODs
*   from Valve.  If you modify this file, you may extend this exception
*   to your version of the file, but you are not obligated to do so.  If
*   you do not wish to do so, delete this exception statement from your
*   version.
*
*/

#include "precompiled.h"

// Determine actual path positions bot will move between along the path
bool CCSBot::ComputePathPositions()
{
	if (m_pathLength == 0)
		return false;

	// start in first area's center
	m_path[0].pos = *m_path[0].area->GetCenter();
	m_path[0].ladder = nullptr;
	m_path[0].how = NUM_TRAVERSE_TYPES;

	for (int i = 1; i < m_pathLength; i++)
	{
		const ConnectInfo *from = &m_path[i - 1];
		ConnectInfo *to = &m_path[i];

		// walk along the floor to the next area
		if (to->how <= GO_WEST)
		{
			to->ladder = nullptr;

			// compute next point, keeping path as straight as possible
			from->area->ComputeClosestPointInPortal(to->area, (NavDirType)to->how, &from->pos, &to->pos);

			// move goal position into the goal area a bit
			// how far to "step into" an area - must be less than min area size
			const float stepInDist = 5.0f;
			AddDirectionVector(&to->pos, (NavDirType)to->how, stepInDist);

			// we need to walk out of "from" area, so keep Z where we can reach it
			to->pos.z = from->area->GetZ(&to->pos);

			// if this is a "jump down" connection, we must insert an additional point on the path
			if (to->area->IsConnected(from->area, NUM_DIRECTIONS) == false)
			{
				// this is a "jump down" link
				// compute direction of path just prior to "jump down"
				Vector2D dir;
				DirectionToVector2D((NavDirType)to->how, &dir);

				// shift top of "jump down" out a bit to "get over the ledge"
				const float pushDist = 25.0f; // 75.0f;
				to->pos.x += pushDist * dir.x;
				to->pos.y += pushDist * dir.y;

				// insert a duplicate node to represent the bottom of the fall
				if (m_pathLength < MAX_PATH_LENGTH - 1)
				{
					// copy nodes down
					for (int j = m_pathLength; j > i; j--)
						m_path[j] = m_path[j - 1];

					// path is one node longer
					m_pathLength++;

					// move index ahead into the new node we just duplicated
					i++;

					m_path[i].pos.x = to->pos.x + pushDist * dir.x;
					m_path[i].pos.y = to->pos.y + pushDist * dir.y;

					// put this one at the bottom of the fall
					m_path[i].pos.z = to->area->GetZ(&m_path[i].pos);
				}
			}
		}
		// to get to next area, must go up a ladder
		else if (to->how == GO_LADDER_UP)
		{
			// find our ladder
			const NavLadderList *list = from->area->GetLadderList(LADDER_UP);
			NavLadderList::const_iterator iter;
			for (iter = list->begin(); iter != list->end(); iter++)
			{
				CNavLadder *ladder = (*iter);

				// can't use "behind" area when ascending...
				if (ladder->m_topForwardArea == to->area || ladder->m_topLeftArea == to->area || ladder->m_topRightArea == to->area)
				{
					to->ladder = ladder;
					to->pos = ladder->m_bottom;
					AddDirectionVector(&to->pos, ladder->m_dir, HalfHumanWidth * 2.0f);
					break;
				}
			}

			if (iter == list->end())
			{
				PrintIfWatched("ERROR: Can't find ladder in path\n");
				return false;
			}
		}
		// to get to next area, must go down a ladder
		else if (to->how == GO_LADDER_DOWN)
		{
			// find our ladder
			const NavLadderList *list = from->area->GetLadderList(LADDER_DOWN);
			NavLadderList::const_iterator iter;
			for (iter = list->begin(); iter != list->end(); iter++)
			{
				CNavLadder *ladder = (*iter);

				if (ladder->m_bottomArea == to->area)
				{
					to->ladder = ladder;
					to->pos = ladder->m_top;
					AddDirectionVector(&to->pos, OppositeDirection(ladder->m_dir), HalfHumanWidth * 2.0f);
					break;
				}
			}

			if (iter == list->end())
			{
				PrintIfWatched("ERROR: Can't find ladder in path\n");
				return false;
			}
		}
	}

	return true;
}

// If next step of path uses a ladder, prepare to traverse it
void CCSBot::SetupLadderMovement()
{
	if (m_pathIndex < 1 || m_pathLength == 0)
		return;

	const ConnectInfo *to = &m_path[m_pathIndex];

	if (to->ladder)
	{
		m_spotEncounter = nullptr;
		m_areaEnteredTimestamp = gpGlobals->time;

		m_pathLadder = to->ladder;
		m_pathLadderTimestamp = gpGlobals->time;

		// to get to next area, we must traverse a ladder
		if (to->how == GO_LADDER_UP)
		{
			m_pathLadderState = APPROACH_ASCENDING_LADDER;
			m_pathLadderFaceIn = true;
			PrintIfWatched("APPROACH_ASCENDING_LADDER\n");
			m_goalPosition = m_pathLadder->m_bottom;

			AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth * 2.0f);
			m_lookAheadAngle = DirectionToAngle(OppositeDirection(m_pathLadder->m_dir));
		}
		else
		{
			// try to mount ladder "face out" first
			m_goalPosition = m_pathLadder->m_top;
			AddDirectionVector(&m_goalPosition, OppositeDirection(m_pathLadder->m_dir), HalfHumanWidth * 2.0f);

			TraceResult result;
			Vector from = m_pathLadder->m_top;
			Vector to = m_goalPosition;

			UTIL_TraceLine(from, to, ignore_monsters, ENT(m_pathLadder->m_entity->pev), &result);

			if (result.flFraction == 1.0f)
			{
				PrintIfWatched("APPROACH_DESCENDING_LADDER (face out)\n");

				m_pathLadderState = APPROACH_DESCENDING_LADDER;
				m_pathLadderFaceIn = false;
				m_lookAheadAngle = DirectionToAngle(m_pathLadder->m_dir);
			}
			else
			{
				PrintIfWatched("APPROACH_DESCENDING_LADDER (face in)\n");
				m_pathLadderState = APPROACH_DESCENDING_LADDER;
				m_pathLadderFaceIn = true;
				m_lookAheadAngle = DirectionToAngle(OppositeDirection(m_pathLadder->m_dir));
				m_goalPosition = m_pathLadder->m_top;
				AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth);
			}
		}
	}
}

// TODO: What about ladders whose top AND bottom are messed up?
void CCSBot::ComputeLadderEndpoint(bool isAscending)
{
	TraceResult result;
	Vector from, to;

	if (isAscending)
	{
		// find actual top in case m_pathLadder penetrates the ceiling
		// trace from our chest height at m_pathLadder base
		from = m_pathLadder->m_bottom;
		from.z = pev->origin.z;
		to = m_pathLadder->m_top;
	}
	else
	{
		// find actual bottom in case m_pathLadder penetrates the floor
		// trace from our chest height at m_pathLadder top
		from = m_pathLadder->m_top;
		from.z = pev->origin.z;
		to = m_pathLadder->m_bottom;
	}

	UTIL_TraceLine(from, to, ignore_monsters, ENT(m_pathLadder->m_entity->pev), &result);

	if (result.flFraction == 1.0f)
		m_pathLadderEnd = to.z;
	else
		m_pathLadderEnd = from.z + result.flFraction * (to.z - from.z);
}

// Navigate our current ladder. Return true if we are doing ladder navigation.
// TODO: Need Push() and Pop() for run/walk context to keep ladder speed contained.
bool CCSBot::UpdateLadderMovement()
{
	if (!m_pathLadder)
		return false;

	bool giveUp = false;

	// check for timeout
	const float ladderTimeoutDuration = 10.0f;
	if (gpGlobals->time - m_pathLadderTimestamp > ladderTimeoutDuration)
	{
		PrintIfWatched("Ladder timeout!\n");
		giveUp = true;
	}

	else if (m_pathLadderState == APPROACH_ASCENDING_LADDER
		|| m_pathLadderState == APPROACH_DESCENDING_LADDER
		|| m_pathLadderState == ASCEND_LADDER
		|| m_pathLadderState == DESCEND_LADDER
		|| m_pathLadderState == DISMOUNT_ASCENDING_LADDER
		|| m_pathLadderState == MOVE_TO_DESTINATION)
	{
		if (m_isStuck)
		{
			PrintIfWatched("Giving up ladder - stuck\n");
			giveUp = true;
		}
	}

	if (giveUp)
	{
		// jump off ladder and give up
		Jump(MUST_JUMP);
		Wiggle();
		ResetStuckMonitor();
		DestroyPath();
		Run();
		return false;
	}

	ResetStuckMonitor();

	// check if somehow we totally missed the ladder
	switch (m_pathLadderState)
	{
	case MOUNT_ASCENDING_LADDER:
	case MOUNT_DESCENDING_LADDER:
	case ASCEND_LADDER:
	case DESCEND_LADDER:
	{
		const float farAway = 200.0f;
		Vector2D d = (m_pathLadder->m_top - pev->origin).Make2D();
		if (d.IsLengthGreaterThan(farAway))
		{
			PrintIfWatched("Missed ladder\n");
			Jump(MUST_JUMP);
			DestroyPath();
			Run();
			return false;
		}
		break;
	}
	}

	m_areaEnteredTimestamp = gpGlobals->time;

	const float tolerance = 10.0f;
	const float closeToGoal = 25.0f;

	switch (m_pathLadderState)
	{
	case APPROACH_ASCENDING_LADDER:
	{
		bool approached = false;
		Vector2D d(pev->origin.x - m_goalPosition.x, pev->origin.y - m_goalPosition.y);

		if (d.x * m_pathLadder->m_dirVector.x + d.y * m_pathLadder->m_dirVector.y < 0.0f)
		{
			Vector2D perp(-m_pathLadder->m_dirVector.y, m_pathLadder->m_dirVector.x);

#ifdef REGAMEDLL_FIXES
			if (Q_abs(d.x * perp.x + d.y * perp.y) < tolerance && d.Length() < closeToGoal)
#else
			if (Q_abs(int64(d.x * perp.x + d.y * perp.y)) < tolerance && d.Length() < closeToGoal)
#endif
				approached = true;
		}

		// small radius will just slow them down a little for more accuracy in hitting their spot
		const float walkRange = 50.0f;
		if (d.IsLengthLessThan(walkRange))
		{
			Walk();
			StandUp();
		}

		// TODO: Check that we are on the ladder we think we are
		if (IsOnLadder())
		{
			m_pathLadderState = ASCEND_LADDER;
			PrintIfWatched("ASCEND_LADDER\n");

			// find actual top in case m_pathLadder penetrates the ceiling
			ComputeLadderEndpoint(true);
		}
		else if (approached)
		{
			// face the m_pathLadder
			m_pathLadderState = FACE_ASCENDING_LADDER;
			PrintIfWatched("FACE_ASCENDING_LADDER\n");
		}
		else
		{
			// move toward ladder mount point
			MoveTowardsPosition(&m_goalPosition);
		}
		break;
	}
	case APPROACH_DESCENDING_LADDER:
	{
		// fall check
		if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight)
		{
			PrintIfWatched("Fell from ladder.\n");

			m_pathLadderState = MOVE_TO_DESTINATION;
			m_path[m_pathIndex].area->GetClosestPointOnArea(&m_pathLadder->m_bottom, &m_goalPosition);

			AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth);
			PrintIfWatched("MOVE_TO_DESTINATION\n");
		}
		else
		{
			bool approached = false;
			Vector2D d(pev->origin.x - m_goalPosition.x, pev->origin.y - m_goalPosition.y);

			if (d.x * m_pathLadder->m_dirVector.x + d.y * m_pathLadder->m_dirVector.y > 0.0f)
			{
				Vector2D perp(-m_pathLadder->m_dirVector.y, m_pathLadder->m_dirVector.x);

				if (Q_abs(int64(d.x * perp.x + d.y * perp.y)) < tolerance && d.Length() < closeToGoal)
					approached = true;
			}

			// if approaching ladder from the side or "ahead", walk
			if (m_pathLadder->m_topBehindArea != m_lastKnownArea)
			{
				const float walkRange = 150.0f;
				if (!IsCrouching() && d.IsLengthLessThan(walkRange))
					Walk();
			}

			// TODO: Check that we are on the ladder we think we are
			if (IsOnLadder())
			{
				// we slipped onto the ladder - climb it
				m_pathLadderState = DESCEND_LADDER;
				Run();
				PrintIfWatched("DESCEND_LADDER\n");

				// find actual bottom in case m_pathLadder penetrates the floor
				ComputeLadderEndpoint(false);
			}
			else if (approached)
			{
				// face the ladder
				m_pathLadderState = FACE_DESCENDING_LADDER;
				PrintIfWatched("FACE_DESCENDING_LADDER\n");
			}
			else
			{
				// move toward ladder mount point
				MoveTowardsPosition(&m_goalPosition);
			}
		}
		break;
	}
	case FACE_ASCENDING_LADDER:
	{
		// find yaw to directly aim at ladder
		Vector to = m_pathLadder->m_bottom - pev->origin;
		Vector idealAngle = UTIL_VecToAngles(to);

		const float angleTolerance = 5.0f;
		if (AnglesAreEqual(pev->v_angle.y, idealAngle.y, angleTolerance))
		{
			// move toward ladder until we become "on" it
			Run();
			ResetStuckMonitor();
			m_pathLadderState = MOUNT_ASCENDING_LADDER;
			PrintIfWatched("MOUNT_ASCENDING_LADDER\n");
		}
		break;
	}
	case FACE_DESCENDING_LADDER:
	{
		// find yaw to directly aim at ladder
		Vector to = m_pathLadder->m_top - pev->origin;
		Vector idealAngle = UTIL_VecToAngles(to);

		const float angleTolerance = 5.0f;
		if (AnglesAreEqual(pev->v_angle.y, idealAngle.y, angleTolerance))
		{
			// move toward ladder until we become "on" it
			m_pathLadderState = MOUNT_DESCENDING_LADDER;
			ResetStuckMonitor();
			PrintIfWatched("MOUNT_DESCENDING_LADDER\n");
		}
		break;
	}
	case MOUNT_ASCENDING_LADDER:
	{
		if (IsOnLadder())
		{
			m_pathLadderState = ASCEND_LADDER;
			PrintIfWatched("ASCEND_LADDER\n");

			// find actual top in case m_pathLadder penetrates the ceiling
			ComputeLadderEndpoint(true);
		}

		MoveForward();
		break;
	}
	case MOUNT_DESCENDING_LADDER:
	{
		// fall check
		if (GetFeetZ() <= m_pathLadder->m_bottom.z + HalfHumanHeight)
		{
			PrintIfWatched("Fell from ladder.\n");

			m_pathLadderState = MOVE_TO_DESTINATION;
			m_path[m_pathIndex].area->GetClosestPointOnArea(&m_pathLadder->m_bottom, &m_goalPosition);

			AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth);
			PrintIfWatched("MOVE_TO_DESTINATION\n");
		}
		else
		{
			if (IsOnLadder())
			{
				m_pathLadderState = DESCEND_LADDER;
				PrintIfWatched("DESCEND_LADDER\n");

				// find actual bottom in case m_pathLadder penetrates the floor
				ComputeLadderEndpoint(false);
			}

			// move toward ladder mount point
			MoveForward();
		}
		break;
	}
	case ASCEND_LADDER:
	{
		// run, so we can make our dismount jump to the side, if necessary
		Run();

		// if our destination area requires us to crouch, do it
		if (m_path[m_pathIndex].area->GetAttributes() & NAV_CROUCH)
			Crouch();

		// did we reach the top?
		if (GetFeetZ() >= m_pathLadderEnd)
		{
			// we reached the top - dismount
			m_pathLadderState = DISMOUNT_ASCENDING_LADDER;
			PrintIfWatched("DISMOUNT_ASCENDING_LADDER\n");

			if (m_path[m_pathIndex].area == m_pathLadder->m_topForwardArea)
				m_pathLadderDismountDir = FORWARD;
			else if (m_path[m_pathIndex].area == m_pathLadder->m_topLeftArea)
				m_pathLadderDismountDir = LEFT;
			else if (m_path[m_pathIndex].area == m_pathLadder->m_topRightArea)
				m_pathLadderDismountDir = RIGHT;

			m_pathLadderDismountTimestamp = gpGlobals->time;
		}
		else if (!IsOnLadder())
		{
			// we fall off the ladder, repath
			DestroyPath();
			return false;
		}

		// move up ladder
		MoveForward();
		break;
	}
	case DESCEND_LADDER:
	{
		Run();

		float destHeight = m_pathLadderEnd + HalfHumanHeight;
		if (!IsOnLadder() || GetFeetZ() <= destHeight)
		{
			// we reached the bottom, or we fell off - dismount
			m_pathLadderState = MOVE_TO_DESTINATION;
			m_path[m_pathIndex].area->GetClosestPointOnArea(&m_pathLadder->m_bottom, &m_goalPosition);

			AddDirectionVector(&m_goalPosition, m_pathLadder->m_dir, HalfHumanWidth);
			PrintIfWatched("MOVE_TO_DESTINATION\n");
		}

		// Move down ladder
		MoveForward();
		break;
	}
	case DISMOUNT_ASCENDING_LADDER:
	{
		if (gpGlobals->time - m_pathLadderDismountTimestamp >= 0.4f)
		{
			m_pathLadderState = MOVE_TO_DESTINATION;
			m_path[m_pathIndex].area->GetClosestPointOnArea(&pev->origin, &m_goalPosition);
			PrintIfWatched("MOVE_TO_DESTINATION\n");
		}

		// We should already be facing the dismount point
		if (m_pathLadderFaceIn)
		{
			switch (m_pathLadderDismountDir)
			{
			case LEFT:    StrafeLeft(); break;
			case RIGHT:   StrafeRight(); break;
			case FORWARD: MoveForward(); break;
			}
		}
		else
		{
			switch (m_pathLadderDismountDir)
			{
			case LEFT:    StrafeRight();  break;
			case RIGHT:   StrafeLeft();   break;
			case FORWARD: MoveBackward(); break;
			}
		}
		break;
	}
	case MOVE_TO_DESTINATION:
	{
		if (m_path[m_pathIndex].area->Contains(&pev->origin))
		{
			// successfully traversed ladder and reached destination area
			// exit ladder state machine
			PrintIfWatched("Ladder traversed.\n");
			m_pathLadder = nullptr;

			// incrememnt path index to next step beyond this ladder
			SetPathIndex(m_pathIndex + 1);

			return false;
		}

		MoveTowardsPosition(&m_goalPosition);
		break;
	}
	}

	return true;
}

// Compute closest point on path to given point
// NOTE: This does not do line-of-sight tests, so closest point may be thru the floor, etc
bool CCSBot::FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const
{
	if (!HasPath() || !close)
		return false;

	Vector along, toWorldPos;
	Vector pos;
	const Vector *from, *to;
	float length;
	float closeLength;
	float closeDistSq = 9999999999.9f;
	float distSq;

	for (int i = startIndex; i <= endIndex; i++)
	{
		from = &m_path[i - 1].pos;
		to = &m_path[i].pos;

		// compute ray along this path segment
		along = *to - *from;

		// make it a unit vector along the path
		length = along.NormalizeInPlace();

		// compute vector from start of segment to our point
		toWorldPos = *worldPos - *from;

		// find distance of closest point on ray
		closeLength = DotProduct(toWorldPos, along);

		// constrain point to be on path segment
		if (closeLength <= 0.0f)
			pos = *from;
		else if (closeLength >= length)
			pos = *to;
		else
			pos = *from + closeLength * along;

		distSq = (pos - *worldPos).LengthSquared();

		// keep the closest point so far
		if (distSq < closeDistSq)
		{
			closeDistSq = distSq;
			*close = pos;
		}
	}

	return true;
}

// Return the closest point to our current position on our current path
// If "local" is true, only check the portion of the path surrounding m_pathIndex.
int CCSBot::FindOurPositionOnPath(Vector *close, bool local) const
{
	if (!HasPath())
		return -1;

	Vector along, toFeet;
	Vector feet(pev->origin.x, pev->origin.y, GetFeetZ());
	Vector eyes = feet + Vector(0, 0, HalfHumanHeight); // in case we're crouching
	Vector pos;
	const Vector *from, *to;
	real_t length;
	float closeLength;
	float closeDistSq = 9999999999.9;
	int closeIndex = -1;
	real_t distSq;

	int start, end;

	if (local)
	{
		start = m_pathIndex - 3;
		if (start < 1)
			start = 1;

		end = m_pathIndex + 3;
		if (end > m_pathLength)
			end = m_pathLength;
	}
	else
	{
		start = 1;
		end = m_pathLength;
	}

	for (int i = start; i < end; i++)
	{
		from = &m_path[i - 1].pos;
		to = &m_path[i].pos;

		// compute ray along this path segment
		along = *to - *from;

		// make it a unit vector along the path
		length = along.NormalizeInPlace();

		// compute vector from start of segment to our point
		toFeet = feet - *from;

		// find distance of closest point on ray
		closeLength = DotProduct(toFeet, along);

		// constrain point to be on path segment
		if (closeLength <= 0.0f)
			pos = *from;
		else if (closeLength >= length)
			pos = *to;
		else
			pos = *from + closeLength * along;

		distSq = (pos - feet).LengthSquared();

		// keep the closest point so far
		if (distSq < closeDistSq)
		{
			// don't use points we cant see
			Vector probe = pos + Vector(0, 0, HalfHumanHeight);
			if (!IsWalkableTraceLineClear(eyes, probe, WALK_THRU_EVERYTHING))
				continue;

			// don't use points we cant reach
			if (!IsStraightLinePathWalkable(&pos))
				continue;

			closeDistSq = distSq;
			if (close)
				*close = pos;

			closeIndex = i - 1;
		}
	}

	return closeIndex;
}

// Test for un-jumpable height change, or unrecoverable fall
bool CCSBot::IsStraightLinePathWalkable(const Vector *goal) const
{
// this is causing hang-up problems when crawling thru ducts/windows that drop off into rooms (they fail the "falling" check)
return true;

	const float inc = GenerationStepSize;

	Vector feet = pev->origin;
	Vector dir = *goal - feet;
	float length = dir.NormalizeInPlace();

	float lastGround;

	//if (!GetSimpleGroundHeight(&pev->origin, &lastGround))
	//	return false;

	lastGround = feet.z;

	float along = 0.0f;
	Vector pos;
	float ground;
	bool done = false;

	while (!done)
	{
		along += inc;
		if (along > length)
		{
			along = length;
			done = true;
		}

		// compute step along path
		pos = feet + along * dir;
		pos.z += HalfHumanHeight;

		if (!GetSimpleGroundHeight(&pos, &ground))
			return false;

		// check for falling
		if (ground - lastGround < -StepHeight)
			return false;

		// check for unreachable jump
		// use slightly shorter jump limit, to allow for some fudge room
		if (ground - lastGround > JumpHeight)
			return false;

		lastGround = ground;
	}

	return true;
}

// Compute a point a fixed distance ahead along our path.
// Returns path index just after point.
int CCSBot::FindPathPoint(float aheadRange, Vector *point, int *prevIndex)
{
	// find path index just past aheadRange
	int afterIndex;

	// finds the closest point on local area of path, and returns the path index just prior to it
	Vector close;
	int startIndex = FindOurPositionOnPath(&close, true);

	if (prevIndex)
		*prevIndex = startIndex;

	if (startIndex <= 0)
	{
		// went off the end of the path
		// or next point in path is unwalkable (ie: jump-down)
		// keep same point
		return m_pathIndex;
	}

	// if we are crouching, just follow the path exactly
	if (IsCrouching())
	{
		// we want to move to the immediately next point along the path from where we are now
		int index = startIndex + 1;
		if (index >= m_pathLength)
			index = m_pathLength - 1;

		*point = m_path[index].pos;

		// if we are very close to the next point in the path, skip ahead to the next one to avoid wiggling
		// we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
		const float closeEpsilon = 20.0f; // 10.0f
		while ((*point - close).Make2D().IsLengthLessThan(closeEpsilon))
		{
			index++;

			if (index >= m_pathLength)
			{
				index = m_pathLength - 1;
				break;
			}

			*point = m_path[index].pos;
		}

		return index;
	}

	// make sure we use a node a minimum distance ahead of us, to avoid wiggling
	while (startIndex < m_pathLength - 1)
	{
		Vector pos = m_path[startIndex + 1].pos;

		// we must do a 2D check here, in case the goal point is floating in space due to jump down, etc
		const float closeEpsilon = 20.0f;
		if ((pos - close).Make2D().IsLengthLessThan(closeEpsilon))
		{
			startIndex++;
		}
		else
		{
			break;
		}
	}

	// if we hit a ladder, stop, or jump area, must stop (dont use ladder behind us)
	if (startIndex > m_pathIndex && startIndex < m_pathLength
		&& (m_path[startIndex].ladder || (m_path[startIndex].area->GetAttributes() & NAV_JUMP)))
	{
		*point = m_path[startIndex].pos;
		return startIndex;
	}

	// we need the point just *ahead* of us
	if (++startIndex >= m_pathLength)
		startIndex = m_pathLength - 1;

	// if we hit a ladder, stop, or jump area, must stop
	if (startIndex < m_pathLength && (m_path[startIndex].ladder || (m_path[startIndex].area->GetAttributes() & NAV_JUMP)))
	{
		*point = m_path[startIndex].pos;
		return startIndex;
	}

	// note direction of path segment we are standing on
	Vector initDir = m_path[startIndex].pos - m_path[startIndex - 1].pos;
	initDir.NormalizeInPlace();

	Vector feet(pev->origin.x, pev->origin.y, GetFeetZ());
	Vector eyes = feet + Vector(0, 0, HalfHumanHeight);
	float rangeSoFar = 0;

	// this flag is true if our ahead point is visible
	bool visible = true;
	Vector prevDir = initDir;

	// step along the path until we pass aheadRange
	bool isCorner = false;
	int i;
	for (i = startIndex; i < m_pathLength; i++)
	{
		Vector pos = m_path[i].pos;
		Vector to = pos - m_path[i - 1].pos;
		Vector dir = to;
		dir.NormalizeInPlace();

		// don't allow path to double-back from our starting direction (going upstairs, down curved passages, etc)
		if (DotProduct(dir, initDir) < 0.0f) // -0.25f
		{
			i--;
			break;
		}

		// if the path turns a corner, we want to move towards the corner, not into the wall/stairs/etc
		if (DotProduct(dir, prevDir) < 0.5f)
		{
			isCorner = true;
			i--;
			break;
		}

		prevDir = dir;

		// don't use points we cant see
		Vector probe = pos + Vector(0, 0, HalfHumanHeight);
		if (!IsWalkableTraceLineClear(eyes, probe, WALK_THRU_BREAKABLES))
		{
			// presumably, the previous point is visible, so we will interpolate
			visible = false;
			break;
		}

		// if we encounter a ladder or jump area, we must stop
		if (i < m_pathLength && (m_path[i].ladder || (m_path[i].area->GetAttributes() & NAV_JUMP)))
			break;

		// Check straight-line path from our current position to this position
		// Test for un-jumpable height change, or unrecoverable fall
		if (!IsStraightLinePathWalkable(&pos))
		{
			i--;
			break;
		}

		Vector along = (i == startIndex) ? (pos - feet) : (pos - m_path[i - 1].pos);
		rangeSoFar += along.Length2D();

		// stop if we have gone farther than aheadRange
		if (rangeSoFar >= aheadRange)
			break;
	}

	if (i < startIndex)
		afterIndex = startIndex;
	else if (i < m_pathLength)
		afterIndex = i;
	else
		afterIndex = m_pathLength - 1;

	// compute point on the path at aheadRange
	if (afterIndex == 0)
	{
		*point = m_path[0].pos;
	}
	else
	{
		// interpolate point along path segment
		const Vector *afterPoint = &m_path[afterIndex].pos;
		const Vector *beforePoint = &m_path[afterIndex - 1].pos;

		Vector to = *afterPoint - *beforePoint;
		float length = to.Length2D();

		float t = 1.0f - ((rangeSoFar - aheadRange) / length);

		if (t < 0.0f)
			t = 0.0f;
		else if (t > 1.0f)
			t = 1.0f;

		*point = *beforePoint + t * to;

		// if afterPoint wasn't visible, slide point backwards towards beforePoint until it is
		if (!visible)
		{
			const float sightStepSize = 25.0f;
			float dt = sightStepSize / length;

			Vector probe = *point + Vector(0, 0, HalfHumanHeight);
			while (t > 0.0f && !IsWalkableTraceLineClear(eyes, probe, WALK_THRU_BREAKABLES))
			{
				t -= dt;
				*point = *beforePoint + t * to;
			}

			if (t <= 0.0f)
				*point = *beforePoint;
		}
	}

	// if position found is too close to us, or behind us, force it farther down the path so we don't stop and wiggle
	if (!isCorner)
	{
		const float epsilon = 50.0f;
		Vector2D toPoint;
		toPoint.x = point->x - pev->origin.x;
		toPoint.y = point->y - pev->origin.y;
		if (DotProduct(toPoint, initDir.Make2D()) < 0.0f || toPoint.IsLengthLessThan(epsilon))
		{
			int i;
			for (i = startIndex; i < m_pathLength; i++)
			{
				toPoint.x = m_path[i].pos.x - pev->origin.x;
				toPoint.y = m_path[i].pos.y - pev->origin.y;
				if (m_path[i].ladder || (m_path[i].area->GetAttributes() & NAV_JUMP) || toPoint.IsLengthGreaterThan(epsilon))
				{
					*point = m_path[i].pos;
					startIndex = i;
					break;
				}
			}

			if (i == m_pathLength)
			{
				*point = GetPathEndpoint();
				startIndex = m_pathLength - 1;
			}
		}
	}

	// m_pathIndex should always be the next point on the path, even if we're not moving directly towards it
	return startIndex;
}

// Set the current index along the path
void CCSBot::SetPathIndex(int newIndex)
{
	m_pathIndex = Q_min(newIndex, m_pathLength - 1);
	m_areaEnteredTimestamp = gpGlobals->time;

	if (m_path[m_pathIndex].ladder)
	{
		SetupLadderMovement();
	}
	else
	{
		// get our "encounter spots" for this leg of the path
		if (m_pathIndex < m_pathLength && m_pathIndex >= 2)
			m_spotEncounter = m_path[m_pathIndex - 1].area->GetSpotEncounter(m_path[m_pathIndex - 2].area, m_path[m_pathIndex].area);
		else
			m_spotEncounter = nullptr;

		m_pathLadder = nullptr;
	}
}

// Return true if nearing a jump in the path
bool CCSBot::IsNearJump() const
{
	if (m_pathIndex == 0 || m_pathIndex >= m_pathLength)
		return false;

	for (int i = m_pathIndex - 1; i < m_pathIndex; i++)
	{
		if (m_path[i].area->GetAttributes() & NAV_JUMP)
		{
			float dz = m_path[i + 1].pos.z - m_path[i].pos.z;
			if (dz > 0.0f)
			{
				return true;
			}
		}
	}

	return false;
}

// Return approximately how much damage will will take from the given fall height
float CCSBot::GetApproximateFallDamage(float height) const
{
	// empirically discovered height values
	const float slope = 0.2178f;
	const float intercept = 26.0f;

	real_t damage = slope * height - intercept;

	if (damage < 0.0f)
		return 0.0f;

	return damage;
}

// Return true if a friend is between us and the given position
bool CCSBot::IsFriendInTheWay(const Vector *goalPos) const
{
	// do this check less often to ease CPU burden
	if (!m_avoidFriendTimer.IsElapsed())
	{
		return m_isFriendInTheWay;
	}

	const float avoidFriendInterval = 0.5f;
	m_avoidFriendTimer.Start(avoidFriendInterval);

	// compute ray along intended path
	Vector moveDir = *goalPos - pev->origin;

	// make it a unit vector
	float length = moveDir.NormalizeInPlace();

	m_isFriendInTheWay = false;

	// check if any friends are overlapping this linear path
	for (int i = 1; i <= gpGlobals->maxClients; i++)
	{
		CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);

		if (!pPlayer)
			continue;

		if (FNullEnt(pPlayer->pev))
			continue;

		if (!pPlayer->IsAlive())
			continue;

		// ignore enemies
		if (BotRelationship(pPlayer) == BOT_ENEMY)
			continue;

		if (pPlayer == this)
			continue;

		// compute vector from us to our friend
		Vector toFriend = pPlayer->pev->origin - pev->origin;

		// check if friend is in our "personal space"
		const float personalSpace = 100.0f;
		if (toFriend.IsLengthGreaterThan(personalSpace))
			continue;

		// find distance of friend along our movement path
		float friendDistAlong = DotProduct(toFriend, moveDir);

		// if friend is behind us, ignore him
		if (friendDistAlong <= 0.0f)
			continue;

		// constrain point to be on path segment
		Vector pos;
		if (friendDistAlong >= length)
			pos = *goalPos;
		else
			pos = pev->origin + friendDistAlong * moveDir;

		// check if friend overlaps our intended line of movement
		const float friendRadius = 30.0f;
		if ((pos - pPlayer->pev->origin).IsLengthLessThan(friendRadius))
		{
			// friend is in our personal space and overlaps our intended line of movement
			m_isFriendInTheWay = true;
			break;
		}
	}

	return m_isFriendInTheWay;
}

// Do reflex avoidance movements if our "feelers" are touched
void CCSBot::FeelerReflexAdjustment(Vector *goalPosition)
{
	// if we are in a "precise" area, do not do feeler adjustments
	if (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_PRECISE))
		return;

	Vector dir(BotCOS(m_forwardAngle), BotSIN(m_forwardAngle), 0.0f);
	Vector lat(-dir.y, dir.x, 0.0f);

	const float feelerOffset = (IsCrouching()) ? 15.0f : 20.0f;
	const float feelerLengthRun = 50.0f; // 100 - too long for tight hallways (cs_747)
	const float feelerLengthWalk = 30.0f;
	const float feelerHeight = StepHeight + 0.1f; // if obstacle is lower than StepHeight, we'll walk right over it

	float feelerLength = (IsRunning()) ? feelerLengthRun : feelerLengthWalk;

	feelerLength = (IsCrouching()) ? 20.0f : feelerLength;

	// Feelers must follow floor slope
	float ground;
	Vector normal;

	//m_eyePos = EyePosition();
	m_eyePos.x = pev->origin.x + pev->view_ofs.x;
	m_eyePos.y = pev->origin.y + pev->view_ofs.y;
	m_eyePos.z = pev->origin.z + pev->view_ofs.z;

	if (GetSimpleGroundHeightWithFloor(&m_eyePos, &ground, &normal) == false)
		return;

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

	// correct the sideways vector
	lat = CrossProduct(dir, normal);

	Vector feet(pev->origin.x, pev->origin.y, GetFeetZ());
	feet.z += feelerHeight;

	Vector from = feet + feelerOffset * lat;
	Vector to = from + feelerLength * dir;

	bool leftClear = IsWalkableTraceLineClear(from, to, WALK_THRU_EVERYTHING);

	// avoid ledges, too
	// use 'from' so it doesn't interfere with legitimate gap jumping (its at our feet)
	// TODO: Rethink this - it causes lots of wiggling when bots jump down from vents, etc
/*
	float ground;
	if (GetSimpleGroundHeightWithFloor(&from, &ground))
	{
		if (GetFeetZ() - ground > JumpHeight)
			leftClear = false;
	}
*/

	if ((cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 10.0f)
	{
		if (leftClear)
			UTIL_DrawBeamPoints(from, to, 1, 0, 255, 0);
		else
			UTIL_DrawBeamPoints(from, to, 1, 255, 0, 0);
	}

	from = feet - feelerOffset * lat;
	to = from + feelerLength * dir;

	bool rightClear = IsWalkableTraceLineClear(from, to, WALK_THRU_EVERYTHING);

/*
	// avoid ledges, too
	if (GetSimpleGroundHeightWithFloor(&from, &ground))
	{
		if (GetFeetZ() - ground > JumpHeight)
			rightClear = false;
	}
*/

	if ((cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 10.0f)
	{
		if (rightClear)
			UTIL_DrawBeamPoints(from, to, 1, 0, 255, 0);
		else
			UTIL_DrawBeamPoints(from, to, 1, 255, 0, 0);
	}

	const float avoidRange = (IsCrouching()) ? 150.0f : 300.0f; // 50.0f : 300.0f

	if (!rightClear)
	{
		if (leftClear)
		{
			// right hit, left clear - veer left
			*goalPosition = *goalPosition + avoidRange * lat;
		}
	}
	else if (!leftClear)
	{
		// right clear, left hit - veer right
		*goalPosition = *goalPosition - avoidRange * lat;
	}
}

// Move along the path. Return false if end of path reached.
CCSBot::PathResult CCSBot::UpdatePathMovement(bool allowSpeedChange)
{
	if (m_pathLength == 0)
		return PATH_FAILURE;

	if (cv_bot_walk.value != 0.0f)
		Walk();

	// If we are navigating a ladder, it overrides all other path movement until complete
	if (UpdateLadderMovement())
		return PROGRESSING;

	// ladder failure can destroy the path
	if (m_pathLength == 0)
		return PATH_FAILURE;

	// we are not supposed to be on a ladder - if we are, jump off
	if (IsOnLadder())
		Jump(MUST_JUMP);

	assert(m_pathIndex < m_pathLength);

	// Check if reached the end of the path
	bool nearEndOfPath = false;
	if (m_pathIndex >= m_pathLength - 1)
	{
		Vector toEnd(pev->origin.x, pev->origin.y, GetFeetZ());
		Vector d = GetPathEndpoint() - toEnd; // can't use 2D because path end may be below us (jump down)

		const float walkRange = 200.0f;

		// walk as we get close to the goal position to ensure we hit it
		if (d.IsLengthLessThan(walkRange))
		{
			// don't walk if crouching - too slow
			if (allowSpeedChange && !IsCrouching())
				Walk();

			// note if we are near the end of the path
			const float nearEndRange = 50.0f;
			if (d.IsLengthLessThan(nearEndRange))
				nearEndOfPath = true;

			const float closeEpsilon = 20.0f;
			if (d.IsLengthLessThan(closeEpsilon))
			{
				// reached goal position - path complete
				DestroyPath();

				// TODO: We should push and pop walk state here, in case we want to continue walking after reaching goal
				if (allowSpeedChange)
					Run();

				return END_OF_PATH;
			}
		}
	}

	// To keep us moving smoothly, we will move towards
	// a point farther ahead of us down our path.
	int prevIndex = 0;				// closest index on path just prior to where we are now
	const float aheadRange = 300.0f;
	int newIndex = FindPathPoint(aheadRange, &m_goalPosition, &prevIndex);

	// BOTPORT: Why is prevIndex sometimes -1?
	if (prevIndex < 0)
		prevIndex = 0;

	// if goal position is near to us, we must be about to go around a corner - so look ahead!
	const float nearCornerRange = 100.0f;
	if (m_pathIndex < m_pathLength - 1 && (m_goalPosition - pev->origin).IsLengthLessThan(nearCornerRange))
	{
		ClearLookAt();
		InhibitLookAround(0.5f);
	}

	// if we moved to a new node on the path, setup movement
	if (newIndex > m_pathIndex)
	{
		SetPathIndex(newIndex);
	}

	if (!IsUsingLadder())
	{
		// Crouching

		// if we are approaching a crouch area, crouch
		// if there are no crouch areas coming up, stand
		const float crouchRange = 50.0f;
		bool didCrouch = false;
		for (int i = prevIndex; i < m_pathLength; i++)
		{
			const CNavArea *to = m_path[i].area;

			// if there is a jump area on the way to the crouch area, don't crouch as it messes up the jump
			// unless we are already higher than the jump area - we must've jumped already but not moved into next area
			if ((to->GetAttributes() & NAV_JUMP)/* && to->GetCenter()->z > GetFeetZ()*/)
				break;

			Vector close;
			to->GetClosestPointOnArea(&pev->origin, &close);

			if ((close - pev->origin).Make2D().IsLengthGreaterThan(crouchRange))
				break;

			if (to->GetAttributes() & NAV_CROUCH)
			{
				Crouch();
				didCrouch = true;
				break;
			}
		}

		if (!didCrouch && !IsJumping())
		{
			// no crouch areas coming up
			StandUp();
		}
		// end crouching logic
	}

	// compute our forward facing angle
	m_forwardAngle = UTIL_VecToYaw(m_goalPosition - pev->origin);

	// Look farther down the path to "lead" our view around corners
	Vector toGoal;

	if (m_pathIndex == 0)
	{
		toGoal = m_path[1].pos;
	}
	else if (m_pathIndex < m_pathLength)
	{
		toGoal = m_path[m_pathIndex].pos - pev->origin;

		// actually aim our view farther down the path
		const float lookAheadRange = 500.0f;
		if (!m_path[m_pathIndex].ladder && !IsNearJump() && toGoal.Make2D().IsLengthLessThan(lookAheadRange))
		{
			float along = toGoal.Length2D();
			int i;
			for (i = m_pathIndex + 1; i < m_pathLength; i++)
			{
				Vector delta = m_path[i].pos - m_path[i - 1].pos;
				float segmentLength = delta.Length2D();

				if (along + segmentLength >= lookAheadRange)
				{
					// interpolate between points to keep look ahead point at fixed distance
					float t = (lookAheadRange - along) / (segmentLength + along);
					Vector target;

					if (t <= 0.0f)
						target = m_path[i - 1].pos;
					else if (t >= 1.0f)
						target = m_path[i].pos;
					else
						target = m_path[i - 1].pos + t * delta;

					toGoal = target - pev->origin;
					break;
				}

				// if we are coming up to a ladder or a jump, look at it
				if (m_path[i].ladder || (m_path[i].area->GetAttributes() & NAV_JUMP))
				{
					toGoal = m_path[i].pos - pev->origin;
					break;
				}

				along += segmentLength;
			}

			if (i == m_pathLength)
			{
				toGoal = GetPathEndpoint() - pev->origin;
			}
		}
	}
	else
	{
		toGoal = GetPathEndpoint() - pev->origin;
	}

	m_lookAheadAngle = UTIL_VecToYaw(toGoal);

	// initialize "adjusted" goal to current goal
	Vector adjustedGoal = m_goalPosition;

	// Use short "feelers" to veer away from close-range obstacles
	// Feelers come from our ankles, just above StepHeight, so we avoid short walls, too
	// Don't use feelers if very near the end of the path, or about to jump
	// TODO: Consider having feelers at several heights to deal with overhangs, etc.
	if (!nearEndOfPath && !IsNearJump() && !IsJumping())
	{
		FeelerReflexAdjustment(&adjustedGoal);
	}

	// draw debug visualization
	if ((cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 10.0f)
	{
		DrawPath();

		const Vector *pos = &m_path[m_pathIndex].pos;
		UTIL_DrawBeamPoints(*pos, *pos + Vector(0, 0, 50), 1, 255, 255, 0);
		UTIL_DrawBeamPoints(adjustedGoal, adjustedGoal + Vector(0, 0, 50), 1, 255, 0, 255);
		UTIL_DrawBeamPoints(pev->origin, adjustedGoal + Vector(0, 0, 50), 1, 255, 0, 255);
	}

	// dont use adjustedGoal, as it can vary wildly from the feeler adjustment
	if (!IsAttacking() && IsFriendInTheWay(&m_goalPosition))
	{
		if (!m_isWaitingBehindFriend)
		{
			m_isWaitingBehindFriend = true;

			const float politeDuration = 5.0f - 3.0f * GetProfile()->GetAggression();
			m_politeTimer.Start(politeDuration);
		}
		else if (m_politeTimer.IsElapsed())
		{
			// we have run out of patience
			m_isWaitingBehindFriend = false;
			ResetStuckMonitor();

			// repath to avoid clump of friends in the way
			DestroyPath();
		}
	}
	else if (m_isWaitingBehindFriend)
	{
		// we're done waiting for our friend to move
		m_isWaitingBehindFriend = false;
		ResetStuckMonitor();
	}

	// Move along our path if there are no friends blocking our way,
	// or we have run out of patience
	if (!m_isWaitingBehindFriend || m_politeTimer.IsElapsed())
	{
		// Move along path
		MoveTowardsPosition(&adjustedGoal);

		// Stuck check
		if (m_isStuck && !IsJumping())
		{
			Wiggle();
		}
	}

	// if our goal is high above us, we must have fallen
	bool didFall = false;
	if (m_goalPosition.z - GetFeetZ() > JumpCrouchHeight)
	{
		const float closeRange = 75.0f;
		Vector2D to(pev->origin.x - m_goalPosition.x, pev->origin.y - m_goalPosition.y);
		if (to.IsLengthLessThan(closeRange))
		{
			// we can't reach the goal position
			// check if we can reach the next node, in case this was a "jump down" situation
			if (m_pathIndex < m_pathLength - 1)
			{
				if (m_path[m_pathIndex + 1].pos.z - GetFeetZ() > JumpCrouchHeight)
				{
					// the next node is too high, too - we really did fall of the path
					didFall = true;
				}
			}
			else
			{
				// fell trying to get to the last node in the path
				didFall = true;
			}
		}
	}

	// This timeout check is needed if the bot somehow slips way off
	// of its path and cannot progress, but also moves around
	// enough that it never becomes "stuck"
	const float giveUpDuration = 5.0f; // 4.0f
	if (didFall || gpGlobals->time - m_areaEnteredTimestamp > giveUpDuration)
	{
		if (didFall)
		{
			PrintIfWatched("I fell off!\n");
		}

		// if we havent made any progress in a long time, give up
		if (m_pathIndex < m_pathLength - 1)
		{
			PrintIfWatched("Giving up trying to get to area #%d\n", m_path[m_pathIndex].area->GetID());
		}
		else
		{
			PrintIfWatched("Giving up trying to get to end of path\n");
		}

		Run();
		StandUp();
		DestroyPath();

		return PATH_FAILURE;
	}

	return PROGRESSING;
}

// Build trivial path to goal, assuming we are already in the same area
void CCSBot::BuildTrivialPath(const Vector *goal)
{
	m_pathIndex = 1;
	m_pathLength = 2;

	m_path[0].area = m_lastKnownArea;
	m_path[0].pos = pev->origin;
	m_path[0].pos.z = m_lastKnownArea->GetZ(&pev->origin);
	m_path[0].ladder = nullptr;
	m_path[0].how = NUM_TRAVERSE_TYPES;

	m_path[1].area = m_lastKnownArea;
	m_path[1].pos = *goal;
	m_path[1].pos.z = m_lastKnownArea->GetZ(goal);
	m_path[1].ladder = nullptr;
	m_path[1].how = NUM_TRAVERSE_TYPES;

	m_areaEnteredTimestamp = gpGlobals->time;
	m_spotEncounter = nullptr;
	m_pathLadder = nullptr;

	m_goalPosition = *goal;
}

// Compute shortest path to goal position via A* algorithm
// If 'goalArea' is NULL, path will get as close as it can.
bool CCSBot::ComputePath(CNavArea *goalArea, const Vector *goal, RouteType route)
{
	// Throttle re-pathing
	if (!m_repathTimer.IsElapsed())
		return false;

	// randomize to distribute CPU load
	m_repathTimer.Start(RANDOM_FLOAT(0.4f, 0.6f));

	DestroyPath();

	CNavArea *startArea = m_lastKnownArea;
	if (!startArea)
		return false;

	// note final specific position
	Vector pathEndPosition;

	if (!goal && !goalArea)
		return false;

	if (!goal)
		pathEndPosition = *goalArea->GetCenter();
	else
		pathEndPosition = *goal;

	// make sure path end position is on the ground
	if (goalArea)
		pathEndPosition.z = goalArea->GetZ(&pathEndPosition);
	else
		GetGroundHeight(&pathEndPosition, &pathEndPosition.z);

	// if we are already in the goal area, build trivial path
	if (startArea == goalArea)
	{
		BuildTrivialPath(&pathEndPosition);
		return true;
	}

	// Compute shortest path to goal
	CNavArea *closestArea = nullptr;
	PathCost pathCost(this, route);
	bool pathToGoalExists = NavAreaBuildPath(startArea, goalArea, goal, pathCost, &closestArea);

	CNavArea *effectiveGoalArea = (pathToGoalExists) ? goalArea : closestArea;

	// Build path by following parent links
	// get count
	int count = 0;
	CNavArea *area;
	for (area = effectiveGoalArea; area; area = area->GetParent())
	{
		count++;
	}

	// save room for endpoint
	if (count > MAX_PATH_LENGTH - 1)
		count = MAX_PATH_LENGTH - 1;

	if (count == 0)
		return false;

	if (count == 1)
	{
		BuildTrivialPath(&pathEndPosition);
		return true;
	}

	// build path
	m_pathLength = count;
	for (area = effectiveGoalArea; count && area; area = area->GetParent())
	{
		count--;
		m_path[count].area = area;
		m_path[count].how = area->GetParentHow();
	}

	// compute path positions
	if (ComputePathPositions() == false)
	{
		PrintIfWatched("Error building path\n");
		DestroyPath();
		return false;
	}

	if (!goal)
	{
		switch (m_path[m_pathLength - 1].how)
		{
		case GO_NORTH:
		case GO_SOUTH:
			pathEndPosition.x = m_path[m_pathLength - 1].pos.x;
			pathEndPosition.y = effectiveGoalArea->GetCenter()->y;
			break;

		case GO_EAST:
		case GO_WEST:
			pathEndPosition.x = effectiveGoalArea->GetCenter()->x;
			pathEndPosition.y = m_path[m_pathLength - 1].pos.y;
			break;
		}

		GetGroundHeight(&pathEndPosition, &pathEndPosition.z);
	}

	// append path end position
	m_path[m_pathLength].area = effectiveGoalArea;
	m_path[m_pathLength].pos = pathEndPosition;
	m_path[m_pathLength].ladder = nullptr;
	m_path[m_pathLength].how = NUM_TRAVERSE_TYPES;
	m_pathLength++;

	// do movement setup
	m_pathIndex = 1;
	m_areaEnteredTimestamp = gpGlobals->time;
	m_spotEncounter = nullptr;
	m_goalPosition = m_path[1].pos;

	if (m_path[1].ladder)
		SetupLadderMovement();
	else
		m_pathLadder = nullptr;

	return true;
}

// Return estimated distance left to travel along path
float CCSBot::GetPathDistanceRemaining() const
{
	if (!HasPath())
		return -1.0f;

	int idx = (m_pathIndex < m_pathLength) ? m_pathIndex : m_pathLength - 1;

	float dist = 0.0f;
	const Vector *prevCenter = m_path[m_pathIndex].area->GetCenter();

	for (int i = idx + 1; i < m_pathLength; i++)
	{
		dist += (*m_path[i].area->GetCenter() - *prevCenter).Length();
		prevCenter = m_path[i].area->GetCenter();
	}

	return dist;
}

// Draw a portion of our current path for debugging.
void CCSBot::DrawPath()
{
	if (!HasPath())
		return;

	for (int i = 1; i < m_pathLength; i++)
	{
		UTIL_DrawBeamPoints(m_path[i - 1].pos, m_path[i].pos, 2, 255, 75, 0);
	}

	Vector close;
	if (FindOurPositionOnPath(&close, true) >= 0)
	{
		UTIL_DrawBeamPoints(close + Vector(0, 0, 25), close, 1, 0, 255, 0);
		UTIL_DrawBeamPoints(close + Vector(25, 0, 0), close + Vector(-25, 0, 0), 1, 0, 255, 0);
		UTIL_DrawBeamPoints(close + Vector(0, 25, 0), close + Vector(0, -25, 0), 1, 0, 255, 0);
	}
}