/*
===========================================================================

Return to Castle Wolfenstein multiplayer GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).  

RTCW MP Source Code 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 3 of the License, or
(at your option) any later version.

RTCW MP Source Code 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 RTCW MP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/


/*****************************************************************************
 * name:		be_ai_move.c
 *
 * desc:		bot movement AI
 *
 *
 *****************************************************************************/

#include "../qcommon/q_shared.h"
#include "l_memory.h"
#include "l_libvar.h"
#include "l_utils.h"
#include "l_script.h"
#include "l_precomp.h"
#include "l_struct.h"
#include "aasfile.h"
#include "botlib.h"
#include "be_aas.h"
#include "be_aas_funcs.h"
#include "be_interface.h"

#include "be_ea.h"
#include "be_ai_goal.h"
#include "be_ai_move.h"


//#define DEBUG_AI_MOVE
//#define DEBUG_ELEVATOR
//#define DEBUG_GRAPPLE
//movement state

//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP must be set outside the movement code
typedef struct bot_movestate_s
{
	//input vars (all set outside the movement code)
	vec3_t origin;                              //origin of the bot
	vec3_t velocity;                            //velocity of the bot
	vec3_t viewoffset;                          //view offset
	int entitynum;                              //entity number of the bot
	int client;                                 //client number of the bot
	float thinktime;                            //time the bot thinks
	int presencetype;                           //presencetype of the bot
	vec3_t viewangles;                          //view angles of the bot
	//state vars
	int areanum;                                //area the bot is in
	int lastareanum;                            //last area the bot was in
	int lastgoalareanum;                        //last goal area number
	int lastreachnum;                           //last reachability number
	vec3_t lastorigin;                          //origin previous cycle
	float lasttime;
	int reachareanum;                           //area number of the reachabilty
	int moveflags;                              //movement flags
	int jumpreach;                              //set when jumped
	float grapplevisible_time;                  //last time the grapple was visible
	float lastgrappledist;                      //last distance to the grapple end
	float reachability_time;                    //time to use current reachability
	int avoidreach[MAX_AVOIDREACH];             //reachabilities to avoid
	float avoidreachtimes[MAX_AVOIDREACH];      //times to avoid the reachabilities
	int avoidreachtries[MAX_AVOIDREACH];        //number of tries before avoiding
} bot_movestate_t;

//used to avoid reachability links for some time after being used
// Ridah, disabled this to prevent wierd navigational behaviour (mostly by Zombie, since it's so slow)
//#define AVOIDREACH
#define AVOIDREACH_TIME         6       //avoid links for 6 seconds after use
#define AVOIDREACH_TRIES        4
//prediction times
#define PREDICTIONTIME_JUMP 3       //in seconds
#define PREDICTIONTIME_MOVE 2       //in seconds
//hook commands
#define CMD_HOOKOFF             "hookoff"
#define CMD_HOOKON              "hookon"
//weapon indexes for weapon jumping
#define WEAPONINDEX_ROCKET_LAUNCHER     5
#define WEAPONINDEX_BFG                 9

#define MODELTYPE_FUNC_PLAT     1
#define MODELTYPE_FUNC_BOB      2

float sv_maxstep;
float sv_maxbarrier;
float sv_gravity;
//type of model, func_plat or func_bobbing
int modeltypes[MAX_MODELS];

bot_movestate_t *botmovestates[MAX_CLIENTS + 1];

//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
int BotAllocMoveState( void ) {
	int i;

	for ( i = 1; i <= MAX_CLIENTS; i++ )
	{
		if ( !botmovestates[i] ) {
			botmovestates[i] = GetClearedMemory( sizeof( bot_movestate_t ) );
			return i;
		} //end if
	} //end for
	return 0;
} //end of the function BotAllocMoveState
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
void BotFreeMoveState( int handle ) {
	if ( handle <= 0 || handle > MAX_CLIENTS ) {
		botimport.Print( PRT_FATAL, "move state handle %d out of range\n", handle );
		return;
	} //end if
	if ( !botmovestates[handle] ) {
		botimport.Print( PRT_FATAL, "invalid move state %d\n", handle );
		return;
	} //end if
	FreeMemory( botmovestates[handle] );
	botmovestates[handle] = NULL;
} //end of the function BotFreeMoveState
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
bot_movestate_t *BotMoveStateFromHandle( int handle ) {
	if ( handle <= 0 || handle > MAX_CLIENTS ) {
		botimport.Print( PRT_FATAL, "move state handle %d out of range\n", handle );
		return NULL;
	} //end if
	if ( !botmovestates[handle] ) {
		botimport.Print( PRT_FATAL, "invalid move state %d\n", handle );
		return NULL;
	} //end if
	return botmovestates[handle];
} //end of the function BotMoveStateFromHandle

// Ridah, provide a means of resetting the avoidreach, so if a bot stops moving, they don't avoid the area they were heading for
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
void BotInitAvoidReach( int handle ) {
	bot_movestate_t *ms;

	ms = BotMoveStateFromHandle( handle );
	if ( !ms ) {
		return;
	}

	memset( ms->avoidreach, 0, sizeof( ms->avoidreach ) );
	memset( ms->avoidreachtries, 0, sizeof( ms->avoidreachtries ) );
	memset( ms->avoidreachtimes, 0, sizeof( ms->avoidreachtimes ) );
}
// done.

//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
void BotInitMoveState( int handle, bot_initmove_t *initmove ) {
	bot_movestate_t *ms;

	ms = BotMoveStateFromHandle( handle );
	if ( !ms ) {
		return;
	}
	VectorCopy( initmove->origin, ms->origin );
	VectorCopy( initmove->velocity, ms->velocity );
	VectorCopy( initmove->viewoffset, ms->viewoffset );
	ms->entitynum = initmove->entitynum;
	ms->client = initmove->client;
	ms->thinktime = initmove->thinktime;
	ms->presencetype = initmove->presencetype;
	VectorCopy( initmove->viewangles, ms->viewangles );
	//
	ms->moveflags &= ~MFL_ONGROUND;
	if ( initmove->or_moveflags & MFL_ONGROUND ) {
		ms->moveflags |= MFL_ONGROUND;
	}
	ms->moveflags &= ~MFL_TELEPORTED;
	if ( initmove->or_moveflags & MFL_TELEPORTED ) {
		ms->moveflags |= MFL_TELEPORTED;
	}
	ms->moveflags &= ~MFL_WATERJUMP;
	if ( initmove->or_moveflags & MFL_WATERJUMP ) {
		ms->moveflags |= MFL_WATERJUMP;
	}
	ms->moveflags &= ~MFL_WALK;
	if ( initmove->or_moveflags & MFL_WALK ) {
		ms->moveflags |= MFL_WALK;
	}
} //end of the function BotInitMoveState
//========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//========================================================================
float AngleDiff( float ang1, float ang2 ) {
	float diff;

	diff = ang1 - ang2;
	if ( ang1 > ang2 ) {
		if ( diff > 180.0 ) {
			diff -= 360.0;
		}
	} //end if
	else
	{
		if ( diff < -180.0 ) {
			diff += 360.0;
		}
	} //end else
	return diff;
} //end of the function AngleDiff
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotFuzzyPointReachabilityArea( vec3_t origin ) {
	int firstareanum, j, x, y, z;
	int areas[10], numareas, areanum, bestareanum;
	float dist, bestdist;
	vec3_t points[10], v, end;

	firstareanum = 0;
	areanum = AAS_PointAreaNum( origin );
	if ( areanum ) {
		firstareanum = areanum;
		if ( AAS_AreaReachability( areanum ) ) {
			return areanum;
		}
	} //end if
	VectorCopy( origin, end );
	end[2] += 4;
	numareas = AAS_TraceAreas( origin, end, areas, points, 10 );
	for ( j = 0; j < numareas; j++ )
	{
		if ( AAS_AreaReachability( areas[j] ) ) {
			return areas[j];
		}
	} //end for
	bestdist = 999999;
	bestareanum = 0;
	for ( z = 1; z >= -1; z -= 1 )
	{
		for ( x = 1; x >= -1; x -= 1 )
		{
			for ( y = 1; y >= -1; y -= 1 )
			{
				VectorCopy( origin, end );
				// Ridah, increased this for Wolf larger bounding boxes
				end[0] += x * 16; //8;
				end[1] += y * 16; //8;
				end[2] += z * 24; //12;
				numareas = AAS_TraceAreas( origin, end, areas, points, 10 );
				for ( j = 0; j < numareas; j++ )
				{
					if ( AAS_AreaReachability( areas[j] ) ) {
						VectorSubtract( points[j], origin, v );
						dist = VectorLength( v );
						if ( dist < bestdist ) {
							bestareanum = areas[j];
							bestdist = dist;
						} //end if
					} //end if
					if ( !firstareanum ) {
						firstareanum = areas[j];
					}
				} //end for
			} //end for
		} //end for
		if ( bestareanum ) {
			return bestareanum;
		}
	} //end for
	return firstareanum;
} //end of the function BotFuzzyPointReachabilityArea
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotReachabilityArea( vec3_t origin, int client ) {
	int modelnum, modeltype, reachnum, areanum;
	aas_reachability_t reach;
	vec3_t org, end, mins, maxs, up = {0, 0, 1};
	bsp_trace_t bsptrace;
	aas_trace_t trace;

	//check if the bot is standing on something
	AAS_PresenceTypeBoundingBox( PRESENCE_CROUCH, mins, maxs );
	VectorMA( origin, -3, up, end );
	bsptrace = AAS_Trace( origin, mins, maxs, end, client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP );
	if ( !bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE ) {
		//if standing on the world the bot should be in a valid area
		if ( bsptrace.ent == ENTITYNUM_WORLD ) {
			return BotFuzzyPointReachabilityArea( origin );
		} //end if

		modelnum = AAS_EntityModelindex( bsptrace.ent );
		modeltype = modeltypes[modelnum];

		//if standing on a func_plat or func_bobbing then the bot is assumed to be
		//in the area the reachability points to
		if ( modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB ) {
			reachnum = AAS_NextModelReachability( 0, modelnum );
			if ( reachnum ) {
				AAS_ReachabilityFromNum( reachnum, &reach );
				return reach.areanum;
			} //end if
		} //end else if

		//if the bot is swimming the bot should be in a valid area
		if ( AAS_Swimming( origin ) ) {
			return BotFuzzyPointReachabilityArea( origin );
		} //end if
		  //
		areanum = BotFuzzyPointReachabilityArea( origin );
		//if the bot is in an area with reachabilities
		if ( areanum && AAS_AreaReachability( areanum ) ) {
			return areanum;
		}
		//trace down till the ground is hit because the bot is standing on some other entity
		VectorCopy( origin, org );
		VectorCopy( org, end );
		end[2] -= 800;
		trace = AAS_TraceClientBBox( org, end, PRESENCE_CROUCH, -1 );
		if ( !trace.startsolid ) {
			VectorCopy( trace.endpos, org );
		} //end if
		  //
		return BotFuzzyPointReachabilityArea( org );
	} //end if
	  //
	return BotFuzzyPointReachabilityArea( origin );
} //end of the function BotReachabilityArea
//===========================================================================
// returns the reachability area the bot is in
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
/*
int BotReachabilityArea(vec3_t origin, int testground)
{
	int firstareanum, i, j, x, y, z;
	int areas[10], numareas, areanum, bestareanum;
	float dist, bestdist;
	vec3_t org, end, points[10], v;
	aas_trace_t trace;

	firstareanum = 0;
	for (i = 0; i < 2; i++)
	{
		VectorCopy(origin, org);
		//if test at the ground (used when bot is standing on an entity)
		if (i > 0)
		{
			VectorCopy(origin, end);
			end[2] -= 800;
			trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1);
			if (!trace.startsolid)
			{
				VectorCopy(trace.endpos, org);
			} //end if
		} //end if

		firstareanum = 0;
		areanum = AAS_PointAreaNum(org);
		if (areanum)
		{
			firstareanum = areanum;
			if (AAS_AreaReachability(areanum)) return areanum;
		} //end if
		bestdist = 999999;
		bestareanum = 0;
		for (z = 1; z >= -1; z -= 1)
		{
			for (x = 1; x >= -1; x -= 1)
			{
				for (y = 1; y >= -1; y -= 1)
				{
					VectorCopy(org, end);
					end[0] += x * 8;
					end[1] += y * 8;
					end[2] += z * 12;
					numareas = AAS_TraceAreas(org, end, areas, points, 10);
					for (j = 0; j < numareas; j++)
					{
						if (AAS_AreaReachability(areas[j]))
						{
							VectorSubtract(points[j], org, v);
							dist = VectorLength(v);
							if (dist < bestdist)
							{
								bestareanum = areas[j];
								bestdist = dist;
							} //end if
						} //end if
					} //end for
				} //end for
			} //end for
			if (bestareanum) return bestareanum;
		} //end for
		if (!testground) break;
	} //end for
//#ifdef DEBUG
	//botimport.Print(PRT_MESSAGE, "no reachability area\n");
//#endif //DEBUG
	return firstareanum;
} //end of the function BotReachabilityArea*/
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotOnMover( vec3_t origin, int entnum, aas_reachability_t *reach ) {
	int i, modelnum;
	vec3_t mins, maxs, modelorigin, org, end;
	vec3_t angles = {0, 0, 0};
	vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8};
	bsp_trace_t trace;

	modelnum = reach->facenum & 0x0000FFFF;
	//get some bsp model info
	AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, NULL );
	//
	if ( !AAS_OriginOfEntityWithModelNum( modelnum, modelorigin ) ) {
		botimport.Print( PRT_MESSAGE, "no entity with model %d\n", modelnum );
		return qfalse;
	} //end if
	  //
	for ( i = 0; i < 2; i++ )
	{
		if ( origin[i] > modelorigin[i] + maxs[i] + 16 ) {
			return qfalse;
		}
		if ( origin[i] < modelorigin[i] + mins[i] - 16 ) {
			return qfalse;
		}
	} //end for
	  //
	VectorCopy( origin, org );
	org[2] += 24;
	VectorCopy( origin, end );
	end[2] -= 48;
	//
	trace = AAS_Trace( org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP );
	if ( !trace.startsolid && !trace.allsolid ) {
		//NOTE: the reachability face number is the model number of the elevator
		if ( trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum( trace.ent ) == modelnum ) {
			return qtrue;
		} //end if
	} //end if
	return qfalse;
} //end of the function BotOnMover
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int MoverDown( aas_reachability_t *reach ) {
	int modelnum;
	vec3_t mins, maxs, origin;
	vec3_t angles = {0, 0, 0};

	modelnum = reach->facenum & 0x0000FFFF;
	//get some bsp model info
	AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, origin );
	//
	if ( !AAS_OriginOfEntityWithModelNum( modelnum, origin ) ) {
		botimport.Print( PRT_MESSAGE, "no entity with model %d\n", modelnum );
		return qfalse;
	} //end if
	  //if the top of the plat is below the reachability start point
	if ( origin[2] + maxs[2] < reach->start[2] ) {
		return qtrue;
	}
	return qfalse;
} //end of the function MoverDown
//========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//========================================================================
void BotSetBrushModelTypes( void ) {
	int ent, modelnum;
	char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY];

	memset( modeltypes, 0, MAX_MODELS * sizeof( int ) );
	//
	for ( ent = AAS_NextBSPEntity( 0 ); ent; ent = AAS_NextBSPEntity( ent ) )
	{
		if ( !AAS_ValueForBSPEpairKey( ent, "classname", classname, MAX_EPAIRKEY ) ) {
			continue;
		}
		if ( !AAS_ValueForBSPEpairKey( ent, "model", model, MAX_EPAIRKEY ) ) {
			continue;
		}
		if ( model[0] ) {
			modelnum = atoi( model + 1 );
		} else { modelnum = 0;}

		if ( modelnum < 0 || modelnum >= MAX_MODELS ) {
			botimport.Print( PRT_MESSAGE, "entity %s model number out of range\n", classname );
			continue;
		} //end if

		if ( !strcmp( classname, "func_bobbing" ) ) {
			modeltypes[modelnum] = MODELTYPE_FUNC_BOB;
		} else if ( !strcmp( classname, "func_plat" ) ) {
			modeltypes[modelnum] = MODELTYPE_FUNC_PLAT;
		}
	} //end for
} //end of the function BotSetBrushModelTypes
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotOnTopOfEntity( bot_movestate_t *ms ) {
	vec3_t mins, maxs, end, up = {0, 0, 1};
	bsp_trace_t trace;

	AAS_PresenceTypeBoundingBox( ms->presencetype, mins, maxs );
	VectorMA( ms->origin, -3, up, end );
	trace = AAS_Trace( ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP );
	if ( !trace.startsolid && ( trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE ) ) {
		return trace.ent;
	} //end if
	return -1;
} //end of the function BotOnTopOfEntity
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotValidTravel( vec3_t origin, aas_reachability_t *reach, int travelflags ) {
	//if the reachability uses an unwanted travel type
	if ( AAS_TravelFlagForType( reach->traveltype ) & ~travelflags ) {
		return qfalse;
	}
	//don't go into areas with bad travel types
	if ( AAS_AreaContentsTravelFlag( reach->areanum ) & ~travelflags ) {
		return qfalse;
	}
	return qtrue;
} //end of the function BotValidTravel
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotAddToAvoidReach( bot_movestate_t *ms, int number, float avoidtime ) {
	int i;

	for ( i = 0; i < MAX_AVOIDREACH; i++ )
	{
		if ( ms->avoidreach[i] == number ) {
			if ( ms->avoidreachtimes[i] > AAS_Time() ) {
				ms->avoidreachtries[i]++;
			} else { ms->avoidreachtries[i] = 1;}
			ms->avoidreachtimes[i] = AAS_Time() + avoidtime;
			return;
		} //end if
	} //end for
	  //add the reachability to the reachabilities to avoid for a while
	for ( i = 0; i < MAX_AVOIDREACH; i++ )
	{
		if ( ms->avoidreachtimes[i] < AAS_Time() ) {
			ms->avoidreach[i] = number;
			ms->avoidreachtimes[i] = AAS_Time() + avoidtime;
			ms->avoidreachtries[i] = 1;
			return;
		} //end if
	} //end for
} //end of the function BotAddToAvoidReach
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
//__inline int AAS_AreaContentsTravelFlag(int areanum);

int BotGetReachabilityToGoal( vec3_t origin, int areanum, int entnum,
							  int lastgoalareanum, int lastareanum,
							  int *avoidreach, float *avoidreachtimes, int *avoidreachtries,
							  bot_goal_t *goal, int travelflags, int movetravelflags ) {
	int t, besttime, bestreachnum, reachnum;
	aas_reachability_t reach;

	//if not in a valid area
	if ( !areanum ) {
		return 0;
	}
	//
	if ( AAS_AreaDoNotEnter( areanum ) || AAS_AreaDoNotEnter( goal->areanum ) ) {
		travelflags |= TFL_DONOTENTER;
		movetravelflags |= TFL_DONOTENTER;
	} //end if
	if ( AAS_AreaDoNotEnterLarge( areanum ) || AAS_AreaDoNotEnterLarge( goal->areanum ) ) {
		travelflags |= TFL_DONOTENTER_LARGE;
		movetravelflags |= TFL_DONOTENTER_LARGE;
	} //end if
	  //use the routing to find the next area to go to
	besttime = 0;
	bestreachnum = 0;
	//
	for ( reachnum = AAS_NextAreaReachability( areanum, 0 ); reachnum;
		  reachnum = AAS_NextAreaReachability( areanum, reachnum ) )
	{
#ifdef AVOIDREACH
		int i;
		//check if it isn't a reachability to avoid
		for ( i = 0; i < MAX_AVOIDREACH; i++ )
		{
			if ( avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time() ) {
				break;
			}
		} //end for
		if ( i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES ) {
#ifdef DEBUG
			if ( botDeveloper ) {
				botimport.Print( PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i] );
			} //end if
#endif //DEBUG
			continue;
		} //end if
#endif //AVOIDREACH
	   //get the reachability from the number
		AAS_ReachabilityFromNum( reachnum, &reach );
		//NOTE: do not go back to the previous area if the goal didn't change
		//NOTE: is this actually avoidance of local routing minima between two areas???
		if ( lastgoalareanum == goal->areanum && reach.areanum == lastareanum ) {
			continue;
		}
		//if (AAS_AreaContentsTravelFlag(reach.areanum) & ~travelflags) continue;
		//if the travel isn't valid
		if ( !BotValidTravel( origin, &reach, movetravelflags ) ) {
			continue;
		}
		//get the travel time
		t = AAS_AreaTravelTimeToGoalArea( reach.areanum, reach.end, goal->areanum, travelflags );
		//if the goal area isn't reachable from the reachable area
		if ( !t ) {
			continue;
		}

		// Ridah, if this sends us to a looped route, ignore it
//		if (AAS_AreaTravelTimeToGoalArea(areanum, reach.start, goal->areanum, travelflags) + reach.traveltime == t)
//			continue;

		//add the travel time towards the area
		// Ridah, not sure why this was disabled, but it causes looped links in the route-cache
		//t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start);
		t += reach.traveltime + AAS_AreaTravelTime( areanum, origin, reach.start );

		// Ridah, if there exists other entities in this area, avoid it
//		if (reach.areanum != goal->areanum && AAS_IsEntityInArea( entnum, goal->entitynum, reach.areanum )) {
//			t += 50;
//		}

		//if the travel time is better than the ones already found
		if ( !besttime || t < besttime ) {
			besttime = t;
			bestreachnum = reachnum;
		} //end if
	} //end for
	  //
	return bestreachnum;
} //end of the function BotGetReachabilityToGoal
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotAddToTarget( vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target ) {
	vec3_t dir;
	float curdist;

	VectorSubtract( end, start, dir );
	curdist = VectorNormalize( dir );
	if ( *dist + curdist < maxdist ) {
		VectorCopy( end, target );
		*dist += curdist;
		return qfalse;
	} //end if
	else
	{
		VectorMA( start, maxdist - *dist, dir, target );
		*dist = maxdist;
		return qtrue;
	} //end else
} //end of the function BotAddToTarget

int BotMovementViewTarget( int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target ) {
	aas_reachability_t reach;
	int reachnum = 0, lastareanum;
	bot_movestate_t *ms;
	vec3_t end;
	float dist;

	ms = BotMoveStateFromHandle( movestate );
	if ( !ms ) {
		return qfalse;
	}
	//if the bot has no goal or no last reachability
	if ( !ms->lastreachnum || !goal ) {
		return qfalse;
	}

	reachnum = ms->lastreachnum;
	VectorCopy( ms->origin, end );
	lastareanum = ms->lastareanum;
	dist = 0;
	while ( reachnum && dist < lookahead )
	{
		AAS_ReachabilityFromNum( reachnum, &reach );
		if ( BotAddToTarget( end, reach.start, lookahead, &dist, target ) ) {
			return qtrue;
		}
		//never look beyond teleporters
		if ( reach.traveltype == TRAVEL_TELEPORT ) {
			return qtrue;
		}
		//don't add jump pad distances
		if ( reach.traveltype != TRAVEL_JUMPPAD &&
			 reach.traveltype != TRAVEL_ELEVATOR &&
			 reach.traveltype != TRAVEL_FUNCBOB ) {
			if ( BotAddToTarget( reach.start, reach.end, lookahead, &dist, target ) ) {
				return qtrue;
			}
		} //end if
		reachnum = BotGetReachabilityToGoal( reach.end, reach.areanum, -1,
											 ms->lastgoalareanum, lastareanum,
											 ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
											 goal, travelflags, travelflags );
		VectorCopy( reach.end, end );
		lastareanum = reach.areanum;
		if ( lastareanum == goal->areanum ) {
			BotAddToTarget( reach.end, goal->origin, lookahead, &dist, target );
			return qtrue;
		} //end if
	} //end while
	  //
	return qfalse;
} //end of the function BotMovementViewTarget
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotVisible( int ent, vec3_t eye, vec3_t target ) {
	bsp_trace_t trace;

	trace = AAS_Trace( eye, NULL, NULL, target, ent, CONTENTS_SOLID | CONTENTS_PLAYERCLIP );
	if ( trace.fraction >= 1 ) {
		return qtrue;
	}
	return qfalse;
} //end of the function BotVisible
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotPredictVisiblePosition( vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target ) {
	aas_reachability_t reach;
	int reachnum, lastgoalareanum, lastareanum, i;
	int avoidreach[MAX_AVOIDREACH];
	float avoidreachtimes[MAX_AVOIDREACH];
	int avoidreachtries[MAX_AVOIDREACH];
	vec3_t end;

	//if the bot has no goal or no last reachability
	if ( !goal ) {
		return qfalse;
	}
	//if the areanum is not valid
	if ( !areanum ) {
		return qfalse;
	}
	//if the goal areanum is not valid
	if ( !goal->areanum ) {
		return qfalse;
	}

	memset( avoidreach, 0, MAX_AVOIDREACH * sizeof( int ) );
	lastgoalareanum = goal->areanum;
	lastareanum = areanum;
	VectorCopy( origin, end );
	//only do 20 hops
	for ( i = 0; i < 20 && ( areanum != goal->areanum ); i++ )
	{
		//
		reachnum = BotGetReachabilityToGoal( end, areanum, -1,
											 lastgoalareanum, lastareanum,
											 avoidreach, avoidreachtimes, avoidreachtries,
											 goal, travelflags, travelflags );
		if ( !reachnum ) {
			return qfalse;
		}
		AAS_ReachabilityFromNum( reachnum, &reach );
		//
		if ( BotVisible( goal->entitynum, goal->origin, reach.start ) ) {
			VectorCopy( reach.start, target );
			return qtrue;
		} //end if
		  //
		if ( BotVisible( goal->entitynum, goal->origin, reach.end ) ) {
			VectorCopy( reach.end, target );
			return qtrue;
		} //end if
		  //
		if ( reach.areanum == goal->areanum ) {
			VectorCopy( reach.end, target );
			return qtrue;
		} //end if
		  //
		lastareanum = areanum;
		areanum = reach.areanum;
		VectorCopy( reach.end, end );
		//
	} //end while
	  //
	return qfalse;
} //end of the function BotPredictVisiblePosition
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void MoverBottomCenter( aas_reachability_t *reach, vec3_t bottomcenter ) {
	int modelnum;
	vec3_t mins, maxs, origin, mids;
	vec3_t angles = {0, 0, 0};

	modelnum = reach->facenum & 0x0000FFFF;
	//get some bsp model info
	AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, origin );
	//
	if ( !AAS_OriginOfEntityWithModelNum( modelnum, origin ) ) {
		botimport.Print( PRT_MESSAGE, "no entity with model %d\n", modelnum );
	} //end if
	  //get a point just above the plat in the bottom position
	VectorAdd( mins, maxs, mids );
	VectorMA( origin, 0.5, mids, bottomcenter );
	bottomcenter[2] = reach->start[2];
} //end of the function MoverBottomCenter
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
float BotGapDistance( vec3_t origin, vec3_t hordir, int entnum ) {
	int dist;
	float startz;
	vec3_t start, end;
	aas_trace_t trace;

	//do gap checking
	//startz = origin[2];
	//this enables walking down stairs more fluidly
	{
		VectorCopy( origin, start );
		VectorCopy( origin, end );
		end[2] -= 60;
		trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, entnum );
		if ( trace.fraction >= 1 ) {
			return 1;
		}
		startz = trace.endpos[2] + 1;
	}
	//
	for ( dist = 8; dist <= 100; dist += 8 )
	{
		VectorMA( origin, dist, hordir, start );
		start[2] = startz + 24;
		VectorCopy( start, end );
		end[2] -= 48 + sv_maxbarrier;
		trace = AAS_TraceClientBBox( start, end, PRESENCE_CROUCH, entnum );
		//if solid is found the bot can't walk any further and fall into a gap
		if ( !trace.startsolid ) {
			//if it is a gap
			if ( trace.endpos[2] < startz - sv_maxstep - 8 ) {
				VectorCopy( trace.endpos, end );
				end[2] -= 20;
				if ( AAS_PointContents( end ) & ( CONTENTS_WATER | CONTENTS_SLIME ) ) {
					break;                                                              //----(SA)	modified since slime is no longer deadly
				}
//				if (AAS_PointContents(end) & CONTENTS_WATER) break;
				//if a gap is found slow down
				//botimport.Print(PRT_MESSAGE, "gap at %i\n", dist);
				return dist;
			} //end if
			startz = trace.endpos[2];
		} //end if
	} //end for
	return 0;
} //end of the function BotGapDistance
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotCheckBarrierJump( bot_movestate_t *ms, vec3_t dir, float speed ) {
	vec3_t start, hordir, end;
	aas_trace_t trace;

	VectorCopy( ms->origin, end );
	end[2] += sv_maxbarrier;
	//trace right up
	trace = AAS_TraceClientBBox( ms->origin, end, PRESENCE_NORMAL, ms->entitynum );
	//this shouldn't happen... but we check anyway
	if ( trace.startsolid ) {
		return qfalse;
	}
	//if very low ceiling it isn't possible to jump up to a barrier
	if ( trace.endpos[2] - ms->origin[2] < sv_maxstep ) {
		return qfalse;
	}
	//
	hordir[0] = dir[0];
	hordir[1] = dir[1];
	hordir[2] = 0;
	VectorNormalize( hordir );
	VectorMA( ms->origin, ms->thinktime * speed * 0.5, hordir, end );
	VectorCopy( trace.endpos, start );
	end[2] = trace.endpos[2];
	//trace from previous trace end pos horizontally in the move direction
	trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, ms->entitynum );
	//again this shouldn't happen
	if ( trace.startsolid ) {
		return qfalse;
	}
	//
	VectorCopy( trace.endpos, start );
	VectorCopy( trace.endpos, end );
	end[2] = ms->origin[2];
	//trace down from the previous trace end pos
	trace = AAS_TraceClientBBox( start, end, PRESENCE_NORMAL, ms->entitynum );
	//if solid
	if ( trace.startsolid ) {
		return qfalse;
	}
	//if no obstacle at all
	if ( trace.fraction >= 1.0 ) {
		return qfalse;
	}
	//if less than the maximum step height
	if ( trace.endpos[2] - ms->origin[2] < sv_maxstep ) {
		return qfalse;
	}
	//
	EA_Jump( ms->client );
	EA_Move( ms->client, hordir, speed );
	ms->moveflags |= MFL_BARRIERJUMP;
	//there is a barrier
	return qtrue;
} //end of the function BotCheckBarrierJump
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotSwimInDirection( bot_movestate_t *ms, vec3_t dir, float speed, int type ) {
	vec3_t normdir;

	VectorCopy( dir, normdir );
	VectorNormalize( normdir );
	EA_Move( ms->client, normdir, speed );
	return qtrue;
} //end of the function BotSwimInDirection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotWalkInDirection( bot_movestate_t *ms, vec3_t dir, float speed, int type ) {
	vec3_t hordir, cmdmove, velocity, tmpdir, origin;
	int presencetype, maxframes, cmdframes, stopevent;
	aas_clientmove_t move;
	float dist;

	//if the bot is on the ground
	if ( ms->moveflags & MFL_ONGROUND ) {
		//if there is a barrier the bot can jump on
		if ( BotCheckBarrierJump( ms, dir, speed ) ) {
			return qtrue;
		}
		//remove barrier jump flag
		ms->moveflags &= ~MFL_BARRIERJUMP;
		//get the presence type for the movement
		if ( ( type & MOVE_CROUCH ) && !( type & MOVE_JUMP ) ) {
			presencetype = PRESENCE_CROUCH;
		} else { presencetype = PRESENCE_NORMAL;}
		//horizontal direction
		hordir[0] = dir[0];
		hordir[1] = dir[1];
		hordir[2] = 0;
		VectorNormalize( hordir );
		//if the bot is not supposed to jump
		if ( !( type & MOVE_JUMP ) ) {
			//if there is a gap, try to jump over it
			if ( BotGapDistance( ms->origin, hordir, ms->entitynum ) > 0 ) {
				type |= MOVE_JUMP;
			}
		} //end if
		  //get command movement
		VectorScale( hordir, speed, cmdmove );
		VectorCopy( ms->velocity, velocity );
		//
		if ( type & MOVE_JUMP ) {
			//botimport.Print(PRT_MESSAGE, "trying jump\n");
			cmdmove[2] = 400;
			maxframes = PREDICTIONTIME_JUMP / 0.1;
			cmdframes = 1;
			stopevent = SE_HITGROUND | SE_HITGROUNDDAMAGE |
						SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA;
		} //end if
		else
		{
			maxframes = 2;
			cmdframes = 2;
			stopevent = SE_HITGROUNDDAMAGE |
						SE_ENTERWATER | SE_ENTERSLIME | SE_ENTERLAVA;
		} //end else
		  //AAS_ClearShownDebugLines();
		  //
		VectorCopy( ms->origin, origin );
		origin[2] += 0.5;
		AAS_PredictClientMovement( &move, ms->entitynum, origin, presencetype, qtrue,
								   velocity, cmdmove, cmdframes, maxframes, 0.1,
								   stopevent, 0, qfalse ); //qtrue);
		//if prediction time wasn't enough to fully predict the movement
		if ( move.frames >= maxframes && ( type & MOVE_JUMP ) ) {
			//botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client);
			return qfalse;
		} //end if
		  //don't enter slime or lava and don't fall from too high
		if ( move.stopevent & ( SE_ENTERLAVA | SE_HITGROUNDDAMAGE ) ) {   //----(SA)	modified since slime is no longer deadly
//		if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE))
			//botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client);
			//if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n");
			//if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n");
			//if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n");
			return qfalse;
		} //end if
		  //if ground was hit
		if ( move.stopevent & SE_HITGROUND ) {
			//check for nearby gap
			VectorNormalize2( move.velocity, tmpdir );
			dist = BotGapDistance( move.endpos, tmpdir, ms->entitynum );
			if ( dist > 0 ) {
				return qfalse;
			}
			//
			dist = BotGapDistance( move.endpos, hordir, ms->entitynum );
			if ( dist > 0 ) {
				return qfalse;
			}
		} //end if
		  //get horizontal movement
		tmpdir[0] = move.endpos[0] - ms->origin[0];
		tmpdir[1] = move.endpos[1] - ms->origin[1];
		tmpdir[2] = 0;
		//
		//AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE);
		//the bot is blocked by something
		if ( VectorLength( tmpdir ) < speed * ms->thinktime * 0.5 ) {
			return qfalse;
		}
		//perform the movement
		if ( type & MOVE_JUMP ) {
			EA_Jump( ms->client );
		}
		if ( type & MOVE_CROUCH ) {
			EA_Crouch( ms->client );
		}
		EA_Move( ms->client, hordir, speed );
		//movement was succesfull
		return qtrue;
	} //end if
	else
	{
		if ( ms->moveflags & MFL_BARRIERJUMP ) {
			//if near the top or going down
			if ( ms->velocity[2] < 50 ) {
				EA_Move( ms->client, dir, speed );
			} //end if
		} //end if
		  //FIXME: do air control to avoid hazards
		return qtrue;
	} //end else
} //end of the function BotWalkInDirection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotMoveInDirection( int movestate, vec3_t dir, float speed, int type ) {
	bot_movestate_t *ms;

	ms = BotMoveStateFromHandle( movestate );
	if ( !ms ) {
		return qfalse;
	}
	//if swimming
	if ( AAS_Swimming( ms->origin ) ) {
		return BotSwimInDirection( ms, dir, speed, type );
	} //end if
	else
	{
		return BotWalkInDirection( ms, dir, speed, type );
	} //end else
} //end of the function BotMoveInDirection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int Intersection( vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out ) {
	float x1, dx1, dy1, x2, dx2, dy2, d;

	dx1 = p2[0] - p1[0];
	dy1 = p2[1] - p1[1];
	dx2 = p4[0] - p3[0];
	dy2 = p4[1] - p3[1];

	d = dy1 * dx2 - dx1 * dy2;
	if ( d != 0 ) {
		x1 = p1[1] * dx1 - p1[0] * dy1;
		x2 = p3[1] * dx2 - p3[0] * dy2;
		out[0] = (int) ( ( dx1 * x2 - dx2 * x1 ) / d );
		out[1] = (int) ( ( dy1 * x2 - dy2 * x1 ) / d );
		return qtrue;
	} //end if
	else
	{
		return qfalse;
	} //end else
} //end of the function Intersection
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotCheckBlocked( bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result ) {
	vec3_t mins, maxs, end, up = {0, 0, 1};
	bsp_trace_t trace;

	//test for entities obstructing the bot's path
	AAS_PresenceTypeBoundingBox( ms->presencetype, mins, maxs );
	//
	if ( fabs( DotProduct( dir, up ) ) < 0.7 ) {
		mins[2] += sv_maxstep; //if the bot can step on
		maxs[2] -= 10; //a little lower to avoid low ceiling
	} //end if
	VectorMA( ms->origin, 3, dir, end );
	trace = AAS_Trace( ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY );
	//if not started in solid and not hitting the world entity
	if ( !trace.startsolid && ( trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE ) ) {
		result->blocked = qtrue;
		result->blockentity = trace.ent;
#ifdef DEBUG
		//botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client);
#endif //DEBUG
	} //end if
	  //if not in an area with reachability
	else if ( checkbottom && !AAS_AreaReachability( ms->areanum ) ) {
		//check if the bot is standing on something
		AAS_PresenceTypeBoundingBox( ms->presencetype, mins, maxs );
		VectorMA( ms->origin, -3, up, end );
		trace = AAS_Trace( ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID | CONTENTS_PLAYERCLIP );
		if ( !trace.startsolid && ( trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE ) ) {
			result->blocked = qtrue;
			result->blockentity = trace.ent;
			result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
#ifdef DEBUG
			//botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client);
#endif //DEBUG
		} //end if
	} //end else
} //end of the function BotCheckBlocked
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Walk( bot_movestate_t *ms, aas_reachability_t *reach ) {
	float dist, speed;
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	//first walk straight to the reachability start
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize( hordir );
	//
	BotCheckBlocked( ms, hordir, qtrue, &result );
	//
	// Ridah, tweaked this
//	if (dist < 10)
	if ( dist < 32 ) {
		//walk straight to the reachability end
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		dist = VectorNormalize( hordir );
	} //end if
	  //if going towards a crouch area

	// Ridah, some areas have a 0 presence (!?!)
//	if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL))
	if (     ( AAS_AreaPresenceType( reach->areanum ) & PRESENCE_CROUCH ) &&
			 !( AAS_AreaPresenceType( reach->areanum ) & PRESENCE_NORMAL ) ) {
		//if pretty close to the reachable area
		if ( dist < 20 ) {
			EA_Crouch( ms->client );
		}
	} //end if
	  //
	dist = BotGapDistance( ms->origin, hordir, ms->entitynum );
	//
	if ( ms->moveflags & MFL_WALK ) {
		if ( dist > 0 ) {
			speed = 200 - ( 180 - 1 * dist );
		} else { speed = 200;}
		EA_Walk( ms->client );
	} //end if
	else
	{
		if ( dist > 0 ) {
			speed = 400 - ( 360 - 2 * dist );
		} else { speed = 400;}
	} //end else
	  //elemantary action move in direction
	EA_Move( ms->client, hordir, speed );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_Walk
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_Walk( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir;
	float dist, speed;

	bot_moveresult_t_cleared( result );
	//if not on the ground and changed areas... don't walk back!!
	//(doesn't seem to help)
	/*
	ms->areanum = BotFuzzyPointReachabilityArea(ms->origin);
	if (ms->areanum == reach->areanum)
	{
#ifdef DEBUG
		botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n");
#endif //DEBUG
		return result;
	} //end if*/
	//go straight to the reachability end
	hordir[0] = reach->end[0] - ms->origin[0];
	hordir[1] = reach->end[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize( hordir );
	//
	if ( dist > 100 ) {
		dist = 100;
	}
	speed = 400 - ( 400 - 3 * dist );
	//
	EA_Move( ms->client, hordir, speed );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotFinishTravel_Walk
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Crouch( bot_movestate_t *ms, aas_reachability_t *reach ) {
	float speed;
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	//
	speed = 400;
	//walk straight to reachability end
	hordir[0] = reach->end[0] - ms->origin[0];
	hordir[1] = reach->end[1] - ms->origin[1];
	hordir[2] = 0;
	VectorNormalize( hordir );
	//
	BotCheckBlocked( ms, hordir, qtrue, &result );
	//elemantary actions
	EA_Crouch( ms->client );
	EA_Move( ms->client, hordir, speed );
	//
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_Crouch
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_BarrierJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	float dist, speed;
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	//walk straight to reachability start
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize( hordir );
	//
	BotCheckBlocked( ms, hordir, qtrue, &result );
	//if pretty close to the barrier
	if ( dist < 9 ) {
		EA_Jump( ms->client );

		// Ridah, do the movement also, so we have momentum to get onto the barrier
		hordir[0] = reach->end[0] - reach->start[0];
		hordir[1] = reach->end[1] - reach->start[1];
		hordir[2] = 0;
		VectorNormalize( hordir );

		dist = 60;
		speed = 360 - ( 360 - 6 * dist );
		EA_Move( ms->client, hordir, speed );
		// done.
	} //end if
	else
	{
		if ( dist > 60 ) {
			dist = 60;
		}
		speed = 360 - ( 360 - 6 * dist );
		EA_Move( ms->client, hordir, speed );
	} //end else
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_BarrierJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_BarrierJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	float dist, speed;
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	//if near the top or going down
	if ( ms->velocity[2] < 250 ) {
		// Ridah, extend the end point a bit, so we strive to get over the ledge more
		vec3_t end;

		VectorSubtract( reach->end, reach->start, end );
		end[2] = 0;
		VectorNormalize( end );
		VectorMA( reach->end, 32, end, end );
		hordir[0] = end[0] - ms->origin[0];
		hordir[1] = end[1] - ms->origin[1];
//		hordir[0] = reach->end[0] - ms->origin[0];
//		hordir[1] = reach->end[1] - ms->origin[1];
		// done.
		hordir[2] = 0;
		dist = VectorNormalize( hordir );
		//
		BotCheckBlocked( ms, hordir, qtrue, &result );
		//
		if ( dist > 60 ) {
			dist = 60;
		}
		speed = 400 - ( 400 - 6 * dist );
		EA_Move( ms->client, hordir, speed );
		VectorCopy( hordir, result.movedir );
	} //end if
	  //
	return result;
} //end of the function BotFinishTravel_BarrierJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Swim( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t dir;
	bot_moveresult_t_cleared( result );

	//swim straight to reachability end
	VectorSubtract( reach->start, ms->origin, dir );
	VectorNormalize( dir );
	//
	BotCheckBlocked( ms, dir, qtrue, &result );
	//elemantary actions
	EA_Move( ms->client, dir, 400 );
	//
	VectorCopy( dir, result.movedir );
	Vector2Angles( dir, result.ideal_viewangles );
	result.flags |= MOVERESULT_SWIMVIEW;
	//
	return result;
} //end of the function BotTravel_Swim
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_WaterJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t dir, hordir;
	float dist;
	bot_moveresult_t_cleared( result );

	//swim straight to reachability end
	VectorSubtract( reach->end, ms->origin, dir );
	VectorCopy( dir, hordir );
	hordir[2] = 0;
	dir[2] += 15 + crandom() * 40;
	//botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]);
	VectorNormalize( dir );
	dist = VectorNormalize( hordir );
	//elemantary actions
	//EA_Move(ms->client, dir, 400);
	EA_MoveForward( ms->client );
	//move up if close to the actual out of water jump spot
	if ( dist < 40 ) {
		EA_MoveUp( ms->client );
	}
	//set the ideal view angles
	Vector2Angles( dir, result.ideal_viewangles );
	result.flags |= MOVERESULT_MOVEMENTVIEW;
	//
	VectorCopy( dir, result.movedir );
	//
	return result;
} //end of the function BotTravel_WaterJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_WaterJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t dir, pnt;
	bot_moveresult_t_cleared( result );

	//botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n");
	//if waterjumping there's nothing to do
	if ( ms->moveflags & MFL_WATERJUMP ) {
		return result;
	}
	//if not touching any water anymore don't do anything
	//otherwise the bot sometimes keeps jumping?
	VectorCopy( ms->origin, pnt );
	pnt[2] -= 32;   //extra for q2dm4 near red armor/mega health
	if ( !( AAS_PointContents( pnt ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) {
		return result;
	}
	//swim straight to reachability end
	VectorSubtract( reach->end, ms->origin, dir );
	dir[0] += crandom() * 10;
	dir[1] += crandom() * 10;
	dir[2] += 70 + crandom() * 10;
	//elemantary actions
	EA_Move( ms->client, dir, 400 );
	//set the ideal view angles
	Vector2Angles( dir, result.ideal_viewangles );
	result.flags |= MOVERESULT_MOVEMENTVIEW;
	//
	VectorCopy( dir, result.movedir );
	//
	return result;
} //end of the function BotFinishTravel_WaterJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_WalkOffLedge( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir, dir;
	float dist, speed, reachhordist;
	bot_moveresult_t_cleared( result );

	//check if the bot is blocked by anything
	VectorSubtract( reach->start, ms->origin, dir );
	VectorNormalize( dir );
	BotCheckBlocked( ms, dir, qtrue, &result );
	//if the reachability start and end are practially above each other
	VectorSubtract( reach->end, reach->start, dir );
	dir[2] = 0;
	reachhordist = VectorLength( dir );
	//walk straight to the reachability start
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize( hordir );
	//if pretty close to the start focus on the reachability end

	// Ridah, tweaked this
#if 0
	if ( dist < 48 ) {
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
#else
	if ( ( dist < 72 ) && ( DotProduct( dir, hordir ) < 0 ) ) { // walk in the direction of start -> end
															   //hordir[0] = reach->end[0] - ms->origin[0];
															   //hordir[1] = reach->end[1] - ms->origin[1];
															   //VectorNormalize( dir );
															   //VectorMA( hordir, 48, dir, hordir );
															   //hordir[2] = 0;

		VectorCopy( dir, hordir );
#endif
		VectorNormalize( hordir );
		//
		if ( reachhordist < 20 ) {
			speed = 100;
		} //end if
		else if ( !AAS_HorizontalVelocityForJump( 0, reach->start, reach->end, &speed ) ) {
			speed = 400;
		} //end if
		  // looks better crouching off a ledge
		EA_Crouch( ms->client );
	} //end if
	else
	{
		if ( reachhordist < 20 ) {
			if ( dist > 64 ) {
				dist = 64;
			}
			speed = 400 - ( 256 - 4 * dist );
		} //end if
		else
		{
			speed = 400;
			// Ridah, tweaked this
			if ( dist < 128 ) {
				speed *= ( dist / 128 );
			}
		} //end else
	} //end else
	  //
	BotCheckBlocked( ms, hordir, qtrue, &result );
	//elemantary action
	EA_Move( ms->client, hordir, speed );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_WalkOffLedge
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotAirControl( vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed ) {
	vec3_t org, vel;
	float dist;
	int i;

	VectorCopy( origin, org );
	VectorScale( velocity, 0.1, vel );
	for ( i = 0; i < 50; i++ )
	{
		vel[2] -= sv_gravity * 0.01;
		//if going down and next position would be below the goal
		if ( vel[2] < 0 && org[2] + vel[2] < goal[2] ) {
			VectorScale( vel, ( goal[2] - org[2] ) / vel[2], vel );
			VectorAdd( org, vel, org );
			VectorSubtract( goal, org, dir );
			dist = VectorNormalize( dir );
			if ( dist > 32 ) {
				dist = 32;
			}
			*speed = 400 - ( 400 - 13 * dist );
			return qtrue;
		} //end if
		else
		{
			VectorAdd( org, vel, org );
		} //end else
	} //end for
	VectorSet( dir, 0, 0, 0 );
	*speed = 400;
	return qfalse;
} //end of the function BotAirControl
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_WalkOffLedge( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t dir, hordir, end, v;
	float dist, speed;
	bot_moveresult_t_cleared( result );

	//
	VectorSubtract( reach->end, ms->origin, dir );
	BotCheckBlocked( ms, dir, qtrue, &result );
	//
	VectorSubtract( reach->end, ms->origin, v );
	v[2] = 0;
	dist = VectorNormalize( v );
	if ( dist > 16 ) {
		VectorMA( reach->end, 16, v, end );
	} else { VectorCopy( reach->end, end );}
	//
	if ( !BotAirControl( ms->origin, ms->velocity, end, hordir, &speed ) ) {
		//go straight to the reachability end
		VectorCopy( dir, hordir );
		hordir[2] = 0;
		//
		speed = 400;
	} //end if
	  //
	  // looks better crouching off a ledge
	EA_Crouch( ms->client );
	EA_Move( ms->client, hordir, speed );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotFinishTravel_WalkOffLedge
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
/*
bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach)
{
	vec3_t hordir;
	float dist, gapdist, speed, horspeed, sv_jumpvel;
	bot_moveresult_t_cleared( result );

	//
	sv_jumpvel = botlibglobals.sv_jumpvel->value;
	//walk straight to the reachability start
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize(hordir);
	//
	speed = 350;
	//
	gapdist = BotGapDistance(ms, hordir, ms->entitynum);
	//if pretty close to the start focus on the reachability end
	if (dist < 50 || (gapdist && gapdist < 50))
	{
		//NOTE: using max speed (400) works best
		//if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed))
		//{
		//	speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value;
		//} //end if
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		VectorNormalize(hordir);
		//elemantary action jump
		EA_Jump(ms->client);
		//
		ms->jumpreach = ms->lastreachnum;
		speed = 600;
	} //end if
	else
	{
		if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed))
		{
			speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value;
		} //end if
	} //end else
	//elemantary action
	EA_Move(ms->client, hordir, speed);
	VectorCopy(hordir, result.movedir);
	//
	return result;
} //end of the function BotTravel_Jump*/
/*
bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach)
{
	vec3_t hordir, dir1, dir2, mins, maxs, start, end;
	int gapdist;
	float dist1, dist2, speed;
	bot_moveresult_t_cleared( result );
	bsp_trace_t trace;

	//
	hordir[0] = reach->start[0] - reach->end[0];
	hordir[1] = reach->start[1] - reach->end[1];
	hordir[2] = 0;
	VectorNormalize(hordir);
	//
	VectorCopy(reach->start, start);
	start[2] += 1;
	//minus back the bouding box size plus 16
	VectorMA(reach->start, 80, hordir, end);
	//
	AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs);
	//check for solids
	trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID);
	if (trace.startsolid) VectorCopy(start, trace.endpos);
	//check for a gap
	for (gapdist = 0; gapdist < 80; gapdist += 10)
	{
		VectorMA(start, gapdist+10, hordir, end);
		end[2] += 1;
		if (AAS_PointAreaNum(end) != ms->reachareanum) break;
	} //end for
	if (gapdist < 80) VectorMA(reach->start, gapdist, hordir, trace.endpos);
//	dist1 = BotGapDistance(start, hordir, ms->entitynum);
//	if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos);
	//
	VectorSubtract(ms->origin, reach->start, dir1);
	dir1[2] = 0;
	dist1 = VectorNormalize(dir1);
	VectorSubtract(ms->origin, trace.endpos, dir2);
	dir2[2] = 0;
	dist2 = VectorNormalize(dir2);
	//if just before the reachability start
	if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5)
	{
		//botimport.Print(PRT_MESSAGE, "between jump start and run to point\n");
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize(hordir);
		//elemantary action jump
		if (dist1 < 24) EA_Jump(ms->client);
		else if (dist1 < 32) EA_DelayedJump(ms->client);
		EA_Move(ms->client, hordir, 600);
		//
		ms->jumpreach = ms->lastreachnum;
	} //end if
	else
	{
		//botimport.Print(PRT_MESSAGE, "going towards run to point\n");
		hordir[0] = trace.endpos[0] - ms->origin[0];
		hordir[1] = trace.endpos[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize(hordir);
		//
		if (dist2 > 80) dist2 = 80;
		speed = 400 - (400 - 5 * dist2);
		EA_Move(ms->client, hordir, speed);
	} //end else
	VectorCopy(hordir, result.movedir);
	//
	return result;
} //end of the function BotTravel_Jump*/
//*
bot_moveresult_t BotTravel_Jump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir, dir1, dir2, start, end, runstart;
//	vec3_t runstart, dir1, dir2, hordir;
	int gapdist;
	float dist1, dist2, speed;
	bot_moveresult_t_cleared( result );

	//
	AAS_JumpReachRunStart( reach, runstart );
	//*
	hordir[0] = runstart[0] - reach->start[0];
	hordir[1] = runstart[1] - reach->start[1];
	hordir[2] = 0;
	VectorNormalize( hordir );
	//
	VectorCopy( reach->start, start );
	start[2] += 1;
	VectorMA( reach->start, 80, hordir, runstart );
	//check for a gap
	for (gapdist = 0; gapdist < 80; gapdist += 10)
	{
		VectorMA(start, gapdist+10, hordir, end);
		end[2] += 1;
		if ( AAS_PointAreaNum( end ) != ms->reachareanum ) {
			break;
		}
	} //end for
	if ( gapdist < 80 ) {
		VectorMA( reach->start, gapdist, hordir, runstart );
	}
	//
	VectorSubtract( ms->origin, reach->start, dir1 );
	dir1[2] = 0;
	dist1 = VectorNormalize( dir1 );
	VectorSubtract( ms->origin, runstart, dir2 );
	dir2[2] = 0;
	dist2 = VectorNormalize( dir2 );
	//if just before the reachability start
	if ( DotProduct( dir1, dir2 ) < -0.8 || dist2 < 5 ) {
//		botimport.Print(PRT_MESSAGE, "between jump start and run start point\n");
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize( hordir );
		//elemantary action jump
		if ( dist1 < 24 ) {
			EA_Jump( ms->client );
		} else if ( dist1 < 32 ) {
			EA_DelayedJump( ms->client );
		}
		EA_Move( ms->client, hordir, 600 );
		//
		ms->jumpreach = ms->lastreachnum;
	} //end if
	else
	{
//		botimport.Print(PRT_MESSAGE, "going towards run start point\n");
		hordir[0] = runstart[0] - ms->origin[0];
		hordir[1] = runstart[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize( hordir );
		//
		if ( dist2 > 80 ) {
			dist2 = 80;
		}
		speed = 400 - ( 400 - 5 * dist2 );
		EA_Move( ms->client, hordir, speed );
	} //end else
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_Jump*/
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_Jump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir, hordir2;
	float speed, dist;
	bot_moveresult_t_cleared( result );

	//if not jumped yet
	if ( !ms->jumpreach ) {
		return result;
	}
	//go straight to the reachability end
	hordir[0] = reach->end[0] - ms->origin[0];
	hordir[1] = reach->end[1] - ms->origin[1];
	hordir[2] = 0;
	dist = VectorNormalize( hordir );
	//
	hordir2[0] = reach->end[0] - reach->start[0];
	hordir2[1] = reach->end[1] - reach->start[1];
	hordir2[2] = 0;
	VectorNormalize( hordir2 );
	//
	if ( DotProduct( hordir, hordir2 ) < -0.5 && dist < 24 ) {
		return result;
	}
	//always use max speed when traveling through the air
	speed = 800;
	//
	EA_Move( ms->client, hordir, speed );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotFinishTravel_Jump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Ladder( bot_movestate_t *ms, aas_reachability_t *reach ) {
	//float dist, speed;
	vec3_t dir, viewdir, hordir, pos;
	vec3_t origin = {0, 0, 0};
//	vec3_t up = {0, 0, 1};
	bot_moveresult_t_cleared( result );
	float dist, speed;

	// RF, heavily modified, wolf has different ladder movement

	//
	if ( ( ms->moveflags & MFL_AGAINSTLADDER )
		 //NOTE: not a good idea for ladders starting in water
		 || !( ms->moveflags & MFL_ONGROUND ) ) {
		//botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n");
		// RF, wolf has different ladder movement
		VectorSubtract( reach->end, reach->start, dir );
		VectorNormalize( dir );
		//set the ideal view angles, facing the ladder up or down
		viewdir[0] = dir[0];
		viewdir[1] = dir[1];
		viewdir[2] = dir[2];
		if ( dir[2] < 0 ) {   // going down, so face the other way
			VectorInverse( viewdir );
		} else {
			viewdir[2] = 0; // straight forward goes up
		}
		Vector2Angles( viewdir, result.ideal_viewangles );
		//elemantary action
		EA_Move( ms->client, origin, 0 );
		EA_MoveForward( ms->client );
		//set movement view flag so the AI can see the view is focussed
		result.flags |= MOVERESULT_MOVEMENTVIEW;
	} //end if
	else
	{
		//botimport.Print(PRT_MESSAGE, "moving towards ladder base\n");
		// find a postion back away from the base of the ladder
		VectorSubtract( reach->end, reach->start, hordir );
		hordir[2] = 0;
		VectorNormalize( hordir );
		VectorMA( reach->start, -24, hordir, pos );
		VectorSubtract( pos, ms->origin, dir );
		//make sure the horizontal movement is large enough
		VectorCopy( dir, hordir );
		hordir[2] = 0;
		dist = VectorNormalize( hordir );
		if ( dist < 32 ) {    // within range, go for the end
			//botimport.Print(PRT_MESSAGE, "found base, moving towards ladder top\n");
			VectorSubtract( reach->end, ms->origin, dir );
			//make sure the horizontal movement is large enough
			VectorCopy( dir, hordir );
			hordir[2] = 0;
			dist = VectorNormalize( hordir );
		}
		//
		dir[0] = hordir[0];
		dir[1] = hordir[1];
//		if (dist < 48) {
//			if (dir[2] > 0) dir[2] = 1;
//			else dir[2] = -1;
//		} else {
		dir[2] = 0;
//		}
		if ( dist > 50 ) {
			dist = 50;
		}
		speed = 400 - ( 200 - 4 * dist );
		EA_Move( ms->client, dir, speed );
	} //end else
	  //save the movement direction
	VectorCopy( dir, result.movedir );
	//
	return result;
} //end of the function BotTravel_Ladder
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Teleport( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir;
	float dist;
	bot_moveresult_t_cleared( result );

	//if the bot is being teleported
	if ( ms->moveflags & MFL_TELEPORTED ) {
		return result;
	}

	//walk straight to center of the teleporter
	VectorSubtract( reach->start, ms->origin, hordir );
	if ( !( ms->moveflags & MFL_SWIMMING ) ) {
		hordir[2] = 0;
	}
	dist = VectorNormalize( hordir );
	//
	BotCheckBlocked( ms, hordir, qtrue, &result );

	if ( dist < 30 ) {
		EA_Move( ms->client, hordir, 200 );
	} else { EA_Move( ms->client, hordir, 400 );}

	if ( ms->moveflags & MFL_SWIMMING ) {
		result.flags |= MOVERESULT_SWIMVIEW;
	}

	VectorCopy( hordir, result.movedir );
	return result;
} //end of the function BotTravel_Teleport
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Elevator( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t dir, dir1, dir2, hordir, bottomcenter;
	float dist, dist1, dist2, speed;
	bot_moveresult_t_cleared( result );

	//if standing on the plat
	if ( BotOnMover( ms->origin, ms->entitynum, reach ) ) {
#ifdef DEBUG_ELEVATOR
		botimport.Print( PRT_MESSAGE, "bot on elevator\n" );
#endif //DEBUG_ELEVATOR
	   //if vertically not too far from the end point
		if ( fabs( ms->origin[2] - reach->end[2] ) < sv_maxbarrier ) {
#ifdef DEBUG_ELEVATOR
			botimport.Print( PRT_MESSAGE, "bot moving to end\n" );
#endif //DEBUG_ELEVATOR
			//move to the end point
			VectorSubtract( reach->end, ms->origin, hordir );
			hordir[2] = 0;
			VectorNormalize( hordir );
			if ( !BotCheckBarrierJump( ms, hordir, 100 ) ) {
				EA_Move( ms->client, hordir, 400 );
			} //end if
			VectorCopy( hordir, result.movedir );
		} //end else
		  //if not really close to the center of the elevator
		else
		{
			MoverBottomCenter( reach, bottomcenter );
			VectorSubtract( bottomcenter, ms->origin, hordir );
			hordir[2] = 0;
			dist = VectorNormalize( hordir );
			//
			if ( dist > 10 ) {
#ifdef DEBUG_ELEVATOR
				botimport.Print( PRT_MESSAGE, "bot moving to center\n" );
#endif //DEBUG_ELEVATOR
				//move to the center of the plat
				if ( dist > 100 ) {
					dist = 100;
				}
				speed = 400 - ( 400 - 4 * dist );
				//
				EA_Move( ms->client, hordir, speed );
				VectorCopy( hordir, result.movedir );
			} //end if
		} //end else
	} //end if
	else
	{
#ifdef DEBUG_ELEVATOR
		botimport.Print( PRT_MESSAGE, "bot not on elevator\n" );
#endif //DEBUG_ELEVATOR
	   //if very near the reachability end
		VectorSubtract( reach->end, ms->origin, dir );
		dist = VectorLength( dir );
		if ( dist < 64 ) {
			if ( dist > 60 ) {
				dist = 60;
			}
			speed = 360 - ( 360 - 6 * dist );
			//
			if ( ( ms->moveflags & MFL_SWIMMING ) || !BotCheckBarrierJump( ms, dir, 50 ) ) {
				if ( speed > 5 ) {
					EA_Move( ms->client, dir, speed );
				}
			} //end if
			VectorCopy( dir, result.movedir );
			//
			if ( ms->moveflags & MFL_SWIMMING ) {
				result.flags |= MOVERESULT_SWIMVIEW;
			}
			//stop using this reachability
			ms->reachability_time = 0;
			return result;
		} //end if
		  //get direction and distance to reachability start
		VectorSubtract( reach->start, ms->origin, dir1 );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			dir1[2] = 0;
		}
		dist1 = VectorNormalize( dir1 );
		//if the elevator isn't down
		if ( !MoverDown( reach ) ) {
#ifdef DEBUG_ELEVATOR
			botimport.Print( PRT_MESSAGE, "elevator not down\n" );
#endif //DEBUG_ELEVATOR
			dist = dist1;
			VectorCopy( dir1, dir );
			//
			BotCheckBlocked( ms, dir, qfalse, &result );
			//
			if ( dist > 60 ) {
				dist = 60;
			}
			speed = 360 - ( 360 - 6 * dist );
			//
			if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) {
				if ( speed > 5 ) {
					EA_Move( ms->client, dir, speed );
				}
			} //end if
			VectorCopy( dir, result.movedir );
			//
			if ( ms->moveflags & MFL_SWIMMING ) {
				result.flags |= MOVERESULT_SWIMVIEW;
			}
			//this isn't a failure... just wait till the elevator comes down
			//result.failure = qtrue;
			result.type = RESULTTYPE_ELEVATORUP;
			result.flags |= MOVERESULT_WAITING;
			return result;
		} //end if
		  //get direction and distance to elevator bottom center
		MoverBottomCenter( reach, bottomcenter );
		VectorSubtract( bottomcenter, ms->origin, dir2 );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			dir2[2] = 0;
		}
		dist2 = VectorNormalize( dir2 );
		//if very close to the reachability start or
		//closer to the elevator center or
		//between reachability start and elevator center
		if ( dist1 < 20 || dist2 < dist1 || DotProduct( dir1, dir2 ) < 0 ) {
#ifdef DEBUG_ELEVATOR
			botimport.Print( PRT_MESSAGE, "bot moving to center\n" );
#endif //DEBUG_ELEVATOR
			dist = dist2;
			VectorCopy( dir2, dir );
		} //end if
		else //closer to the reachability start
		{
#ifdef DEBUG_ELEVATOR
			botimport.Print( PRT_MESSAGE, "bot moving to start\n" );
#endif //DEBUG_ELEVATOR
			dist = dist1;
			VectorCopy( dir1, dir );
		} //end else
		  //
		BotCheckBlocked( ms, dir, qfalse, &result );
		//
		if ( dist > 60 ) {
			dist = 60;
		}
		speed = 400 - ( 400 - 6 * dist );
		//
		if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) {
			EA_Move( ms->client, dir, speed );
		} //end if
		VectorCopy( dir, result.movedir );
		//
		if ( ms->moveflags & MFL_SWIMMING ) {
			result.flags |= MOVERESULT_SWIMVIEW;
		}
	} //end else
	return result;
} //end of the function BotTravel_Elevator
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_Elevator( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t bottomcenter, bottomdir, topdir;
	bot_moveresult_t_cleared( result );

	//
	MoverBottomCenter( reach, bottomcenter );
	VectorSubtract( bottomcenter, ms->origin, bottomdir );
	//
	VectorSubtract( reach->end, ms->origin, topdir );
	//
	if ( fabs( bottomdir[2] ) < fabs( topdir[2] ) ) {
		VectorNormalize( bottomdir );
		EA_Move( ms->client, bottomdir, 300 );
	} //end if
	else
	{
		VectorNormalize( topdir );
		EA_Move( ms->client, topdir, 300 );
	} //end else
	return result;
} //end of the function BotFinishTravel_Elevator
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotFuncBobStartEnd( aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin ) {
	int spawnflags, modelnum;
	vec3_t mins, maxs, mid, angles = {0, 0, 0};
	int num0, num1;

	modelnum = reach->facenum & 0x0000FFFF;
	if ( !AAS_OriginOfEntityWithModelNum( modelnum, origin ) ) {
		botimport.Print( PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum );
		VectorSet( start, 0, 0, 0 );
		VectorSet( end, 0, 0, 0 );
		return;
	} //end if
	AAS_BSPModelMinsMaxsOrigin( modelnum, angles, mins, maxs, NULL );
	VectorAdd( mins, maxs, mid );
	VectorScale( mid, 0.5, mid );
	VectorCopy( mid, start );
	VectorCopy( mid, end );
	spawnflags = reach->facenum >> 16;
	num0 = reach->edgenum >> 16;
	if ( num0 > 0x00007FFF ) {
		num0 |= 0xFFFF0000;
	}
	num1 = reach->edgenum & 0x0000FFFF;
	if ( num1 > 0x00007FFF ) {
		num1 |= 0xFFFF0000;
	}
	if ( spawnflags & 1 ) {
		start[0] = num0;
		end[0] = num1;
		//
		origin[0] += mid[0];
		origin[1] = mid[1];
		origin[2] = mid[2];
	} //end if
	else if ( spawnflags & 2 ) {
		start[1] = num0;
		end[1] = num1;
		//
		origin[0] = mid[0];
		origin[1] += mid[1];
		origin[2] = mid[2];
	} //end else if
	else
	{
		start[2] = num0;
		end[2] = num1;
		//
		origin[0] = mid[0];
		origin[1] = mid[1];
		origin[2] += mid[2];
	} //end else
} //end of the function BotFuncBobStartEnd
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_FuncBobbing( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin;
	float dist, dist1, dist2, speed;
	bot_moveresult_t_cleared( result );

	//
	BotFuncBobStartEnd( reach, bob_start, bob_end, bob_origin );
	//if standing ontop of the func_bobbing
	if ( BotOnMover( ms->origin, ms->entitynum, reach ) ) {
#ifdef DEBUG_FUNCBOB
		botimport.Print( PRT_MESSAGE, "bot on func_bobbing\n" );
#endif
		//if near end point of reachability
		VectorSubtract( bob_origin, bob_end, dir );
		if ( VectorLength( dir ) < 24 ) {
#ifdef DEBUG_FUNCBOB
			botimport.Print( PRT_MESSAGE, "bot moving to reachability end\n" );
#endif
			//move to the end point
			VectorSubtract( reach->end, ms->origin, hordir );
			hordir[2] = 0;
			VectorNormalize( hordir );
			if ( !BotCheckBarrierJump( ms, hordir, 100 ) ) {
				EA_Move( ms->client, hordir, 400 );
			} //end if
			VectorCopy( hordir, result.movedir );
		} //end else
		  //if not really close to the center of the elevator
		else
		{
			MoverBottomCenter( reach, bottomcenter );
			VectorSubtract( bottomcenter, ms->origin, hordir );
			hordir[2] = 0;
			dist = VectorNormalize( hordir );
			//
			if ( dist > 10 ) {
#ifdef DEBUG_FUNCBOB
				botimport.Print( PRT_MESSAGE, "bot moving to func_bobbing center\n" );
#endif
				//move to the center of the plat
				if ( dist > 100 ) {
					dist = 100;
				}
				speed = 400 - ( 400 - 4 * dist );
				//
				EA_Move( ms->client, hordir, speed );
				VectorCopy( hordir, result.movedir );
			} //end if
		} //end else
	} //end if
	else
	{
#ifdef DEBUG_FUNCBOB
		botimport.Print( PRT_MESSAGE, "bot not ontop of func_bobbing\n" );
#endif
		//if very near the reachability end
		VectorSubtract( reach->end, ms->origin, dir );
		dist = VectorLength( dir );
		if ( dist < 64 ) {
#ifdef DEBUG_FUNCBOB
			botimport.Print( PRT_MESSAGE, "bot moving to end\n" );
#endif
			if ( dist > 60 ) {
				dist = 60;
			}
			speed = 360 - ( 360 - 6 * dist );
			//if swimming or no barrier jump
			if ( ( ms->moveflags & MFL_SWIMMING ) || !BotCheckBarrierJump( ms, dir, 50 ) ) {
				if ( speed > 5 ) {
					EA_Move( ms->client, dir, speed );
				}
			} //end if
			VectorCopy( dir, result.movedir );
			//
			if ( ms->moveflags & MFL_SWIMMING ) {
				result.flags |= MOVERESULT_SWIMVIEW;
			}
			//stop using this reachability
			ms->reachability_time = 0;
			return result;
		} //end if
		  //get direction and distance to reachability start
		VectorSubtract( reach->start, ms->origin, dir1 );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			dir1[2] = 0;
		}
		dist1 = VectorNormalize( dir1 );
		//if func_bobbing is Not its start position
		VectorSubtract( bob_origin, bob_start, dir );
		if ( VectorLength( dir ) > 16 ) {
#ifdef DEBUG_FUNCBOB
			botimport.Print( PRT_MESSAGE, "func_bobbing not at start\n" );
#endif
			dist = dist1;
			VectorCopy( dir1, dir );
			//
			BotCheckBlocked( ms, dir, qfalse, &result );
			//
			if ( dist > 60 ) {
				dist = 60;
			}
			speed = 360 - ( 360 - 6 * dist );
			//
			if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) {
				if ( speed > 5 ) {
					EA_Move( ms->client, dir, speed );
				}
			} //end if
			VectorCopy( dir, result.movedir );
			//
			if ( ms->moveflags & MFL_SWIMMING ) {
				result.flags |= MOVERESULT_SWIMVIEW;
			}
			//this isn't a failure... just wait till the func_bobbing arrives
			result.type = RESULTTYPE_ELEVATORUP;
			result.flags |= MOVERESULT_WAITING;
			return result;
		} //end if
		  //get direction and distance to func_bob bottom center
		MoverBottomCenter( reach, bottomcenter );
		VectorSubtract( bottomcenter, ms->origin, dir2 );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			dir2[2] = 0;
		}
		dist2 = VectorNormalize( dir2 );
		//if very close to the reachability start or
		//closer to the elevator center or
		//between reachability start and func_bobbing center
		if ( dist1 < 20 || dist2 < dist1 || DotProduct( dir1, dir2 ) < 0 ) {
#ifdef DEBUG_FUNCBOB
			botimport.Print( PRT_MESSAGE, "bot moving to func_bobbing center\n" );
#endif
			dist = dist2;
			VectorCopy( dir2, dir );
		} //end if
		else //closer to the reachability start
		{
#ifdef DEBUG_FUNCBOB
			botimport.Print( PRT_MESSAGE, "bot moving to reachability start\n" );
#endif
			dist = dist1;
			VectorCopy( dir1, dir );
		} //end else
		  //
		BotCheckBlocked( ms, dir, qfalse, &result );
		//
		if ( dist > 60 ) {
			dist = 60;
		}
		speed = 400 - ( 400 - 6 * dist );
		//
		if ( !( ms->moveflags & MFL_SWIMMING ) && !BotCheckBarrierJump( ms, dir, 50 ) ) {
			EA_Move( ms->client, dir, speed );
		} //end if
		VectorCopy( dir, result.movedir );
		//
		if ( ms->moveflags & MFL_SWIMMING ) {
			result.flags |= MOVERESULT_SWIMVIEW;
		}
	} //end else
	return result;
} //end of the function BotTravel_FuncBobbing
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_FuncBobbing( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter;
	bot_moveresult_t_cleared( result );
	float dist, speed;

	//
	BotFuncBobStartEnd( reach, bob_start, bob_end, bob_origin );
	//
	VectorSubtract( bob_origin, bob_end, dir );
	dist = VectorLength( dir );
	//if the func_bobbing is near the end
	if ( dist < 16 ) {
		VectorSubtract( reach->end, ms->origin, hordir );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			hordir[2] = 0;
		}
		dist = VectorNormalize( hordir );
		//
		if ( dist > 60 ) {
			dist = 60;
		}
		speed = 360 - ( 360 - 6 * dist );
		//
		if ( speed > 5 ) {
			EA_Move( ms->client, dir, speed );
		}
		VectorCopy( dir, result.movedir );
		//
		if ( ms->moveflags & MFL_SWIMMING ) {
			result.flags |= MOVERESULT_SWIMVIEW;
		}
	} //end if
	else
	{
		MoverBottomCenter( reach, bottomcenter );
		VectorSubtract( bottomcenter, ms->origin, hordir );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			hordir[2] = 0;
		}
		dist = VectorNormalize( hordir );
		//
		if ( dist > 5 ) {
			//move to the center of the plat
			if ( dist > 100 ) {
				dist = 100;
			}
			speed = 400 - ( 400 - 4 * dist );
			//
			EA_Move( ms->client, hordir, speed );
			VectorCopy( hordir, result.movedir );
		} //end if
	} //end else
	return result;
} //end of the function BotFinishTravel_FuncBobbing
//===========================================================================
// 0  no valid grapple hook visible
// 1  the grapple hook is still flying
// 2  the grapple hooked into a wall
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
/*
int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach)
{
	static int grapplemodelindex;
	int i;
	vec3_t dir;
	aas_entityinfo_t entinfo;

	if (!grapplemodelindex)
	{
		grapplemodelindex = AAS_IndexFromModel("models/weapons/grapple/hook/tris.md2");
	} //end if
	for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i))
	{
		if (AAS_EntityModelindex(i) == grapplemodelindex)
		{
			AAS_EntityInfo(i, &entinfo);
			if (VectorCompare(entinfo.origin, entinfo.old_origin))
			{
				VectorSubtract(entinfo.origin, reach->end, dir);
				//if hooked near the reachability end
				if (VectorLength(dir) < 32) return 2;
			} //end if
			else
			{
				//still shooting hook
				return 1;
			} //end else
		} //end if
	} //end if
	//no valid grapple at all
	return 0;
} //end of the function GrappleState*/
//===========================================================================
// 0  no valid grapple hook visible
// 1  the grapple hook is still flying
// 2  the grapple hooked into a wall
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int GrappleState( bot_movestate_t *ms, aas_reachability_t *reach ) {
	static int grapplemodelindex;
	static libvar_t *laserhook;
	int i;
	vec3_t dir;
	aas_entityinfo_t entinfo;

	if ( !laserhook ) {
		laserhook = LibVar( "laserhook", "0" );
	}
	if ( !laserhook->value && !grapplemodelindex ) {
		grapplemodelindex = AAS_IndexFromModel( "models/weapons/grapple/hook/tris.md2" );
	} //end if
	for ( i = AAS_NextEntity( 0 ); i; i = AAS_NextEntity( i ) )
	{
		if ( ( !laserhook->value && AAS_EntityModelindex( i ) == grapplemodelindex )
//			|| (laserhook->value && (AAS_EntityRenderFX(i) & RF_BEAM))
			 ) {
			AAS_EntityInfo( i, &entinfo );
			//if the origin is equal to the last visible origin
			if ( VectorCompare( entinfo.origin, entinfo.lastvisorigin ) ) {
				VectorSubtract( entinfo.origin, reach->end, dir );
				//if hooked near the reachability end
				if ( VectorLength( dir ) < 32 ) {
					return 2;
				}
			} //end if
			else
			{
				//still shooting hook
				return 1;
			} //end else
		} //end if
	} //end for
	  //no valid grapple at all
	return 0;
} //end of the function GrappleState
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetGrapple( bot_movestate_t *ms ) {
	aas_reachability_t reach;

	AAS_ReachabilityFromNum( ms->lastreachnum, &reach );
	//if not using the grapple hook reachability anymore
	if ( reach.traveltype != TRAVEL_GRAPPLEHOOK ) {
		if ( ( ms->moveflags & MFL_ACTIVEGRAPPLE ) || ms->grapplevisible_time ) {
			EA_Command( ms->client, CMD_HOOKOFF );
			ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
			ms->grapplevisible_time = 0;
#ifdef DEBUG_GRAPPLE
			botimport.Print( PRT_MESSAGE, "reset grapple\n" );
#endif //DEBUG_GRAPPLE
		} //end if
	} //end if
} //end of the function BotResetGrapple
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_Grapple( bot_movestate_t *ms, aas_reachability_t *reach ) {
	bot_moveresult_t_cleared( result );
	float dist, speed;
	vec3_t dir, viewdir, org;
	int state, areanum;

#ifdef DEBUG_GRAPPLE
	static int debugline;
	if ( !debugline ) {
		debugline = botimport.DebugLineCreate();
	}
	botimport.DebugLineShow( debugline, reach->start, reach->end, LINECOLOR_BLUE );
#endif //DEBUG_GRAPPLE

	//
	if ( ms->moveflags & MFL_GRAPPLERESET ) {
		EA_Command( ms->client, CMD_HOOKOFF );
		ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
		return result;
	} //end if
	  //
	if ( ms->moveflags & MFL_ACTIVEGRAPPLE ) {
#ifdef DEBUG_GRAPPLE
		botimport.Print( PRT_MESSAGE, "BotTravel_Grapple: active grapple\n" );
#endif //DEBUG_GRAPPLE
	   //
		state = GrappleState( ms, reach );
		//
		VectorSubtract( reach->end, ms->origin, dir );
		dir[2] = 0;
		dist = VectorLength( dir );
		//if very close to the grapple end or
		//the grappled is hooked and the bot doesn't get any closer
		if ( state && dist < 48 ) {
			if ( ms->lastgrappledist - dist < 1 ) {
				EA_Command( ms->client, CMD_HOOKOFF );
				ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
				ms->moveflags |= MFL_GRAPPLERESET;
				ms->reachability_time = 0;  //end the reachability
#ifdef DEBUG_GRAPPLE
				botimport.Print( PRT_ERROR, "grapple normal end\n" );
#endif //DEBUG_GRAPPLE
			} //end if
		} //end if
		  //if no valid grapple at all, or the grapple hooked and the bot
		  //isn't moving anymore
		else if ( !state || ( state == 2 && dist > ms->lastgrappledist - 2 ) ) {
			if ( ms->grapplevisible_time < AAS_Time() - 0.4 ) {
#ifdef DEBUG_GRAPPLE
				botimport.Print( PRT_ERROR, "grapple not visible\n" );
#endif //DEBUG_GRAPPLE
				EA_Command( ms->client, CMD_HOOKOFF );
				ms->moveflags &= ~MFL_ACTIVEGRAPPLE;
				ms->moveflags |= MFL_GRAPPLERESET;
				ms->reachability_time = 0;  //end the reachability
				//result.failure = qtrue;
				//result.type = RESULTTYPE_INVISIBLEGRAPPLE;
				return result;
			} //end if
		} //end if
		else
		{
			ms->grapplevisible_time = AAS_Time();
		} //end else
		  //remember the current grapple distance
		ms->lastgrappledist = dist;
	} //end if
	else
	{
#ifdef DEBUG_GRAPPLE
		botimport.Print( PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n" );
#endif //DEBUG_GRAPPLE
	   //
		ms->grapplevisible_time = AAS_Time();
		//
		VectorSubtract( reach->start, ms->origin, dir );
		if ( !( ms->moveflags & MFL_SWIMMING ) ) {
			dir[2] = 0;
		}
		VectorAdd( ms->origin, ms->viewoffset, org );
		VectorSubtract( reach->end, org, viewdir );
		//
		dist = VectorNormalize( dir );
		Vector2Angles( viewdir, result.ideal_viewangles );
		result.flags |= MOVERESULT_MOVEMENTVIEW;
		//
		if ( dist < 5 &&
			 fabs( AngleDiff( result.ideal_viewangles[0], ms->viewangles[0] ) ) < 2 &&
			 fabs( AngleDiff( result.ideal_viewangles[1], ms->viewangles[1] ) ) < 2 ) {
#ifdef DEBUG_GRAPPLE
			botimport.Print( PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n" );
#endif //DEBUG_GRAPPLE
			EA_Command( ms->client, CMD_HOOKON );
			ms->moveflags |= MFL_ACTIVEGRAPPLE;
			ms->lastgrappledist = 999999;
		} //end if
		else
		{
			if ( dist < 70 ) {
				speed = 300 - ( 300 - 4 * dist );
			} else { speed = 400;}
			//
			BotCheckBlocked( ms, dir, qtrue, &result );
			//elemantary action move in direction
			EA_Move( ms->client, dir, speed );
			VectorCopy( dir, result.movedir );
		} //end else
		  //if in another area before actually grappling
		areanum = AAS_PointAreaNum( ms->origin );
		if ( areanum && areanum != ms->reachareanum ) {
			ms->reachability_time = 0;
		}
	} //end else
	return result;
} //end of the function BotTravel_Grapple
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_RocketJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir;
	float dist, speed;
	bot_moveresult_t_cleared( result );

	//botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n");
	//
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	//
	dist = VectorNormalize( hordir );
	//
	if ( dist < 5 ) {
//		botimport.Print(PRT_MESSAGE, "between jump start and run start point\n");
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize( hordir );
		//elemantary action jump
		EA_Jump( ms->client );
		EA_Attack( ms->client );
		EA_Move( ms->client, hordir, 800 );
		//
		ms->jumpreach = ms->lastreachnum;
	} //end if
	else
	{
		if ( dist > 80 ) {
			dist = 80;
		}
		speed = 400 - ( 400 - 5 * dist );
		EA_Move( ms->client, hordir, speed );
	} //end else
	  //
/*
	vec3_t hordir, dir1, dir2, start, end, runstart;
	float dist1, dist2, speed;
	bot_moveresult_t_cleared( result );

	botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n");
	AAS_JumpReachRunStart(reach, runstart);
	//
	hordir[0] = runstart[0] - reach->start[0];
	hordir[1] = runstart[1] - reach->start[1];
	hordir[2] = 0;
	VectorNormalize(hordir);
	//
	VectorCopy(reach->start, start);
	start[2] += 1;
	VectorMA(reach->start, 80, hordir, runstart);
	//check for a gap
	for (dist1 = 0; dist1 < 80; dist1 += 10)
	{
		VectorMA(start, dist1+10, hordir, end);
		end[2] += 1;
		if (AAS_PointAreaNum(end) != ms->reachareanum) break;
	} //end for
	if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart);
	//
	VectorSubtract(ms->origin, reach->start, dir1);
	dir1[2] = 0;
	dist1 = VectorNormalize(dir1);
	VectorSubtract(ms->origin, runstart, dir2);
	dir2[2] = 0;
	dist2 = VectorNormalize(dir2);
	//if just before the reachability start
	if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5)
	{
//		botimport.Print(PRT_MESSAGE, "between jump start and run start point\n");
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize(hordir);
		//elemantary action jump
		if (dist1 < 24) EA_Jump(ms->client);
		else if (dist1 < 32) EA_DelayedJump(ms->client);
		EA_Attack(ms->client);
		EA_Move(ms->client, hordir, 800);
		//
		ms->jumpreach = ms->lastreachnum;
	} //end if
	else
	{
//		botimport.Print(PRT_MESSAGE, "going towards run start point\n");
		hordir[0] = runstart[0] - ms->origin[0];
		hordir[1] = runstart[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize(hordir);
		//
		if (dist2 > 80) dist2 = 80;
		speed = 400 - (400 - 5 * dist2);
		EA_Move(ms->client, hordir, speed);
	} //end else
  */
	//look in the movement direction
	Vector2Angles( hordir, result.ideal_viewangles );
	//look straight down
	result.ideal_viewangles[PITCH] = 90;
	//set the view angles directly
	EA_View( ms->client, result.ideal_viewangles );
	//view is important for the movment
	result.flags |= MOVERESULT_MOVEMENTVIEWSET;
	//select the rocket launcher
	EA_SelectWeapon( ms->client, WEAPONINDEX_ROCKET_LAUNCHER );
	//weapon is used for movement
	result.weapon = WEAPONINDEX_ROCKET_LAUNCHER;
	result.flags |= MOVERESULT_MOVEMENTWEAPON;
	//
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_RocketJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_BFGJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	bot_moveresult_t_cleared( result );

	return result;
} //end of the function BotTravel_BFGJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_WeaponJump( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	//if not jumped yet
	if ( !ms->jumpreach ) {
		return result;
	}
	//go straight to the reachability end
	hordir[0] = reach->end[0] - ms->origin[0];
	hordir[1] = reach->end[1] - ms->origin[1];
	hordir[2] = 0;
	VectorNormalize( hordir );
	//always use max speed when traveling through the air
	EA_Move( ms->client, hordir, 800 );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotFinishTravel_WeaponJump
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotTravel_JumpPad( bot_movestate_t *ms, aas_reachability_t *reach ) {
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	//first walk straight to the reachability start
	hordir[0] = reach->start[0] - ms->origin[0];
	hordir[1] = reach->start[1] - ms->origin[1];
	hordir[2] = 0;
	//
	BotCheckBlocked( ms, hordir, qtrue, &result );
	//elemantary action move in direction
	EA_Move( ms->client, hordir, 400 );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotTravel_JumpPad
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotFinishTravel_JumpPad( bot_movestate_t *ms, aas_reachability_t *reach ) {
	float speed;
	vec3_t hordir;
	bot_moveresult_t_cleared( result );

	if ( !BotAirControl( ms->origin, ms->velocity, reach->end, hordir, &speed ) ) {
		hordir[0] = reach->end[0] - ms->origin[0];
		hordir[1] = reach->end[1] - ms->origin[1];
		hordir[2] = 0;
		VectorNormalize( hordir );
		speed = 400;
	} //end if
	BotCheckBlocked( ms, hordir, qtrue, &result );
	//elemantary action move in direction
	EA_Move( ms->client, hordir, speed );
	VectorCopy( hordir, result.movedir );
	//
	return result;
} //end of the function BotFinishTravel_JumpPad
//===========================================================================
// time before the reachability times out
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int BotReachabilityTime( aas_reachability_t *reach ) {
	switch ( reach->traveltype )
	{
	case TRAVEL_WALK: return 5;
	case TRAVEL_CROUCH: return 5;
	case TRAVEL_BARRIERJUMP: return 5;
	case TRAVEL_LADDER: return 6;
	case TRAVEL_WALKOFFLEDGE: return 5;
	case TRAVEL_JUMP: return 5;
	case TRAVEL_SWIM: return 5;
	case TRAVEL_WATERJUMP: return 5;
	case TRAVEL_TELEPORT: return 5;
	case TRAVEL_ELEVATOR: return 10;
	case TRAVEL_GRAPPLEHOOK: return 8;
	case TRAVEL_ROCKETJUMP: return 6;
		//case TRAVEL_BFGJUMP: return 6;
	case TRAVEL_JUMPPAD: return 10;
	case TRAVEL_FUNCBOB: return 10;
	default:
	{
		botimport.Print( PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype );
		return 8;
	}     //end case
	} //end switch
} //end of the function BotReachabilityTime
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
bot_moveresult_t BotMoveInGoalArea( bot_movestate_t *ms, bot_goal_t *goal ) {
	bot_moveresult_t_cleared( result );
	vec3_t dir;
	float dist, speed;

#ifdef DEBUG
	//botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1));
	//AAS_ClearShownDebugLines();
	//AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED);
#endif //DEBUG
	//walk straight to the goal origin
	dir[0] = goal->origin[0] - ms->origin[0];
	dir[1] = goal->origin[1] - ms->origin[1];
	if ( ms->moveflags & MFL_SWIMMING ) {
		dir[2] = goal->origin[2] - ms->origin[2];
		result.traveltype = TRAVEL_SWIM;
	} //end if
	else
	{
		dir[2] = 0;
		result.traveltype = TRAVEL_WALK;
	} //endif
	  //
	dist = VectorNormalize( dir );
	if ( dist > 100 || ( goal->flags & GFL_NOSLOWAPPROACH ) ) {
		dist = 100;
	}
	speed = 400 - ( 400 - 4 * dist );
	if ( speed < 10 ) {
		speed = 0;
	}
	//
	BotCheckBlocked( ms, dir, qtrue, &result );
	//elemantary action move in direction
	EA_Move( ms->client, dir, speed );
	VectorCopy( dir, result.movedir );
	//
	if ( ms->moveflags & MFL_SWIMMING ) {
		Vector2Angles( dir, result.ideal_viewangles );
		result.flags |= MOVERESULT_SWIMVIEW;
	} //end if
	  //if (!debugline) debugline = botimport.DebugLineCreate();
	  //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE);
	  //
	ms->lastreachnum = 0;
	ms->lastareanum = 0;
	ms->lastgoalareanum = goal->areanum;
	VectorCopy( ms->origin, ms->lastorigin );
	ms->lasttime = AAS_Time();
	//
	return result;
} //end of the function BotMoveInGoalArea
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
int AAS_AreaRouteToGoalArea( int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum );
extern float VectorDistance( vec3_t v1, vec3_t v2 );
void BotMoveToGoal( bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags ) {
	int reachnum = 0; // TTimo (might be used uninitialized in this function)
	int lastreachnum, foundjumppad, ent;
	aas_reachability_t reach, lastreach;
	bot_movestate_t *ms;
	//vec3_t mins, maxs, up = {0, 0, 1};
	//bsp_trace_t trace;
	//static int debugline;

	result->failure = qfalse;
	result->type = 0;
	result->blocked = qfalse;
	result->blockentity = 0;
	result->traveltype = 0;
	result->flags = 0;
	//
	ms = BotMoveStateFromHandle( movestate );
	if ( !ms ) {
		return;
	}
	//reset the grapple before testing if the bot has a valid goal
	//because the bot could lose all it's goals when stuck to a wall
	BotResetGrapple( ms );
	//
	if ( !goal ) {
#ifdef DEBUG
		botimport.Print( PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client );
#endif //DEBUG
		result->failure = qtrue;
		return;
	} //end if
	  //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach);
	  //remove some of the move flags
	ms->moveflags &= ~( MFL_SWIMMING | MFL_AGAINSTLADDER );
	//set some of the move flags
	//NOTE: the MFL_ONGROUND flag is also set in the higher AI
	if ( AAS_OnGround( ms->origin, ms->presencetype, ms->entitynum ) ) {
		ms->moveflags |= MFL_ONGROUND;
	}
	//

	if ( ms->moveflags & MFL_ONGROUND ) {
		int modeltype, modelnum;

		ent = BotOnTopOfEntity( ms );

		if ( ent != -1 ) {
			modelnum = AAS_EntityModelindex( ent );
			if ( modelnum >= 0 && modelnum < MAX_MODELS ) {
				modeltype = modeltypes[modelnum];

				if ( modeltype == MODELTYPE_FUNC_PLAT ) {
					AAS_ReachabilityFromNum( ms->lastreachnum, &reach );
					//if the bot is Not using the elevator
					if ( reach.traveltype != TRAVEL_ELEVATOR ||
						 //NOTE: the face number is the plat model number
						 ( reach.facenum & 0x0000FFFF ) != modelnum ) {
						reachnum = AAS_NextModelReachability( 0, modelnum );
						if ( reachnum ) {
							//botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client);
							AAS_ReachabilityFromNum( reachnum, &reach );
							ms->lastreachnum = reachnum;
							ms->reachability_time = AAS_Time() + BotReachabilityTime( &reach );
						} //end if
						else
						{
							if ( botDeveloper ) {
								botimport.Print( PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client );
							} //end if
							result->blocked = qtrue;
							result->blockentity = ent;
							result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
							return;
						} //end else
					} //end if
					result->flags |= MOVERESULT_ONTOPOF_ELEVATOR;
				} //end if
				else if ( modeltype == MODELTYPE_FUNC_BOB ) {
					AAS_ReachabilityFromNum( ms->lastreachnum, &reach );
					//if the bot is Not using the func bobbing
					if ( reach.traveltype != TRAVEL_FUNCBOB ||
						 //NOTE: the face number is the func_bobbing model number
						 ( reach.facenum & 0x0000FFFF ) != modelnum ) {
						reachnum = AAS_NextModelReachability( 0, modelnum );
						if ( reachnum ) {
							//botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client);
							AAS_ReachabilityFromNum( reachnum, &reach );
							ms->lastreachnum = reachnum;
							ms->reachability_time = AAS_Time() + BotReachabilityTime( &reach );
						} //end if
						else
						{
							if ( botDeveloper ) {
								botimport.Print( PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client );
							} //end if
							result->blocked = qtrue;
							result->blockentity = ent;
							result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
							return;
						} //end else
					} //end if
					result->flags |= MOVERESULT_ONTOPOF_FUNCBOB;
				} //end if
				  /* Ridah, disabled this, or standing on little fragments causes problems
				  else
				  {
					  result->blocked = qtrue;
					  result->blockentity = ent;
					  result->flags |= MOVERESULT_ONTOPOFOBSTACLE;
					  return;
				  } //end else
				  */
			} //end if
		} //end if
	} //end if
	  //if swimming
	if ( AAS_Swimming( ms->origin ) ) {
		ms->moveflags |= MFL_SWIMMING;
	}
	//if against a ladder
	if ( AAS_AgainstLadder( ms->origin, ms->areanum ) ) {
		ms->moveflags |= MFL_AGAINSTLADDER;
	}
	//if the bot is on the ground, swimming or against a ladder
	if ( ms->moveflags & ( MFL_ONGROUND | MFL_SWIMMING | MFL_AGAINSTLADDER ) ) {
		//botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1));
		//
		AAS_ReachabilityFromNum( ms->lastreachnum, &lastreach );
		//reachability area the bot is in
		//ms->areanum = BotReachabilityArea(ms->origin, (lastreach.traveltype != TRAVEL_ELEVATOR));
		ms->areanum = BotFuzzyPointReachabilityArea( ms->origin );
		//if the bot is in the goal area
		if ( ms->areanum == goal->areanum ) {
			*result = BotMoveInGoalArea( ms, goal );
			return;
		} //end if
		  //assume we can use the reachability from the last frame
		reachnum = ms->lastreachnum;
		//if there is a last reachability

// Ridah, best calc it each frame, especially for Zombie, which moves so slow that we can't afford to take a long route
//		reachnum = 0;
// done.
		if ( reachnum ) {
			AAS_ReachabilityFromNum( reachnum, &reach );
			//check if the reachability is still valid
			if ( !( AAS_TravelFlagForType( reach.traveltype ) & travelflags ) ) {
				reachnum = 0;
			} //end if
			  //special grapple hook case
			else if ( reach.traveltype == TRAVEL_GRAPPLEHOOK ) {
				if ( ms->reachability_time < AAS_Time() ||
					 ( ms->moveflags & MFL_GRAPPLERESET ) ) {
					reachnum = 0;
				} //end if
			} //end if
			  //special elevator case
			else if ( reach.traveltype == TRAVEL_ELEVATOR || reach.traveltype == TRAVEL_FUNCBOB ) {
				if ( ( result->flags & MOVERESULT_ONTOPOF_ELEVATOR ) ||
					 ( result->flags & MOVERESULT_ONTOPOF_FUNCBOB ) ) {
					ms->reachability_time = AAS_Time() + 5;
				} //end if
				  //if the bot was going for an elevator and reached the reachability area
				if ( ms->areanum == reach.areanum ||
					 ms->reachability_time < AAS_Time() ) {
					reachnum = 0;
				} //end if
			} //end if
			else
			{
#ifdef DEBUG
				if ( botDeveloper ) {
					if ( ms->reachability_time < AAS_Time() ) {
						botimport.Print( PRT_MESSAGE, "client %d: reachability timeout in ", ms->client );
						AAS_PrintTravelType( reach.traveltype );
						botimport.Print( PRT_MESSAGE, "\n" );
					} //end if
					  /*
					  if (ms->lastareanum != ms->areanum)
					  {
						  botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum);
					  } //end if*/
				} //end if
#endif //DEBUG
				//if the goal area changed or the reachability timed out
				//or the area changed
				if ( ms->lastgoalareanum != goal->areanum ||
					 ms->reachability_time < AAS_Time() ||
					 ms->lastareanum != ms->areanum ||
					 ( ( ms->lasttime > ( AAS_Time() - 0.5 ) ) && ( VectorDistance( ms->origin, ms->lastorigin ) < 20 * ( AAS_Time() - ms->lasttime ) ) ) ) {
					reachnum = 0;
					//botimport.Print(PRT_MESSAGE, "area change or timeout\n");
				} //end else if
			} //end else
		} //end if
		  //if the bot needs a new reachability
		if ( !reachnum ) {
			//if the area has no reachability links
			if ( !AAS_AreaReachability( ms->areanum ) ) {
#ifdef DEBUG
				if ( botDeveloper ) {
					botimport.Print( PRT_MESSAGE, "area %d no reachability\n", ms->areanum );
				} //end if
#endif //DEBUG
			} //end if
			  //get a new reachability leading towards the goal
			reachnum = BotGetReachabilityToGoal( ms->origin, ms->areanum, ms->entitynum,
												 ms->lastgoalareanum, ms->lastareanum,
												 ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
												 goal, travelflags, travelflags );
			//the area number the reachability starts in
			ms->reachareanum = ms->areanum;
			//reset some state variables
			ms->jumpreach = 0;                      //for TRAVEL_JUMP
			ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK
			//if there is a reachability to the goal
			if ( reachnum ) {
				AAS_ReachabilityFromNum( reachnum, &reach );
				//set a timeout for this reachability
				ms->reachability_time = AAS_Time() + BotReachabilityTime( &reach );
				//
#ifdef AVOIDREACH
				//add the reachability to the reachabilities to avoid for a while
				BotAddToAvoidReach( ms, reachnum, AVOIDREACH_TIME );
#endif //AVOIDREACH
			} //end if
#ifdef DEBUG

			else if ( botDeveloper ) {
				botimport.Print( PRT_MESSAGE, "goal not reachable\n" );
				memset( &reach, 0, sizeof( aas_reachability_t ) ); //make compiler happy
			} //end else
			if ( botDeveloper ) {
				//if still going for the same goal
				if ( ms->lastgoalareanum == goal->areanum ) {
					if ( ms->lastareanum == reach.areanum ) {
						botimport.Print( PRT_MESSAGE, "same goal, going back to previous area\n" );
					} //end if
				} //end if
			} //end if
#endif //DEBUG
		} //end else
		  //
		ms->lastreachnum = reachnum;
		ms->lastgoalareanum = goal->areanum;
		ms->lastareanum = ms->areanum;
		//if the bot has a reachability
		if ( reachnum ) {
			//get the reachability from the number
			AAS_ReachabilityFromNum( reachnum, &reach );
			result->traveltype = reach.traveltype;
			//
#ifdef DEBUG_AI_MOVE
			AAS_ClearShownDebugLines();
			AAS_PrintTravelType( reach.traveltype );
			AAS_ShowReachability( &reach );
#endif //DEBUG_AI_MOVE
			//
#ifdef DEBUG
			//botimport.Print(PRT_MESSAGE, "client %d: ", ms->client);
			//AAS_PrintTravelType(reach.traveltype);
			//botimport.Print(PRT_MESSAGE, "\n");
#endif //DEBUG
			switch ( reach.traveltype )
			{
			case TRAVEL_WALK: *result = BotTravel_Walk( ms, &reach ); break;
			case TRAVEL_CROUCH: *result = BotTravel_Crouch( ms, &reach ); break;
			case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump( ms, &reach ); break;
			case TRAVEL_LADDER: *result = BotTravel_Ladder( ms, &reach ); break;
			case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge( ms, &reach ); break;
			case TRAVEL_JUMP: *result = BotTravel_Jump( ms, &reach ); break;
			case TRAVEL_SWIM: *result = BotTravel_Swim( ms, &reach ); break;
			case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump( ms, &reach ); break;
			case TRAVEL_TELEPORT: *result = BotTravel_Teleport( ms, &reach ); break;
			case TRAVEL_ELEVATOR: *result = BotTravel_Elevator( ms, &reach ); break;
			case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple( ms, &reach ); break;
			case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump( ms, &reach ); break;
				//case TRAVEL_BFGJUMP:
			case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad( ms, &reach ); break;
			case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing( ms, &reach ); break;
			default:
			{
				botimport.Print( PRT_FATAL, "travel type %d not implemented yet\n", reach.traveltype );
				break;
			}     //end case
			} //end switch
		} //end if
		else
		{
			result->failure = qtrue;
			memset( &reach, 0, sizeof( aas_reachability_t ) );
		} //end else
#ifdef DEBUG
		if ( botDeveloper ) {
			if ( result->failure ) {
				botimport.Print( PRT_MESSAGE, "client %d: movement failure in ", ms->client );
				AAS_PrintTravelType( reach.traveltype );
				botimport.Print( PRT_MESSAGE, "\n" );
			} //end if
		} //end if
#endif //DEBUG
	} //end if
	else
	{
		int i, numareas, areas[16];
		vec3_t end;

		//special handling of jump pads when the bot uses a jump pad without knowing it
		foundjumppad = qfalse;
		VectorMA( ms->origin, -2 * ms->thinktime, ms->velocity, end );
		numareas = AAS_TraceAreas( ms->origin, end, areas, NULL, 16 );
		for ( i = numareas - 1; i >= 0; i-- )
		{
			if ( AAS_AreaJumpPad( areas[i] ) ) {
				//botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]);
				foundjumppad = qtrue;
				lastreachnum = BotGetReachabilityToGoal( end, areas[i], ms->entitynum,
														 ms->lastgoalareanum, ms->lastareanum,
														 ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries,
														 goal, travelflags, TFL_JUMPPAD );
				if ( lastreachnum ) {
					ms->lastreachnum = lastreachnum;
					ms->lastareanum = areas[i];
					//botimport.Print(PRT_MESSAGE, "found jumppad reachability\n");
					break;
				} //end if
				else
				{
					for ( lastreachnum = AAS_NextAreaReachability( areas[i], 0 ); lastreachnum;
						  lastreachnum = AAS_NextAreaReachability( areas[i], lastreachnum ) )
					{
						//get the reachability from the number
						AAS_ReachabilityFromNum( lastreachnum, &reach );
						if ( reach.traveltype == TRAVEL_JUMPPAD ) {
							ms->lastreachnum = lastreachnum;
							ms->lastareanum = areas[i];
							//botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n");
						} //end if
					} //end for
					if ( lastreachnum ) {
						break;
					}
				} //end else
			} //end if
		} //end for
		if ( botDeveloper ) {
			//if a jumppad is found with the trace but no reachability is found
			if ( foundjumppad && !ms->lastreachnum ) {
				botimport.Print( PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client );
			} //end if
		} //end if
		  //
		if ( ms->lastreachnum ) {
			//botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1));
			AAS_ReachabilityFromNum( ms->lastreachnum, &reach );
			result->traveltype = reach.traveltype;
#ifdef DEBUG
			//botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client);
			//AAS_PrintTravelType(reach.traveltype);
			//botimport.Print(PRT_MESSAGE, "\n");
#endif //DEBUG
			//
			switch ( reach.traveltype )
			{
			case TRAVEL_WALK: *result = BotTravel_Walk( ms, &reach ); break;  //BotFinishTravel_Walk(ms, &reach); break;
			case TRAVEL_CROUCH: /*do nothing*/ break;
			case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump( ms, &reach ); break;
			case TRAVEL_LADDER: *result = BotTravel_Ladder( ms, &reach ); break;
			case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge( ms, &reach ); break;
			case TRAVEL_JUMP: *result = BotFinishTravel_Jump( ms, &reach ); break;
			case TRAVEL_SWIM: *result = BotTravel_Swim( ms, &reach ); break;
			case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump( ms, &reach ); break;
			case TRAVEL_TELEPORT: /*do nothing*/ break;
			case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator( ms, &reach ); break;
			case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple( ms, &reach ); break;
			case TRAVEL_ROCKETJUMP: *result = BotFinishTravel_WeaponJump( ms, &reach ); break;
				//case TRAVEL_BFGJUMP:
			case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad( ms, &reach ); break;
			case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing( ms, &reach ); break;
			default:
			{
				botimport.Print( PRT_FATAL, "(last) travel type %d not implemented yet\n", reach.traveltype );
				break;
			}     //end case
			} //end switch
#ifdef DEBUG
			if ( botDeveloper ) {
				if ( result->failure ) {
					botimport.Print( PRT_MESSAGE, "client %d: movement failure in finish ", ms->client );
					AAS_PrintTravelType( reach.traveltype );
					botimport.Print( PRT_MESSAGE, "\n" );
				} //end if
			} //end if
#endif //DEBUG
		} //end if
	} //end else
	  //FIXME: is it right to do this here?
	if ( result->blocked ) {
		ms->reachability_time -= 10 * ms->thinktime;
	}
	//copy the last origin
	VectorCopy( ms->origin, ms->lastorigin );
	ms->lasttime = AAS_Time();

	// RF, try to look in the direction we will be moving ahead of time
	if ( reachnum > 0 && !( result->flags & ( MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) ) {
		vec3_t dir;
		int ftraveltime, freachnum;

		AAS_ReachabilityFromNum( reachnum, &reach );
		if ( reach.areanum != goal->areanum ) {
			if ( AAS_AreaRouteToGoalArea( reach.areanum, reach.end, goal->areanum, travelflags, &ftraveltime, &freachnum ) ) {
				AAS_ReachabilityFromNum( freachnum, &reach );
				VectorSubtract( reach.end, ms->origin, dir );
				VectorNormalize( dir );
				vectoangles( dir, result->ideal_viewangles );
				result->flags |= MOVERESULT_FUTUREVIEW;
			}
		} else {
			VectorSubtract( goal->origin, ms->origin, dir );
			VectorNormalize( dir );
			vectoangles( dir, result->ideal_viewangles );
			result->flags |= MOVERESULT_FUTUREVIEW;
		}
	}
} //end of the function BotMoveToGoal
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetAvoidReach( int movestate ) {
	bot_movestate_t *ms;

	ms = BotMoveStateFromHandle( movestate );
	if ( !ms ) {
		return;
	}
	memset( ms->avoidreach, 0, MAX_AVOIDREACH * sizeof( int ) );
	memset( ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof( float ) );
	memset( ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof( int ) );

	// RF, also clear movestate stuff
	ms->lastareanum = 0;
	ms->lastgoalareanum = 0;
	ms->lastreachnum = 0;
} //end of the function BotResetAvoidReach
//===========================================================================
//
// Parameter:				-
// Returns:					-
// Changes Globals:		-
//===========================================================================
void BotResetLastAvoidReach( int movestate ) {
	int i, latest;
	float latesttime;
	bot_movestate_t *ms;

	ms = BotMoveStateFromHandle( movestate );
	if ( !ms ) {
		return;
	}
	latesttime = 0;
	latest = 0;
	for ( i = 0; i < MAX_AVOIDREACH; i++ )
	{
		if ( ms->avoidreachtimes[i] > latesttime ) {
			latesttime = ms->avoidreachtimes[i];
			latest = i;
		} //end if
	} //end for
	if ( latesttime ) {
		ms->avoidreachtimes[latest] = 0;
		if ( ms->avoidreachtries[latest] > 0 ) {
			ms->avoidreachtries[latest]--;
		}
	} //end if
} //end of the function BotResetLastAvoidReach
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotResetMoveState( int movestate ) {
	bot_movestate_t *ms;

	ms = BotMoveStateFromHandle( movestate );
	if ( !ms ) {
		return;
	}
	memset( ms, 0, sizeof( bot_movestate_t ) );
} //end of the function BotResetMoveState
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
int BotSetupMoveAI( void ) {
	BotSetBrushModelTypes();
	sv_maxstep = LibVarValue( "sv_step", "18" );
	sv_maxbarrier = LibVarValue( "sv_maxbarrier", "32" );
	sv_gravity = LibVarValue( "sv_gravity", "800" );
	return BLERR_NOERROR;
} //end of the function BotSetupMoveAI
//===========================================================================
//
// Parameter:			-
// Returns:				-
// Changes Globals:		-
//===========================================================================
void BotShutdownMoveAI( void ) {
	int i;

	for ( i = 1; i <= MAX_CLIENTS; i++ )
	{
		if ( botmovestates[i] ) {
			FreeMemory( botmovestates[i] );
			botmovestates[i] = NULL;
		} //end if
	} //end for
} //end of the function BotShutdownMoveAI
