diff --git a/src/game/GameEdit.cpp b/src/game/GameEdit.cpp
index 57804f8..78558d5 100644
--- a/src/game/GameEdit.cpp
+++ b/src/game/GameEdit.cpp
@@ -818,7 +818,9 @@ void idEditEntities::DisplayEntities( void ) {
 */
 
 idGameEdit			gameEditLocal;
+#if !defined( OPENQ4_STATIC_SP_GAME ) && !defined( OPENQ4_STATIC_MP_GAME )
 idGameEdit *		gameEdit = &gameEditLocal;
+#endif
 
 
 /*
@@ -1957,4 +1959,3 @@ void rvModviewModel::Event_Speak ( const char* lipsync ) {
 }	
 
 // RAVEN END
-
diff --git a/src/game/Game_local.cpp b/src/game/Game_local.cpp
index 10376d9..b40a877 100644
--- a/src/game/Game_local.cpp
+++ b/src/game/Game_local.cpp
@@ -15,7 +15,13 @@
 
 //#define UI_DEBUG	1
 
-#ifdef GAME_DLL
+#if defined( OPENQ4_STATIC_SP_GAME ) && defined( OPENQ4_STATIC_MP_GAME )
+#error "OPENQ4_STATIC_SP_GAME and OPENQ4_STATIC_MP_GAME are mutually exclusive"
+#elif defined( OPENQ4_STATIC_SP_GAME ) || defined( OPENQ4_STATIC_MP_GAME )
+#define OPENQ4_STATIC_GAME 1
+#endif
+
+#if defined( GAME_DLL ) && !defined( OPENQ4_STATIC_GAME )
 
 idSys *						sys = NULL;
 idCommon *					common = NULL;
@@ -50,6 +56,10 @@ int Sys_Milliseconds( void ) {
 	return sys ? sys->Milliseconds() : 0;
 }
 
+#elif defined( OPENQ4_STATIC_GAME )
+
+extern idCVar com_forceGenericSIMD;
+
 #endif
 
 idRenderWorld *				gameRenderWorld = NULL;		// all drawing is done to this world
@@ -336,7 +346,9 @@ idAnimManager				*animationLib = NULL;
 
 // the rest of the engine will only reference the "game" variable, while all local aspects stay hidden
 idGameLocal					gameLocal;
+#if !defined( OPENQ4_STATIC_GAME )
 idGame *					game = &gameLocal;	// statically pointed at an idGameLocal
+#endif
 
 bool OpenQ4_TurboModeActive( void ) {
 	if ( gameLocal.isMultiplayer ) {
@@ -402,7 +414,14 @@ GetGameAPI
 #if __GNUC__ >= 4
 #pragma GCC visibility push(default)
 #endif
-extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) {
+#if defined( OPENQ4_STATIC_MP_GAME )
+#define OPENQ4_GAME_API_ENTRY OpenQ4MP_GetGameAPI
+#elif defined( OPENQ4_STATIC_SP_GAME )
+#define OPENQ4_GAME_API_ENTRY OpenQ4SP_GetGameAPI
+#else
+#define OPENQ4_GAME_API_ENTRY GetGameAPI
+#endif
+extern "C" gameExport_t *OPENQ4_GAME_API_ENTRY( gameImport_t *import ) {
 
 	if ( import->version == GAME_API_VERSION ) {
 
@@ -442,8 +461,14 @@ extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) {
 
 	// setup export interface
 	gameExport.version = GAME_API_VERSION;
+#if defined( OPENQ4_STATIC_GAME ) || defined( __EMSCRIPTEN__ )
+	gameExport.game = &gameLocal;
+	extern idGameEdit gameEditLocal;
+	gameExport.gameEdit = &gameEditLocal;
+#else
 	gameExport.game = game;
 	gameExport.gameEdit = gameEdit;
+#endif
 // RAVEN BEGIN
 // bdube: game log
 	gameExport.gameLog = gameLog;
@@ -483,9 +508,11 @@ void TestGameAPI( void ) {
 	testImport.bse						= ::bse;
 // RAVEN END
 
-	testExport = *GetGameAPI( &testImport );
+	testExport = *OPENQ4_GAME_API_ENTRY( &testImport );
 }
 
+#undef OPENQ4_GAME_API_ENTRY
+
 /*
 ================
 idGameLocal::BuildModList
@@ -793,6 +820,7 @@ void idGameLocal::Init( void ) {
 	Memory::InitAllocator(allocator, deallocator, msize);
 #endif
 // RAVEN END
+#if !defined( OPENQ4_STATIC_GAME )
 	// initialize idLib
 	idLib::Init();
 
@@ -801,6 +829,7 @@ void idGameLocal::Init( void ) {
 
 	// initialize processor specific SIMD
 	idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() );
+#endif
 
 #endif
 // RAVEN BEGIN
@@ -1100,12 +1129,18 @@ void idGameLocal::Shutdown( void ) {
 	// enable leak test
 	Mem_EnableLeakTest( "game" );
 
+#if !defined( OPENQ4_STATIC_GAME )
 	// shutdown idLib
 	idLib::ShutDown();
+#endif
 
 #endif
 }
 
+#if defined( OPENQ4_STATIC_GAME )
+#undef OPENQ4_STATIC_GAME
+#endif
+
 /*
 ===========
 idGameLocal::SaveGame
diff --git a/src/game/bots/Bot_weapons.cpp b/src/game/bots/Bot_weapons.cpp
index e7f9ed5..c9b3a93 100644
--- a/src/game/bots/Bot_weapons.cpp
+++ b/src/game/bots/Bot_weapons.cpp
@@ -283,7 +283,7 @@ void idBotWeaponInfoManager::LoadWeaponConfig( char* filename )
 		{
 			if( projectileinfo[j].name == weaponinfo[i].projectile )
 			{
-				memcpy( &weaponinfo[i].proj, &projectileinfo[j], sizeof( projectileinfo_t ) );
+				weaponinfo[i].proj = projectileinfo[j];
 				break;
 			}
 		}
diff --git a/src/game/gamesys/Class.h b/src/game/gamesys/Class.h
index c87dd8e..65dc5b2 100644
--- a/src/game/gamesys/Class.h
+++ b/src/game/gamesys/Class.h
@@ -44,7 +44,7 @@ public:
 	idEventArg(const trace_t* data) { type = D_EVENT_TRACE; value = reinterpret_cast<intptr_t>(data); };
 
 // jmarshall - this can't be called from doomscript!
-#if defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__)
+#if defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__) || defined(__EMSCRIPTEN__)
 	idEventArg(intptr_t data) { type = D_EVENT_INTEGER64bit; value = data; };
 #else
 	idEventArg(long long data) { type = D_EVENT_INTEGER64bit; value = static_cast<intptr_t>(data); };
diff --git a/src/mpgame/Actor.cpp b/src/mpgame/Actor.cpp
index f5adebc..dca89e1 100644
--- a/src/mpgame/Actor.cpp
+++ b/src/mpgame/Actor.cpp
@@ -3871,3 +3871,15 @@ void idActor::GuidedProjectileIncoming( idGuidedProjectile *projectile )
 	}
 }
 // RAVEN END
+/*
+=====================
+idActor::PointVisible
+=====================
+*/
+bool idActor::PointVisible( const idVec3 &point ) const {
+	trace_t results;
+	idVec3 end = point;
+	end[2] += 1.0f;
+	gameLocal.clip[0]->TracePoint( results, GetEyePosition(), end, MASK_OPAQUE, this );
+	return results.fraction >= 1.0f;
+}
diff --git a/src/mpgame/Actor.h b/src/mpgame/Actor.h
index 3476c35..ad54797 100644
--- a/src/mpgame/Actor.h
+++ b/src/mpgame/Actor.h
@@ -144,6 +144,7 @@ public:
 	void					SetupBody( void );
 
 	virtual void			CheckBlink( void );
+	bool					PointVisible( const idVec3 &point ) const;
 
 	virtual bool			GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis );
 	virtual bool			GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis );
diff --git a/src/mpgame/Entity.cpp b/src/mpgame/Entity.cpp
index 692d1bc..ef7d144 100644
--- a/src/mpgame/Entity.cpp
+++ b/src/mpgame/Entity.cpp
@@ -3009,6 +3009,28 @@ void idEntity::QuitTeam( void ) {
 	teamChain = NULL;
 }
 
+const char* idEntity::GetKey( const char* key ) {
+	const char* value = "";
+	spawnArgs.GetString( key, "", &value );
+	return value;
+}
+
+float idEntity::GetFloat( const char* key ) {
+	return spawnArgs.GetFloat( key, "0" );
+}
+
+int idEntity::GetInt( const char* key ) {
+	return spawnArgs.GetInt( key, "0" );
+}
+
+bool idEntity::GetBool( const char* key ) {
+	return spawnArgs.GetBool( key, "0" );
+}
+
+idVec3 idEntity::GetOrigin( void ) {
+	return GetLocalCoordinates( GetPhysics()->GetOrigin() );
+}
+
 /***********************************************************************
 
   Physics.
@@ -6885,4 +6907,3 @@ void idAnimatedEntity::Event_CollapseJoints ( const char* jointnames, const char
 	animator.CollapseJoints ( jointnames, collapseToJoint );	
 }
 // RAVEN END
-
diff --git a/src/mpgame/Entity.h b/src/mpgame/Entity.h
index f46748a..2705ddc 100644
--- a/src/mpgame/Entity.h
+++ b/src/mpgame/Entity.h
@@ -215,6 +215,13 @@ public:
 	void					BecomeInactive( int flags );
 	void					UpdatePVSAreas( const idVec3 &pos );
 
+	idVec3					GetOrigin( void );
+	float					GetFloat( const char* key );
+	const char*				GetKey( const char* key );
+	int						GetInt( const char* key );
+	bool					GetBool( const char* key );
+	virtual void			InflictedDamageEvent( idEntity* target ) {}
+
 // RAVEN BEGIN
 // abahr:
 	bool					IsActive( int flags ) const { return (flags & thinkFlags) > 0; }
diff --git a/src/mpgame/GameEdit.cpp b/src/mpgame/GameEdit.cpp
index 50db640..79e79b9 100644
--- a/src/mpgame/GameEdit.cpp
+++ b/src/mpgame/GameEdit.cpp
@@ -818,7 +818,9 @@ void idEditEntities::DisplayEntities( void ) {
 */
 
 idGameEdit			gameEditLocal;
+#if !defined( OPENQ4_STATIC_SP_GAME ) && !defined( OPENQ4_STATIC_MP_GAME )
 idGameEdit *		gameEdit = &gameEditLocal;
+#endif
 
 
 /*
@@ -1957,4 +1959,3 @@ void rvModviewModel::Event_Speak ( const char* lipsync ) {
 }	
 
 // RAVEN END
-
diff --git a/src/mpgame/Game_local.cpp b/src/mpgame/Game_local.cpp
index 0042d59..346ab3a 100644
--- a/src/mpgame/Game_local.cpp
+++ b/src/mpgame/Game_local.cpp
@@ -26,7 +26,13 @@ idCVar gamename( "gamename", game_name.string, CVAR_GAME | CVAR_SERVERINFO | CVA
 
 //#define UI_DEBUG	1
 
-#ifdef GAME_DLL
+#if defined( OPENQ4_STATIC_SP_GAME ) && defined( OPENQ4_STATIC_MP_GAME )
+#error "OPENQ4_STATIC_SP_GAME and OPENQ4_STATIC_MP_GAME are mutually exclusive"
+#elif defined( OPENQ4_STATIC_SP_GAME ) || defined( OPENQ4_STATIC_MP_GAME )
+#define OPENQ4_STATIC_GAME 1
+#endif
+
+#if defined( GAME_DLL ) && !defined( OPENQ4_STATIC_GAME )
 
 idSys *						sys = NULL;
 idCommon *					common = NULL;
@@ -61,6 +67,10 @@ int Sys_Milliseconds( void ) {
 	return sys ? sys->Milliseconds() : 0;
 }
 
+#elif defined( OPENQ4_STATIC_GAME )
+
+extern idCVar com_forceGenericSIMD;
+
 #endif
 
 idRenderWorld *				gameRenderWorld = NULL;		// all drawing is done to this world
@@ -133,7 +143,9 @@ idAnimManager				*animationLib = NULL;
 
 // the rest of the engine will only reference the "game" variable, while all local aspects stay hidden
 idGameLocal					gameLocal;
+#if !defined( OPENQ4_STATIC_GAME )
 idGame *					game = &gameLocal;	// statically pointed at an idGameLocal
+#endif
 
 const char *idGameLocal::sufaceTypeNames[ MAX_SURFACE_TYPES ] = {
 	"none",	"metal", "stone", "flesh", "wood", "cardboard", "liquid", "glass", "plastic",
@@ -149,7 +161,14 @@ GetGameAPI
 #if __GNUC__ >= 4
 #pragma GCC visibility push(default)
 #endif
-extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) {
+#if defined( OPENQ4_STATIC_MP_GAME )
+#define OPENQ4_GAME_API_ENTRY OpenQ4MP_GetGameAPI
+#elif defined( OPENQ4_STATIC_SP_GAME )
+#define OPENQ4_GAME_API_ENTRY OpenQ4SP_GetGameAPI
+#else
+#define OPENQ4_GAME_API_ENTRY GetGameAPI
+#endif
+extern "C" gameExport_t *OPENQ4_GAME_API_ENTRY( gameImport_t *import ) {
 
 	if ( import->version == GAME_API_VERSION ) {
 
@@ -189,8 +208,14 @@ extern "C" gameExport_t *GetGameAPI( gameImport_t *import ) {
 
 	// setup export interface
 	gameExport.version = GAME_API_VERSION;
+#if defined( OPENQ4_STATIC_GAME ) || defined( __EMSCRIPTEN__ )
+	gameExport.game = &gameLocal;
+	extern idGameEdit gameEditLocal;
+	gameExport.gameEdit = &gameEditLocal;
+#else
 	gameExport.game = game;
 	gameExport.gameEdit = gameEdit;
+#endif
 // RAVEN BEGIN
 // bdube: game log
 	gameExport.gameLog = gameLog;
@@ -230,9 +255,11 @@ void TestGameAPI( void ) {
 	testImport.bse						= ::bse;
 // RAVEN END
 
-	testExport = *GetGameAPI( &testImport );
+	testExport = *OPENQ4_GAME_API_ENTRY( &testImport );
 }
 
+#undef OPENQ4_GAME_API_ENTRY
+
 /*
 ================
 idGameLocal::BuildModList
@@ -320,6 +347,7 @@ void idGameLocal::Clear( void ) {
 	serverInfo.Clear();
 	repeaterInfo.Clear();
 	numClients = 0;
+	nextBotPopulationCheckTime = 0;
 	for ( i = 0; i < MAX_CLIENTS; i++ ) {
 		userInfo[i].Clear();
 		persistentPlayerInfo[i].Clear();
@@ -478,6 +506,8 @@ void idGameLocal::Clear( void ) {
 		networkSystem->UseSortFunction( filterByMod, false );
 	}
 	clientAckSequence = -1;
+	botItemTable = NULL;
+	registeredBots.Clear();
 }
 
 /*
@@ -516,6 +546,7 @@ void idGameLocal::Init( void ) {
 	Memory::InitAllocator(allocator, deallocator, msize);
 #endif
 // RAVEN END
+#if !defined( OPENQ4_STATIC_GAME )
 	// initialize idLib
 	idLib::Init();
 
@@ -524,6 +555,7 @@ void idGameLocal::Init( void ) {
 
 	// initialize processor specific SIMD
 	idSIMD::InitProcessor( "game", com_forceGenericSIMD.GetBool() );
+#endif
 
 #endif
 // RAVEN BEGIN
@@ -632,6 +664,16 @@ void idGameLocal::Init( void ) {
 		kv = dict->MatchPrefix( "type", kv );
 	}
 
+	botItemTable = FindEntityDef( "bot_itemtable", false );
+	if ( botItemTable == NULL ) {
+		common->Warning( "bot_itemtable decl not found. Bot support disabled.\n" );
+	} else {
+		botCharacterStatsManager.Init();
+		botFuzzyWeightManager.Init();
+		botWeaponInfoManager.Init();
+		botGoalManager.BotSetupGoalAI();
+	}
+
 	gamestate = GAMESTATE_NOMAP;
 
 	Printf( "...%d aas types\n", aasList.Num() );
@@ -786,12 +828,18 @@ void idGameLocal::Shutdown( void ) {
 	// enable leak test
 	Mem_EnableLeakTest( "game" );
 
+#if !defined( OPENQ4_STATIC_GAME )
 	// shutdown idLib
 	idLib::ShutDown();
+#endif
 
 #endif
 }
 
+#if defined( OPENQ4_STATIC_GAME )
+#undef OPENQ4_STATIC_GAME
+#endif
+
 /*
 ===========
 idGameLocal::SaveGame
@@ -2227,6 +2275,10 @@ void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorl
 			Sys_Milliseconds() - initStartMsec );
 	}
 
+	if ( botItemTable && isServer ) {
+		botGoalManager.InitLevelItems();
+	}
+
 	gamestate = GAMESTATE_ACTIVE;
 	autoExecAfterMapLoadStartTime = 0;
 	const char *autoExecAfterMapLoad = g_autoExecAfterMapLoad.GetString();
@@ -2953,7 +3005,7 @@ void idGameLocal::GetShakeSounds( const idDict *dict ) {
 	idStr soundName;
 
 	soundShaderName = dict->GetString( "s_shader" );
-	if ( soundShaderName != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) {
+	if ( soundShaderName[0] != '\0' && dict->GetFloat( "s_shakes" ) != 0.0f ) {
 		soundShader = declManager->FindSound( soundShaderName );
 
 		for ( int i = 0; i < soundShader->GetNumSounds(); i++ ) {
@@ -3309,9 +3361,6 @@ idGameLocal::SpawnPlayer
 ============
 */
 void idGameLocal::SpawnPlayer( int clientNum, bool isBot, const char* botName ) {
-	(void)isBot;
-	(void)botName;
-
 	TIME_THIS_SCOPE( __FUNCLINE__);
 
 	idEntity	*ent;
@@ -3328,7 +3377,19 @@ void idGameLocal::SpawnPlayer( int clientNum, bool isBot, const char* botName )
 	args.Set( "name", va( "player%d", clientNum + 1 ) );
 // RAVEN BEGIN
 // bdube: changed marine class
-	args.Set( "classname", idPlayer::GetSpawnClassname() );
+	const char* playerSpawnClassname = idPlayer::GetSpawnClassname();
+	if ( isBot ) {
+		idStr botClassname = va( "%s_bot", playerSpawnClassname );
+		if ( FindEntityDef( botClassname.c_str(), false ) ) {
+			args.Set( "classname", botClassname.c_str() );
+		} else {
+			args.Set( "classname", playerSpawnClassname );
+			args.Set( "spawnclass", "rvmBot" );
+		}
+		args.Set( "botname", ( botName && botName[0] ) ? botName : "bot" );
+	} else {
+		args.Set( "classname", playerSpawnClassname );
+	}
 // RAVEN END
 	
 	// This takes a really long time.
@@ -3907,6 +3968,8 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds, int activeEdito
 		time += GetMSec();
 		msec = time - previousTime;
 
+		MaintainBotMinPlayers();
+
 		realClientTime = time;
 		{
 TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()");
@@ -4098,6 +4161,9 @@ TIME_THIS_SCOPE("idGameLocal::RunFrame - gameDebug.BeginFrame()");
 
 		// do multiplayer related stuff
 		if ( isMultiplayer ) {
+			if ( isServer && botItemTable && registeredBots.Num() > 0 ) {
+				botGoalManager.UpdateEntityItems();
+			}
 			mpGame.Run();
 			CheckAutoExecAfterMapLoad();
 		}
@@ -6050,14 +6116,14 @@ void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) {
 idGameLocal::RequirementMet
 ================
 */
-bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requires, int removeItem ) {
-	if ( requires.Length() ) {
+bool idGameLocal::RequirementMet( idEntity *activator, const idStr &requirement, int removeItem ) {
+	if ( requirement.Length() ) {
 // RAVEN BEGIN
 // jnewquist: Use accessor for static class type 
 		if ( activator->IsType( idPlayer::GetClassType() ) ) {
 // RAVEN END
 			idPlayer *player = static_cast<idPlayer *>(activator);
-			idDict *item = player->FindInventoryItem( requires );
+			idDict *item = player->FindInventoryItem( requirement );
 			if ( item ) {
 				if ( removeItem ) {
 					player->RemoveInventoryItem( item );
@@ -8939,6 +9005,148 @@ void idGameLocal::GetRandomBotName( int clientNum, idStr& name ) {
 	name = "Quake4Bot";
 }
 
+/*
+===============
+idGameLocal::Trace
+===============
+*/
+void idGameLocal::Trace( trace_t& results, const idVec3& start, const idVec3& end, int contentMask, int passEntity ) {
+	idMat3 axis;
+	axis.Identity();
+
+	const int mask = contentMask == 0 ? CONTENTS_SOLID : contentMask;
+	idEntity* pass = ( passEntity >= 0 && passEntity < MAX_GENTITIES ) ? entities[passEntity] : NULL;
+	clip[0]->Translation( results, start, end, NULL, axis, mask, pass );
+}
+
+/*
+================
+idGameLocal::TravelTimeToGoal
+================
+*/
+int idGameLocal::TravelTimeToGoal( const idVec3& origin, const idVec3& goal, int travelFlags ) {
+	idAAS* aas = GetBotAAS();
+	if ( aas == NULL ) {
+		return 0;
+	}
+
+	idVec3 start = origin;
+	const int startArea = aas->AdjustPositionAndGetArea( start );
+	idVec3 end = goal;
+	const int goalArea = aas->AdjustPositionAndGetArea( end );
+	if ( startArea <= 0 || goalArea <= 0 ) {
+		return 0;
+	}
+	if ( startArea == goalArea ) {
+		return Max( 1, idMath::FtoiFast( ( end - start ).LengthFast() ) );
+	}
+
+	const int flags = travelFlags != 0 ? travelFlags : ( TFL_WALK | TFL_AIR );
+	int travelTime = 0;
+	idReachability* reach = NULL;
+	if ( !aas->RouteToGoalArea( startArea, start, goalArea, flags, travelTime, &reach ) ) {
+		return 0;
+	}
+	return Max( 1, travelTime );
+}
+
+/*
+===============
+idGameLocal::GetBotItemEntry
+===============
+*/
+int idGameLocal::GetBotItemEntry( const char* name ) {
+	if ( !name || !name[0] || !botItemTable ) {
+		return 9;
+	}
+	const idKeyValue* keyValue = botItemTable->dict.FindKey( name );
+	if ( !keyValue ) {
+		return 9;
+	}
+	return botItemTable->dict.GetInt( name );
+}
+
+/*
+======================
+idGameLocal::AddBot
+======================
+*/
+int idGameLocal::AddBot( const char* botName ) {
+	const char* requestedBotName = ( botName && botName[0] ) ? botName : "major";
+	if ( !isServer ) {
+		common->Warning( "idGameLocal::AddBot: Bots can only be added on the server\n" );
+		return -1;
+	}
+
+	idAAS* botAAS = GetBotAAS();
+	if ( botAAS == NULL || !botAAS->IsValid() ) {
+		idStr expectedAASPaths;
+		for ( int i = 0; !mapFileName.IsEmpty() && i < aasNames.Num(); ++i ) {
+			idStr aasPath = mapFileName;
+			aasPath.SetFileExtension( aasNames[i] );
+			if ( i > 0 ) {
+				expectedAASPaths += ", ";
+			}
+			expectedAASPaths += aasPath;
+		}
+
+		common->Warning(
+			"idGameLocal::AddBot: Bot '%s' was not added because no valid AAS is loaded for map '%s'\n",
+			requestedBotName,
+			mapFileName.IsEmpty() ? "<unknown>" : mapFileName.c_str()
+		);
+		if ( !expectedAASPaths.IsEmpty() ) {
+			common->Warning( "idGameLocal::AddBot: Expected one of these AAS files to be present: %s\n", expectedAASPaths.c_str() );
+		}
+		return -1;
+	}
+
+	const int maxPlayers = idMath::ClampInt( 1, 16, serverInfo.GetInt( "si_maxPlayers", "8" ) );
+	return networkSystem->AllocateClientSlotForBot( requestedBotName, maxPlayers );
+}
+
+/*
+======================
+idGameLocal::MaintainBotMinPlayers
+======================
+*/
+void idGameLocal::MaintainBotMinPlayers( void ) {
+	if ( !isServer || !isMultiplayer ) {
+		return;
+	}
+	if ( time < nextBotPopulationCheckTime ) {
+		return;
+	}
+	nextBotPopulationCheckTime = time + 1000;
+
+	const int maxPlayers = idMath::ClampInt( 1, 16, serverInfo.GetInt( "si_maxPlayers", "8" ) );
+	const int targetBots = idMath::ClampInt( 0, maxPlayers, bot_minplayers.GetInteger() );
+	int activeClients = networkSystem->ServerGetNumActiveClients();
+	int botClients = networkSystem->ServerGetNumBotClients();
+
+	while ( botClients > targetBots ) {
+		if ( networkSystem->RemoveClientSlotForBot( "was removed by bot_minplayers" ) < 0 ) {
+			break;
+		}
+		activeClients--;
+		botClients--;
+	}
+
+	if ( botClients >= targetBots || activeClients >= maxPlayers ) {
+		return;
+	}
+	const idAAS* botAAS = GetBotAAS();
+	if ( botAAS == NULL || !botAAS->IsValid() ) {
+		return;
+	}
+	while ( botClients < targetBots && activeClients < maxPlayers ) {
+		if ( AddBot( "major" ) < 0 ) {
+			break;
+		}
+		activeClients++; botClients++;
+	}
+}
+
 // RAVEN BEGIN
 // mwhitlock: Dynamic memory consolidation
 #if defined(_RV_MEM_SYS_SUPPORT)
diff --git a/src/mpgame/Game_local.h b/src/mpgame/Game_local.h
index 900cb14..0b9ec5a 100644
--- a/src/mpgame/Game_local.h
+++ b/src/mpgame/Game_local.h
@@ -36,6 +36,7 @@ class idWorldspawn;
 class idTestModel;
 class idAAS;
 class idAI;
+class rvmBot;
 // RAVEN BEGIN
 // bdube: not using id effects
 //class idSmokeParticles;
@@ -622,6 +623,30 @@ public:
 	virtual void			ServerClientConnect( int clientNum, const char *guid );
 	virtual void			ServerClientBegin( int clientNum, bool isBot = false, const char* botName = "" );
 	virtual void			GetRandomBotName( int clientNum, idStr& name );
+	virtual int				TravelTimeToGoal( const idVec3& origin, const idVec3& goal, int travelFlags = 0 );
+	virtual int				GetBotItemEntry( const char* name );
+	int						AddBot( const char* botName );
+	void					MaintainBotMinPlayers( void );
+
+	idAAS*					GetBotAAS( void ) {
+		return NumAAS() > 0 ? GetAAS( 0 ) : NULL;
+	}
+
+	void					RegisterBot( rvmBot* bot ) {
+		registeredBots.AddUnique( bot );
+	}
+	void					UnRegisterBot( rvmBot* bot ) {
+		registeredBots.Remove( bot );
+	}
+
+	float					SysScriptTime( void ) const {
+		return MS2SEC( realClientTime );
+	}
+	float					SysScriptFrameTime( void ) const {
+		return MS2SEC( time - previousTime );
+	}
+
+	void					Trace( trace_t& results, const idVec3& start, const idVec3& end, int contentMask, int passEntity );
 	virtual void			ServerClientDisconnect( int clientNum );
 	virtual void			ServerWriteInitialReliableMessages( int clientNum );
 	virtual allowReply_t	RepeaterAllowClient( int clientId, int numClients, const char *IP, const char *guid, bool repeater, const char *password, const char *privatePassword, char reason[MAX_STRING_CHARS] );
@@ -785,7 +810,7 @@ public:
 	// used to skip one when registering entities, leaving an empty entity in the array
 	void					SkipEntityIndex( void );
 
-	bool					RequirementMet( idEntity *activator, const idStr &requires, int removeItem );
+	bool					RequirementMet( idEntity *activator, const idStr &requirement, int removeItem );
 
 // RITUAL BEGIN
 // squirrel: accessor for si_weaponStay checks
@@ -1048,13 +1073,14 @@ public:
 	void					ServerSetEntityIndexWatermark( int instanceID );
 
 	idLagometer				lagometer;
-private:
 // RAVEN BEGIN
 // ddynerman: multiple instance for MP
 	idList<idClip*>			clip;					// collision detection
 	idList<rvInstance*>		instances;
 // RAVEN END
 
+private:
+
 	// keep watermarks on the high entity index
 	// server transmits this to clients so they use the right entity layout
 	idList<int>				instancesEntityIndexWatermarks;
@@ -1259,6 +1285,10 @@ private:
 	idList<mpBanInfo_t>		banList;
 	bool					banListLoaded;
 	bool					banListChanged;
+
+	const idDeclEntityDef*	botItemTable;
+	idList<rvmBot*>			registeredBots;
+	int						nextBotPopulationCheckTime;
 // RAVEN END
 };
 
@@ -1555,4 +1585,6 @@ ID_INLINE idEntityPtr<type>::operator type * ( void ) const {
 
 #include "../idlib/containers/ListGame.h"
 
+#include "bots/Bot.h"
+
 #endif	/* !__GAME_LOCAL_H__ */
diff --git a/src/mpgame/Item.cpp b/src/mpgame/Item.cpp
index dcb89ea..a9cf15a 100644
--- a/src/mpgame/Item.cpp
+++ b/src/mpgame/Item.cpp
@@ -425,6 +425,8 @@ void idItem::Spawn( void ) {
 	RV_PUSH_HEAP_MEM(this);
 // RAVEN END
 
+	modelindex = gameLocal.GetBotItemEntry( spawnArgs.GetString( "modelindex" ) );
+
 	physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
 
 // RAVEN BEGIN
diff --git a/src/mpgame/Item.h b/src/mpgame/Item.h
index a2fd488..8efa1d1 100644
--- a/src/mpgame/Item.h
+++ b/src/mpgame/Item.h
@@ -39,6 +39,8 @@ public:
 	virtual void			InstanceLeave( void );
 	virtual bool			GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis );
 
+	int						GetModelIndex( void ) const { return modelindex; }
+
 // RAVEN BEGIN
 // mekberg: added
 	virtual bool			Collide( const trace_t &collision, const idVec3 &velocity );
@@ -123,6 +125,8 @@ private: // FIXME: Temp hack while Eric gets back to me about why GameState.cpp
 // abahr
 	void					Event_SetGravity();
 // RAVEN END
+
+	int						modelindex;
 };
 
 /*
diff --git a/src/mpgame/Mover.cpp b/src/mpgame/Mover.cpp
index abd808e..60572b7 100644
--- a/src/mpgame/Mover.cpp
+++ b/src/mpgame/Mover.cpp
@@ -4066,7 +4066,7 @@ idDoor::idDoor( void ) {
 	nextSndTriggerTime = 0;
 	localTriggerOrigin.Zero();
 	localTriggerAxis.Identity();
-	requires.Clear();
+	requirement.Clear();
 	removeItem = 0;
 	syncLock.Clear();
 	companionDoor = NULL;
@@ -4107,7 +4107,7 @@ void idDoor::Save( idSaveGame *savefile ) const {
 	savefile->WriteVec3( localTriggerOrigin );
 	savefile->WriteMat3( localTriggerAxis );
 
-	savefile->WriteString( requires );
+	savefile->WriteString( requirement );
 	savefile->WriteInt( removeItem );
 	savefile->WriteString( syncLock );
 	savefile->WriteInt( normalAxisIndex );
@@ -4141,7 +4141,7 @@ void idDoor::Restore( idRestoreGame *savefile ) {
 	savefile->ReadVec3( localTriggerOrigin );
 	savefile->ReadMat3( localTriggerAxis );
 
-	savefile->ReadString( requires );
+	savefile->ReadString( requirement );
 	savefile->ReadInt( removeItem );
 	savefile->ReadString( syncLock );
 	savefile->ReadInt( normalAxisIndex );
@@ -4203,7 +4203,7 @@ void idDoor::Spawn( void ) {
 
 	spawnArgs.GetString( "buddy", "", buddyStr );
 
-	spawnArgs.GetString( "requires", "", requires );
+	spawnArgs.GetString( "requires", "", requirement );
 	spawnArgs.GetInt( "removeItem", "0", removeItem );
 
 	// ever separate piece of a door is considered solid when other team mates push entities
@@ -4445,7 +4445,7 @@ idDoor::Use
 ================
 */
 void idDoor::Use( idEntity *other, idEntity *activator ) {
-	if ( gameLocal.RequirementMet( activator, requires, removeItem ) ) {
+	if ( gameLocal.RequirementMet( activator, requirement, removeItem ) ) {
 		if ( syncLock.Length() ) {
 			idEntity *sync = gameLocal.FindEntity( syncLock );
 			if ( sync && sync->IsType( idDoor::Type ) ) {
diff --git a/src/mpgame/Mover.h b/src/mpgame/Mover.h
index 0797c6e..0cd0d26 100644
--- a/src/mpgame/Mover.h
+++ b/src/mpgame/Mover.h
@@ -510,7 +510,7 @@ private:
 	int						nextSndTriggerTime;
 	idVec3					localTriggerOrigin;
 	idMat3					localTriggerAxis;
-	idStr					requires;
+	idStr					requirement;
 	int						removeItem;
 	idStr					syncLock;
 	int						normalAxisIndex;		// door faces X or Y for spectator teleports
diff --git a/src/mpgame/Player.cpp b/src/mpgame/Player.cpp
index 900fae2..f6fe446 100644
--- a/src/mpgame/Player.cpp
+++ b/src/mpgame/Player.cpp
@@ -14834,3 +14834,31 @@ bool idPlayer::IsSpectatedClient( void ) const {
 	}
 	return false;
 }
+/*
+===================
+idPlayer bot accessors
+===================
+*/
+bool idPlayer::IsShooting( void ) const {
+	return pfl.attackHeld;
+}
+
+float idPlayer::GetViewHeight( void ) const {
+	if ( spectating ) {
+		return 0.0f;
+	}
+	if ( health <= 0 ) {
+		return pm_deadviewheight.GetFloat();
+	}
+	if ( physicsObj.IsCrouching() ) {
+		return pm_crouchviewheight.GetFloat();
+	}
+	if ( IsInVehicle() ) {
+		return 0.0f;
+	}
+	return pm_normalviewheight.GetFloat();
+}
+
+const char *idPlayer::GetNetName( void ) const {
+	return gameLocal.userInfo[entityNumber].GetString( "ui_name" );
+}
diff --git a/src/mpgame/Player.h b/src/mpgame/Player.h
index ab50199..4ee4f77 100644
--- a/src/mpgame/Player.h
+++ b/src/mpgame/Player.h
@@ -441,7 +441,10 @@ public:
 	void					SetupWeaponEntity( void );
 	bool					SelectSpawnPoint( idVec3 &origin, idAngles &angles );
 	void					SpawnFromSpawnSpot( void );
-	void					SpawnToPoint( const idVec3	&spawn_origin, const idAngles &spawn_angles );
+	virtual void			SpawnToPoint( const idVec3	&spawn_origin, const idAngles &spawn_angles );
+	bool					IsShooting( void ) const;
+	float					GetViewHeight( void ) const;
+	const char *			GetNetName( void ) const;
 	void					SetClipModel( bool forceSpectatorBBox = false );	// spectator mode uses a different bbox size
 
 	void					SavePersistantInfo( void );
@@ -536,6 +539,7 @@ public:
 	void					DiscoverSecretArea			( const char *description);
 	
 	void					StartBossBattle				( idEntity* ent );
+	virtual bool			IsBot						( void ) { return false; }
 
 	// Powerups
 	bool					GivePowerUp					( int powerup, int time, bool team = false );
diff --git a/src/mpgame/Trigger.cpp b/src/mpgame/Trigger.cpp
index 36949e9..b179f93 100644
--- a/src/mpgame/Trigger.cpp
+++ b/src/mpgame/Trigger.cpp
@@ -288,7 +288,7 @@ void idTrigger_Multi::Save( idSaveGame *savefile ) const {
 	savefile->WriteFloat( delay );
 	savefile->WriteFloat( random_delay );
 	savefile->WriteInt( nextTriggerTime );
-	savefile->WriteString( requires );
+	savefile->WriteString( requirement );
 	savefile->WriteInt( removeItem );
 	savefile->WriteBool( touchClient );
 	savefile->WriteBool( touchOther );
@@ -308,7 +308,7 @@ void idTrigger_Multi::Restore( idRestoreGame *savefile ) {
 	savefile->ReadFloat( delay );
 	savefile->ReadFloat( random_delay );
 	savefile->ReadInt( nextTriggerTime );
-	savefile->ReadString( requires );
+	savefile->ReadString( requirement );
 	savefile->ReadInt( removeItem );
 	savefile->ReadBool( touchClient );
 	savefile->ReadBool( touchOther );
@@ -360,7 +360,7 @@ void idTrigger_Multi::Spawn( void ) {
 		gameLocal.Warning( "idTrigger_Multi '%s' at (%s) has random_delay >= delay", name.c_str(), GetPhysics()->GetOrigin().ToString(0) );
 	}
 
-	spawnArgs.GetString( "requires", "", requires );
+	spawnArgs.GetString( "requires", "", requirement );
 	spawnArgs.GetInt( "removeItem", "0", removeItem );
 	spawnArgs.GetBool( "triggerFirst", "0", triggerFirst );
 	spawnArgs.GetBool( "triggerWithSelf", "0", triggerWithSelf );
@@ -513,7 +513,7 @@ void idTrigger_Multi::Event_Trigger( idEntity *activator ) {
 	}
 
 	// see if this trigger requires an item
-	if ( !gameLocal.RequirementMet( activator, requires, removeItem ) ) {
+	if ( !gameLocal.RequirementMet( activator, requirement, removeItem ) ) {
 		return;
 	}
 
@@ -703,7 +703,7 @@ void idTrigger_Multi::Event_Touch( idEntity *other, trace_t *trace ) {
 	}
 
 	// see if this trigger requires an item
-	if ( !gameLocal.RequirementMet( other, requires, removeItem ) ) {
+	if ( !gameLocal.RequirementMet( other, requirement, removeItem ) ) {
 		return;
 	}
 
diff --git a/src/mpgame/Trigger.h b/src/mpgame/Trigger.h
index 96deb36..3bb04bf 100644
--- a/src/mpgame/Trigger.h
+++ b/src/mpgame/Trigger.h
@@ -73,7 +73,7 @@ private:
 	float				delay;
 	float				random_delay;
 	int					nextTriggerTime;
-	idStr				requires;
+	idStr				requirement;
 	int					removeItem;
 	bool				touchClient;
 	bool				touchOther;
diff --git a/src/mpgame/ai/AAS.cpp b/src/mpgame/ai/AAS.cpp
index f5c08a4..129c523 100644
--- a/src/mpgame/ai/AAS.cpp
+++ b/src/mpgame/ai/AAS.cpp
@@ -124,7 +124,7 @@ size_t idAASLocal::StatsSummary( void ) const {
 			+ ( areaCacheIndexSize * sizeof( idRoutingCache * ) ) 
 			+ ( portalCacheIndexSize * sizeof( idRoutingCache * ) );
 
-	return( file->GetMemorySize() + size );
+	return( size );
 }
 // RAVEN END
 
@@ -221,7 +221,7 @@ float idAASLocal::AreaRadius( int areaNum ) const {
 idAASLocal::AreaBounds
 ============
 */
-idBounds & idAASLocal::AreaBounds( int areaNum ) const {
+const idBounds & idAASLocal::AreaBounds( int areaNum ) const {
 	return file->GetArea( areaNum ).bounds;
 }
 /*
@@ -347,7 +347,7 @@ idAASCallback::testResult_t idAASCallback::Test ( class idAAS *aas, int areaNum,
 	}
 	
 	// Get area for edges
-	aasArea_t& area = file->GetArea ( areaNum );
+	const aasArea_t& area = file->GetArea ( areaNum );
 
 	if ( ai_debugTactical.GetInteger ( ) > 1 ) {
 		gameRenderWorld->DebugLine ( colorYellow, area.center, area.center + idVec3(0,0,80.0f), 10000 );
@@ -384,7 +384,7 @@ idAASCallback::testResult_t idAASCallback::Test ( class idAAS *aas, int areaNum,
 	int	f;
 	int	e;
 	for ( f = 0; f < area.numFaces; f ++ ) {
-		aasFace_t& face = file->GetFace ( abs ( file->GetFaceIndex (area.firstFace + f ) ) );
+		const aasFace_t& face = file->GetFace ( abs ( file->GetFaceIndex (area.firstFace + f ) ) );
 		
 		// for each edge test a point between the center of the edge and the center
 		for ( e = 0; e < face.numEdges; e ++ ) {
@@ -460,3 +460,13 @@ bool idAASCallback::TestPoint ( class idAAS *aas, const idVec3& pos, const float
 }
 
 // RAVEN END
+/*
+====================
+idAASLocal::AdjustPositionAndGetArea
+====================
+*/
+int idAASLocal::AdjustPositionAndGetArea( idVec3 &origin ) {
+	int area = PointReachableAreaNum( origin, DefaultSearchBounds(), AREA_REACHABLE_WALK );
+	PushPointIntoAreaNum( area, origin );
+	return area;
+}
diff --git a/src/mpgame/ai/AAS.h b/src/mpgame/ai/AAS.h
index cc8dbe6..f7ab95b 100644
--- a/src/mpgame/ai/AAS.h
+++ b/src/mpgame/ai/AAS.h
@@ -102,7 +102,7 @@ public:
 // bdube: added
 								// Returns a reachable point inside the given area.
 	virtual float				AreaRadius( int areaNum ) const = 0;
-	virtual idBounds &			AreaBounds( int areaNum ) const = 0;
+	virtual const idBounds &	AreaBounds( int areaNum ) const = 0;
 	virtual float				AreaCeiling( int areaNum ) const = 0;
 // RAVEN END	
 								// Returns the area flags.
@@ -157,6 +157,11 @@ public:
 // rjohnson: added more debug drawing
 	virtual void				ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const = 0;
 	virtual bool				IsValid( void ) const = 0;
+	// Bot navigation helpers shared with the single-player game module.
+	virtual int					AdjustPositionAndGetArea( idVec3 &origin ) = 0;
+	virtual void				ShowArea( const idVec3 &origin ) const = 0;
+	virtual idAASFile *			GetAASFile( void ) = 0;
+	virtual const idBounds &	DefaultSearchBounds( void ) const = 0;
 // RAVEN END
 };
 
diff --git a/src/mpgame/ai/AASCallback_AvoidLocation.cpp b/src/mpgame/ai/AASCallback_AvoidLocation.cpp
new file mode 100644
index 0000000..bdd22b6
--- /dev/null
+++ b/src/mpgame/ai/AASCallback_AvoidLocation.cpp
@@ -0,0 +1,126 @@
+// Copyright (C) 2007 Id Software, Inc.
+//
+
+
+
+
+#include "AAS_local.h"
+#include "../Game_local.h"		// for print and error
+#include "AASCallback_AvoidLocation.h"
+
+/*
+============
+idAASCallback_AvoidLocation::idAASCallback_AvoidLocation
+============
+*/
+idAASCallback_AvoidLocation::idAASCallback_AvoidLocation()
+{
+	avoidLocation.Zero();
+	avoidDist = 0.0f;
+	obstacles = NULL;
+	numObstacles = 0;
+}
+
+/*
+============
+idAASCallback_AvoidLocation::~idAASCallback_AvoidLocation
+============
+*/
+idAASCallback_AvoidLocation::~idAASCallback_AvoidLocation()
+{
+}
+
+/*
+============
+idAASCallback_AvoidLocation::SetAvoidLocation
+============
+*/
+void idAASCallback_AvoidLocation::SetAvoidLocation( const idVec3& start, const idVec3& avoidLocation )
+{
+	this->avoidLocation = avoidLocation;
+	this->avoidDist = ( avoidLocation - start ).Length();
+}
+
+/*
+============
+idAASCallback_AvoidLocation::SetObstacles
+============
+*/
+void idAASCallback_AvoidLocation::SetObstacles( const idAAS* aas, const idAASObstacle* obstacles, int numObstacles )
+{
+	this->obstacles = obstacles;
+	this->numObstacles = numObstacles;
+
+	for( int i = 0; i < numObstacles; i++ )
+	{
+		obstacles[i].expAbsBounds[0] = obstacles[i].absBounds[0] - aas->GetSettings()->boundingBoxes[0][1];
+		obstacles[i].expAbsBounds[1] = obstacles[i].absBounds[1] - aas->GetSettings()->boundingBoxes[0][0];
+	}
+}
+
+/*
+============
+idAASCallback_AvoidLocation::PathValid
+============
+*/
+bool idAASCallback_AvoidLocation::PathValid( const idAAS* aas, const idVec3& start, const idVec3& end )
+{
+	// path may not go through any obstacles
+	for( int i = 0; i < numObstacles; i++ )
+	{
+		// if the movement vector intersects the expanded obstacle bounds
+		if( obstacles[i].expAbsBounds.LineIntersection( start, end ) )
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+============
+idAASCallback_AvoidLocation::AdditionalTravelTimeForPath
+============
+*/
+int idAASCallback_AvoidLocation::AdditionalTravelTimeForPath( const idAAS* aas, const idVec3& start, const idVec3& end )
+{
+	if( avoidDist <= 0.0f )
+	{
+		return 0;
+	}
+
+	// project avoidLocation origin onto movement vector
+	idVec3 v1 = end - start;
+	v1.Normalize();
+	idVec3 v2 = avoidLocation - start;
+	idVec3 p = start + ( v2 * v1 ) * v1;
+
+	// get the point on the path closest to the avoidLocation
+	int i;
+	for( i = 0; i < 3; i++ )
+	{
+		if( ( p[i] > start[i] + 0.1f && p[i] > end[i] + 0.1f ) ||
+				( p[i] < start[i] - 0.1f && p[i] < end[i] - 0.1f ) )
+		{
+			break;
+		}
+	}
+
+	float dist;
+	if( i >= 3 )
+	{
+		dist = ( avoidLocation - p ).Length();
+	}
+	else
+	{
+		dist = ( avoidLocation - end ).Length();
+	}
+
+	// avoid moving closer to the avoidLocation
+	if( dist < avoidDist )
+	{
+		return idMath::Ftoi( ( avoidDist - dist ) * 10.0f );
+	}
+
+	return 0;
+}
diff --git a/src/mpgame/ai/AASCallback_AvoidLocation.h b/src/mpgame/ai/AASCallback_AvoidLocation.h
new file mode 100644
index 0000000..4431ee8
--- /dev/null
+++ b/src/mpgame/ai/AASCallback_AvoidLocation.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2007 Id Software, Inc.
+//
+
+#ifndef __AASCALLBACK_AVOIDLOCATION_H__
+#define __AASCALLBACK_AVOIDLOCATION_H__
+
+/*
+===============================================================================
+
+	idAASCallback_AvoidLocation
+
+===============================================================================
+*/
+
+struct idAASObstacle
+{
+	idBounds				absBounds;		// absolute bounds of obstacle
+	mutable idBounds		expAbsBounds;	// expanded absolute bounds of obstacle
+};
+
+class idAASCallback_AvoidLocation : public idAASCallback
+{
+public:
+	idAASCallback_AvoidLocation();
+	~idAASCallback_AvoidLocation();
+
+	void					SetAvoidLocation( const idVec3& start, const idVec3& avoidLocation );
+	void					SetObstacles( const idAAS* aas, const idAASObstacle* obstacles, int numObstacles );
+
+	virtual bool			PathValid( const idAAS* aas, const idVec3& start, const idVec3& end );
+	virtual int				AdditionalTravelTimeForPath( const idAAS* aas, const idVec3& start, const idVec3& end );
+	virtual bool			AreaIsGoal( const idAAS* aas, int areaNum ) = 0;
+
+private:
+	idVec3					avoidLocation;
+	float					avoidDist;
+	const idAASObstacle* 	obstacles;
+	int						numObstacles;
+};
+
+#endif /* !__AASCALLBACK_AVOIDLOCATION_H__ */
diff --git a/src/mpgame/ai/AASCallback_FindCoverArea.cpp b/src/mpgame/ai/AASCallback_FindCoverArea.cpp
new file mode 100644
index 0000000..1029f3d
--- /dev/null
+++ b/src/mpgame/ai/AASCallback_FindCoverArea.cpp
@@ -0,0 +1,58 @@
+// Copyright (C) 2007 Id Software, Inc.
+//
+
+
+
+
+#include "AAS_local.h"
+#include "../Game_local.h"		// for print and error
+
+#include "AASCallback_FindCoverArea.h"
+
+/*
+============
+idAASCallback_FindCoverArea::idAASCallback_FindCoverArea
+============
+*/
+idAASCallback_FindCoverArea::idAASCallback_FindCoverArea( const idVec3& hideFromPos )
+{
+	int			numPVSAreas;
+	idBounds	bounds( hideFromPos - idVec3( 16, 16, 0 ), hideFromPos + idVec3( 16, 16, 64 ) );
+
+	// setup PVS
+	numPVSAreas = gameLocal.pvs.GetPVSAreas( bounds, PVSAreas, MAX_PVS_AREAS );
+	hidePVS		= gameLocal.pvs.SetupCurrentPVS( PVSAreas, numPVSAreas );
+}
+
+/*
+============
+idAASCallback_FindCoverArea::~idAASCallback_FindCoverArea
+============
+*/
+idAASCallback_FindCoverArea::~idAASCallback_FindCoverArea()
+{
+	gameLocal.pvs.FreeCurrentPVS( hidePVS );
+}
+
+/*
+============
+idAASCallback_FindCoverArea::AreaIsGoal
+============
+*/
+bool idAASCallback_FindCoverArea::AreaIsGoal( const idAAS* aas, int areaNum )
+{
+	idVec3	areaCenter;
+	int		numPVSAreas;
+	int		PVSAreas[ MAX_PVS_AREAS ];
+
+	areaCenter = aas->AreaCenter( areaNum );
+	areaCenter[ 2 ] += 1.0f;
+
+	numPVSAreas = gameLocal.pvs.GetPVSAreas( idBounds( areaCenter ).Expand( 16.0f ), PVSAreas, MAX_PVS_AREAS );
+	if( !gameLocal.pvs.InCurrentPVS( hidePVS, PVSAreas, numPVSAreas ) )
+	{
+		return true;
+	}
+
+	return false;
+}
diff --git a/src/mpgame/ai/AASCallback_FindCoverArea.h b/src/mpgame/ai/AASCallback_FindCoverArea.h
new file mode 100644
index 0000000..d48cf69
--- /dev/null
+++ b/src/mpgame/ai/AASCallback_FindCoverArea.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2007 Id Software, Inc.
+//
+
+#ifndef __AASCALLBACK_FINDCOVERAREA_H__
+#define __AASCALLBACK_FINDCOVERAREA_H__
+
+#include "AASCallback_AvoidLocation.h"
+
+/*
+===============================================================================
+
+	idAASCallback_FindCoverArea
+
+===============================================================================
+*/
+
+class idAASCallback_FindCoverArea : public idAASCallback_AvoidLocation
+{
+public:
+	idAASCallback_FindCoverArea( const idVec3& hideFromPos );
+	~idAASCallback_FindCoverArea();
+
+	virtual bool			AreaIsGoal( const idAAS* aas, int areaNum );
+
+private:
+	static const int		MAX_PVS_AREAS = 4;
+
+	pvsHandle_t				hidePVS;
+	int						PVSAreas[ MAX_PVS_AREAS ];
+};
+
+#endif /* !__AASCALLBACK_FINDCOVERAREA_H__ */
diff --git a/src/mpgame/ai/AAS_local.h b/src/mpgame/ai/AAS_local.h
index 35a7f73..d646652 100644
--- a/src/mpgame/ai/AAS_local.h
+++ b/src/mpgame/ai/AAS_local.h
@@ -76,7 +76,7 @@ public:
 // RAVEN BEGIN
 // bdube: added
 	virtual float				AreaRadius( int areaNum ) const;
-	virtual idBounds &			AreaBounds( int areaNum ) const;
+	virtual const idBounds &	AreaBounds( int areaNum ) const;
 	virtual float				AreaCeiling( int areaNum ) const;
 // RAVEN END
 	virtual int					AreaFlags( int areaNum ) const;
@@ -108,6 +108,10 @@ public:
 // rjohnson: added more debug drawing
 	virtual bool				IsValid( void ) const;
 	virtual void				ShowAreas( const idVec3 &origin, bool ShowProblemAreas = false ) const;
+	virtual int					AdjustPositionAndGetArea( idVec3 &origin );
+	virtual void				ShowArea( const idVec3 &origin ) const;
+	virtual idAASFile *			GetAASFile( void ) { return file; }
+	virtual const idBounds &	DefaultSearchBounds( void ) const;
 // RAVEN END
 
 
@@ -164,14 +168,12 @@ private:	// pathing
 	idVec3						SubSampleFlyPath( int areaNum, const idVec3 &origin, const idVec3 &start, const idVec3 &end, int travelFlags, int &endAreaNum ) const;
 
 private:	// debug
-	const idBounds &			DefaultSearchBounds( void ) const;
 	void						DrawCone( const idVec3 &origin, const idVec3 &dir, float radius, const idVec4 &color ) const;
 	void						DrawAreaBounds( int areaNum ) const;
 	void						DrawArea( int areaNum ) const;
 	void						DrawFace( int faceNum, bool side ) const;
 	void						DrawEdge( int edgeNum, bool arrow ) const;
 	void						DrawReachability( const idReachability *reach ) const;
-	void						ShowArea( const idVec3 &origin ) const;
 	void						ShowWallEdges( const idVec3 &origin ) const;
 	void						ShowHideArea( const idVec3 &origin, int targerAreaNum ) const;
 	bool						PullPlayer( const idVec3 &origin, int toAreaNum ) const;
diff --git a/src/mpgame/ai/AAS_tactical.cpp b/src/mpgame/ai/AAS_tactical.cpp
index 7229aa9..7d8b085 100644
--- a/src/mpgame/ai/AAS_tactical.cpp
+++ b/src/mpgame/ai/AAS_tactical.cpp
@@ -695,7 +695,7 @@ void	rvAASTacticalSensorLocal::Update()
 	const idVec3&	ownerOrigin			= mOwner->GetPhysics()->GetOrigin();
 	int				ownerAreaNum		= mOwnerAI ? mOwnerAI->PointReachableAreaNum ( ownerOrigin ) : aas->PointReachableAreaNum(ownerOrigin, mOwner->GetPhysics()->GetBounds(), (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) );
 	aasFeature_t*	feature				= 0;
-	aasArea_t&		area				= file->GetArea(ownerAreaNum);
+	const aasArea_t&		area				= file->GetArea(ownerAreaNum);
 	idActor*		teammate			= NULL;
 	float			featureDistance		= 0.0f;
 	float			featureDotLeft		= 0.0f;
@@ -1762,4 +1762,3 @@ void rvAASTacticalSensorLocal::DrawDebugInfo()
 
 
 
-
diff --git a/src/mpgame/ai/AI_Move.cpp b/src/mpgame/ai/AI_Move.cpp
index de1f0b6..b6810f3 100644
--- a/src/mpgame/ai/AI_Move.cpp
+++ b/src/mpgame/ai/AI_Move.cpp
@@ -3250,7 +3250,7 @@ struct rvObstacle {
 			// Negative nodeNum signifies a Leaf Area
 			if ( nodeNum < 0 ) {
 				if ( searchFile->GetArea(-nodeNum).flags&AREA_REACHABLE_WALK ) {
-					areas.AddUnique( &searchFile->GetArea(-nodeNum) );
+					areas.AddUnique( (aasArea_t *)&searchFile->GetArea(-nodeNum) );
 				}
 				break;
 			}
@@ -3553,7 +3553,8 @@ struct rvObstacleFinder {
 			if (gameLocal.GetAAS(i)) {
 				idAASFile* file = gameLocal.GetAAS(i)->GetFile();
 				for (int a=0; a<file->GetNumAreas(); a++) {
-					file->GetArea(a).firstMarker = NULL;
+					aasArea_t* area = (aasArea_t*)&file->GetArea(a);
+					area->firstMarker = NULL;
 				}
 			}
 		}
@@ -3968,7 +3969,7 @@ public:
 
 				// If The Smoothed Position Is Not Blocked By An Obstacle
 				//--------------------------------------------------------
-				aasArea_t* area = &myAAS->GetFile()->GetArea(pathAt.reach->toAreaNum);
+				aasArea_t* area = (aasArea_t*)&myAAS->GetFile()->GetArea(pathAt.reach->toAreaNum);
 				for (marker=area->firstMarker; marker; marker=marker->next) {
 					if (!marker->obstacle || marker->obstacle->entity.GetEntity()==myIgnoreEntity || marker->obstacle->entity.GetEntity()==myIgnoreEntity2) {
 						continue;
@@ -4529,7 +4530,7 @@ void idAI::RVMasterMove( void ) {
 		move.myArea			= PointReachableAreaNum(move.myPos);
 		myPosMoved			= true;
 	}
-	aasArea_t*					myArea		= &aas->GetFile()->GetArea(move.myArea);
+	aasArea_t*					myArea		= (aasArea_t*)&aas->GetFile()->GetArea(move.myArea);
 
 	// Update Goal Position And Area
 	//-------------------------------
@@ -4856,4 +4857,3 @@ void idAI::Move	( void ) {
 		aiManager.timerMove.Stop ( );
 	}
 }
-
diff --git a/src/mpgame/ai/AI_States.cpp b/src/mpgame/ai/AI_States.cpp
index b821dc6..666ccfd 100644
--- a/src/mpgame/ai/AI_States.cpp
+++ b/src/mpgame/ai/AI_States.cpp
@@ -2106,7 +2106,7 @@ Returns 'AICHECKTACTICAL_NOMOVE' if the given tactical state should be used but
 */
 aiCTResult_t idAI::CheckTactical ( aiTactical_t tactical ) {
 	// Handle non movement tactical states first
-	if ( BIT(tactical) & AITACTICAL_NONMOVING_BITS ) {
+	if ( ( 1u << tactical ) & AITACTICAL_NONMOVING_BITS ) {
 		// If we are moving 
 		if ( move.fl.moving ) {
 			return AICTRESULT_OK;
diff --git a/src/mpgame/ai/Monster_Harvester.cpp b/src/mpgame/ai/Monster_Harvester.cpp
index dd6ccb7..6f4f3a1 100644
--- a/src/mpgame/ai/Monster_Harvester.cpp
+++ b/src/mpgame/ai/Monster_Harvester.cpp
@@ -813,13 +813,13 @@ const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) {
 	if ( yaw < -10.0f ) {
 		if ( partHealth[PART_LEG_FR] <= 0 )
 		{
-			return false;
+			return NULL;
 		}
 		animName = "attack_rleg_fw_rt";
 	} else if ( yaw > 10.0f ) {
 		if ( partHealth[PART_LEG_FL] <= 0 )
 		{
-			return false;
+			return NULL;
 		}
 		animName = "attack_lleg_fw_lt";
 	} else{
@@ -827,7 +827,7 @@ const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) {
 		{
 			if ( partHealth[PART_LEG_FL] <= 0 )
 			{
-				return false;
+				return NULL;
 			}
 			animName = "attack_lleg_fw";
 		}
@@ -835,7 +835,7 @@ const char* rvMonsterHarvester::GetMeleeAttackAnim ( const idVec3& target ) {
 		{
 			if ( partHealth[PART_LEG_FR] <= 0 )
 			{
-				return false;
+				return NULL;
 			}
 			animName = "attack_rleg_fw";
 		}
diff --git a/src/mpgame/bots/Bot.cpp b/src/mpgame/bots/Bot.cpp
new file mode 100644
index 0000000..ccfa946
--- /dev/null
+++ b/src/mpgame/bots/Bot.cpp
@@ -0,0 +1,517 @@
+// Bot.cpp
+//
+
+
+#include "../Game_local.h"
+#include "../Weapon.h"
+
+idCVar bot_pathdebug( "bot_pathdebug", "0", CVAR_BOOL | CVAR_CHEAT, "force the bot to path to player" );
+idCVar bot_goaldist( "bot_goaldist", "20", CVAR_INTEGER | CVAR_CHEAT, "" );
+idCVar bot_debugnav( "bot_debugnav", "0", CVAR_BOOL | CVAR_CHEAT, "draws navmesh paths for the bot" );
+idCVar bot_showstate( "bot_showstate", "0", CVAR_BOOL | CVAR_CHEAT, "draws the bot state above the bot" );
+idCVar bot_debug( "bot_debug", "0", CVAR_BOOL, "shows debug info for the bot" );
+idCVar bot_skill( "bot_skill", "3", CVAR_INTEGER | CVAR_ARCHIVE, "bot skill depth (1-5)", 1, 5 );
+
+CLASS_DECLARATION( idPlayer, rvmBot )
+END_CLASS
+
+CLASS_STATES_DECLARATION(rvmBot)
+	STATE("state_Chase", rvmBot::state_Chase)
+	STATE("state_BattleFight", rvmBot::state_BattleFight)
+	STATE("state_BattleNBG", rvmBot::state_BattleNBG)
+	STATE("state_Retreat", rvmBot::state_Retreat)
+	STATE("state_Respawn", rvmBot::state_Respawn)
+	STATE("state_SeekNBG", rvmBot::state_SeekNBG)
+	STATE("state_SeekLTG", rvmBot::state_SeekLTG)
+	STATE("state_Attacked", rvmBot::state_Attacked)
+END_CLASS_STATES
+
+/*
+===================
+rvmBot::rvmBot
+===================
+*/
+rvmBot::rvmBot()
+{
+	//bs.action = NULL;
+	hasSpawned = false;
+	gameLocal.RegisterBot( this );
+}
+
+/*
+===================
+rvmBot::~rvmBot
+===================
+*/
+rvmBot::~rvmBot()
+{
+	gameLocal.UnRegisterBot( this );
+}
+
+/*
+==================
+rvmBot::SetEnemy
+==================
+*/
+void rvmBot::SetEnemy( idPlayer* player, idVec3 origin)
+{
+	if( player == NULL || player == this )
+	{
+		return;
+	}
+
+	if(bs.enemy == -1)
+	{
+		bs.enemy = player->entityNumber;
+		bs.aggressiveAttackTime = gameLocal.SysScriptTime() + 2.0f;
+		bs.lastenemyorigin = origin;
+		bs.last_enemy_visible_position = origin;
+		bs.chase_time = Bot_Time();
+		//bs.action = &botAIBattleRetreat;
+		stateThread.SetState("state_Attacked");
+	}
+}
+
+/*
+==================
+rvmBot::BotUpdateInventory
+==================
+*/
+void rvmBot::BotUpdateInventory( void )
+{
+	bs.inventory[INVENTORY_ARMOR]			= inventory.armor;
+	bs.inventory[INVENTORY_GAUNTLET]		= 1;
+	bs.inventory[INVENTORY_SHOTGUN]			= HasWeapon( weapon_shotgun );
+	bs.inventory[INVENTORY_MACHINEGUN]		= HasWeapon( weapon_machinegun );
+	bs.inventory[INVENTORY_GRENADELAUNCHER] = 0;
+	bs.inventory[INVENTORY_ROCKETLAUNCHER]  = HasWeapon( weapon_rocketlauncher );
+	bs.inventory[INVENTORY_LIGHTNING]		= 0;
+	bs.inventory[INVENTORY_RAILGUN]			= 0;
+		bs.inventory[INVENTORY_PLASMAGUN]		= HasWeapon( weapon_plasmagun );
+	bs.inventory[INVENTORY_BFG10K]			= 0;
+	bs.inventory[INVENTORY_GRAPPLINGHOOK]	= 0;
+	bs.inventory[INVENTORY_SHELLS]			= inventory.ammo[rvWeapon::GetAmmoIndexForName( "ammo_shotgun" )];
+	bs.inventory[INVENTORY_BULLETS]			= inventory.ammo[rvWeapon::GetAmmoIndexForName( "ammo_machinegun" )];
+	bs.inventory[INVENTORY_GRENADES]		= 0;
+		bs.inventory[INVENTORY_CELLS]			= inventory.ammo[rvWeapon::GetAmmoIndexForName( "ammo_hyperblaster" )];
+	bs.inventory[INVENTORY_LIGHTNINGAMMO]	= 0;
+	bs.inventory[INVENTORY_ROCKETS]			= inventory.ammo[rvWeapon::GetAmmoIndexForName( "ammo_rocketlauncher" )];
+	bs.inventory[INVENTORY_SLUGS]			= 0;
+	bs.inventory[INVENTORY_BFGAMMO]			= 0;
+	bs.inventory[INVENTORY_HEALTH]			= health;
+	bs.inventory[INVENTORY_TELEPORTER]		= 0;
+	bs.inventory[INVENTORY_MEDKIT]			= 0;
+	bs.inventory[INVENTORY_QUAD]			= 0;
+	bs.inventory[INVENTORY_ENVIRONMENTSUIT] = 0;
+	bs.inventory[INVENTORY_HASTE]			= 0;
+	bs.inventory[INVENTORY_INVISIBILITY]	= 0;
+	bs.inventory[INVENTORY_REGEN]			= 0;
+	bs.inventory[INVENTORY_FLIGHT]			= 0;
+	bs.inventory[INVENTORY_REDFLAG]			= 0;
+	bs.inventory[INVENTORY_BLUEFLAG]		= 0;
+}
+
+
+/*
+===================
+rvmBot::Spawn
+===================
+*/
+void rvmBot::Spawn( void )
+{
+	idStr botName;
+	idStr botCharacterFile;
+	char filename[256];
+	int errnum;
+	int requestedSkill;
+
+	stateThread.SetOwner(this);
+
+	idPlayer::Spawn();
+
+	if( gameLocal.isServer )
+	{
+		weapon_machinegun = SlotForWeapon( "weapon_machinegun" );
+		weapon_shotgun = SlotForWeapon( "weapon_shotgun" );
+		weapon_plasmagun = SlotForWeapon( "weapon_hyperblaster" );
+		weapon_rocketlauncher = SlotForWeapon( "weapon_rocketlauncher" );
+
+		WP_MACHINEGUN = weapon_machinegun;
+		WP_SHOTGUN = weapon_shotgun;
+		WP_PLASMAGUN = weapon_plasmagun;
+		WP_ROCKET_LAUNCHER = weapon_rocketlauncher;
+
+		botName = spawnArgs.GetString( "botname" );
+		botCharacterFile = va( "bots/%s_c.c", botName.c_str() );
+		requestedSkill = idMath::ClampInt( 1, 5, bot_skill.GetInteger() );
+
+		aas = gameLocal.GetBotAAS();
+		if( aas == NULL )
+		{
+			gameLocal.Error( "Missing AAS\n" );
+			return;
+		}
+
+		// Load in the bot character.
+		bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( botCharacterFile.c_str(), requestedSkill );
+		if( !bs.character ) {
+			for( int fallbackSkill = requestedSkill - 1; fallbackSkill >= 1; fallbackSkill-- ) {
+				bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( botCharacterFile.c_str(), fallbackSkill );
+				if( bs.character ) {
+					common->Warning(
+						"Bot '%s': requested skill %d not found, using skill %d\n",
+						botName.c_str(),
+						requestedSkill,
+						fallbackSkill
+					);
+					break;
+				}
+			}
+		}
+		if( !bs.character ) {
+			bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( botCharacterFile.c_str(), -1 );
+			if( bs.character ) {
+				common->Warning(
+					"Bot '%s': no explicit skill tier found, using first available tier in %s\n",
+					botName.c_str(),
+					botCharacterFile.c_str()
+				);
+			}
+		}
+		if( !bs.character )
+		{
+			gameLocal.Error( "Failed to load character file for bot %s\n", botName.c_str() );
+		}
+
+		// Allocate the goal state.
+		bs.gs = botGoalManager.BotAllocGoalState( entityNumber );
+
+		// Get the bot items weights file name.
+		botCharacterStatsManager.Characteristic_String( bs.character, CHARACTERISTIC_ITEMWEIGHTS, filename, 256 );
+		errnum = botGoalManager.BotLoadItemWeights( bs.gs, filename );
+		if( errnum != BLERR_NOERROR )
+		{
+			gameLocal.Error( "Failed to load bot item weights!" );
+			botGoalManager.BotFreeGoalState( bs.gs );
+			return;
+		}
+
+		//allocate a weapon state
+		bs.ws = botWeaponInfoManager.BotAllocWeaponState();
+
+		//load the weapon weights
+		botCharacterStatsManager.Characteristic_String( bs.character, CHARACTERISTIC_WEAPONWEIGHTS, filename, 256 );
+		errnum = botWeaponInfoManager.BotLoadWeaponWeights( bs.ws, filename );
+		if( errnum != BLERR_NOERROR )
+		{
+			//	trap_BotFreeGoalState(bs->gs);
+			botWeaponInfoManager.BotFreeWeaponState( bs.ws );
+			return;
+		}
+
+		bs.client = entityNumber;
+		bs.entitynum = entityNumber;
+		bs.setupcount = 4;
+		bs.entergame_time = Bot_Time();
+
+		hasSpawned = true;
+
+		bs.botinput.respawn = true;
+
+		stateThread.SetState("state_Respawn");
+	}
+}
+
+/*
+===================
+rvmBot::Think
+===================
+*/
+void rvmBot::BotMoveToGoalOrigin(idVec3 goalOrigin)
+{
+	bs.botinput.dir = (goalOrigin - firstPersonViewOrigin);
+	idAngles desiredAngles = bs.botinput.dir.ToAngles();
+	if( bs.enemy >= 0 )
+	{
+		idPlayer* enemy = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
+		if( enemy )
+		{
+			desiredAngles = (enemy->firstPersonViewOrigin - firstPersonViewOrigin).ToAngles();
+		}
+	}
+
+	bs.botinput.viewangles = desiredAngles;
+
+	bs.botinput.speed = pm_speed.GetInteger();
+
+	bs.botinput.dir.Normalize();
+}
+
+/*
+===================
+rvmBot::SpawnToPoint
+===================
+*/
+void rvmBot::SpawnToPoint( const idVec3& spawn_origin, const idAngles& spawn_angles )
+{
+	idPlayer::SpawnToPoint( spawn_origin, spawn_angles );
+
+	if( gameLocal.isServer )
+	{
+		bs.ltg_time = 0;
+		stateThread.SetState("state_SeekLTG");
+	}
+}
+/*
+===================
+rvmBot::StateThreadChanged
+===================
+*/
+void rvmBot::StateThreadChanged(void) {
+	// Ensure if we are switching states, pop the last goal.
+	bs.ltg_time = 0;
+}
+
+/*
+===================
+rvmBot::ServerThink
+===================
+*/
+void rvmBot::ServerThink( void )
+{
+	bs.origin = GetPhysics()->GetOrigin();
+	bs.eye = GetEyePosition();
+	bs.thinktime = Bot_Time();
+	bs.botinput.actionflags = 0;
+
+	BotUpdateInventory();
+
+	if( bot_pathdebug.IsModified() )
+	{
+		if( bot_pathdebug.GetBool() )
+		{
+			bs.currentGoal.origin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin();
+			bs.currentGoal.framenum = gameLocal.framenum;
+		}
+
+		bot_pathdebug.ClearModified();
+		bot_pathdebug.SetBool( false );
+	}
+
+	stateThread.Execute();
+
+	if( aas == NULL )
+	{
+		bs.botinput.dir.Zero();
+		bs.botinput.speed = 0;
+		bs.viewangles = bs.botinput.viewangles;
+		bs.useRandomPosition = false;
+		bs.attackerEntity = NULL;
+		bs.botinput.weapon = bs.weaponnum;
+		BotInputFrame();
+		return;
+	}
+
+	const float invalidGoalAvoidTime = 5.0f;
+	const int travelFlags = TFL_WALK | TFL_AIR;
+
+	bool wantsRandomGoal = bs.useRandomPosition;
+	idVec3 navigationGoal = wantsRandomGoal ? bs.random_move_position : bs.currentGoal.origin;
+
+	// If no goal is active yet, keep moving with a temporary random point.
+	if( !wantsRandomGoal && bs.currentGoal.framenum < 0 )
+	{
+		BotMoveInRandomDirection( &bs );
+		wantsRandomGoal = true;
+		navigationGoal = bs.random_move_position;
+	}
+
+	aasPath_t path;
+	idVec3 org = bs.origin;
+	int curAreaNum = aas->AdjustPositionAndGetArea( org );
+	idVec3 goalPos = navigationGoal;
+	int goalArea = aas->AdjustPositionAndGetArea( goalPos );
+	bool hasPath = false;
+
+	if( curAreaNum > 0 && goalArea > 0 )
+	{
+		hasPath = aas->WalkPathToGoal( path, curAreaNum, org, goalArea, goalPos, travelFlags );
+	}
+
+	// If the current LTG cannot be pathed, invalidate it and force a short random detour.
+	if( !hasPath && !wantsRandomGoal )
+	{
+		if( ( bs.currentGoal.flags & GFL_ITEM ) && bs.currentGoal.number > 0 )
+		{
+			botGoalManager.BotSetAvoidGoalTime( bs.gs, bs.currentGoal.number, invalidGoalAvoidTime );
+		}
+		bs.ltg_time = 0;
+		bs.nbg_time = 0;
+
+		BotMoveInRandomDirection( &bs );
+		wantsRandomGoal = true;
+		navigationGoal = bs.random_move_position;
+
+		org = bs.origin;
+		curAreaNum = aas->AdjustPositionAndGetArea( org );
+		goalPos = navigationGoal;
+		goalArea = aas->AdjustPositionAndGetArea( goalPos );
+		if( curAreaNum > 0 && goalArea > 0 )
+		{
+			hasPath = aas->WalkPathToGoal( path, curAreaNum, org, goalArea, goalPos, travelFlags );
+		}
+	}
+
+	if( bot_debug.GetBool() )
+	{
+		if( goalArea > 0 )
+		{
+			aas->ShowWalkPath( GetOrigin(), goalArea, goalPos );
+		}
+		aas->ShowArea( GetOrigin() );
+	}
+
+	if( hasPath )
+	{
+		BotMoveToGoalOrigin( path.moveGoal );
+	}
+	else if( goalArea > 0 )
+	{
+		BotMoveToGoalOrigin( goalPos );
+	}
+	else
+	{
+		bs.botinput.dir.Zero();
+		bs.botinput.speed = 0;
+	}
+
+	bs.viewangles = bs.botinput.viewangles;
+
+	bs.useRandomPosition = false;
+	bs.attackerEntity = NULL; // Has to be consumed immedaitly.
+	bs.botinput.weapon = bs.weaponnum;
+
+	BotInputFrame();
+}
+
+/*
+=======================
+rvmBot::Damage
+=======================
+*/
+void rvmBot::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location )
+{
+	idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
+
+	idPlayer* player = attacker ? attacker->Cast<idPlayer>() : NULL;
+	if (health <= 0)
+	{
+		if (player)
+		{
+			BotSendChatMessage(DEATH, player->GetNetName());
+		}
+	}
+
+	if (attacker == NULL) {
+		return;
+	}
+
+	//bs.attackerEntity = attacker;
+	if( player )
+	{
+		SetEnemy( player, attacker->GetOrigin() );
+	}
+}
+
+/*
+=======================
+rvmBot::InflictedDamageEvent
+=======================
+*/
+void rvmBot::InflictedDamageEvent(idEntity* target) {
+	idPlayer* player = target->Cast<idPlayer>();
+
+	// Don't flood the chat with death and insults.
+	if (!player->IsBot() && player->health <= 0)
+	{
+		BotSendChatMessage(KILL, player->GetNetName());
+	}
+}
+
+/*
+=======================
+rvmBot::PresenceTypeBoundingBox
+=======================
+*/
+void rvmBot::PresenceTypeBoundingBox( int presencetype, idVec3& mins, idVec3& maxs )
+{
+	int index;
+	//bounding box size for each presence type
+	//idVec3 boxmins[3] = { {0, 0, 0}, {-15, -15, -24}, {-15, -15, -24} };
+	//idVec3 boxmaxs[3] = { {0, 0, 0}, { 15,  15,  32}, { 15,  15,   8} };
+
+	idVec3 boxmins[3];
+	idVec3 boxmaxs[3];
+
+	boxmins[0] = idVec3( 0, 0, 0 );
+	boxmins[1] = idVec3( -15, -15, -24 );
+	boxmins[2] = idVec3( -15, -15, -24 );
+
+	boxmaxs[0] = idVec3( 0, 0, 0 );
+	boxmaxs[1] = idVec3( 15, 15, 32 );
+	boxmaxs[2] = idVec3( 15, 15, 8 );
+
+
+	if( presencetype == PRESENCE_NORMAL )
+	{
+		index = 1;
+	}
+	else if( presencetype == PRESENCE_CROUCH )
+	{
+		index = 2;
+	}
+	else
+	{
+		//botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n");
+		index = 2;
+	}
+	mins = boxmins[index];
+	maxs = boxmaxs[index];
+}
+
+
+/*
+===================
+rvmBot::Think
+===================
+*/
+void rvmBot::Think( void )
+{
+	if( !hasSpawned )
+	{
+		return;
+	}
+
+	if( gameLocal.isServer )
+	{
+		ServerThink();
+
+		if( bot_debug.GetBool() )
+		{
+			idVec4 color;
+			color = idVec4(1, 1, 1, 1);
+			if (bs.enemy >= 0)
+				color = idVec4(1, 0, 0, 1);
+
+			idBounds bounds = idBounds( idVec3( -10, -10, -10 ), idVec3( 10, 10, 10 ) );
+			gameRenderWorld->DebugBounds( color, bounds, GetOrigin() );
+
+			//idMat3 axis = viewAngles.ToMat3();
+			//gameRenderWorld->DrawTextA(stateThread.GetState()->state.c_str(), GetOrigin(), 1.0f, color, axis);
+		}
+	}
+
+	deltaViewAngles.Zero();
+
+	idPlayer::Think();
+}
diff --git a/src/mpgame/bots/Bot.h b/src/mpgame/bots/Bot.h
new file mode 100644
index 0000000..7b53117
--- /dev/null
+++ b/src/mpgame/bots/Bot.h
@@ -0,0 +1,926 @@
+// Bot.h
+//
+
+class rvmBotAIBotActionBase;
+
+// These need to match items.def
+#define INVENTORY_ARMOR				1
+#define INVENTORY_GAUNTLET			4
+#define INVENTORY_SHOTGUN			5
+#define INVENTORY_MACHINEGUN		6
+#define INVENTORY_GRENADELAUNCHER	7
+#define INVENTORY_ROCKETLAUNCHER	8
+#define INVENTORY_LIGHTNING			9
+#define INVENTORY_RAILGUN			10
+#define INVENTORY_PLASMAGUN			11
+#define INVENTORY_BFG10K			13
+#define INVENTORY_GRAPPLINGHOOK		14
+#define INVENTORY_NAILGUN			15
+#define INVENTORY_PROXLAUNCHER		16
+#define INVENTORY_CHAINGUN			17
+#define INVENTORY_SHELLS			18
+#define INVENTORY_BULLETS			19
+#define INVENTORY_GRENADES			20
+#define INVENTORY_CELLS				21
+#define INVENTORY_LIGHTNINGAMMO		22
+#define INVENTORY_ROCKETS			23
+#define INVENTORY_SLUGS				24
+#define INVENTORY_BFGAMMO			25
+#define INVENTORY_NAILS				26
+#define INVENTORY_MINES				27
+#define INVENTORY_BELT			28
+#define INVENTORY_HEALTH			29
+#define INVENTORY_TELEPORTER		30
+#define INVENTORY_MEDKIT			31
+#define INVENTORY_KAMIKAZE			32
+#define INVENTORY_PORTAL			33
+#define INVENTORY_INVULNERABILITY	34
+#define INVENTORY_QUAD				35
+#define INVENTORY_ENVIRONMENTSUIT	36
+#define INVENTORY_HASTE				37
+#define INVENTORY_INVISIBILITY		38
+#define INVENTORY_REGEN				39
+#define INVENTORY_FLIGHT			40
+#define INVENTORY_SCOUT				41
+#define INVENTORY_GUARD				42
+#define INVENTORY_DOUBLER			43
+#define INVENTORY_AMMOREGEN			44
+#define INVENTORY_REDFLAG			45
+#define INVENTORY_BLUEFLAG			46
+#define INVENTORY_NEUTRALFLAG		47
+#define INVENTORY_REDCUBE			48
+#define INVENTORY_BLUECUBE			49
+#define INVENTORY_DOUBLESHOTGUN		50
+
+#define MODELINDEX_DEFAULT			0
+#define MODELINDEX_ARMORSHARD		1
+#define MODELINDEX_ARMORCOMBAT		2
+#define MODELINDEX_ARMORBODY		3
+#define MODELINDEX_HEALTHSMALL		4
+#define MODELINDEX_HEALTH			5
+#define MODELINDEX_HEALTHLARGE		6
+#define MODELINDEX_HEALTHMEGA		7
+#define MODELINDEX_GAUNTLET			8
+#define MODELINDEX_SHOTGUN			9
+#define MODELINDEX_MACHINEGUN		10
+#define MODELINDEX_GRENADELAUNCHER	11
+#define MODELINDEX_ROCKETLAUNCHER	12
+#define MODELINDEX_LIGHTNING		13
+#define MODELINDEX_RAILGUN			14
+#define MODELINDEX_PLASMAGUN		15
+#define MODELINDEX_BFG10K			16
+#define MODELINDEX_GRAPPLINGHOOK	17
+#define MODELINDEX_SHELLS			18
+#define MODELINDEX_BULLETS			19
+#define MODELINDEX_GRENADES			20
+#define MODELINDEX_CELLS			21
+#define MODELINDEX_LIGHTNINGAMMO	22
+#define MODELINDEX_ROCKETS			23
+#define MODELINDEX_SLUGS			24
+#define MODELINDEX_BFGAMMO			25
+#define MODELINDEX_TELEPORTER		26
+#define MODELINDEX_MEDKIT			27
+#define MODELINDEX_QUAD				28
+#define MODELINDEX_ENVIRONMENTSUIT	29
+#define MODELINDEX_HASTE			30
+#define MODELINDEX_INVISIBILITY		31
+#define MODELINDEX_REGEN			32
+#define MODELINDEX_FLIGHT			33
+#define MODELINDEX_REDFLAG			34
+#define MODELINDEX_BLUEFLAG			35
+#define MODELINDEX_KAMIKAZE			36
+#define MODELINDEX_PORTAL			37
+#define MODELINDEX_INVULNERABILITY	38
+#define MODELINDEX_NAILS			39
+#define MODELINDEX_MINES			40
+#define MODELINDEX_BELT				41
+#define MODELINDEX_SCOUT			42
+#define MODELINDEX_GUARD			43
+#define MODELINDEX_DOUBLER			44
+#define MODELINDEX_AMMOREGEN		45
+#define MODELINDEX_NEUTRALFLAG		46
+#define MODELINDEX_REDCUBE			47
+#define MODELINDEX_BLUECUBE			48
+#define MODELINDEX_NAILGUN			49
+#define MODELINDEX_PROXLAUNCHER		50
+#define MODELINDEX_CHAINGUN			51
+#define MODELINDEX_POINTABLUE		52
+#define MODELINDEX_POINTBBLUE		53
+#define MODELINDEX_POINTARED		54
+#define MODELINDEX_POINTBRED		55
+#define MODELINDEX_POINTAWHITE		56
+#define MODELINDEX_POINTBWHITE		57
+#define MODELINDEX_POINTWHITE		58
+#define MODELINDEX_POINTRED			59
+#define MODELINDEX_POINTBLUE		60
+#define WEAPONINDEX_GAUNTLET			1
+#define WEAPONINDEX_MACHINEGUN			2
+#define WEAPONINDEX_SHOTGUN				3
+#define WEAPONINDEX_GRENADE_LAUNCHER	4
+#define WEAPONINDEX_ROCKET_LAUNCHER		5
+#define WEAPONINDEX_LIGHTNING			6
+#define WEAPONINDEX_RAILGUN				7
+#define WEAPONINDEX_PLASMAGUN			8
+#define WEAPONINDEX_BFG					9
+#define WEAPONINDEX_GRAPPLING_HOOK		10
+#define WEAPONINDEX_NAILGUN				11
+#define WEAPONINDEX_PROXLAUNCHER		12
+#define WEAPONINDEX_CHAINGUN			13
+
+//enemy stuff
+#define ENEMY_HORIZONTAL_DIST		200
+#define ENEMY_HEIGHT				201
+
+#define MAX_AVOIDGOALS			256
+#define MAX_GOALSTACK			8
+
+#define GFL_NONE				0
+#define GFL_ITEM				1
+#define GFL_ROAM				2
+#define GFL_DROPPED				4
+
+#define BLERR_NOERROR					0	//no error
+#define BLERR_LIBRARYNOTSETUP			1	//library not setup
+#define BLERR_INVALIDENTITYNUMBER		2	//invalid entity number
+#define BLERR_NOAASFILE					3	//no AAS file available
+#define BLERR_CANNOTOPENAASFILE			4	//cannot open AAS file
+#define BLERR_WRONGAASFILEID			5	//incorrect AAS file id
+#define BLERR_WRONGAASFILEVERSION		6	//incorrect AAS file version
+#define BLERR_CANNOTREADAASLUMP			7	//cannot read AAS file lump
+#define BLERR_CANNOTLOADICHAT			8	//cannot load initial chats
+#define BLERR_CANNOTLOADITEMWEIGHTS		9	//cannot load item weights
+#define BLERR_CANNOTLOADITEMCONFIG		10	//cannot load item config
+#define BLERR_CANNOTLOADWEAPONWEIGHTS	11	//cannot load weapon weights
+#define BLERR_CANNOTLOADWEAPONCONFIG	12	//cannot load weapon config
+
+#define WT_BALANCE			1
+#define MAX_WEIGHTS			128
+
+//fuzzy seperator
+struct fuzzyseperator_t
+{
+	fuzzyseperator_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		inUse = false;
+		index = 0;
+		value = 0;
+		type = 0;
+		weight = 0;
+		minweight = 0.0f;
+		maxweight = 0.0f;
+		child = nullptr;
+		next = nullptr;
+	}
+
+	bool inUse;
+	int index;
+	int value;
+	int type;
+	float weight;
+	float minweight;
+	float maxweight;
+	fuzzyseperator_t* child;
+	fuzzyseperator_t* next;
+};
+
+//fuzzy weight
+struct weight_t
+{
+	weight_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		name.Clear();
+		firstseperator = nullptr;
+	}
+
+	idStr name;
+	fuzzyseperator_t* firstseperator;
+};
+
+//weight configuration
+struct weightconfig_t
+{
+	weightconfig_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		inUse = false;
+		numweights = 0;
+		filename.Clear();
+
+		for( int i = 0; i < MAX_WEIGHTS; i++ )
+		{
+			weights[i].Reset();
+		}
+	}
+
+	bool inUse;
+	int numweights;
+	weight_t weights[MAX_WEIGHTS];
+	idStr filename;
+};
+
+// ------------------------------------------------------------------------------------
+
+#define BOTFILESBASEFOLDER		"botfiles"
+//debug line colors
+#define LINECOLOR_NONE			-1
+#define LINECOLOR_RED			1//0xf2f2f0f0L
+#define LINECOLOR_GREEN			2//0xd0d1d2d3L
+#define LINECOLOR_BLUE			3//0xf3f3f1f1L
+#define LINECOLOR_YELLOW		4//0xdcdddedfL
+#define LINECOLOR_ORANGE		5//0xe0e1e2e3L
+
+//Print types
+#define PRT_MESSAGE				1
+#define PRT_WARNING				2
+#define PRT_ERROR				3
+#define PRT_FATAL				4
+#define PRT_EXIT				5
+
+//console message types
+#define CMS_NORMAL				0
+#define CMS_CHAT				1
+
+//action flags
+#define ACTION_ATTACK			0x0000001
+#define ACTION_USE				0x0000002
+#define ACTION_RESPAWN			0x0000008
+#define ACTION_JUMP				0x0000010
+#define ACTION_MOVEUP			0x0000020
+#define ACTION_CROUCH			0x0000080
+#define ACTION_MOVEDOWN			0x0000100
+#define ACTION_MOVEFORWARD		0x0000200
+#define ACTION_MOVEBACK			0x0000800
+#define ACTION_MOVELEFT			0x0001000
+#define ACTION_MOVERIGHT		0x0002000
+#define ACTION_DELAYEDJUMP		0x0008000
+#define ACTION_TALK				0x0010000
+#define ACTION_GESTURE			0x0020000
+#define ACTION_WALK				0x0080000
+#define ACTION_AFFIRMATIVE		0x0100000
+#define ACTION_NEGATIVE			0x0200000
+#define ACTION_GETFLAG			0x0800000
+#define ACTION_GUARDBASE		0x1000000
+#define ACTION_PATROL			0x2000000
+#define ACTION_FOLLOWME			0x8000000
+
+
+//#define DEBUG
+#define CTF
+
+#define MAX_ITEMS					256
+//bot flags
+#define BFL_STRAFERIGHT				1	//strafe to the right
+#define BFL_ATTACKED				2	//bot has attacked last ai frame
+#define BFL_ATTACKJUMPED			4	//bot jumped during attack last frame
+#define BFL_AIMATENEMY				8	//bot aimed at the enemy this frame
+#define BFL_AVOIDRIGHT				16	//avoid obstacles by going to the right
+#define BFL_IDEALVIEWSET			32	//bot has ideal view angles set
+#define BFL_FIGHTSUICIDAL			64	//bot is in a suicidal fight
+//long term goal types
+#define LTG_TEAMHELP				1	//help a team mate
+#define LTG_TEAMACCOMPANY			2	//accompany a team mate
+#define LTG_DEFENDKEYAREA			3	//defend a key area
+#define LTG_GETFLAG					4	//get the enemy flag
+#define LTG_RUSHBASE				5	//rush to the base
+#define LTG_RETURNFLAG				6	//return the flag
+#define LTG_CAMP					7	//camp somewhere
+#define LTG_CAMPORDER				8	//ordered to camp somewhere
+#define LTG_PATROL					9	//patrol
+#define LTG_GETITEM					10	//get an item
+#define LTG_KILL					11	//kill someone
+#define LTG_HARVEST					12	//harvest skulls
+#define LTG_ATTACKENEMYBASE			13	//attack the enemy base
+#define LTG_MAKELOVE_UNDER			14
+#define LTG_MAKELOVE_ONTOP			15
+//some goal dedication times
+#define TEAM_HELP_TIME				60	//1 minute teamplay help time
+#define TEAM_ACCOMPANY_TIME			600	//10 minutes teamplay accompany time
+#define TEAM_DEFENDKEYAREA_TIME		600	//10 minutes ctf defend base time
+#define TEAM_CAMP_TIME				600	//10 minutes camping time
+#define TEAM_PATROL_TIME			600	//10 minutes patrolling time
+#define TEAM_LEAD_TIME				600	//10 minutes taking the lead
+#define TEAM_GETITEM_TIME			60	//1 minute
+#define	TEAM_KILL_SOMEONE			180	//3 minute to kill someone
+#define TEAM_ATTACKENEMYBASE_TIME	600	//10 minutes
+#define TEAM_HARVEST_TIME			120	//2 minutes
+#define CTF_GETFLAG_TIME			600	//10 minutes ctf get flag time
+#define CTF_RUSHBASE_TIME			120	//2 minutes ctf rush base time
+#define CTF_RETURNFLAG_TIME			180	//3 minutes to return the flag
+#define CTF_ROAM_TIME				60	//1 minute ctf roam time
+//patrol flags
+#define PATROL_LOOP					1
+#define PATROL_REVERSE				2
+#define PATROL_BACK					4
+//teamplay task preference
+#define TEAMTP_DEFENDER				1
+#define TEAMTP_ATTACKER				2
+//CTF strategy
+#define CTFS_AGRESSIVE				1
+//copied from the aas file header
+#define PRESENCE_NONE				1
+#define PRESENCE_NORMAL				2
+#define PRESENCE_CROUCH				4
+//
+#define MAX_PROXMINES				64
+
+
+#define MAX_CHARACTERISTICS		80
+
+#define CT_INTEGER				1
+#define CT_FLOAT				2
+#define CT_STRING				3
+
+#define DEFAULT_CHARACTER		"bots/default_c.c"
+
+#define MAX_AVOIDGOALS			256
+#define MAX_GOALSTACK			8
+
+#define GFL_NONE				0
+#define GFL_ITEM				1
+#define GFL_ROAM				2
+#define GFL_DROPPED				4
+#define MAX_EPAIRKEY		128
+
+//characteristic value
+struct cvalue
+{
+	cvalue()
+	{
+		integer = 0;
+		_float = 0.0f;
+		string = "";
+	}
+
+	int integer;
+	float _float;
+	idStr string;
+};
+
+//a characteristic
+struct bot_characteristic_t
+{
+	bot_characteristic_t()
+	{
+		type = 0;
+	}
+
+	char type;						//characteristic type
+	cvalue value;				//characteristic value
+};
+
+//a bot character
+struct bot_character_t
+{
+	bot_character_t()
+	{
+		filename = "";
+		inUse = false;
+		skill = 0.0f;
+	}
+
+	idStr filename;
+	bool inUse;
+	float skill;
+	bot_characteristic_t c[MAX_CHARACTERISTICS];
+};
+
+//the bot input, will be converted to an usercmd_t
+//the bot input, will be converted to an usercmd_t
+struct bot_input_t
+{
+	bot_input_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		thinktime = 0;
+		dir.Zero();
+		speed = 0;
+		viewangles.Zero();
+		actionflags = 0;
+		weapon = 0;
+		lastWeaponNum = 0;
+		respawn = false;
+	}
+
+	float thinktime;		//time since last output (in seconds)
+	idVec3 dir;				//movement direction
+	float speed;			//speed in the range [0, 400]
+	idAngles viewangles;		//the view angles
+	int actionflags;		//one of the ACTION_? flags
+	int weapon;				//weapon to use
+	int lastWeaponNum;
+	bool respawn;
+};
+
+#if 0
+//entity state
+typedef struct bot_entitystate_s
+{
+	int		type;			// entity type
+	int		flags;			// entity flags
+	vec3_t	origin;			// origin of the entity
+	vec3_t	angles;			// angles of the model
+	vec3_t	old_origin;		// for lerping
+	vec3_t	mins;			// bounding box minimums
+	vec3_t	maxs;			// bounding box maximums
+	int		groundent;		// ground entity
+	int		solid;			// solid type
+	int		modelindex;		// model used
+	int		modelindex2;	// weapons, CTF flags, etc
+	int		frame;			// model frame number
+	int		event;			// impulse events -- muzzle flashes, footsteps, etc
+	int		eventParm;		// even parameter
+	int		powerups;		// bit flags
+	int		weapon;			// determines weapon and flash model, etc
+	int		legsAnim;		// mask off ANIM_TOGGLEBIT
+	int		torsoAnim;		// mask off ANIM_TOGGLEBIT
+} bot_entitystate_t;
+
+//check points
+typedef struct bot_waypoint_s
+{
+	int			inuse;
+	char		name[32];
+	bot_goal_t	goal;
+	struct		bot_waypoint_s* next, * prev;
+} bot_waypoint_t;
+
+//bot settings
+typedef struct bot_settings_s
+{
+	char characterfile[MAX_QPATH];
+	float skill;
+	char team[MAX_QPATH];
+} bot_settings_t;
+
+#define MAX_ACTIVATESTACK		8
+#define MAX_ACTIVATEAREAS		32
+
+typedef struct bot_activategoal_s
+{
+	int inuse;
+	bot_goal_t goal;						//goal to activate (buttons etc.)
+	float time;								//time to activate something
+	float start_time;						//time starting to activate something
+	float justused_time;					//time the goal was used
+	int shoot;								//true if bot has to shoot to activate
+	int weapon;								//weapon to be used for activation
+	vec3_t target;							//target to shoot at to activate something
+	vec3_t origin;							//origin of the blocking entity to activate
+	int areas[MAX_ACTIVATEAREAS];			//routing areas disabled by blocking entity
+	int numareas;							//number of disabled routing areas
+	int areasdisabled;						//true if the areas are disabled for the routing
+	struct bot_activategoal_s* next;		//next activate goal on stack
+} bot_activategoal_t;
+
+//bot state
+typedef struct bot_state_s
+{
+	int inuse;										//true if this state is used by a bot client
+	int botthink_residual;							//residual for the bot thinks
+	int client;										//client number of the bot
+	int entitynum;									//entity number of the bot
+	playerState_t cur_ps;							//current player state
+	int last_eFlags;								//last ps flags
+	usercmd_t lastucmd;								//usercmd from last frame
+	int entityeventTime[1024];						//last entity event time
+	//
+	bot_settings_t settings;						//several bot settings
+	int ( *ainode )( struct bot_state_s* bs );			//current AI node
+	float thinktime;								//time the bot thinks this frame
+	vec3_t origin;									//origin of the bot
+	vec3_t velocity;								//velocity of the bot
+	int presencetype;								//presence type of the bot
+	vec3_t eye;										//eye coordinates of the bot
+	int areanum;									//the number of the area the bot is in
+	int inventory[MAX_ITEMS];						//string with items amounts the bot has
+	int tfl;										//the travel flags the bot uses
+	int flags;										//several flags
+	int respawn_wait;								//wait until respawned
+	int lasthealth;									//health value previous frame
+	int lastkilledplayer;							//last killed player
+	int lastkilledby;								//player that last killed this bot
+	int botdeathtype;								//the death type of the bot
+	int enemydeathtype;								//the death type of the enemy
+	int botsuicide;									//true when the bot suicides
+	int enemysuicide;								//true when the enemy of the bot suicides
+	int setupcount;									//true when the bot has just been setup
+	int map_restart;									//true when the map is being restarted
+	int entergamechat;								//true when the bot used an enter game chat
+	int num_deaths;									//number of time this bot died
+	int num_kills;									//number of kills of this bot
+	int revenge_enemy;								//the revenge enemy
+	int revenge_kills;								//number of kills the enemy made
+	int lastframe_health;							//health value the last frame
+	int lasthitcount;								//number of hits last frame
+	int chatto;										//chat to all or team
+	float walker;									//walker charactertic
+	float ltime;									//local bot time
+	float entergame_time;							//time the bot entered the game
+	float ltg_time;									//long term goal time
+	float nbg_time;									//nearby goal time
+	float respawn_time;								//time the bot takes to respawn
+	float respawnchat_time;							//time the bot started a chat during respawn
+	float chase_time;								//time the bot will chase the enemy
+	float enemyvisible_time;						//time the enemy was last visible
+	float check_time;								//time to check for nearby items
+	float stand_time;								//time the bot is standing still
+	float lastchat_time;							//time the bot last selected a chat
+	float kamikaze_time;							//time to check for kamikaze usage
+	float invulnerability_time;						//time to check for invulnerability usage
+	float standfindenemy_time;						//time to find enemy while standing
+	float attackstrafe_time;						//time the bot is strafing in one dir
+	float attackcrouch_time;						//time the bot will stop crouching
+	float attackchase_time;							//time the bot chases during actual attack
+	float attackjump_time;							//time the bot jumped during attack
+	float enemysight_time;							//time before reacting to enemy
+	float enemydeath_time;							//time the enemy died
+	float enemyposition_time;						//time the position and velocity of the enemy were stored
+	float defendaway_time;							//time away while defending
+	float defendaway_range;							//max travel time away from defend area
+	float rushbaseaway_time;						//time away from rushing to the base
+	float attackaway_time;							//time away from attacking the enemy base
+	float harvestaway_time;							//time away from harvesting
+	float ctfroam_time;								//time the bot is roaming in ctf
+	float killedenemy_time;							//time the bot killed the enemy
+	float arrive_time;								//time arrived (at companion)
+	float lastair_time;								//last time the bot had air
+	float teleport_time;							//last time the bot teleported
+	float camp_time;								//last time camped
+	float camp_range;								//camp range
+	float weaponchange_time;						//time the bot started changing weapons
+	float firethrottlewait_time;					//amount of time to wait
+	float firethrottleshoot_time;					//amount of time to shoot
+	float notblocked_time;							//last time the bot was not blocked
+	float blockedbyavoidspot_time;					//time blocked by an avoid spot
+	float predictobstacles_time;					//last time the bot predicted obstacles
+	int predictobstacles_goalareanum;				//last goal areanum the bot predicted obstacles for
+	vec3_t aimtarget;
+	vec3_t enemyvelocity;							//enemy velocity 0.5 secs ago during battle
+	vec3_t enemyorigin;								//enemy origin 0.5 secs ago during battle
+	//
+	int kamikazebody;								//kamikaze body
+	int proxmines[MAX_PROXMINES];
+	int numproxmines;
+	//
+	bot_character_t* character;						//the bot character
+	int ms;											//move state of the bot
+	int gs;											//goal state of the bot
+	int cs;											//chat state of the bot
+	int ws;											//weapon state of the bot
+	//
+	int enemy;										//enemy entity number
+	int lastenemyareanum;							//last reachability area the enemy was in
+	vec3_t lastenemyorigin;							//last origin of the enemy in the reachability area
+	int weaponnum;									//current weapon number
+	vec3_t viewangles;								//current view angles
+	vec3_t ideal_viewangles;						//ideal view angles
+	vec3_t viewanglespeed;
+	//
+	int ltgtype;									//long term goal type
+	// team goals
+	int teammate;									//team mate involved in this team goal
+	int decisionmaker;								//player who decided to go for this goal
+	int ordered;									//true if ordered to do something
+	float order_time;								//time ordered to do something
+	int owndecision_time;							//time the bot made it's own decision
+	bot_goal_t teamgoal;							//the team goal
+	bot_goal_t altroutegoal;						//alternative route goal
+	float reachedaltroutegoal_time;					//time the bot reached the alt route goal
+	float teammessage_time;							//time to message team mates what the bot is doing
+	float teamgoal_time;							//time to stop helping team mate
+	float teammatevisible_time;						//last time the team mate was NOT visible
+	int teamtaskpreference;							//team task preference
+	// last ordered team goal
+	int lastgoal_decisionmaker;
+	int lastgoal_ltgtype;
+	int lastgoal_teammate;
+	bot_goal_t lastgoal_teamgoal;
+	// for leading team mates
+	int lead_teammate;								//team mate the bot is leading
+	bot_goal_t lead_teamgoal;						//team goal while leading
+	float lead_time;								//time leading someone
+	float leadvisible_time;							//last time the team mate was visible
+	float leadmessage_time;							//last time a messaged was sent to the team mate
+	float leadbackup_time;							//time backing up towards team mate
+	//
+	char teamleader[32];							//netname of the team leader
+	float askteamleader_time;						//time asked for team leader
+	float becometeamleader_time;					//time the bot will become the team leader
+	float teamgiveorders_time;						//time to give team orders
+	float lastflagcapture_time;						//last time a flag was captured
+	int numteammates;								//number of team mates
+	int redflagstatus;								//0 = at base, 1 = not at base
+	int blueflagstatus;								//0 = at base, 1 = not at base
+	int neutralflagstatus;							//0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag
+	int flagstatuschanged;							//flag status changed
+	int forceorders;								//true if forced to give orders
+	int flagcarrier;								//team mate carrying the enemy flag
+	int ctfstrategy;								//ctf strategy
+	char subteam[32];								//sub team name
+	float formation_dist;							//formation team mate intervening space
+	char formation_teammate[16];					//netname of the team mate the bot uses for relative positioning
+	float formation_angle;							//angle relative to the formation team mate
+	vec3_t formation_dir;							//the direction the formation is moving in
+	vec3_t formation_origin;						//origin the bot uses for relative positioning
+	bot_goal_t formation_goal;						//formation goal
+
+	bot_activategoal_t* activatestack;				//first activate goal on the stack
+	bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK];	//activate goal heap
+
+	bot_waypoint_t* checkpoints;					//check points
+	bot_waypoint_t* patrolpoints;					//patrol points
+	bot_waypoint_t* curpatrolpoint;					//current patrol point the bot is going for
+	int patrolflags;								//patrol flags
+
+// jmarshall
+	bot_goal_t  currentgoal;
+
+	vec3_t		currentMoveGoal;
+	vec3_t		movement_waypoints[NAV_MAX_PATHSTEPS];
+	int			numMovementWaypoints;
+	int			currentWaypoint;
+
+	vec3_t		last_origin;
+	vec3_t		very_short_term_origin;
+
+	int			stuck_time;
+
+	vec3_t		last_enemy_visible_position;
+	vec3_t		random_move_position;
+	// jmarshall end
+
+	bot_input_t input;
+} bot_state_t;
+
+void BotUpdateInput( bot_state_t* bs, int time, int elapsed_time );
+qboolean BotIsDead( bot_state_t* bs );
+
+void AIEnter_Respawn( bot_state_t* bs, char* s );
+
+extern float floattime;
+#define FloatTime() floattime
+
+
+float Characteristic_BFloat( bot_character_t* ch, int index, float min, float max );
+void Characteristic_String( bot_character_t* ch, int index, char* buf, int size );
+
+bot_character_t* BotLoadCharacterFromFile( char* charfile, int skill );
+
+void BotInitLevelItems( void );
+
+void BotChooseWeapon( bot_state_t* bs );
+
+inline float AAS_Time()
+{
+	return floattime;
+}
+
+unsigned short int BotTravelTime( vec3_t start, vec3_t end );
+#endif
+
+#define MAX_BOT_INVENTORY 256
+
+typedef enum
+{
+	NULLMOVEFLAG = -1,
+	MOVE_PRONE,
+	MOVE_CROUCH,
+	MOVE_WALK,
+	MOVE_RUN2,
+	MOVE_SPRINT,
+	MOVE_JUMP,
+} botMoveFlags_t;
+
+//
+// rvmBotUtil
+//
+class rvmBotUtil
+{
+public:
+	static float random()
+	{
+		return ( ( rand() & 0x7fff ) / ( ( float )0x7fff ) );
+	}
+
+	static float crandom()
+	{
+		return ( 2.0 * ( random() - 0.5 ) );
+	}
+};
+
+#include "Bot_char.h"
+#include "Bot_weights.h"
+#include "Bot_weapons.h"
+#include "Bot_goal.h"
+
+struct bot_state_t
+{
+	bot_state_t()
+	{
+		character = NULL;
+		gs = 0;
+		ws = 0;
+		Reset();
+	}
+	void Reset()
+	{
+		attackerEntity = NULL;
+		client = 0;
+		entitynum = 0;
+		setupcount = 0;
+		entergame_time = 0;
+		weaponnum = 0;
+		lasthealth = 0;
+		ltg_time = 0;
+		weaponchange_time = 0;
+		enemy = -1;
+		enemyvisible_time = 0;
+		enemysuicide = 0;
+		enemysight_time = 0;
+		check_time = 0;
+		nbg_time = 0;
+		enemydeath_time = 0;
+		teleport_time = 0;
+		flags = 0;
+		firethrottlewait_time = 0;
+		attackchase_time = 0;
+		attackcrouch_time = 0;
+		attackstrafe_time = 0;
+		attackjump_time = 0;
+		firethrottleshoot_time = 0;
+		chase_time = 0;
+		thinktime = 0;
+		aggressiveAttackTime = 0.0f;
+		useRandomPosition = false;
+		aimtarget.Zero();
+		lastenemyorigin.Zero();
+		origin.Zero();
+		enemyorigin.Zero();
+		random_move_position.Zero();
+		last_enemy_visible_position.Zero();
+		viewangles.Zero();
+		eye.Zero();
+		memset( &inventory[0], 0, sizeof( inventory ) );
+	}
+
+	bot_character_t* character;
+	int gs;
+	int ws;
+	int enemy;
+	int client;
+	idEntity* attackerEntity;
+	int lasthealth;
+	int entitynum;
+	int setupcount;
+	int ltg_time;
+	int flags;
+	int weaponnum;
+	bool useRandomPosition;
+	float thinktime;
+	float chase_time;
+	float attackjump_time;
+	float attackcrouch_time;
+	float attackstrafe_time;
+	float attackchase_time;
+	float firethrottlewait_time;
+	float firethrottleshoot_time;
+	float nbg_time;									//nearby goal time
+	float entergame_time;
+	float weaponchange_time;
+	float check_time;
+	float teleport_time;
+	float enemyvisible_time;						//time the enemy was last visible
+	int enemysuicide;								//true when the enemy of the bot suicides
+	float enemysight_time;							//time before reacting to enemy
+	float enemydeath_time;							//time the enemy died
+	float aggressiveAttackTime;
+	idVec3 origin;
+	idVec3 aimtarget;
+	idVec3 random_move_position;
+	idVec3 last_enemy_visible_position;
+	idAngles viewangles;
+	idVec3 enemyorigin;
+	idVec3 eye;
+	idVec3 lastenemyorigin;
+	int inventory[MAX_BOT_INVENTORY];
+	bot_goal_t	currentGoal;
+	bot_input_t	botinput;
+};
+
+#include "Bot_chat.h"
+
+#define Bot_Time() ((float)gameLocal.time / 1000.0f)
+
+//
+// rvmBot
+//
+class rvmBot : public idPlayer
+{
+public:
+	friend class rvmBotAI;
+
+	CLASS_PROTOTYPE( rvmBot );
+
+	rvmBot();
+	~rvmBot();
+
+	void			Spawn( void );
+	virtual void	Think( void ) override;
+	virtual void	SpawnToPoint( const idVec3& spawn_origin, const idAngles& spawn_angles ) override;
+	virtual	void	Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) override;
+	virtual void	InflictedDamageEvent(idEntity* target) override;
+	virtual void	StateThreadChanged(void) override;
+	virtual bool	IsBot(void) override { return true; }
+
+	void			SetEnemy( idPlayer* player, idVec3 origin );
+
+	void			BotInputFrame( void );
+	void			Bot_ResetUcmd( usercmd_t& ucmd );
+
+
+	static void		PresenceTypeBoundingBox( int presencetype, idVec3& mins, idVec3& maxs );
+private:
+	void			BotSendChatMessage(botChat_t chat, const char *targetName);
+
+	void			BotInputToUserCommand( bot_input_t* bi, usercmd_t* ucmd, int time );
+
+	void			BotMoveToGoalOrigin( idVec3 goalOrigin );
+
+	void			ServerThink( void );
+	void			BotUpdateInventory( void );
+
+	bool			HasWeapon( int index )
+	{
+		return inventory.weapons & ( 1 << index );
+	}
+private:
+	bot_state_t		bs;
+	bool			hasSpawned;
+
+private:
+	int				weapon_machinegun;
+	int				weapon_shotgun;
+	int				weapon_plasmagun;
+	int				weapon_rocketlauncher;
+protected:
+	bool			BotIsDead(bot_state_t* bs);
+	bool			BotReachedGoal(bot_state_t* bs, bot_goal_t* goal);
+	int				BotGetItemLongTermGoal(bot_state_t* bs, int tfl, bot_goal_t* goal);
+	void			BotChooseWeapon(bot_state_t* bs);
+	int				BotFindEnemy(bot_state_t* bs, int curenemy);
+	bool			EntityIsDead(idEntity* entity);
+	float			BotEntityVisible(int viewer, idVec3 eye, idAngles viewangles, float fov, int ent);
+	float			BotEntityVisibleTest(int viewer, idVec3 eye, idAngles viewangles, float fov, int ent, bool allowHeightTest);
+	void			BotUpdateBattleInventory(bot_state_t* bs, int enemy);
+	float			BotAggression(bot_state_t* bs);
+	int				BotWantsToRetreat(bot_state_t* bs);
+	void			BotBattleUseItems(bot_state_t* bs);
+	void			BotAimAtEnemy(bot_state_t* bs);
+	void			BotCheckAttack(bot_state_t* bs);
+	bool			BotWantsToChase(bot_state_t* bs);
+	int				BotNearbyGoal(bot_state_t* bs, int tfl, bot_goal_t* ltg, float range);
+	void			BotGetRandomPointNearPosition(idVec3 point, idVec3& randomPoint, float radius);
+	int				BotMoveInRandomDirection(bot_state_t* bs);
+	void			BotMoveToGoal(bot_state_t* bs, bot_goal_t* goal);
+
+	void			MoveToCoverPoint(void);
+
+	static int	WP_MACHINEGUN;
+	static int	WP_SHOTGUN;
+	static int	WP_PLASMAGUN;
+	static int	WP_ROCKET_LAUNCHER;
+public:
+	stateResult_t	state_Chase(const stateParms_t& parms);
+	stateResult_t	state_BattleFight(const stateParms_t& parms);
+	stateResult_t	state_BattleNBG(const stateParms_t& parms);
+	stateResult_t	state_Retreat(const stateParms_t& parms);
+	stateResult_t	state_Respawn(const stateParms_t& parms);
+	stateResult_t	state_SeekNBG(const stateParms_t& parms);
+	stateResult_t	state_SeekLTG(const stateParms_t& parms);
+	stateResult_t	state_Attacked(const stateParms_t& parms);
+
+	CLASS_STATES_PROTOTYPE(rvmBot);
+private:
+	idAAS*			aas;
+};
+
+
+extern idCVar bot_skill;
diff --git a/src/mpgame/bots/BotAI.cpp b/src/mpgame/bots/BotAI.cpp
new file mode 100644
index 0000000..11f70bc
--- /dev/null
+++ b/src/mpgame/bots/BotAI.cpp
@@ -0,0 +1,1446 @@
+// BotAI.cpp
+//
+
+
+
+#include "../Game_local.h"
+#include "../ai/AASCallback_FindCoverArea.h"
+
+#define IDEAL_ATTACKDIST			140
+#define MASK_SHOT	MASK_SHOT_BOUNDINGBOX
+
+int	rvmBot::WP_MACHINEGUN = -1;
+int	rvmBot::WP_SHOTGUN = -1;
+int	rvmBot::WP_PLASMAGUN = -1;
+int	rvmBot::WP_ROCKET_LAUNCHER = -1;
+
+/*
+=========================
+rvmBot::BotIsDead
+=========================
+*/
+bool rvmBot::BotIsDead( bot_state_t* bs )
+{
+	idPlayer* player = gameLocal.GetClientByNum( bs->client );
+	if( player->health <= 0 )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/*
+==================
+rvmBot::BotReachedGoal
+==================
+*/
+bool rvmBot::BotReachedGoal( bot_state_t* bs, bot_goal_t* goal )
+{
+	if( goal->flags & GFL_ITEM )
+	{
+		//if touching the goal
+		if( botGoalManager.BotTouchingGoal( bs->origin, goal ) )
+		{
+			if( !( goal->flags & GFL_DROPPED ) )
+			{
+				botGoalManager.BotSetAvoidGoalTime( bs->gs, goal->number, -1 );
+			}
+			return true;
+		}
+		//if the goal isn't there
+		if( botGoalManager.BotItemGoalInVisButNotVisible( bs->entitynum, bs->eye, bs->viewangles, goal ) )
+		{
+			return true;
+		}
+	}
+	else
+	{
+		//if touching the goal
+		if( botGoalManager.BotTouchingGoal( bs->origin, goal ) )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+==================
+rvmBot::BotChooseWeapon
+==================
+*/
+void rvmBot::BotChooseWeapon( bot_state_t* bs )
+{
+	int newweaponnum;
+
+	//if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
+	//	bs->cur_ps.weaponstate == WEAPON_DROPPING) {
+	//	//trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+	//	bs->input.weapon = bs->weaponnum;
+	//}
+	//else {
+	newweaponnum = botWeaponInfoManager.BotChooseBestFightWeapon( bs->ws, bs->inventory );
+	if( bs->weaponnum != newweaponnum )
+	{
+		bs->weaponchange_time = Bot_Time();
+	}
+	bs->weaponnum = newweaponnum;
+	//BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
+	//trap_EA_SelectWeapon(bs->client, bs->weaponnum);
+	bs->botinput.weapon = bs->weaponnum;
+	//}
+}
+
+
+/*
+==================
+rvmBot::BotGetItemLongTermGoal
+==================
+*/
+int rvmBot::BotGetItemLongTermGoal( bot_state_t* bs, int tfl, bot_goal_t* goal )
+{
+	const float invalidGoalAvoidTime = 5.0f;
+
+	//if the bot has no goal
+	if( !botGoalManager.BotGetTopGoal( bs->gs, goal ) )
+	{
+		//BotAI_Print(PRT_MESSAGE, "no ltg on stack\n");
+		bs->ltg_time = 0;
+	}
+	//if the bot touches the current goal
+	else if( BotReachedGoal( bs, goal ) )
+	{
+		BotChooseWeapon( bs );
+		bs->ltg_time = 0;
+	}
+	else
+	{
+		const int travelTime = gameLocal.TravelTimeToGoal( bs->origin, goal->origin, tfl );
+		if( travelTime <= 0 )
+		{
+			if( ( goal->flags & GFL_ITEM ) && goal->number > 0 )
+			{
+				botGoalManager.BotSetAvoidGoalTime( bs->gs, goal->number, invalidGoalAvoidTime );
+			}
+			bs->ltg_time = 0;
+		}
+	}
+
+	// Check to see that we can get to our goal, if not get a new goal.
+	//if (bs->numMovementWaypoints > 0)
+	//{
+	//	trace_t tr;
+	//	gentity_t* ent = &g_entities[bs->client];
+	//	vec3_t waypoint;
+	//
+	//	VectorCopy(bs->movement_waypoints[bs->currentWaypoint], waypoint);
+	//	waypoint[2] += 5.0f;
+	//
+	//	trap_Trace(&tr, ent->r.currentOrigin, NULL, NULL, waypoint, bs->client, CONTENTS_SOLID);
+	//
+	//	if (tr.fraction <= 0.7f)
+	//	{
+	//		BotChooseWeapon(bs);
+	//		bs->ltg_time = 0;
+	//	}
+	//}
+
+	//if it is time to find a new long term goal
+	if( bs->ltg_time == 0 )
+	{
+		//pop the current goal from the stack
+		botGoalManager.BotPopGoal( bs->gs );
+		//BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname)));
+		//choose a new goal
+		//BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", Bot_Time(), bs->client);
+		if( botGoalManager.BotChooseLTGItem( bs->gs, bs->origin, bs->inventory, tfl ) )
+		{
+			char buf[128];
+			//get the goal at the top of the stack
+			botGoalManager.BotGetTopGoal( bs->gs, goal );
+			botGoalManager.BotGoalName( goal->number, buf, sizeof( buf ) );
+			common->Printf( "%1.1f: new long term goal %s\n", Bot_Time(), buf );
+
+			bs->ltg_time = Bot_Time() + 20;
+			bs->currentGoal.framenum = gameLocal.framenum;
+		}
+		else  //the bot gets sorta stuck with all the avoid timings, shouldn't happen though
+		{
+			//
+			//trap_BotDumpAvoidGoals(bs->gs);
+			//reset the avoid goals and the avoid reach
+			botGoalManager.BotResetAvoidGoals( bs->gs );
+			//BotResetAvoidReach(bs->ms);
+		}
+		//get the goal at the top of the stack
+		if( !botGoalManager.BotGetTopGoal( bs->gs, goal ) )
+		{
+			return false;
+		}
+
+		bs->currentGoal.framenum = gameLocal.framenum;
+
+		return true;
+	}
+	return true;
+}
+
+/*
+==================
+rvmBot::EntityIsDead
+==================
+*/
+bool rvmBot::EntityIsDead( idEntity* entity )
+{
+	{
+		idPlayer* player = entity->Cast<idPlayer>();
+		if( player && player->health <= 0 )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+==================
+BotEntityVisibleTest
+
+returns visibility in the range [0, 1] taking fog and water surfaces into account
+==================
+*/
+float rvmBot::BotEntityVisibleTest( int viewer, idVec3 eye, idAngles viewangles, float fov, int ent, bool allowHeightTest )
+{
+	int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
+	float squaredfogdist, waterfactor, vis, bestvis;
+	trace_t trace;
+	idEntity* entinfo;
+	idVec3 dir, start, end, middle;
+	idAngles entangles;
+	idPlayer* viewEnt;
+
+	//calculate middle of bounding box
+	//BotEntityInfo(ent, &entinfo);
+	entinfo = gameLocal.entities[ent];
+	viewEnt = gameLocal.entities[viewer]->Cast<idPlayer>();
+
+	//VectorAdd(entinfo->r.mins, entinfo->r.maxs, middle);
+	//VectorScale(middle, 0.5, middle);
+	//VectorAdd(entinfo->GetOrigin(), middle, middle);
+	middle = entinfo->GetPhysics()->GetBounds().GetCenter();
+	middle += entinfo->GetOrigin();
+
+	//check if entity is within field of vision
+	//VectorSubtract(middle, eye, dir);
+	dir = middle - eye;
+	entangles = dir.ToAngles();
+
+	if (!viewEnt->CheckFOV(entinfo->GetOrigin()))
+	{
+		return 0;
+	}
+
+	pc = gameLocal.clip[0]->PointContents( eye );
+	infog = ( pc & CONTENTS_FOG );
+	inwater = ( pc & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) );
+	bestvis = 0;
+	for( i = 0; i < 3; i++ )
+	{
+		//if the point is not in potential visible sight
+		//if (!AAS_inPVS(eye, middle)) continue;
+		//
+		contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
+		passent = viewer;
+		hitent = ent;
+		//VectorCopy(eye, start);
+		start = eye;
+		//VectorCopy(middle, end);
+		end = middle;
+		//if the entity is in water, lava or slime
+		if(gameLocal.clip[0]->PointContents( middle ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) )
+		{
+			contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
+		}
+		//if eye is in water, lava or slime
+		if( inwater )
+		{
+			if( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) )
+			{
+				passent = ent;
+				hitent = viewer;
+				VectorCopy( middle, start );
+				VectorCopy( eye, end );
+			}
+			contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
+		}
+
+		//trace from start to end
+		gameLocal.Trace( trace, start, end, contents_mask, passent );
+		// jmarshall
+		if( trace.fraction < 0.9f && allowHeightTest )
+		{
+			end[2] += 50.0f;
+			gameLocal.Trace( trace, start, end, contents_mask, passent );
+		}
+		// jmarshall end
+
+		//if water was hit
+		waterfactor = 1.0;
+		if( trace.c.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) )
+		{
+			//if the water surface is translucent
+			if( 1 )
+			{
+				//trace through the water
+				contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
+				//trap_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
+				gameLocal.Trace( trace, trace.endpos, end, contents_mask, passent );
+				waterfactor = 0.5;
+			}
+		}
+
+		//if a full trace or the hitent was hit
+		if( trace.fraction >= 1 || trace.c.entityNum == hitent )
+		{
+			//check for fog, assuming there's only one fog brush where
+			//either the viewer or the entity is in or both are in
+			otherinfog = ( gameLocal.clip[0]->PointContents( middle ) & CONTENTS_FOG );
+			if( infog && otherinfog )
+			{
+				VectorSubtract( trace.endpos, eye, dir );
+				squaredfogdist = dir.LengthSqr();
+			}
+			else if( infog )
+			{
+				VectorCopy( trace.endpos, start );
+				//trap_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
+				gameLocal.Trace( trace, start, eye, CONTENTS_FOG, viewer );
+				VectorSubtract( eye, trace.endpos, dir );
+				squaredfogdist = dir.LengthSqr();
+			}
+			else if( otherinfog )
+			{
+				VectorCopy( trace.endpos, end );
+				//trap_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
+				gameLocal.Trace( trace, eye, end, CONTENTS_FOG, viewer );
+				VectorSubtract( end, trace.endpos, dir );
+				squaredfogdist = dir.LengthSqr();
+			}
+			else
+			{
+				//if the entity and the viewer are not in fog assume there's no fog in between
+				squaredfogdist = 0;
+			}
+			//decrease visibility with the view distance through fog
+			vis = 1 / ( ( squaredfogdist * 0.001 ) < 1 ? 1 : ( squaredfogdist * 0.001 ) );
+
+			//if entering water visibility is reduced
+			vis *= waterfactor;
+
+			if( vis > bestvis )
+			{
+				bestvis = vis;
+			}
+
+			//if pretty much no fog
+			if( bestvis >= 0.95 )
+			{
+				return bestvis;
+			}
+		}
+		//check bottom and top of bounding box as well
+		if( i == 0 )
+		{
+			middle[2] += entinfo->GetPhysics()->GetBounds()[0][2];    // r.mins[2];
+		}
+		else if( i == 1 )
+		{
+			middle[2] += entinfo->GetPhysics()->GetBounds()[1][2] - entinfo->GetPhysics()->GetBounds()[0][2];    //entinfo->r.maxs[2] - entinfo->r.mins[2];
+		}
+	}
+	return bestvis;
+}
+
+/*
+==================
+rvmBot::BotEntityVisible
+==================
+*/
+float rvmBot::BotEntityVisible( int viewer, idVec3 eye, idAngles viewangles, float fov, int ent )
+{
+	return BotEntityVisibleTest( viewer, eye, viewangles, fov, ent, true );
+}
+
+/*
+==================
+rvmBot::BotUpdateBattleInventory
+==================
+*/
+void rvmBot::BotUpdateBattleInventory( bot_state_t* bs, int enemy )
+{
+	idVec3 dir;
+	idEntity* entinfo;
+
+	entinfo = gameLocal.entities[enemy];
+
+	VectorSubtract( entinfo->GetOrigin(), bs->origin, dir );
+	bs->inventory[ENEMY_HEIGHT] = ( int )dir[2];
+	dir[2] = 0;
+	bs->inventory[ENEMY_HORIZONTAL_DIST] = ( int )dir.Length();
+	//FIXME: add num visible enemies and num visible team mates to the inventory
+}
+
+/*
+==================
+rvmBot::BotAggression
+==================
+*/
+float rvmBot::BotAggression( bot_state_t* bs )
+{
+	//if the bot has quad
+	if( bs->inventory[INVENTORY_QUAD] )
+	{
+		//if the bot is not holding the gauntlet or the enemy is really nearby
+		if( bs->weaponnum != 0 ||
+				bs->inventory[ENEMY_HORIZONTAL_DIST] < 80 )
+		{
+			return 70;
+		}
+	}
+	//if the enemy is located way higher than the bot
+	if( bs->inventory[ENEMY_HEIGHT] > 200 )
+	{
+		return 0;
+	}
+	//if the bot is very low on health
+	if( bs->inventory[INVENTORY_HEALTH] < 60 )
+	{
+		return 0;
+	}
+	//if the bot is low on health
+	if( bs->inventory[INVENTORY_HEALTH] < 80 )
+	{
+		//if the bot has insufficient armor
+		if( bs->inventory[INVENTORY_ARMOR] < 40 )
+		{
+			return 0;
+		}
+	}
+	//if the bot can use the bfg
+	if( bs->inventory[INVENTORY_BFG10K] > 0 &&
+			bs->inventory[INVENTORY_BFGAMMO] > 7 )
+	{
+		return 100;
+	}
+	//if the bot can use the railgun
+	if( bs->inventory[INVENTORY_RAILGUN] > 0 &&
+			bs->inventory[INVENTORY_SLUGS] > 5 )
+	{
+		return 95;
+	}
+	//if the bot can use the lightning gun
+	if( bs->inventory[INVENTORY_LIGHTNING] > 0 &&
+			bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50 )
+	{
+		return 90;
+	}
+	//if the bot can use the rocketlauncher
+	if( bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
+			bs->inventory[INVENTORY_ROCKETS] > 5 )
+	{
+		return 90;
+	}
+	//if the bot can use the plasmagun
+	if( bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
+			bs->inventory[INVENTORY_CELLS] > 40 )
+	{
+		return 85;
+	}
+	//if the bot can use the grenade launcher
+	if( bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
+			bs->inventory[INVENTORY_GRENADES] > 10 )
+	{
+		return 80;
+	}
+	//if the bot can use the shotgun
+	if( bs->inventory[INVENTORY_SHOTGUN] > 0 &&
+			bs->inventory[INVENTORY_SHELLS] > 10 )
+	{
+		return 50;
+	}
+
+	if( bs->inventory[INVENTORY_BULLETS] > 0 )
+	{
+		return 60;
+	}
+
+	//otherwise the bot is not feeling too good
+	return 0;
+}
+
+
+/*
+==================
+rvmBot::BotWantsToRetreat
+==================
+*/
+int rvmBot::BotWantsToRetreat( bot_state_t* bs )
+{
+	if( BotAggression( bs ) < 50 )
+	{
+		return true;
+	}
+	return false;
+}
+
+/*
+==================
+rvmBot::BotBattleUseItems
+==================
+*/
+void rvmBot::BotBattleUseItems( bot_state_t* bs )
+{
+	if( bs->inventory[INVENTORY_HEALTH] < 40 )
+	{
+		if( bs->inventory[INVENTORY_TELEPORTER] > 0 )
+		{
+			bs->botinput.actionflags |= ACTION_USE;
+		}
+	}
+	if( bs->inventory[INVENTORY_HEALTH] < 60 )
+	{
+		if( bs->inventory[INVENTORY_MEDKIT] > 0 )
+		{
+			//trap_EA_Use(bs->client);
+			bs->botinput.actionflags |= ACTION_USE;
+		}
+	}
+}
+
+/*
+==================
+rvmBot::BotFindEnemy
+==================
+*/
+int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy )
+{
+	int i, healthdecrease;
+	float f, alertness, easyfragger, vis;
+	float squaredist, cursquaredist;
+	idPlayer* entinfo = NULL;
+	idPlayer* curenemyinfo = NULL;
+	idVec3 dir;
+	idAngles angles;
+	idPlayer* clientEnt;
+
+	alertness = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_ALERTNESS, 0, 1 );
+	easyfragger = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1 );
+
+	clientEnt = gameLocal.entities[bs->client]->Cast<idPlayer>();
+
+	//check if the health decreased
+	healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
+
+	//remember the current health value
+	bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
+	//
+	if( curenemy >= 0 )
+	{
+		//BotEntityInfo(curenemy, &curenemyinfo);
+		curenemyinfo = gameLocal.entities[curenemy]->Cast<idPlayer>();
+		if( curenemyinfo == NULL || EntityIsDead( curenemyinfo ) )
+		{
+			curenemy = -1;
+			cursquaredist = 0;
+		}
+		else
+		{
+		// jmarshall - add flag support.
+		//if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
+		// jmarshall end
+		//VectorSubtract(curenemyinfo->r.currentOrigin, bs->origin, dir);
+		dir = curenemyinfo->GetPhysics()->GetOrigin() - bs->origin;
+		cursquaredist = dir.LengthSqr();// VectorLengthSquared(dir);
+		}
+	}
+	else
+	{
+		cursquaredist = 0;
+	}
+
+	for( i = 0; i < MAX_CLIENTS; i++ )
+	{
+
+		if( i == bs->client )
+		{
+			continue;
+		}
+
+		//if it's the current enemy
+		if( i == curenemy )
+		{
+			continue;
+		}
+
+		entinfo = gameLocal.entities[i]->Cast<idPlayer>();
+		if( !entinfo )
+		{
+			continue;
+		}
+
+		//if the enemy isn't dead and the enemy isn't the bot self
+		if( EntityIsDead( entinfo ) || i == bs->entitynum )
+		{
+			continue;
+		}
+
+		if (entinfo->spectating) {
+			continue;
+		}
+
+		//if the enemy is invisible and not shooting
+// jmarshall - add invis
+		//if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
+		//	continue;
+		//}
+// jmarshall end
+
+// jmarshall - eval, looks like code to not shoot chatting players or players that just spawned in.
+//			   do we care about this?
+		//if not an easy fragger don't shoot at chatting players
+		//if (easyfragger < 0.5 && EntityIsChatting(&entinfo))
+		//	continue;
+
+		//
+		//if (lastteleport_time > Bot_Time() - 3) {
+		//	VectorSubtract(entinfo.origin, lastteleport_origin, dir);
+		//	if (VectorLengthSquared(dir) < Square(70))
+		//		continue;
+		//}
+// jmarshall end
+
+		//calculate the distance towards the enemy
+		idVec3 potentialTargetOrigin = entinfo->GetPhysics()->GetOrigin();
+		dir = potentialTargetOrigin - bs->origin;
+		squaredist = dir.LengthSqr();
+
+		// jmarshall
+		//if this entity is not carrying a flag
+		//if (!EntityCarriesFlag(&entinfo))
+		//{
+		//if this enemy is further away than the current one
+		if( curenemy >= 0 && squaredist > cursquaredist )
+		{
+			continue;
+		}
+		//}
+// jmarshall end
+
+		//if the bot has no
+		if( squaredist > Square( 900.0 + alertness * 4000.0 ) )
+		{
+			continue;
+		}
+
+		// jmarshall - teams!
+		//if on the same team
+		//if (BotSameTeam(bs, i))
+		//	continue;
+		// jmarshall end
+		//if the bot's health decreased or the enemy is shooting
+		if( curenemy < 0 && ( healthdecrease || entinfo->IsShooting() ) )
+		{
+			f = 360;
+		}
+		else
+		{
+			f = 90 + 90 - ( 90 - ( squaredist > Square( 810 ) ? Square( 810 ) : squaredist ) / ( 810 * 9 ) );
+		}
+		//check if the enemy is visible
+
+		// If we were last hit by someone then assume they are visible.
+		if( bs->attackerEntity != entinfo )
+		{
+			vis = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, f, i );
+			if( vis <= 0 )
+			{
+				continue;
+			}
+		}
+
+		//if the enemy is quite far away, not shooting and the bot is not damaged
+		if( curenemy < 0 && squaredist > Square( 100 ) && !healthdecrease && !entinfo->IsShooting() )
+		{
+			//check if we can avoid this enemy
+			VectorSubtract( bs->origin, entinfo->GetOrigin(), dir );
+			angles = dir.ToAngles();
+
+			//if the bot isn't in the fov of the enemy
+			if( !clientEnt->CheckFOV( entinfo->GetOrigin() ) )
+			{
+				//update some stuff for this enemy
+				BotUpdateBattleInventory( bs, i );
+
+				//if the bot doesn't really want to fight
+				if( BotWantsToRetreat( bs ) )
+				{
+					continue;
+				}
+			}
+		}
+		//found an enemy
+		bs->enemy = i;//entinfo.number;
+		bs->lastenemyorigin = entinfo->GetPhysics()->GetOrigin();
+		bs->last_enemy_visible_position = bs->lastenemyorigin;
+		bs->chase_time = Bot_Time();
+		if( curenemy >= 0 )
+		{
+			bs->enemysight_time = Bot_Time() - 2;
+		}
+		else
+		{
+			bs->enemysight_time = Bot_Time();
+		}
+		bs->enemysuicide = false;
+		bs->enemydeath_time = 0;
+		bs->enemyvisible_time = Bot_Time();
+		return true;
+	}
+	return false;
+}
+
+/*
+==================
+rvmBot::BotMoveToGoal
+==================
+*/
+void rvmBot::BotMoveToGoal( bot_state_t* bs, bot_goal_t* goal )
+{
+	bs->currentGoal = *goal;
+	bs->currentGoal.framenum = gameLocal.framenum;
+}
+
+/*
+==================
+rvmBot::BotAimAtEnemy
+==================
+*/
+void rvmBot::BotAimAtEnemy( bot_state_t* bs )
+{
+	int i, enemyvisible;
+	float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
+	idVec3 dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
+	idVec3 mins( -4, -4, -4 );
+	idVec3 maxs( 4, 4, 4 );
+	weaponinfo_t wi;
+	//aas_entityinfo_t entinfo;
+	idPlayer* entinfo;
+	bot_goal_t goal;
+	trace_t trace;
+	idVec3 target;
+	idPlayer* self;
+
+	//if the bot has no enemy
+	if( bs->enemy < 0 )
+	{
+		return;
+	}
+
+	self = gameLocal.entities[bs->entitynum]->Cast<idPlayer>();
+
+	//get the enemy entity information
+	//BotEntityInfo(bs->enemy, &entinfo);
+	entinfo = gameLocal.entities[bs->enemy]->Cast<idPlayer>();
+
+	//if this is not a player (should be an obelisk)
+	if( bs->enemy >= MAX_CLIENTS )
+	{
+		//if the obelisk is visible
+		VectorCopy( entinfo->GetOrigin(), target );
+#ifdef MISSIONPACK
+		// if attacking an obelisk
+		if( bs->enemy == redobelisk.entitynum ||
+				bs->enemy == blueobelisk.entitynum )
+		{
+			target[2] += 32;
+		}
+#endif
+		//aim at the obelisk
+		VectorSubtract( target, bs->eye, dir );
+		//vectoangles(dir, bs->viewangles);
+		bs->viewangles = dir.ToAngles();
+
+		//set the aim target before trying to attack
+		VectorCopy( target, bs->aimtarget );
+		return;
+	}
+
+	//
+	//BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
+	//
+	aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1 );
+	aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 );
+	//
+	if( aim_skill > 0.95 )
+	{
+		//don't aim too early
+		reactiontime = 0.5 * botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 );
+		if( bs->enemysight_time > Bot_Time() - reactiontime )
+		{
+			return;
+		}
+		if( bs->teleport_time > Bot_Time() - reactiontime )
+		{
+			return;
+		}
+	}
+
+	//get the weapon information
+	botWeaponInfoManager.BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi );
+
+	//get the weapon specific aim accuracy and or aim skill
+	if( wi.number == WP_MACHINEGUN )
+	{
+		aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1 );
+	}
+	else if( wi.number == WP_SHOTGUN )
+	{
+		aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1 );
+	}
+	//else if (wi.number == WP_GRENADE_LAUNCHER) {
+	//	aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
+	//	aim_skill = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
+	//}
+	else if( wi.number == WP_ROCKET_LAUNCHER )
+	{
+		aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1 );
+		aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1 );
+	}
+	//else if (wi.number == WP_LIGHTNING) {
+	//	aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1);
+	//}
+	//else if (wi.number == WP_RAILGUN) {
+	//	aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
+	//}
+	else if( wi.number == WP_PLASMAGUN )
+	{
+		aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1 );
+		aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1 );
+	}
+	//else if (wi.number == WP_BFG) {
+	//	aim_accuracy = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
+	//	aim_skill = Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
+	//}
+	//
+	if( aim_accuracy <= 0 )
+	{
+		aim_accuracy = 0.0001f;
+	}
+	//get the enemy entity information
+	//BotEntityInfo(bs->enemy, &entinfo);
+	entinfo = gameLocal.entities[bs->enemy]->Cast<idPlayer>();
+
+	//if the enemy is invisible then shoot crappy most of the time
+	//if (entinfo->IsInvisible()) {
+	//	if (rvmBotUtil::random() > 0.1)
+	//		aim_accuracy *= 0.4f;
+	//}
+	// jmarshall - fix aim accuracy.
+	//VectorSubtract(entinfo->GetOrigin(), entinfo.lastvisorigin, enemyvelocity);
+	//VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
+	////enemy origin and velocity is remembered every 0.5 seconds
+	//if (bs->enemyposition_time < Bot_Time()) {
+	//	//
+	//	bs->enemyposition_time = Bot_Time() + 0.5;
+	//	VectorCopy(enemyvelocity, bs->enemyvelocity);
+	//	VectorCopy(entinfo.origin, bs->enemyorigin);
+	//}
+	////if not extremely skilled
+	//if (aim_skill < 0.9) {
+	//	VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
+	//	//if the enemy moved a bit
+	//	if (VectorLengthSquared(dir) > Square(48)) {
+	//		//if the enemy changed direction
+	//		if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
+	//			//aim accuracy should be worse now
+	//			aim_accuracy *= 0.7f;
+	//		}
+	//	}
+	//}
+	// jmarshall end
+
+	//check visibility of enemy
+	enemyvisible = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy );
+
+	//if the enemy is visible
+	if( enemyvisible )
+	{
+		bs->lastenemyorigin = entinfo->GetOrigin();
+		bs->last_enemy_visible_position = entinfo->GetOrigin();
+		bs->chase_time = Bot_Time();
+		//
+		VectorCopy( entinfo->GetOrigin(), bestorigin );
+		bestorigin[2] += 8;
+		//get the start point shooting from
+		//NOTE: the x and y projectile start offsets are ignored
+		VectorCopy( bs->origin, start );
+		start[2] += self->GetViewHeight();// bs->cur_ps.viewheight;
+		start[2] += wi.offset[2];
+		//
+		//trap_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
+		gameLocal.Trace( trace, start, bestorigin, MASK_SHOT, bs->entitynum );
+		//if the enemy is NOT hit
+		if( trace.fraction <= 1 && trace.c.entityNum != bs->enemy )
+		{
+			bestorigin[2] += 16;
+		}
+		//if it is not an instant hit weapon the bot might want to predict the enemy
+		if( wi.speed )
+		{
+			//
+			VectorSubtract( bestorigin, bs->origin, dir );
+			dist = dir.Length();
+			VectorSubtract( entinfo->GetOrigin(), bs->enemyorigin, dir );
+			//if the enemy is NOT pretty far away and strafing just small steps left and right
+			if( !( dist > 100 && dir.LengthSqr() < Square( 32 ) ) )
+			{
+				//if skilled anough do exact prediction
+				if( aim_skill > 0.8 &&
+						//if the weapon is ready to fire
+						!self->IsShooting() )
+				{
+					//aas_clientmove_t move;
+					idVec3 origin;
+					idVec3 last_enemy_visible_position;
+					VectorCopy( bs->last_enemy_visible_position, last_enemy_visible_position );
+					last_enemy_visible_position[2] += 20.0f;
+
+					//
+					VectorSubtract( entinfo->GetOrigin(), bs->origin, dir );
+
+					////distance towards the enemy
+					//dist = VectorLength(dir);
+					////direction the enemy is moving in
+					VectorSubtract( entinfo->GetOrigin(), last_enemy_visible_position, dir );
+					////
+					//VectorScale(dir, 1 / entinfo->update_time, dir);
+					////
+					//VectorCopy(entinfo->origin, origin);
+					//origin[2] += 1;
+
+					////
+					//VectorClear(cmdmove);
+					////AAS_ClearShownDebugLines();
+					//trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
+					//	PRESENCE_CROUCH, qfalse,
+					//	dir, cmdmove, 0,
+					//	dist * 10 / wi.speed, 0.1f, 0, 0, qfalse);
+					//VectorCopy(move.endpos, bestorigin);
+					//BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", Bot_Time(), VectorLength(dir), dist * 10 / wi.speed);
+
+					bot_goal_t goal;
+					VectorMA( entinfo->GetOrigin(), 30, dir, goal.origin );
+					BotMoveToGoal( bs, &goal );
+				}
+				//if not that skilled do linear prediction
+				else if( aim_skill > 0.4 )
+				{
+					// jmarshall - fix linear prediction.
+					idVec3 last_enemy_visible_position;
+					VectorCopy( bs->last_enemy_visible_position, last_enemy_visible_position );
+					last_enemy_visible_position[2] += 20.0f;
+
+					//VectorSubtract(entinfo->GetOrigin(), bs->origin, dir);
+					////distance towards the enemy
+					//dist = VectorLength(dir);
+					////direction the enemy is moving in
+					VectorSubtract( entinfo->GetOrigin(), last_enemy_visible_position, dir );
+					//dir[2] = 0;
+					////
+					//speed = VectorNormalize(dir) / entinfo.update_time;
+					////botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
+					////best spot to aim at
+					//VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
+					bot_goal_t goal;
+					VectorMA( entinfo->GetOrigin(), 30, dir, goal.origin );
+					BotMoveToGoal( bs, &goal );
+					// jmarshall end
+				}
+			}
+		}
+		//if the projectile does radial damage
+		if( aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL )
+		{
+			//if the enemy isn't standing significantly higher than the bot
+			if( entinfo->GetOrigin()[2] < bs->origin[2] + 16 )
+			{
+				//try to aim at the ground in front of the enemy
+				VectorCopy( entinfo->GetOrigin(), end );
+				end[2] -= 64;
+				//trap_Trace(&trace, entinfo->GetOrigin(), NULL, NULL, end, bs->enemy, MASK_SHOT);
+				gameLocal.Trace( trace, entinfo->GetOrigin(), end, MASK_SHOT, bs->enemy );
+				//
+				VectorCopy( bestorigin, groundtarget );
+// jmarshall - add start solid
+				//if (trace.startsolid)
+				//	groundtarget[2] = entinfo->GetOrigin()[2] - 16;
+				//else
+				groundtarget[2] = trace.endpos[2] - 8;
+// jmarshall end
+				//trace a line from projectile start to ground target
+				//trap_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
+				gameLocal.Trace( trace, start, groundtarget, MASK_SHOT, bs->entitynum );
+				//if hitpoint is not vertically too far from the ground target
+				if( idMath::Fabs( trace.endpos[2] - groundtarget[2] ) < 50 )
+				{
+					VectorSubtract( trace.endpos, groundtarget, dir );
+					//if the hitpoint is near anough the ground target
+					if( dir.LengthSqr() < Square( 60 ) )
+					{
+						VectorSubtract( trace.endpos, start, dir );
+						//if the hitpoint is far anough from the bot
+						if( dir.LengthSqr() > Square( 100 ) )
+						{
+							//check if the bot is visible from the ground target
+							trace.endpos[2] += 1;
+							//trap_Trace(&trace, trace.endpos, NULL, NULL, entinfo->GetOrigin(), bs->enemy, MASK_SHOT);
+							gameLocal.Trace( trace, trace.endpos, entinfo->GetOrigin(), MASK_SHOT, bs->enemy );
+							if( trace.fraction >= 1 )
+							{
+								//botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
+								VectorCopy( groundtarget, bestorigin );
+							}
+						}
+					}
+				}
+			}
+		}
+		bestorigin[0] += 20 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
+		bestorigin[1] += 20 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
+		bestorigin[2] += 10 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
+	}
+	else
+	{
+		//
+		VectorCopy( bs->lastenemyorigin, bestorigin );
+		bestorigin[2] += 8;
+		// jmarshall - fix this.
+		//if the bot is skilled anough
+		//if (aim_skill > 0.5) {
+		//	//do prediction shots around corners
+		//	if (wi.number == WP_BFG ||
+		//		wi.number == WP_ROCKET_LAUNCHER ||
+		//		wi.number == WP_GRENADE_LAUNCHER) {
+		//		//create the chase goal
+		//		goal.entitynum = bs->client;
+		//		goal.areanum = bs->areanum;
+		//		VectorCopy(bs->eye, goal.origin);
+		//		VectorSet(goal.mins, -8, -8, -8);
+		//		VectorSet(goal.maxs, 8, 8, 8);
+		//		//
+		//		if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
+		//			VectorSubtract(target, bs->eye, dir);
+		//			if (VectorLengthSquared(dir) > Square(80)) {
+		//				VectorCopy(target, bestorigin);
+		//				bestorigin[2] -= 20;
+		//			}
+		//		}
+		//		aim_accuracy = 1;
+		//	}
+		//}
+		// jmarshall end
+	}
+	//
+	if( enemyvisible )
+	{
+		//trap_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
+		gameLocal.Trace( trace, bs->eye, bestorigin, MASK_SHOT, bs->entitynum );
+		VectorCopy( trace.endpos, bs->aimtarget );
+	}
+	else
+	{
+		VectorCopy( bestorigin, bs->aimtarget );
+	}
+	//get aim direction
+	VectorSubtract( bestorigin, bs->eye, dir );
+	//
+	if( wi.number == WP_MACHINEGUN ||
+			wi.number == WP_SHOTGUN /*|| // jmarshall add lighting railgun.
+		wi.number == WP_LIGHTNING ||
+		wi.number == WP_RAILGUN*/ )
+	{
+		//distance towards the enemy
+		dist = dir.Length();// VectorLength(dir);
+		if( dist > 150 )
+		{
+			dist = 150;
+		}
+		f = 0.6 + dist / 150 * 0.4;
+		aim_accuracy *= f;
+	}
+	//add some random stuff to the aim direction depending on the aim accuracy
+	if( aim_accuracy < 0.8 )
+	{
+		dir.Normalize();
+		for( i = 0; i < 3; i++ )
+		{
+			dir[i] += 0.3 * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
+		}
+	}
+	//set the ideal view angles
+	//vectoangles(dir, bs->viewangles);
+	bs->viewangles = dir.ToAngles();
+
+	//take the weapon spread into account for lower skilled bots
+	bs->viewangles[PITCH] += 6 * wi.vspread * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
+	bs->viewangles[PITCH] = idMath::AngleMod( bs->viewangles[PITCH] );
+	bs->viewangles[YAW] += 6 * wi.hspread * rvmBotUtil::crandom() * ( 1 - aim_accuracy );
+	bs->viewangles[YAW] = idMath::AngleMod( bs->viewangles[YAW] );
+	// jmarshall - add bot_challenge.
+	//if the bots should be really challenging
+	//if (bot_challenge.integer) {
+	//	//if the bot is really accurate and has the enemy in view for some time
+	//	if (aim_accuracy > 0.9 && bs->enemysight_time < Bot_Time() - 1) {
+	//		//set the view angles directly
+	//		if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
+	//		VectorCopy(bs->ideal_viewangles, bs->viewangles);
+	//		trap_EA_View(bs->client, bs->viewangles);
+	//	}
+	//}
+	//vectoangles(bi->dir, bi->viewangles);
+	// jmarshall end
+}
+
+
+/*
+==================
+rvmBot::BotWantsToChase
+==================
+*/
+bool rvmBot::BotWantsToChase( bot_state_t* bs )
+{
+	if( BotAggression( bs ) > 50 )
+	{
+		return true;
+	}
+	return false;
+}
+
+
+
+/*
+==================
+rvmBot::BotNearbyGoal
+==================
+*/
+int rvmBot::BotNearbyGoal( bot_state_t* bs, int tfl, bot_goal_t* ltg, float range )
+{
+	int ret;
+
+	// jmarshall - check for air.
+	//check if the bot should go for air
+	//if (BotGoForAir(bs, tfl, ltg, range)) return qtrue;
+	////if the bot is carrying the enemy flag
+	//if (BotCTFCarryingFlag(bs)) {
+	//	//if the bot is just a few secs away from the base
+	//	if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
+	//		bs->teamgoal.areanum, TFL_DEFAULT) < 300) {
+	//		//make the range really small
+	//		range = 50;
+	//	}
+	//}
+	// jmarshall end
+
+	//
+	ret = botGoalManager.BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range );
+
+	return ret;
+}
+
+
+/*
+=======================
+rvmBot::BotGetRandomPointNearPosition
+=======================
+*/
+void rvmBot::BotGetRandomPointNearPosition( idVec3 point, idVec3& randomPoint, float radius )
+{
+	idAAS* aas = gameLocal.GetBotAAS();
+	if( aas == NULL )
+	{
+		randomPoint = point;
+		return;
+	}
+
+	idAASFile* file = aas->GetAASFile();
+	if( file == NULL )
+	{
+		randomPoint = point;
+		return;
+	}
+
+	int areaNum = aas->PointAreaNum(point);
+	if( areaNum <= 0 )
+	{
+		randomPoint = point;
+		return;
+	}
+
+	const aasArea_t& area = file->GetArea(areaNum);
+	if( area.numFaces <= 0 )
+	{
+		randomPoint = point;
+		return;
+	}
+
+	// justin: I'm changing this from the method I used in the legacy engine to a method that doesn't require me
+	// to change the AAS format
+	//int firstEdge = area.firstEdge;
+	//int i = rvRandom::irand(0, area.numEdges);
+
+	int firstFace = area.firstFace;
+	int numFaces = area.numFaces;
+	int faceId = rvRandom::irand( firstFace, firstFace + numFaces - 1 );
+
+	const aasIndex_t index = abs(file->GetFaceIndex(faceId));
+	const aasFace_t& face = file->GetFace(index);
+	if( face.numEdges <= 0 )
+	{
+		randomPoint = point;
+		return;
+	}
+
+	int firstEdge = face.firstEdge;
+	int i = rvRandom::irand( 0, face.numEdges - 1 );
+
+	const aasEdge_t &edge = file->GetEdge(abs(file->GetEdgeIndex(firstEdge + i)));
+
+	randomPoint = file->GetVertex(edge.vertexNum[0]);
+}
+
+/*
+=======================
+rvmBot::BotMoveInRandomDirection
+=======================
+*/
+int rvmBot::BotMoveInRandomDirection( bot_state_t* bs )
+{
+	rvmBot* ent = gameLocal.entities[bs->client]->Cast<rvmBot>();
+
+	//ent->ResetPathFinding();
+
+	float dist = idMath::Distance( ent->GetPhysics()->GetOrigin(), bs->random_move_position );
+
+	if( !ent->PointVisible( bs->random_move_position ) )
+	{
+		dist = 0;
+	}
+
+	if( dist < 25 || bs->random_move_position.Length() == 0 )
+	{
+		BotGetRandomPointNearPosition( ent->GetPhysics()->GetOrigin(), bs->random_move_position, 50.0f );
+	}
+
+	VectorSubtract( bs->random_move_position, ent->GetPhysics()->GetOrigin(), bs->botinput.dir );
+
+	idAngles ang( 0, bs->botinput.dir.ToYaw(), 0 );
+	bs->botinput.speed = pm_speed.GetInteger();
+	bs->botinput.dir.Normalize();
+	bs->useRandomPosition = true;
+	bs->botinput.speed = 400; // 200 = walk, 400 = run.
+	return 0;
+}
+
+/*
+============
+rvmBot::ShowHideArea
+============
+*/
+void rvmBot::MoveToCoverPoint(void)
+{
+	int areaNum, numObstacles;
+	idVec3 target;
+	aasGoal_t goal;
+	aasObstacle_t obstacles[10];
+	idVec3 origin = GetOrigin();
+	idAAS* aas = gameLocal.GetBotAAS();
+
+	areaNum = gameLocal.GetBotAAS()->PointReachableAreaNum(origin, aas->DefaultSearchBounds(), (AREA_REACHABLE_WALK | AREA_REACHABLE_FLY));
+	target = aas->AreaCenter(aas->PointAreaNum(gameLocal.GetLocalPlayer()->GetOrigin()));
+
+	// consider the target an obstacle
+	obstacles[0].absBounds = idBounds(target).Expand(16);
+	numObstacles = 1;
+
+	idAASCallback_FindCoverArea findCover(target);
+	if (aas->FindNearestGoal(goal, areaNum, origin, target, TFL_WALK | TFL_AIR, 0.0f, 0.0f, obstacles, numObstacles, findCover))
+	{
+		bs.currentGoal.origin = goal.origin;
+		bs.currentGoal.framenum = gameLocal.framenum;
+	}
+}
+
+/*
+==================
+rvmBot::BotCheckAttack
+==================
+*/
+void rvmBot::BotCheckAttack( bot_state_t* bs )
+{
+	float points, reactiontime, fov, firethrottle;
+	int attackentity;
+	trace_t bsptrace;
+	//float selfpreservation;
+	idVec3 forward, right, start, end, dir;
+	idAngles angles;
+	weaponinfo_t wi;
+	trace_t trace;
+	//aas_entityinfo_t entinfo;
+	idEntity* entinfo;
+	idVec3 mins( -8, -8, -8 );
+	idVec3 maxs( 8, 8, 8 );
+	idPlayer* self;
+
+	attackentity = bs->enemy;
+	//
+	//BotEntityInfo(attackentity, &entinfo);
+	entinfo = gameLocal.entities[attackentity];
+
+	self = gameLocal.entities[bs->client]->Cast<idPlayer>();
+
+	//
+	reactiontime = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 );
+	if( bs->enemysight_time > Bot_Time() - reactiontime )
+	{
+		return;
+	}
+	if( bs->teleport_time > Bot_Time() - reactiontime )
+	{
+		return;
+	}
+
+	//if changing weapons
+	if( bs->weaponchange_time > Bot_Time() - 0.1 )
+	{
+		return;
+	}
+
+	//check fire throttle characteristic
+	if( bs->firethrottlewait_time > Bot_Time() )
+	{
+		return;
+	}
+	// jmarshall - this wasn't original behaivor, but multiplying it by 40 feels better for gameplay
+	firethrottle = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1 ) * 40.0f;
+	// jmarshall end
+	if( bs->firethrottleshoot_time < Bot_Time() )
+	{
+		if( rvmBotUtil::random() > firethrottle )
+		{
+			bs->firethrottlewait_time = Bot_Time() + firethrottle;
+			bs->firethrottleshoot_time = 0;
+		}
+		else
+		{
+			bs->firethrottleshoot_time = Bot_Time() + 1 - firethrottle;
+			bs->firethrottlewait_time = 0;
+		}
+	}
+
+	VectorSubtract( bs->aimtarget, bs->eye, dir );
+
+// jmarshall - add gauntlet
+	//if (bs->weaponnum == WP_GAUNTLET) {
+	//	if (dir.LengthSqr() > Square(60)) {
+	//		return;
+	//	}
+	//}
+// jmarshall end
+	if( dir.LengthSqr() < Square( 100 ) )
+	{
+		fov = 120;
+	}
+	else
+	{
+		fov = 50;
+	}
+
+	//vectoangles(dir, angles);
+	angles = dir.ToAngles();
+	if( !self->CheckFOV( entinfo->GetPhysics()->GetOrigin() ) )
+	{
+		return;
+	}
+
+	//trap_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP);
+	gameLocal.Trace( bsptrace, bs->eye, bs->aimtarget, CONTENTS_SOLID | CONTENTS_PLAYERCLIP, bs->client );
+
+	if( bsptrace.fraction < 1 && bsptrace.c.entityNum != attackentity )
+	{
+		return;
+	}
+
+	//get the weapon info
+	botWeaponInfoManager.BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi );
+
+	//get the start point shooting from
+	VectorCopy( bs->origin, start );
+	start[2] += self->GetViewHeight();// bs->cur_ps.viewheight;
+	bs->viewangles.ToVectors( &forward, &right, NULL );
+
+	start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
+	start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
+	start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
+	//end point aiming at
+	VectorMA( start, 1000, forward, end );
+	//a little back to make sure not inside a very close enemy
+	VectorMA( start, -12, forward, start );
+	//trap_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
+	gameLocal.Trace( trace, start, end, MASK_SHOT, bs->entitynum );
+
+	//if the entity is a client
+	if( trace.c.entityNum > 0 && trace.c.entityNum <= MAX_CLIENTS )
+	{
+		if( trace.c.entityNum != attackentity )
+		{
+			// jmarshall - teams
+			//if a teammate is hit
+			//if (BotSameTeam(bs, trace.entityNum))
+			//	return;
+			// jmarshall end
+		}
+	}
+	//if won't hit the enemy or not attacking a player (obelisk)
+	if( trace.c.entityNum != attackentity || attackentity >= MAX_CLIENTS )
+	{
+		//if the projectile does radial damage
+		if( wi.proj.damagetype & DAMAGETYPE_RADIAL )
+		{
+			if( trace.fraction * 1000 < wi.proj.radius )
+			{
+				points = ( wi.proj.damage - 0.5 * trace.fraction * 1000 ) * 0.5;
+				if( points > 0 )
+				{
+					return;
+				}
+			}
+			//FIXME: check if a teammate gets radial damage
+		}
+	}
+	//if fire has to be release to activate weapon
+	if( wi.flags & WFL_FIRERELEASED )
+	{
+		if( bs->flags & BFL_ATTACKED )
+		{
+			//trap_EA_Attack(bs->client);
+			bs->botinput.actionflags |= ACTION_ATTACK;
+		}
+	}
+	else
+	{
+		//trap_EA_Attack(bs->client);
+		bs->botinput.actionflags |= ACTION_ATTACK;
+	}
+	bs->flags ^= BFL_ATTACKED;
+}
diff --git a/src/mpgame/bots/BotAI_Battle_Attacked.cpp b/src/mpgame/bots/BotAI_Battle_Attacked.cpp
new file mode 100644
index 0000000..15c117b
--- /dev/null
+++ b/src/mpgame/bots/BotAI_Battle_Attacked.cpp
@@ -0,0 +1,51 @@
+// BotAI_Battle_Attacked.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+==================
+rvmBot::state_Attacked
+==================
+*/
+stateResult_t rvmBot::state_Attacked(const stateParms_t& parms) {
+	// respawn if dead.
+	if (BotIsDead(&bs))
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	if (gameLocal.SysScriptTime() > bs.aggressiveAttackTime || bs.weaponnum == 0) {
+		stateThread.SetState("state_Retreat");
+		return SRESULT_DONE;
+	}
+
+	// Ensure the target is a player.
+	idPlayer *entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
+	if (!entinfo)
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	// If our enemy is dead, search for another LTG.
+	if (EntityIsDead(entinfo))
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	bs.currentGoal.origin = bs.lastenemyorigin;
+	bs.chase_time = Bot_Time();
+
+	//aim at the enemy
+	BotAimAtEnemy(&bs);
+
+	//attack the enemy if possible
+	BotCheckAttack(&bs);
+
+	return SRESULT_WAIT;
+}
diff --git a/src/mpgame/bots/BotAI_Battle_Chase.cpp b/src/mpgame/bots/BotAI_Battle_Chase.cpp
new file mode 100644
index 0000000..b7ad10f
--- /dev/null
+++ b/src/mpgame/bots/BotAI_Battle_Chase.cpp
@@ -0,0 +1,208 @@
+// BotAI_Battle_Chase.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+
+/*
+=====================
+rvmBot::state_Chase
+=====================
+*/
+stateResult_t rvmBot::state_Chase(const stateParms_t& parms)
+{
+	bot_goal_t goal;
+	idVec3 target, dir;
+	//bot_moveresult_t moveresult;
+	float range;
+
+	//if (BotIsObserver(bs)) {
+	//	AIEnter_Observer(bs, "battle chase: observer");
+	//	return qfalse;
+	//}
+	//
+	////if in the intermission
+	//if (BotIntermission(bs)) {
+	//	AIEnter_Intermission(bs, "battle chase: intermission");
+	//	return qfalse;
+	//}
+
+	// respawn if dead.
+	if( BotIsDead( &bs ) )
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	//if no enemy
+	if( bs.enemy < 0 )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	//if the enemy is visible
+	if( BotEntityVisibleTest( bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy, false ) )
+	{
+		//AIEnter_Battle_Fight(bs, "battle chase");
+		stateThread.SetState("state_BattleFight");
+		return SRESULT_DONE;
+	}
+
+	//if there is another enemy
+	if( BotFindEnemy( &bs, -1 ) )
+	{
+		//AIEnter_Battle_Fight(bs, "battle chase: better enemy");
+		//stateThread.SetState("state_BattleFight");
+		stateThread.SetState("state_BattleFight");
+		return SRESULT_DONE;
+	}
+	////there is no last enemy area
+	//if (!bs.lastenemyareanum) {
+	//	AIEnter_Seek_LTG(bs, "battle chase: no enemy area");
+	//	return qfalse;
+	//}
+// jmarshall
+	//
+	//bs.tfl = TFL_DEFAULT;
+	//if (bot_grapple.integer) bs.tfl |= TFL_GRAPPLEHOOK;
+	////if in lava or slime the bot should be able to get out
+	//if (BotInLavaOrSlime(bs)) bs.tfl |= TFL_LAVA | TFL_SLIME;
+	////
+	//if (BotCanAndWantsToRocketJump(bs)) {
+	//	bs.tfl |= TFL_ROCKETJUMP;
+	//}
+	//map specific code
+	//BotMapScripts(bs);
+// jmarshall end
+
+	//create the chase goal
+	goal.entitynum = bs.enemy;
+//	goal.areanum = bs.lastenemyareanum;
+	VectorCopy( bs.lastenemyorigin, goal.origin );
+
+	goal.mins = idMath::CreateVector( -8, -8, -8 );
+	goal.maxs = idMath::CreateVector( 8, 8, 8 );
+
+	// jmarshall - goal origin is last visible position
+	{
+		// Do a trace between the last_enemy_visible_position and the goal origin,
+		// if for some reason we don't have line of sight to it, switch to LTG.
+		trace_t trace;
+
+		gameLocal.Trace( trace, bs.last_enemy_visible_position, gameLocal.entities[bs.client]->GetOrigin(), CONTENTS_SOLID, 0 );
+
+		if( trace.fraction <= 0.9f )
+		{
+			//AIEnter_Seek_LTG(bs, "can't see last enemy position");
+			stateThread.SetState("state_SeekLTG");
+			return SRESULT_DONE;
+		}
+	}
+	VectorCopy( bs.last_enemy_visible_position, goal.origin );
+	// jmarshall end
+
+	//if the last seen enemy spot is reached the enemy could not be found
+	if( botGoalManager.BotTouchingGoal( bs.origin, &goal ) )
+	{
+		bs.chase_time = 0;
+	}
+
+	//if there's no chase time left
+	if( !bs.chase_time || bs.chase_time < Bot_Time() - 10 )
+	{
+		//AIEnter_Seek_LTG(bs, "battle chase: time out");
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+	//check for nearby goals periodicly
+	if( bs.check_time < Bot_Time() )
+	{
+		bs.check_time = Bot_Time() + 1;
+		range = 150;
+		//
+		if( BotNearbyGoal( &bs, 0, &goal, range ) )
+		{
+			//the bot gets 5 seconds to pick up the nearby goal item
+			bs.nbg_time = Bot_Time() + 0.1 * range + 1;
+			//BotResetLastAvoidReach(bs.ms);
+			//AIEnter_Battle_NBG(bs, "battle chase: nbg");
+			stateThread.SetState("state_BattleNBG");
+			return SRESULT_DONE;
+		}
+	}
+
+	BotUpdateBattleInventory( &bs, bs.enemy );
+
+	////initialize the movement state
+	//BotSetupForMovement(bs);
+
+	//move towards the goal
+	//trap_BotMoveToGoal(&moveresult, bs.ms, &goal, bs.tfl);
+
+	if( idMath::FRandRange( 0.0f, 4.0f ) <= 2.0f )
+	{
+		BotMoveToGoal( &bs, &goal );
+	}
+	else
+	{
+		BotMoveInRandomDirection( &bs );
+	}
+
+
+	//if the movement failed
+	//if (moveresult.failure) {
+	//	//reset the avoid reach, otherwise bot is stuck in current area
+	//	trap_BotResetAvoidReach(bs.ms);
+	//	//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+	//	bs.ltg_time = 0;
+	//}
+	////
+	//BotAIBlocked(bs, &moveresult, qfalse);
+	//
+	//if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
+	//	VectorCopy(moveresult.ideal_viewangles, bs.ideal_viewangles);
+	//}
+
+	if( !( bs.flags & BFL_IDEALVIEWSET ) )
+	{
+		BotAimAtEnemy( &bs );
+		// jmarshall
+		//if (bs.chase_time > FloatTime() - 2) {
+		//	BotAimAtEnemy(bs);
+		//}
+		//else {
+		//	if (BotMovementViewTarget(bs.ms, &goal, bs.tfl, 300, target)) {
+		//		VectorSubtract(target, bs.origin, dir);
+		//		vectoangles(dir, bs.viewangles);
+		//	}
+		//	//else {
+		//	//	vectoangles(moveresult.movedir, bs.ideal_viewangles);
+		//	//}
+		//}
+		// jmarshall end
+//		bs.ideal_viewangles[2] *= 0.5; // jmarshall <-- view angles!
+	}
+
+	if (BotEntityVisible(bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy))
+	{
+		//attack the enemy if possible
+		BotCheckAttack(&bs);
+	}
+
+	//if the weapon is used for the bot movement
+	//if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs.weaponnum = moveresult.weapon;
+	//if the bot is in the area the enemy was last seen in
+	//if (bs.areanum == bs.lastenemyareanum) bs.chase_time = 0;
+	//if the bot wants to retreat (the bot could have been damage during the chase)
+	if( BotWantsToRetreat( &bs ) )
+	{
+		//AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat");
+		stateThread.SetState("state_Retreat");
+		return SRESULT_DONE;
+	}
+
+	return SRESULT_WAIT;
+}
\ No newline at end of file
diff --git a/src/mpgame/bots/BotAI_Battle_Fight.cpp b/src/mpgame/bots/BotAI_Battle_Fight.cpp
new file mode 100644
index 0000000..719de68
--- /dev/null
+++ b/src/mpgame/bots/BotAI_Battle_Fight.cpp
@@ -0,0 +1,170 @@
+// BotAI_Battle_Fight.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+=====================
+rvmBot::state_BattleFight
+=====================
+*/
+stateResult_t rvmBot::state_BattleFight(const stateParms_t& parms)
+{
+	idVec3 target;
+	idPlayer* entinfo;
+
+	// respawn if dead.
+	if( BotIsDead( &bs ) )
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	//if there is another better enemy
+	if( BotFindEnemy( &bs, bs.enemy ) )
+	{
+		common->DPrintf( "found new better enemy\n" );
+	}
+
+	//if no enemy
+	if( bs.enemy < 0 )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	//BotEntityInfo(bs.enemy, &entinfo);
+	entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();// &g_entities[bs.enemy];
+
+	//if the enemy is dead
+	if( bs.enemydeath_time )
+	{
+		if( bs.enemydeath_time < Bot_Time() - 1.0 )
+		{
+			bs.enemydeath_time = 0;
+			if( bs.enemysuicide )
+			{
+				// jmarshall - bot chat
+				//BotChat_EnemySuicide(bs);
+				// jmarshall end
+			}
+			// jmarshall - bot chat stand
+			//			if (bs.lastkilledplayer == bs.enemy && BotChat_Kill(bs)) {
+			//				bs.stand_time = FloatTime() + BotChatTime(bs);
+			//				AIEnter_Stand(bs, "battle fight: enemy dead");
+			//			}
+			//			else {
+			bs.ltg_time = 0;
+			//AIEnter_Seek_LTG(bs, "battle fight: enemy dead");
+			stateThread.SetState("state_SeekLTG");
+			//			}
+			// jmarshall end
+			return SRESULT_DONE;
+		}
+	}
+	else
+	{
+		if( EntityIsDead( entinfo ) )
+		{
+			bs.enemydeath_time = Bot_Time();
+		}
+	}
+// jmarshall - isinvisible
+	//if the enemy is invisible and not shooting the bot looses track easily
+	//if (entinfo->IsInvisible() && !entinfo->IsShooting()) {
+	//	if (rvmBotUtil::random() < 0.2) {
+	//		stateThread.SetState("state_SeekLTG");
+	//		return;
+	//	}
+	//}
+	//
+	target = entinfo->GetOrigin();
+	bs.lastenemyorigin = target;
+	bs.last_enemy_visible_position = target;
+	bs.chase_time = Bot_Time();
+
+	//update the reachability area and origin if possible
+	//areanum = BotPointAreaNum(target);
+	//if (areanum && trap_AAS_AreaReachability(areanum)) {
+	//	VectorCopy(target, bs.lastenemyorigin);
+	//	bs.lastenemyareanum = areanum;
+	//}
+//	bs.lastenemyareanum = areanum;
+
+	//update the attack inventory values
+	BotUpdateBattleInventory( &bs, bs.enemy );
+
+	//if the bot's health decreased
+// jmarshall - bot chat
+	//if (bs.lastframe_health > bs.inventory[INVENTORY_HEALTH]) {
+	//	if (BotChat_HitNoDeath(bs)) {
+	//		bs.stand_time = FloatTime() + BotChatTime(bs);
+	//		AIEnter_Stand(bs, "battle fight: chat health decreased");
+	//		return qfalse;
+	//	}
+	//}
+
+	//if the bot hit someone
+	//if (bs.cur_ps.persistant[PERS_HITS] > bs.lasthitcount) {
+	//	if (BotChat_HitNoKill(bs)) {
+	//		bs.stand_time = FloatTime() + BotChatTime(bs);
+	//		AIEnter_Stand(bs, "battle fight: chat hit someone");
+	//		return qfalse;
+	//	}
+	//}
+// jmarshall end
+
+	//if the enemy is not visible
+	if( !BotEntityVisible( bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy ) )
+	{
+		if( BotWantsToChase( &bs ) )
+		{
+			//AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight");
+			stateThread.SetState("state_Chase");
+			return SRESULT_DONE;
+		}
+		else
+		{
+			//AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight");
+			stateThread.SetState("state_SeekLTG");
+			return SRESULT_DONE;
+		}
+	}
+	//use holdable items
+	BotBattleUseItems( &bs );
+	//
+	//bs.tfl = TFL_DEFAULT;
+	//if (bot_grapple.integer) bs.tfl |= TFL_GRAPPLEHOOK;
+	////if in lava or slime the bot should be able to get out
+	//if (BotInLavaOrSlime(bs)) bs.tfl |= TFL_LAVA | TFL_SLIME;
+	////
+	//if (BotCanAndWantsToRocketJump(bs)) {
+	//	bs.tfl |= TFL_ROCKETJUMP;
+	//}
+	//choose the best weapon to fight with
+	BotChooseWeapon( &bs );
+
+	// Move randomly around our AAS area.
+	BotMoveInRandomDirection(&bs);
+
+	//aim at the enemy
+	BotAimAtEnemy( &bs );
+
+	//attack the enemy if possible
+	BotCheckAttack( &bs );
+
+	//if the bot wants to retreat
+	if( !( bs.flags & BFL_FIGHTSUICIDAL ) )
+	{
+		if( BotWantsToRetreat( &bs ) )
+		{
+			//AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat");
+			stateThread.SetState("state_Retreat");
+			return SRESULT_DONE;
+		}
+	}
+
+	return SRESULT_WAIT;
+}
diff --git a/src/mpgame/bots/BotAI_Battle_NBG.cpp b/src/mpgame/bots/BotAI_Battle_NBG.cpp
new file mode 100644
index 0000000..e825a3a
--- /dev/null
+++ b/src/mpgame/bots/BotAI_Battle_NBG.cpp
@@ -0,0 +1,180 @@
+// BotAI_Battle_NBG.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+=====================
+rvmBot::state_BattleNBG
+=====================
+*/
+stateResult_t rvmBot::state_BattleNBG(const stateParms_t& parms)
+{
+	int areanum;
+	bot_goal_t goal;
+	//aas_entityinfo_t entinfo;
+	idEntity* entinfo;
+	//bot_moveresult_t moveresult;
+	float attack_skill;
+	idVec3 target, dir;
+
+	//if (BotIsObserver(bs)) {
+	//	AIEnter_Observer(bs, "battle nbg: observer");
+	//	return qfalse;
+	//}
+	////if in the intermission
+	//if (BotIntermission(bs)) {
+	//	AIEnter_Intermission(bs, "battle nbg: intermission");
+	//	return qfalse;
+	//}
+
+	// respawn if dead.
+	if( BotIsDead( &bs ) )
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	// if no enemy.
+	if( bs.enemy < 0 )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	//BotEntityInfo(bs.enemy, &entinfo);
+	entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
+	if( entinfo->health <= 0 )
+	{
+		//AIEnter_Seek_NBG(bs, "battle nbg: enemy dead");
+		stateThread.SetState("state_SeekNBG");
+		return SRESULT_DONE;
+	}
+
+	//bs.tfl = TFL_DEFAULT;
+	//if (bot_grapple.integer) bs.tfl |= TFL_GRAPPLEHOOK;
+	////if in lava or slime the bot should be able to get out
+	//if (BotInLavaOrSlime(bs)) bs.tfl |= TFL_LAVA | TFL_SLIME;
+	////
+	//if (BotCanAndWantsToRocketJump(bs)) {
+	//	bs.tfl |= TFL_ROCKETJUMP;
+	//}
+	////map specific code
+	//BotMapScripts(bs);
+
+	//update the last time the enemy was visible
+	if( BotEntityVisible( bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy ) )
+	{
+		bs.enemyvisible_time = Bot_Time();
+		//VectorCopy(entinfo->GetOrigin(), target);
+		target = entinfo->GetOrigin();
+		bs.last_enemy_visible_position = target;
+		bs.chase_time = Bot_Time();
+		// if not a player enemy
+		if( bs.enemy >= MAX_CLIENTS )
+		{
+#ifdef MISSIONPACK
+			// if attacking an obelisk
+			if( bs.enemy == redobelisk.entitynum ||
+					bs.enemy == blueobelisk.entitynum )
+			{
+				target[2] += 16;
+			}
+#endif
+		}
+		//update the reachability area and origin if possible
+		//areanum = BotPointAreaNum(target);
+		//if (areanum && trap_AAS_AreaReachability(areanum)) {
+		VectorCopy( target, bs.lastenemyorigin );
+		//	bs.lastenemyareanum = areanum;
+		//}
+	}
+
+	//if the bot has no goal or touches the current goal
+	if( !botGoalManager.BotGetTopGoal( bs.gs, &goal ) )
+	{
+		bs.nbg_time = 0;
+	}
+	else if( BotReachedGoal( &bs, &goal ) )
+	{
+		bs.nbg_time = 0;
+	}
+	//
+	if( bs.nbg_time < Bot_Time() )
+	{
+		//pop the current goal from the stack
+		botGoalManager.BotPopGoal( bs.gs );
+		//if the bot still has a goal
+		if( botGoalManager.BotGetTopGoal( bs.gs, &goal ) )
+		{
+			//AIEnter_Battle_Retreat(bs, "battle nbg: time out");
+			stateThread.SetState("state_Retreat");
+		}
+		else
+		{
+			//AIEnter_Battle_Fight(bs, "battle nbg: time out");
+			stateThread.SetState("state_BattleFight");
+		}
+
+		return SRESULT_DONE;
+	}
+
+	//move towards the goal
+	BotMoveToGoal( &bs, &goal );
+
+	//initialize the movement state
+	//BotSetupForMovement(bs);
+	////move towards the goal
+	//trap_BotMoveToGoal(&moveresult, bs.ms, &goal, bs.tfl);
+	////if the movement failed
+	//if (moveresult.failure) {
+	//	//reset the avoid reach, otherwise bot is stuck in current area
+	//	trap_BotResetAvoidReach(bs.ms);
+	//	//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
+	//	bs.nbg_time = 0;
+	//}
+	////
+	//BotAIBlocked(bs, &moveresult, qfalse);
+
+	//update the attack inventory values
+	BotUpdateBattleInventory( &bs, bs.enemy );
+
+	//choose the best weapon to fight with
+	BotChooseWeapon( &bs );
+
+	//if the view is fixed for the movement
+	//if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
+	//	VectorCopy(moveresult.ideal_viewangles, bs.ideal_viewangles);
+	//}
+	//else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
+	//	&& !(bs.flags & BFL_IDEALVIEWSET)) {
+	//	attack_skill = trap_Characteristic_BFloat(bs.character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
+	//	//if the bot is skilled anough and the enemy is visible
+	//	if (attack_skill > 0.3) {
+	//		//&& BotEntityVisible(bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy)
+	//		BotAimAtEnemy(bs);
+	//	}
+	//	else {
+	//		if (trap_BotMovementViewTarget(bs.ms, &goal, bs.tfl, 300, target)) {
+	//			VectorSubtract(target, bs.origin, dir);
+	//			vectoangles(dir, bs.ideal_viewangles);
+	//		}
+	//		else {
+	//			vectoangles(moveresult.movedir, bs.ideal_viewangles);
+	//		}
+	//		bs.ideal_viewangles[2] *= 0.5;
+	//	}
+	//}
+	//if (attack_skill > 0.3) {
+	//&& BotEntityVisible(bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy)
+	BotAimAtEnemy( &bs );
+	//}
+
+	//if the weapon is used for the bot movement
+	//if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs.weaponnum = moveresult.weapon;
+	//attack the enemy if possible
+	BotCheckAttack( &bs );
+	return SRESULT_WAIT;
+}
diff --git a/src/mpgame/bots/BotAI_Battle_Retreat.cpp b/src/mpgame/bots/BotAI_Battle_Retreat.cpp
new file mode 100644
index 0000000..eaaf1ff
--- /dev/null
+++ b/src/mpgame/bots/BotAI_Battle_Retreat.cpp
@@ -0,0 +1,146 @@
+// BotAI_Battle_Retreat.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+=====================
+rvmBot::state_Retreat
+=====================
+*/
+stateResult_t rvmBot::state_Retreat(const stateParms_t& parms)
+{
+	bot_goal_t goal;
+	idPlayer* entinfo;
+	rvmBot* owner;
+	idVec3 target, dir;
+	float attack_skill, range;
+
+	// respawn if dead.
+	if( BotIsDead( &bs ) )
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	// if no enemy.
+	if( bs.enemy < 0 )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	// Ensure the target is a player.
+	entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
+	if( !entinfo )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	owner = gameLocal.entities[bs.entitynum]->Cast<rvmBot>();
+
+	// If our enemy is dead, search for another LTG.
+	if( EntityIsDead( entinfo ) )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	//if there is another better enemy
+	if( BotFindEnemy( &bs, bs.enemy ) )
+	{
+		common->DPrintf( "found new better enemy\n" );
+	}
+
+	//update the attack inventory values
+	BotUpdateBattleInventory( &bs, bs.enemy );
+
+	//if the bot doesn't want to retreat anymore... probably picked up some nice items
+	if( BotWantsToChase( &bs ) )
+	{
+		//empty the goal stack, when chasing, only the enemy is the goal
+		botGoalManager.BotEmptyGoalStack( bs.gs );
+
+		//go chase the enemy
+		//AIEnter_Battle_Chase(bs, "battle retreat: wants to chase");
+		stateThread.SetState("state_Chase");
+		return SRESULT_DONE;
+	}
+
+	//update the last time the enemy was visible
+	if( BotEntityVisible( bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy ) )
+	{
+		bs.enemyvisible_time = Bot_Time();
+		target = entinfo->GetOrigin();
+		bs.lastenemyorigin = target;
+		bs.last_enemy_visible_position = target;
+		bs.chase_time = Bot_Time();
+	}
+
+	//if the enemy is NOT visible for 4 seconds
+	if( bs.enemyvisible_time < Bot_Time() - 4 )
+	{
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+	//else if the enemy is NOT visible
+	else if( bs.enemyvisible_time < Bot_Time() )
+	{
+		//if there is another enemy
+		if( BotFindEnemy( &bs, -1 ) )
+		{
+			//AIEnter_Battle_Fight(bs, "battle retreat: another enemy");
+			stateThread.SetState("state_BattleFight");
+			return SRESULT_DONE;
+		}
+	}
+
+	//use holdable items
+	BotBattleUseItems( &bs );
+
+	//get the current long term goal while retreating
+	if( !BotGetItemLongTermGoal( &bs, 0, &bs.currentGoal ) )
+	{
+		//AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out");
+		stateThread.SetState("state_BattleFight");
+		bs.flags |= BFL_FIGHTSUICIDAL;
+		return SRESULT_DONE;
+	}
+
+	//check for nearby goals periodicly
+	if( bs.check_time < Bot_Time() )
+	{
+		bs.check_time = Bot_Time() + 1;
+		range = 150;
+
+		//
+		if( BotNearbyGoal( &bs, 0, &goal, range ) )
+		{
+			//trap_BotResetLastAvoidReach(bs.ms);
+			//time the bot gets to pick up the nearby goal item
+			bs.nbg_time = Bot_Time() + range / 100 + 1;
+			//AIEnter_Battle_NBG(bs, "battle retreat: nbg");
+			stateThread.SetState("state_BattleNBG");
+			return SRESULT_DONE;
+		}
+	}
+
+	MoveToCoverPoint();
+
+	if (bot_skill.GetInteger() > 1)
+	{
+		bs.firethrottlewait_time = 0;
+	}
+
+	BotChooseWeapon( &bs );
+
+	BotAimAtEnemy( &bs );
+
+	//attack the enemy if possible
+	BotCheckAttack( &bs );
+
+	return SRESULT_WAIT;
+}
diff --git a/src/mpgame/bots/BotAI_SeekLTG.cpp b/src/mpgame/bots/BotAI_SeekLTG.cpp
new file mode 100644
index 0000000..740cef1
--- /dev/null
+++ b/src/mpgame/bots/BotAI_SeekLTG.cpp
@@ -0,0 +1,49 @@
+// BotAI_SeekLTG.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+=====================
+rvmBot::state_SeekLTG
+=====================
+*/
+stateResult_t rvmBot::state_SeekLTG(const stateParms_t& parms)
+{
+	if( BotIsDead( &bs ) )
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	BotGetItemLongTermGoal( &bs, 0, &bs.currentGoal );
+
+	// No Enemy.
+	bs.enemy = -1;
+
+	//if there is an enemy
+	if( BotFindEnemy( &bs, -1 ) )
+	{
+		if( BotWantsToRetreat( &bs ) )
+		{
+			//keep the current long term goal and retreat
+			//AIEnter_Battle_Retreat(bs, "seek ltg: found enemy");
+			stateThread.SetState("state_Retreat");
+			return SRESULT_DONE;
+		}
+		else
+		{
+			//trap_BotResetLastAvoidReach(bs.ms);
+			//empty the goal stack
+			botGoalManager.BotEmptyGoalStack( bs.gs );
+
+			//go fight
+			stateThread.SetState("state_BattleFight");
+			return SRESULT_DONE;
+		}
+	}
+
+	return SRESULT_WAIT;
+}
diff --git a/src/mpgame/bots/BotAI_Seek_NBG.cpp b/src/mpgame/bots/BotAI_Seek_NBG.cpp
new file mode 100644
index 0000000..5f57b41
--- /dev/null
+++ b/src/mpgame/bots/BotAI_Seek_NBG.cpp
@@ -0,0 +1,142 @@
+// BotAI_Seek_NBG.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+=====================
+rvmBot::state_SeekNBG
+=====================
+*/
+stateResult_t rvmBot::state_SeekNBG(const stateParms_t& parms)
+{
+	bot_goal_t goal;
+	idVec3 target, dir;
+	//bot_moveresult_t moveresult;
+
+	//if (BotIsObserver(bs)) {
+	//	AIEnter_Observer(bs, "seek nbg: observer");
+	//	return qfalse;
+	//}
+	////if in the intermission
+	//if (BotIntermission(bs)) {
+	//	AIEnter_Intermission(bs, "seek nbg: intermision");
+	//	return qfalse;
+	//}
+
+	// respawn if dead.
+	if( BotIsDead( &bs ) )
+	{
+		stateThread.SetState("state_Respawn");
+		return SRESULT_DONE;
+	}
+
+	//bs.tfl = TFL_DEFAULT;
+	//if (bot_grapple.integer) bs.tfl |= TFL_GRAPPLEHOOK;
+	////if in lava or slime the bot should be able to get out
+	//if (BotInLavaOrSlime(bs)) bs.tfl |= TFL_LAVA | TFL_SLIME;
+	////
+	//if (BotCanAndWantsToRocketJump(bs)) {
+	//	bs.tfl |= TFL_ROCKETJUMP;
+	//}
+	////map specific code
+	//BotMapScripts(bs);
+	//no enemy
+	bs.enemy = -1;
+	//if the bot has no goal
+	if( !botGoalManager.BotGetTopGoal( bs.gs, &goal ) )
+	{
+		bs.nbg_time = 0;
+	}
+	//if the bot touches the current goal
+	else if( BotReachedGoal( &bs, &goal ) )
+	{
+		BotChooseWeapon( &bs );
+		bs.nbg_time = 0;
+	}
+
+	if( bs.nbg_time < Bot_Time() )
+	{
+		//pop the current goal from the stack
+		botGoalManager.BotPopGoal( bs.gs );
+		//check for new nearby items right away
+		//NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches
+		bs.check_time = Bot_Time() + 0.05;
+		//go back to seek ltg
+//		AIEnter_Seek_LTG(bs, "seek nbg: time out");
+		stateThread.SetState("state_SeekLTG");
+		return SRESULT_DONE;
+	}
+
+	//predict obstacles
+	//if (BotAIPredictObstacles(bs, &goal))
+	//	return qfalse;
+	////initialize the movement state
+	//BotSetupForMovement(bs);
+	////move towards the goal
+	//trap_BotMoveToGoal(&moveresult, bs.ms, &goal, bs.tfl);
+	////if the movement failed
+	//if (moveresult.failure) {
+	//	//reset the avoid reach, otherwise bot is stuck in current area
+	//	trap_BotResetAvoidReach(bs.ms);
+	//	bs.nbg_time = 0;
+	//}
+	BotMoveToGoal( &bs, &goal );
+
+	//check if the bot is blocked
+	//BotAIBlocked(bs, &moveresult, qtrue);
+	////
+	//BotClearPath(bs, &moveresult);
+
+// jmarshall - fix look at code.
+	//if the viewangles are used for the movement
+	//if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW)) {
+	//	VectorCopy(moveresult.ideal_viewangles, bs.ideal_viewangles);
+	//}
+	////if waiting for something
+	//else if (moveresult.flags & MOVERESULT_WAITING) {
+	//	if (random() < bs.thinktime * 0.8) {
+	//		BotRoamGoal(bs, target);
+	//		VectorSubtract(target, bs.origin, dir);
+	//		vectoangles(dir, bs.ideal_viewangles);
+	//		bs.ideal_viewangles[2] *= 0.5;
+	//	}
+	//}
+	//else if (!(bs.flags & BFL_IDEALVIEWSET)) {
+	//	if (!trap_BotGetSecondGoal(bs.gs, &goal)) trap_BotGetTopGoal(bs.gs, &goal);
+	//	if (trap_BotMovementViewTarget(bs.ms, &goal, bs.tfl, 300, target)) {
+	//		VectorSubtract(target, bs.origin, dir);
+	//		vectoangles(dir, bs.ideal_viewangles);
+	//	}
+	//	//FIXME: look at cluster portals?
+	//	else vectoangles(moveresult.movedir, bs.ideal_viewangles);
+	//	bs.ideal_viewangles[2] *= 0.5;
+	//}
+	////if the weapon is used for the bot movement
+	//if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs.weaponnum = moveresult.weapon;
+// jmarshall end
+	//if there is an enemy
+	if( BotFindEnemy( &bs, -1 ) )
+	{
+		if( BotWantsToRetreat( &bs ) )
+		{
+			//keep the current long term goal and retreat
+			//AIEnter_Battle_NBG(bs, "seek nbg: found enemy");
+			stateThread.SetState("state_BattleNBG");
+			return SRESULT_DONE;
+		}
+		else
+		{
+			//trap_BotResetLastAvoidReach(bs.ms);
+			//empty the goal stack
+			botGoalManager.BotEmptyGoalStack( bs.gs );
+			//go fight
+			//AIEnter_Battle_Fight(bs, "seek nbg: found enemy");
+			stateThread.SetState("state_BattleFight");
+			return SRESULT_DONE;
+		}
+	}
+	return SRESULT_WAIT;
+}
\ No newline at end of file
diff --git a/src/mpgame/bots/BotAI_respawn.cpp b/src/mpgame/bots/BotAI_respawn.cpp
new file mode 100644
index 0000000..7719997
--- /dev/null
+++ b/src/mpgame/bots/BotAI_respawn.cpp
@@ -0,0 +1,33 @@
+// BotAI_respawn.cpp
+//
+
+
+
+#include "../Game_local.h"
+
+/*
+=====================
+rvmBot::state_Respawn
+=====================
+*/
+stateResult_t rvmBot::state_Respawn(const stateParms_t& parms)
+{
+	if (parms.stage == 0)
+	{
+		bs.botinput.respawn = true;
+		((stateParms_t &)parms).stage = 1;
+		return SRESULT_WAIT;
+	}
+
+	if (parms.stage == 1)
+	{
+		if (spectating)
+		{
+			return SRESULT_WAIT; // Wait until we have moved from spectator back into the game.
+		}
+	}
+
+	bs.botinput.respawn = false;
+	stateThread.SetState("state_SeekLTG");
+	return SRESULT_DONE;
+}
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_Input.cpp b/src/mpgame/bots/Bot_Input.cpp
new file mode 100644
index 0000000..a46b077
--- /dev/null
+++ b/src/mpgame/bots/Bot_Input.cpp
@@ -0,0 +1,165 @@
+// Bot_Input.cpp
+//
+
+
+
+
+#include "../Game_local.h"
+
+/*
+==============
+rvmBot::BotInputToUserCommand
+==============
+*/
+void rvmBot::BotInputToUserCommand(bot_input_t* bi, usercmd_t* ucmd, int time)
+{
+	idVec3 forward, right;
+
+	short temp;
+	int j;
+
+	//clear the whole structure
+//	memset(ucmd, 0, sizeof(usercmd_t));
+	//
+	//common->Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
+	//the duration for the user command in milli seconds
+	//
+	if (bi->actionflags & ACTION_DELAYEDJUMP)
+	{
+		bi->actionflags |= ACTION_JUMP;
+		bi->actionflags &= ~ACTION_DELAYEDJUMP;
+	}
+	//set the buttons
+	if (bi->actionflags & ACTION_RESPAWN)
+	{
+		ucmd->buttons = BUTTON_ATTACK;
+	}
+	if (bi->actionflags & ACTION_ATTACK)
+	{
+		ucmd->buttons |= BUTTON_ATTACK;
+	}
+	//if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
+	//if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
+	//if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
+	if (bi->actionflags & ACTION_WALK)
+	{
+		ucmd->buttons |= BUTTON_RUN;
+	}
+	//if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
+	//if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
+	//if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
+	//if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
+	//if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
+	//if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
+	//
+	ucmd->impulse |= bi->weapon;
+	if (bi->lastWeaponNum != bi->weapon)
+	{
+		//ucmd->flags = UCF_IMPULSE_SEQUENCE;
+		bi->lastWeaponNum = bi->weapon;
+	}
+	else
+	{
+		//ucmd->flags = 0;
+	}
+
+	idAngles botViewAngles = viewAngles;
+
+	{
+		int i;
+		float move;
+		float angMod = (1.0f / 12.0f);
+
+		for (i = 0; i < 3; i++) {
+			move = idMath::AngleDelta(bi->viewangles[i], botViewAngles[i]);
+			botViewAngles[i] += (move * angMod);
+		}
+	}
+
+	//set the view angles
+	//NOTE: the ucmd->angles are the angles WITHOUT the delta angles
+	ucmd->angles[0] = ANGLE2SHORT(botViewAngles[0] - deltaViewAngles[0]);
+	ucmd->angles[1] = ANGLE2SHORT(botViewAngles[1] - deltaViewAngles[1]);
+	ucmd->angles[2] = ANGLE2SHORT(botViewAngles[2] - deltaViewAngles[2]);
+
+	bi->viewangles.ToVectors(&forward, &right, NULL);
+
+	//bot input speed is in the range [0, 400]
+	bi->speed = bi->speed * 127 / 400;
+	//set the view independent movement
+	ucmd->forwardmove = idMath::ClampChar(DotProduct(forward, bi->dir) * bi->speed);
+	ucmd->rightmove = idMath::ClampChar(DotProduct(right, bi->dir) * bi->speed);
+	//ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
+
+	//normal keyboard movement
+	if (bi->actionflags & ACTION_MOVEFORWARD)
+	{
+		ucmd->forwardmove += 127;
+	}
+	if (bi->actionflags & ACTION_MOVEBACK)
+	{
+		ucmd->forwardmove -= 127;
+	}
+	if (bi->actionflags & ACTION_MOVELEFT)
+	{
+		ucmd->rightmove -= 127;
+	}
+	if (bi->actionflags & ACTION_MOVERIGHT)
+	{
+		ucmd->rightmove += 127;
+	}
+
+	//jump/moveup
+	//if (bi->actionflags & ACTION_JUMP)
+	//	ucmd->buttons |= BUTTON_JUMP;
+
+	//	ucmd->upmove += 127;
+	//
+	////crouch/movedown
+	//if (bi->actionflags & ACTION_CROUCH)
+	//	ucmd->upmove -= 127;
+	//
+	//Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
+	//Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
+
+	if( bi->respawn )
+	{
+		ucmd->buttons |= BUTTON_ATTACK;
+	}
+}
+
+/*
+================
+rvmBot::ResetUcmd
+================
+*/
+void rvmBot::Bot_ResetUcmd( usercmd_t& ucmd )
+{
+	ucmd.forwardmove = 0;
+	ucmd.rightmove = 0;
+	//ucmd.upmove = 0;
+	ucmd.impulse = 0;
+	//ucmd.flags = 0;
+	memset( &ucmd.buttons, 0, sizeof( ucmd.buttons ) );
+}
+
+
+/*
+========================
+rvmBot::BotInputFrame
+========================
+*/
+void rvmBot::BotInputFrame( void )
+{
+	usercmd_t& ucmd = (usercmd_t &)gameLocal.usercmds[entityNumber];
+
+	ucmd.gameTime = gameLocal.time;
+	ucmd.gameFrame = gameLocal.framenum;
+	ucmd.duplicateCount = 0;
+
+	Bot_ResetUcmd(ucmd);
+	BotInputToUserCommand( &bs.botinput, &ucmd, gameLocal.time );
+
+	// pass the bot's user cmds off to the engine, so that other clients can predict this bot
+	networkSystem->ServerSetBotUserCommand(entityNumber, gameLocal.framenum, ucmd);
+}
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_char.cpp b/src/mpgame/bots/Bot_char.cpp
new file mode 100644
index 0000000..5030f28
--- /dev/null
+++ b/src/mpgame/bots/Bot_char.cpp
@@ -0,0 +1,355 @@
+// Bot_char.cpp
+//
+
+
+#include "../Game_local.h"
+
+idBotCharacterStatsManager botCharacterStatsManager;
+
+/*
+====================
+idBotCharacterStatsManager::idBotCharacterStatsManager
+====================
+*/
+idBotCharacterStatsManager::idBotCharacterStatsManager()
+{
+	default_char_profile = nullptr;
+}
+
+/*
+====================
+idBotCharacterStatsManager::Init
+====================
+*/
+void idBotCharacterStatsManager::Init( void )
+{
+	default_char_profile = BotLoadCharacterFromFile( "bots/default_c.c", 1 );
+	if( default_char_profile == NULL )
+	{
+		common->FatalError( "Failed to load default characteristic def file\n" );
+	}
+}
+
+/*
+============================
+idBotCharacterStatsManager::AllocBotCharacter
+============================
+*/
+bot_character_t* idBotCharacterStatsManager::AllocBotCharacter( void )
+{
+	bot_character_t* ch = NULL;
+
+	for( int i = 0; i < MAX_CHAR_STATS; i++ )
+	{
+		if( charStatsList[i].inUse == false )
+		{
+			charStatsList[i].inUse = true;
+			ch = &charStatsList[i];
+			break;
+		}
+	}
+
+	if( ch == nullptr )
+	{
+		gameLocal.Error( "idBotCharacterStatsManager::AllocBotCharacter: Too many bot characters!" );
+		return nullptr;
+	}
+
+	if( default_char_profile == NULL )
+	{
+		memset( &ch->c[0], 0, MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) );
+		ch->filename = "";
+		ch->inUse = false;
+		ch->skill = 0;
+	}
+	else
+	{
+		//memcpy( ch, default_char_profile, sizeof( bot_character_t ) + MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) );
+		*ch = *default_char_profile;
+	}
+
+
+
+	return ch;
+}
+
+/*
+============================
+idBotCharacterStatsManager::FreeCharacterFile
+============================
+*/
+void idBotCharacterStatsManager::FreeCharacterFile( bot_character_t* ch )
+{
+	ch->inUse = false;
+}
+
+/*
+============================
+idBotCharacterStatsManager::BotLoadCharacterFromFile
+============================
+*/
+bot_character_t* idBotCharacterStatsManager::BotLoadCharacterFromFile( const char* charfile, int skill )
+{
+	int indent, index;
+	bool foundcharacter;
+	bot_character_t* ch;
+	idParser parser;
+	idToken token;
+
+	foundcharacter = false;
+
+	// Check to see if we already loaded this bot file.
+	for( int i = 0; i < MAX_CHAR_STATS; i++ )
+	{
+		if( charStatsList[i].inUse &&
+			charStatsList[i].filename == charfile &&
+			( skill < 0 || charStatsList[i].skill == skill ) )
+		{
+			return &charStatsList[i];
+		}
+	}
+
+	rvmScopedLexerBaseFolder scopedBaseFolder( BOTFILESBASEFOLDER );
+
+	//a bot character is parsed in two phases
+	if( !parser.LoadFile( charfile ) )
+	{
+		common->Warning( "BotLoadCharacterFromFile: counldn't load %s\n", charfile );
+		return NULL;
+	}
+
+	ch = AllocBotCharacter();
+	ch->filename = charfile;
+
+	while( parser.ReadToken( &token ) )
+	{
+		if( token == "skill" )
+		{
+			if( !parser.ExpectTokenType( TT_NUMBER, 0, &token ) )
+			{
+				FreeCharacterFile( ch );
+				parser.Warning( "Expected token type number\n" );
+				return NULL;
+			}
+
+			if( !parser.ExpectTokenString( "{" ) )
+			{
+				FreeCharacterFile( ch );
+				parser.Warning( "Expected token {\n" );
+				return NULL;
+			}
+
+			//if it's the correct skill
+			if( skill < 0 || token.GetIntValue() == skill )
+			{
+				foundcharacter = true;
+				ch->skill = token.GetIntValue();
+				while( parser.ReadToken( &token ) )
+				{
+					if( token == "}" )
+					{
+						break;
+					}
+
+					if( token.type != TT_NUMBER || !( token.subtype & TT_INTEGER ) )
+					{
+						FreeCharacterFile( ch );
+						parser.Error( "expected integer index, found %s\n", token.c_str() );
+						return NULL;
+					}
+
+					index = token.GetIntValue();
+					if( index < 0 || index > MAX_CHARACTERISTICS )
+					{
+						FreeCharacterFile( ch );
+						parser.Error( "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS );
+						return NULL;
+					}
+					// jmarshall - not sure what we loose by removing this check, basically duplicate definition check?
+					//if (ch->c[index].type)
+					//{
+					//	G_Error("characteristic %d already initialized\n", index);
+					//	trap_PC_FreeSource(source);
+					//	BotFreeCharacterStrings(ch);
+					//	//FreeMemory(ch);
+					//	return NULL;
+					//}
+					// jmarshall end
+
+					if( !parser.ReadToken( &token ) )
+					{
+						FreeCharacterFile( ch );
+						parser.Error( "Unexpected EOF during parse characesistic" );
+						return NULL;
+					}
+
+					if( token.type == TT_NUMBER )
+					{
+						if( token.subtype & TT_FLOAT )
+						{
+							ch->c[index].value._float = token.GetFloatValue();
+							ch->c[index].type = CT_FLOAT;
+						}
+						else
+						{
+							ch->c[index].value.integer = token.GetIntValue();
+							ch->c[index].type = CT_INTEGER;
+						}
+					}
+					else if( token.type == TT_STRING )
+					{
+						token.StripDoubleQuotes();
+						ch->c[index].value.string = token;
+						ch->c[index].type = CT_STRING;
+					}
+					else
+					{
+						FreeCharacterFile( ch );
+						gameLocal.Error( "expected integer, float or string, found %s\n", token.c_str() );
+						return NULL;
+					}
+				}
+				break;
+			}
+			else
+			{
+				indent = 1;
+				while( indent )
+				{
+					if( !parser.ReadToken( &token ) )
+					{
+						FreeCharacterFile( ch );
+						return NULL;
+					}
+					if( token == "{" )
+					{
+						indent++;
+					}
+					else if( token == "}" )
+					{
+						indent--;
+					}
+				}
+			}
+		}
+		else
+		{
+			FreeCharacterFile( ch );
+			gameLocal.Error( "unknown definition %s\n", token.c_str() );
+			return NULL;
+		}
+	}
+
+	if( !foundcharacter )
+	{
+		FreeCharacterFile( ch );
+		return NULL;
+	}
+
+
+	return ch;
+}
+
+
+/*
+============================
+idBotCharacterStatsManager::CheckCharacteristicIndex
+============================
+*/
+int idBotCharacterStatsManager::CheckCharacteristicIndex( bot_character_t* ch, int index )
+{
+	if( index < 0 || index >= MAX_CHARACTERISTICS )
+	{
+		gameLocal.Error( "characteristic %d does not exist\n", index );
+		return false;
+	}
+	if( !ch->c[index].type )
+	{
+		gameLocal.Error( "characteristic %d is not initialized\n", index );
+		return false;
+	}
+	return true;
+}
+
+/*
+============================
+idBotCharacterStatsManager::Characteristic_Float
+============================
+*/
+float idBotCharacterStatsManager::Characteristic_Float( bot_character_t* ch, int index )
+{
+	if( !ch )
+	{
+		return 0;
+	}
+	//check if the index is in range
+	if( !CheckCharacteristicIndex( ch, index ) )
+	{
+		return 0;
+	}
+
+	if( ch->c[index].type == CT_INTEGER )
+	{
+		//an integer will be converted to a float
+		return ( float )ch->c[index].value.integer;
+	}
+	else if( ch->c[index].type == CT_FLOAT )
+	{
+		//floats are just returned
+		return ch->c[index].value._float;
+	}
+
+	//cannot convert a string pointer to a float
+	gameLocal.Error( "characteristic %d is not a float\n", index );
+	return 0;
+}
+
+/*
+============================
+Characteristic_String
+============================
+*/
+void idBotCharacterStatsManager::Characteristic_String( bot_character_t* ch, int index, char* buf, int size )
+{
+	//check if the index is in range
+	if( !CheckCharacteristicIndex( ch, index ) )
+	{
+		return;
+	}
+
+	//an integer will be converted to a float
+	if( ch->c[index].type == CT_STRING )
+	{
+		strcpy( buf, ch->c[index].value.string );
+		buf[size - 1] = '\0';
+		return;
+	}
+	gameLocal.Error( "characteristic %d is not a string\n", index );
+}
+
+/*
+============================
+idBotCharacterStatsManager::Characteristic_BFloat
+============================
+*/
+float idBotCharacterStatsManager::Characteristic_BFloat( bot_character_t* ch, int index, float min, float max )
+{
+	float value;
+
+	if( min > max )
+	{
+		gameLocal.Error( "cannot bound characteristic %d between %f and %f\n", index, min, max );
+		return 0;
+	}
+
+	value = Characteristic_Float( ch, index );
+	if( value < min )
+	{
+		return min;
+	}
+	if( value > max )
+	{
+		return max;
+	}
+	return value;
+}
diff --git a/src/mpgame/bots/Bot_char.h b/src/mpgame/bots/Bot_char.h
new file mode 100644
index 0000000..e73f29b
--- /dev/null
+++ b/src/mpgame/bots/Bot_char.h
@@ -0,0 +1,153 @@
+// Bot_char.h
+//
+
+#define MAX_CHAR_STATS				256
+
+//========================================================
+//========================================================
+//name
+#define CHARACTERISTIC_NAME							0	//string
+//gender of the bot
+#define CHARACTERISTIC_GENDER						1	//string ("male", "female", "it")
+//attack skill
+// >  0.0 && <  0.2 = don't move
+// >  0.3 && <  1.0 = aim at enemy during retreat
+// >  0.0 && <  0.4 = only move forward/backward
+// >= 0.4 && <  1.0 = circle strafing
+// >  0.7 && <  1.0 = random strafe direction change
+#define CHARACTERISTIC_ATTACK_SKILL					2	//float [0, 1]
+//weapon weight file
+#define CHARACTERISTIC_WEAPONWEIGHTS				3	//string
+//view angle difference to angle change factor
+#define CHARACTERISTIC_VIEW_FACTOR					4	//float <0, 1]
+//maximum view angle change
+#define CHARACTERISTIC_VIEW_MAXCHANGE				5	//float [1, 360]
+//reaction time in seconds
+#define CHARACTERISTIC_REACTIONTIME					6	//float [0, 5]
+//accuracy when aiming
+#define CHARACTERISTIC_AIM_ACCURACY					7	//float [0, 1]
+//weapon specific aim accuracy
+#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN		8	//float [0, 1]
+#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN			9	//float [0, 1]
+#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER	10	//float [0, 1]
+#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER	11	//float [0, 1]
+#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING		12
+#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN		13	//float [0, 1]
+#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN			14
+#define CHARACTERISTIC_AIM_ACCURACY_BFG10K			15	//float [0, 1]
+//skill when aiming
+// >  0.0 && <  0.9 = aim is affected by enemy movement
+// >  0.4 && <= 0.8 = enemy linear leading
+// >  0.8 && <= 1.0 = enemy exact movement leading
+// >  0.5 && <= 1.0 = prediction shots when enemy is not visible
+// >  0.6 && <= 1.0 = splash damage by shooting nearby geometry
+#define CHARACTERISTIC_AIM_SKILL					16	//float [0, 1]
+//weapon specific aim skill
+#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER		17	//float [0, 1]
+#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER	18	//float [0, 1]
+#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN			19	//float [0, 1]
+#define CHARACTERISTIC_AIM_SKILL_BFG10K				20	//float [0, 1]
+//========================================================
+//chat
+//========================================================
+//file with chats
+#define CHARACTERISTIC_CHAT_FILE					21	//string
+//name of the chat character
+#define CHARACTERISTIC_CHAT_NAME					22	//string
+//characters per minute type speed
+#define CHARACTERISTIC_CHAT_CPM						23	//integer [1, 4000]
+//tendency to insult/praise
+#define CHARACTERISTIC_CHAT_INSULT					24	//float [0, 1]
+//tendency to chat misc
+#define CHARACTERISTIC_CHAT_MISC					25	//float [0, 1]
+//tendency to chat at start or end of level
+#define CHARACTERISTIC_CHAT_STARTENDLEVEL			26	//float [0, 1]
+//tendency to chat entering or exiting the game
+#define CHARACTERISTIC_CHAT_ENTEREXITGAME			27	//float [0, 1]
+//tendency to chat when killed someone
+#define CHARACTERISTIC_CHAT_KILL					28	//float [0, 1]
+//tendency to chat when died
+#define CHARACTERISTIC_CHAT_DEATH					29	//float [0, 1]
+//tendency to chat when enemy suicides
+#define CHARACTERISTIC_CHAT_ENEMYSUICIDE			30	//float [0, 1]
+//tendency to chat when hit while talking
+#define CHARACTERISTIC_CHAT_HITTALKING				31	//float [0, 1]
+//tendency to chat when bot was hit but didn't dye
+#define CHARACTERISTIC_CHAT_HITNODEATH				32	//float [0, 1]
+//tendency to chat when bot hit the enemy but enemy didn't dye
+#define CHARACTERISTIC_CHAT_HITNOKILL				33	//float [0, 1]
+//tendency to randomly chat
+#define CHARACTERISTIC_CHAT_RANDOM					34	//float [0, 1]
+//tendency to reply
+#define CHARACTERISTIC_CHAT_REPLY					35	//float [0, 1]
+//========================================================
+//movement
+//========================================================
+//tendency to crouch
+#define CHARACTERISTIC_CROUCHER						36	//float [0, 1]
+//tendency to jump
+#define CHARACTERISTIC_JUMPER						37	//float [0, 1]
+//tendency to walk
+#define CHARACTERISTIC_WALKER						48	//float [0, 1]
+//tendency to jump using a weapon
+#define CHARACTERISTIC_WEAPONJUMPING				38	//float [0, 1]
+//tendency to use the grapple hook when available
+#define CHARACTERISTIC_GRAPPLE_USER					39	//float [0, 1]	//use this!!
+//========================================================
+//goal
+//========================================================
+//item weight file
+#define CHARACTERISTIC_ITEMWEIGHTS					40	//string
+//the aggression of the bot
+#define CHARACTERISTIC_AGGRESSION					41	//float [0, 1]
+//the self preservation of the bot (rockets near walls etc.)
+#define CHARACTERISTIC_SELFPRESERVATION				42	//float [0, 1]
+//how likely the bot is to take revenge
+#define CHARACTERISTIC_VENGEFULNESS					43	//float [0, 1]	//use this!!
+//tendency to camp
+#define CHARACTERISTIC_CAMPER						44	//float [0, 1]
+//========================================================
+//========================================================
+//tendency to get easy frags
+#define CHARACTERISTIC_EASY_FRAGGER					45	//float [0, 1]
+//how alert the bot is (view distance)
+#define CHARACTERISTIC_ALERTNESS					46	//float [0, 1]
+//how much the bot fires it's weapon
+#define CHARACTERISTIC_FIRETHROTTLE					47	//float [0, 1]
+
+//
+// idBotCharacterStatsManager
+//
+class idBotCharacterStatsManager
+{
+public:
+	idBotCharacterStatsManager();
+
+	// Inits the stats manager.
+	void	Init( void );
+
+	// Loads a character file.
+	bot_character_t*				BotLoadCharacterFromFile( const char* charfile, int skill );
+
+	// Free character file.
+	void							FreeCharacterFile( bot_character_t* ch );
+
+	// Returns the default character stats profile.
+	bot_character_t*				GetDefaultCharProfile( void )
+	{
+		return default_char_profile;
+	}
+
+	int								CheckCharacteristicIndex( bot_character_t* ch, int index );
+	float							Characteristic_Float( bot_character_t* ch, int index );
+	void							Characteristic_String( bot_character_t* ch, int index, char* buf, int size );
+	float							Characteristic_BFloat( bot_character_t* ch, int index, float min, float max );
+private:
+	bot_character_t* AllocBotCharacter( void );
+
+	bot_character_t*				default_char_profile;
+
+	bot_character_t					charStatsList[MAX_CHAR_STATS];
+};
+
+extern idBotCharacterStatsManager botCharacterStatsManager;
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_chat.cpp b/src/mpgame/bots/Bot_chat.cpp
new file mode 100644
index 0000000..da27909
--- /dev/null
+++ b/src/mpgame/bots/Bot_chat.cpp
@@ -0,0 +1,54 @@
+// Bot_Chat.cpp
+//
+
+
+#include "../Game_local.h"
+
+const char *bot_kill_insult[] = {
+	"%s: Try aiming next time %s",
+	"%s: Trust me when I say this %s, you suck.",
+	"%s: Maybe you should go back to go cart racing %s?",
+	"%s: Your technique reminds me of a story %s, a very dull one."
+};
+
+const char* bot_death_insult[] = {
+	"%s: Mmmmmmm. Was it good for you too %s?",
+	"%s: Well I guess %s won the lottery.",
+	"%s: My mother fragged me once %s. Once.",
+	"%s: I'm gonna pull out your bowels %s",
+	"%s: Beginners luck %s... Again",
+	"%s: So %s... you're up to what ... 3 frags an hour?"
+};
+
+const char* bot_death_praise[] = {
+	"%s: %s not bad for an amateur.",
+	"%s: %s alright that was pretty good. Your still a dousche.",
+	"%s: That was definitely ... um ... pretty good %s",
+	"%s: I've seen better %s, but not many.",
+	"%s: Take a moment to reflect on your accomplishment %s",
+	"%s: Your pretty good for a dousche %s"
+};
+
+/*
+====================
+rvmBot::BotSendChatMessage
+====================
+*/
+void rvmBot::BotSendChatMessage(botChat_t chat, const char* targetName) {
+	switch (chat)
+	{
+		case KILL:
+			gameLocal.mpGame.AddChatLine(bot_kill_insult[rvRandom::irand(0, 3)], GetNetName(), targetName);
+			break;
+		case DEATH:
+			if (rvRandom::irand(0, 10) < 5)
+			{
+				gameLocal.mpGame.AddChatLine(bot_death_insult[rvRandom::irand(0, 5)], GetNetName(), targetName);
+			}
+			else
+			{
+				gameLocal.mpGame.AddChatLine(bot_death_praise[rvRandom::irand(0, 5)], GetNetName(), targetName);
+			}
+			break;
+	}
+}
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_chat.h b/src/mpgame/bots/Bot_chat.h
new file mode 100644
index 0000000..4a58488
--- /dev/null
+++ b/src/mpgame/bots/Bot_chat.h
@@ -0,0 +1,9 @@
+// Bot_chat.h
+//
+
+#pragma once
+
+enum botChat_t {
+	KILL = 0,
+	DEATH,
+};
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_goal.cpp b/src/mpgame/bots/Bot_goal.cpp
new file mode 100644
index 0000000..0ef0540
--- /dev/null
+++ b/src/mpgame/bots/Bot_goal.cpp
@@ -0,0 +1,2069 @@
+// Bot_goal.cpp
+//
+
+
+#include "../Game_local.h"
+
+idCVar bot_droppedweight( "bot_droppedweight", "1000", CVAR_CHEAT | CVAR_INTEGER, "" );
+idCVar bot_itemsfile( "bot_itemsfile", "items.c", CVAR_CHEAT, "" );
+
+idBotGoalManager botGoalManager;
+
+/*
+==================
+idBotGoalManager::idBotGoalManager
+==================
+*/
+idBotGoalManager::idBotGoalManager()
+{
+	freelevelitems = NULL;
+	levelitems = NULL;
+}
+
+/*
+==================
+idBotGoalManager::BotGoalStateFromHandle
+==================
+*/
+bot_goalstate_t* idBotGoalManager::BotGoalStateFromHandle( int handle )
+{
+	if( handle <= 0 || handle > MAX_CLIENTS )
+	{
+		gameLocal.Error( "goal state handle %d out of range\n", handle );
+		return NULL;
+	}
+
+	if( !botgoalstates[handle].InUse() )
+	{
+		gameLocal.Error( "invalid goal state %d\n", handle );
+		return NULL;
+	}
+
+	return &botgoalstates[handle];
+}
+
+/*
+==================
+idBotGoalManager::BotInterbreedGoalFuzzyLogic
+==================
+*/
+void idBotGoalManager::BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child )
+{
+	bot_goalstate_t* p1, * p2, * c;
+
+	p1 = BotGoalStateFromHandle( parent1 );
+	p2 = BotGoalStateFromHandle( parent2 );
+	c = BotGoalStateFromHandle( child );
+
+	botFuzzyWeightManager.InterbreedWeightConfigs( p1->itemweightconfig, p2->itemweightconfig, c->itemweightconfig );
+}
+
+/*
+========================
+idBotGoalManager::BotSaveGoalFuzzyLogic
+========================
+*/
+void idBotGoalManager::BotSaveGoalFuzzyLogic( int goalstate, char* filename )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+
+	//WriteWeightConfig(filename, gs->itemweightconfig);
+}
+
+/*
+========================
+idBotGoalManager::BotMutateGoalFuzzyLogic
+========================
+*/
+void idBotGoalManager::BotMutateGoalFuzzyLogic( int goalstate, float range )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+
+	botFuzzyWeightManager.EvolveWeightConfig( gs->itemweightconfig );
+}
+
+/*
+========================
+idBotGoalManager::ParseItemInfo
+========================
+*/
+void idBotGoalManager::ParseItemInfo( idParser& parser, iteminfo_t* itemInfo )
+{
+	idToken token;
+
+	parser.ExpectTokenString( "{" );
+
+	while( true )
+	{
+		if( !parser.ReadToken( &token ) )
+		{
+			parser.Error( "Unexpected end of file found while parsing weaponinfo!" );
+		}
+
+		if( token == "}" )
+		{
+			break;
+		}
+
+		if( token == "name" )
+		{
+			idToken valueToken;
+			if( !parser.ReadToken( &valueToken ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->name = valueToken;
+		}
+		else if( token == "model" )
+		{
+			idToken valueToken;
+			if( !parser.ReadToken( &valueToken ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->model = valueToken;
+		}
+		else if( token == "modelindex" )
+		{
+			idToken valueToken;
+			if( !parser.ReadToken( &valueToken ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->modelindex = valueToken.GetIntValue();
+		}
+		else if( token == "type" )
+		{
+			idToken valueToken;
+			if( !parser.ReadToken( &valueToken ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->type = valueToken.GetIntValue();
+		}
+		else if( token == "index" )
+		{
+			idToken valueToken;
+			if( !parser.ReadToken( &valueToken ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->index = valueToken.GetIntValue();
+		}
+		else if( token == "respawntime" )
+		{
+			idToken valueToken;
+			if( !parser.ReadToken( &valueToken ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->respawntime = valueToken.GetIntValue();
+		}
+		else if( token == "mins" )
+		{
+			float value[3];
+			if( !parser.Parse1DMatrix( 3, &value[0] ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->mins.Set( value[0], value[1], value[2] );
+		}
+		else if( token == "maxs" )
+		{
+			float value[3];
+			if( !parser.Parse1DMatrix( 3, &value[0] ) )
+			{
+				parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+			}
+			itemInfo->maxs.Set( value[0], value[1], value[2] );
+		}
+		else
+		{
+			parser.Error( "ParseProjectileInfo: Unexpected token %s\n", token.c_str() );
+		}
+	}
+}
+
+/*
+========================
+idBotGoalManager::LoadItemConfig
+========================
+*/
+itemconfig_t* idBotGoalManager::LoadItemConfig( char* filename )
+{
+	idToken token;
+	idStr path;
+	itemconfig_t ic;
+	iteminfo_t* ii;
+	idParser source;
+
+	path = filename;
+	rvmScopedLexerBaseFolder scopedBaseFolder( BOTFILESBASEFOLDER );
+
+	if( !source.LoadFile( path ) )
+	{
+		gameLocal.Error( "counldn't load %s\n", path.c_str() );
+		return NULL;
+	}
+
+	ic.Reset();
+
+	//parse the item config file
+	while( source.ReadToken( &token ) )
+	{
+		if( token == "iteminfo" )
+		{
+			if( ic.numiteminfo >= MAX_BOT_ITEM_INFO )
+			{
+				source.Error( "more than %d item info defined\n", MAX_BOT_ITEM_INFO );
+				return NULL;
+			}
+
+			ii = &ic.iteminfo[ic.numiteminfo];
+			if( !source.ExpectTokenType( TT_STRING, 0, &token ) )
+			{
+				return NULL;
+			}
+
+			token.StripDoubleQuotes();
+			ii->classname = token;
+
+			ParseItemInfo( source, ii );
+
+			ii->number = ic.numiteminfo;
+			ic.numiteminfo++;
+		}
+		else
+		{
+			gameLocal.Error( "unknown definition %s\n", token.c_str() );
+			return NULL;
+		}
+	}
+
+	if( !ic.numiteminfo )
+	{
+		common->Warning( "no item info loaded\n" );
+	}
+
+	common->DPrintf( "loaded %s\n", path.c_str() );
+	itemconfiglocal = ic;
+	itemconfig = &itemconfiglocal;
+
+	return itemconfig;
+}
+
+/*
+========================
+idBotGoalManager::ItemWeightIndex
+========================
+*/
+int* idBotGoalManager::ItemWeightIndex( weightconfig_t* iwc, itemconfig_t* ic )
+{
+	int* index, i;
+
+	//initialize item weight index
+	index = new int[ic->numiteminfo];
+
+	for( i = 0; i < ic->numiteminfo; i++ )
+	{
+		index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )ic->iteminfo[i].classname.c_str() );
+		if( index[i] < 0 )
+		{
+			common->Warning( "item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname.c_str() );
+		}
+	}
+	return index;
+}
+
+/*
+========================
+idBotGoalManager::InitLevelItemHeap
+========================
+*/
+void idBotGoalManager::InitLevelItemHeap( void )
+{
+	for( int i = 0; i < MAX_BOT_LEVEL_ITEMS - 1; i++ )
+	{
+		levelitemheap[i].next = &levelitemheap[i + 1];
+	}
+
+	levelitemheap[MAX_BOT_LEVEL_ITEMS - 1].next = NULL;
+
+	freelevelitems = levelitemheap;
+}
+
+/*
+========================
+idBotGoalManager::AllocLevelItem
+========================
+*/
+levelitem_t* idBotGoalManager::AllocLevelItem( void )
+{
+	levelitem_t* li;
+
+	li = freelevelitems;
+	if( !li )
+	{
+		gameLocal.Error( "out of level items\n" );
+		return NULL;
+	}
+
+	freelevelitems = freelevelitems->next;
+	//memset(li, 0, sizeof(levelitem_t));
+	li->Reset();
+	return li;
+}
+
+/*
+========================
+idBotGoalManager::FreeLevelItem
+========================
+*/
+void idBotGoalManager::FreeLevelItem( levelitem_t* li )
+{
+	li->next = freelevelitems;
+	freelevelitems = li;
+}
+
+/*
+========================
+idBotGoalManager::AddLevelItemToList
+========================
+*/
+void idBotGoalManager::AddLevelItemToList( levelitem_t* li )
+{
+	if( levelitems )
+	{
+		levelitems->prev = li;
+	}
+
+	li->prev = NULL;
+	li->next = levelitems;
+	levelitems = li;
+}
+
+/*
+========================
+idBotGoalManager::RemoveLevelItemFromList
+========================
+*/
+void idBotGoalManager::RemoveLevelItemFromList( levelitem_t* li )
+{
+	if( li->prev )
+	{
+		li->prev->next = li->next;
+	}
+	else
+	{
+		levelitems = li->next;
+	}
+	if( li->next )
+	{
+		li->next->prev = li->prev;
+	}
+}
+
+/*
+========================
+idBotGoalManager::BotFreeInfoEntities
+========================
+*/
+void idBotGoalManager::BotFreeInfoEntities( void )
+{
+	maplocation_t* ml, * nextml;
+	campspot_t* cs, * nextcs;
+
+//	for (ml = maplocations; ml; ml = nextml)
+//	{
+//		nextml = ml->next;
+//		// jmarshall
+//				//FreeMemory(ml);
+//		// jmarshall end
+//	}
+	maplocations.Clear();
+	//for (cs = campspots; cs; cs = nextcs)
+	//{
+	//	nextcs = cs->next;
+	//	// jmarshall
+	//			//FreeMemory(cs);
+	//	// jmarshall end
+	//}
+	campspots.Clear();
+}
+
+/*
+========================
+idBotGoalManager::BotInitInfoEntities
+========================
+*/
+void idBotGoalManager::BotInitInfoEntities( void )
+{
+	//char classname[MAX_EPAIRKEY];
+	idStr classname;
+	maplocation_t* ml;
+	campspot_t* cs;
+
+	BotFreeInfoEntities();
+
+	maplocations.Clear();
+	campspots.Clear();
+
+	for( int i = 0; i < gameLocal.num_entities; i++ )
+	{
+		idEntity* ent = gameLocal.entities[i];
+
+		if( ent == NULL )
+		{
+			continue;
+		}
+
+		classname = ent->spawnArgs.GetString( "classname" );
+
+		//map locations
+		if( classname == "target_location" )
+		{
+			maplocation_t ml;
+			ml.origin = ent->GetPhysics()->GetOrigin();
+			ml.name = ent->GetKey( "message" );
+			maplocations.Append( ml );
+		}
+		//camp spots
+		else if( classname == "info_camp" )
+		{
+			//cs = (campspot_t*)G_AllocClearedMemory(sizeof(campspot_t));
+			campspot_t cs;
+			cs.origin = ent->GetPhysics()->GetOrigin();
+			cs.name = ent->GetKey( "message" );
+			cs.range = ent->GetFloat( "range" );
+			cs.weight = ent->GetFloat( "weight" );
+			cs.wait = ent->GetFloat( "wait" );
+			cs.random = ent->GetFloat( "random" );
+			campspots.Append( cs );
+		}
+	}
+	common->Printf( "%d map locations\n", maplocations.Num() );
+	common->Printf( "%d camp spots\n", campspots.Num() );
+}
+
+/*
+=======================
+idBotGoalManager::InitLevelItems
+=======================
+*/
+void idBotGoalManager::InitLevelItems( void )
+{
+	int i, spawnflags, value;
+	idStr classname;
+	idVec3 origin, end;
+	int ent, goalareanum;
+	itemconfig_t* ic;
+	levelitem_t* li;
+	//bsp_trace_t trace;
+
+	//initialize the map locations and camp spots
+	BotInitInfoEntities();
+
+	//initialize the level item heap
+	InitLevelItemHeap();
+	levelitems = NULL;
+	numlevelitems = 0;
+
+	ic = itemconfig;
+	if( !ic )
+	{
+		return;
+	}
+
+	//update the modelindexes of the item info
+	//for (i = 0; i < ic->numiteminfo; i++)
+	//{
+	//	if (!ic->iteminfo[i].modelindex)
+	//	{
+	//		Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname);
+	//	}
+	//}
+
+	//for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent))
+	for( int idx = 0; idx < gameLocal.num_entities; idx++ )
+	{
+		idEntity* entity = gameLocal.entities[idx];
+		idItem* ent = entity ? entity->Cast<idItem>() : NULL;
+
+		if( ent == nullptr )
+		{
+			continue;
+		}
+
+		spawnflags = ent->GetInt( "spawnflags" );
+		classname = ent->spawnArgs.GetString( "classname" );
+
+		for( i = 0; i < ic->numiteminfo; i++ )
+		{
+			if( !strcmp( classname, ic->iteminfo[i].classname ) )
+			{
+				break;
+			}
+		}
+		if( i >= ic->numiteminfo )
+		{
+			gameLocal.Warning( "entity %s unknown item\r\n", classname.c_str() );
+			continue;
+		}
+
+		//get the origin of the item.
+		origin = ent->GetPhysics()->GetOrigin();
+		goalareanum = 0;
+		// jmarshall - fix floating items
+		//if it is a floating item
+		//if (spawnflags & 1)
+		//{
+		//	//if the item is not floating in water
+		//	if (!(AAS_PointContents(origin) & CONTENTS_WATER))
+		//	{
+		//		VectorCopy(origin, end);
+		//		end[2] -= 32;
+		//		trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID | CONTENTS_PLAYERCLIP);
+		//		//if the item not near the ground
+		//		if (trace.fraction >= 1)
+		//		{
+		//			//if the item is not reachable from a jumppad
+		//			goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs);
+		//			Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum);
+		//			//G_Printf( "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum);
+		//			if (!goalareanum) continue;
+		//		} //end if
+		//	} //end if
+		//} //end if
+		// jmarshall end
+
+		li = AllocLevelItem();
+		if( !li )
+		{
+			return;
+		}
+
+		li->number = ++numlevelitems;
+		li->timeout = 0;
+		li->item = ent;
+		li->name = classname;
+
+		li->flags = 0;
+		if( ent->GetBool( "notfree" ) )
+		{
+			li->flags |= IFL_NOTFREE;
+		}
+		if( ent->GetBool( "notteam" ) )
+		{
+			li->flags |= IFL_NOTTEAM;
+		}
+		if( ent->GetBool( "notsingle" ) )
+		{
+			li->flags |= IFL_NOTSINGLE;
+		}
+		if( ent->GetBool( "notbot" ) )
+		{
+			li->flags |= IFL_NOTBOT;
+		}
+
+		if( classname == "item_botroam" )
+		{
+			li->flags |= IFL_ROAM;
+			//AAS_FloatForBSPEpairKey(ent, "weight", &li->weight);
+			li->weight = ent->GetFloat( "weight" );
+		}
+
+		////if not a stationary item
+		//if (!(spawnflags & 1))
+		//{
+		//	if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs))
+		//	{
+		//		G_Printf( "%s in solid at (%1.1f %1.1f %1.1f)\n",
+		//			classname, origin[0], origin[1], origin[2]);
+		//	} //end if
+		//} //end if
+
+		//item info of the level item
+		li->iteminfo = i;
+
+		//origin of the item
+		VectorCopy( origin, li->origin );
+		VectorCopy( origin, li->goalorigin );
+
+		AddLevelItemToList( li );
+	}
+	gameLocal.Printf( "found %d level items\n", numlevelitems );
+}
+
+/*
+=======================
+idBotGoalManager::BotGoalName
+=======================
+*/
+void idBotGoalManager::BotGoalName( int number, char* name, int size )
+{
+	levelitem_t* li;
+
+	if( !itemconfig )
+	{
+		return;
+	}
+
+	for( li = levelitems; li; li = li->next )
+	{
+		if( li->number == number )
+		{
+			name[size - 1] = '\0';
+			strcpy( name, itemconfig->iteminfo[li->iteminfo].name );
+			return;
+		}
+	}
+}
+
+/*
+=======================
+idBotGoalManager::BotResetAvoidGoals
+=======================
+*/
+void idBotGoalManager::BotResetAvoidGoals( int goalstate )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	memset( gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof( int ) );
+	memset( gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof( float ) );
+}
+
+/*
+=======================
+idBotGoalManager::BotResetAvoidGoals
+=======================
+*/
+void idBotGoalManager::BotDumpAvoidGoals( int goalstate )
+{
+	int i;
+	bot_goalstate_t* gs;
+	char name[32];
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	for( i = 0; i < MAX_AVOIDGOALS; i++ )
+	{
+		if( gs->avoidgoaltimes[i] >= Bot_Time() )
+		{
+			BotGoalName( gs->avoidgoals[i], name, 32 );
+			//Log_Write("avoid goal %s, number %d for %f seconds", name,
+			//	gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time());
+		}
+	}
+}
+
+/*
+=======================
+idBotGoalManager::BotAddToAvoidGoals
+=======================
+*/
+void idBotGoalManager::BotAddToAvoidGoals( bot_goalstate_t* gs, int number, float avoidtime )
+{
+	int i;
+
+	for( i = 0; i < MAX_AVOIDGOALS; i++ )
+	{
+		//if the avoid goal is already stored
+		if( gs->avoidgoals[i] == number )
+		{
+			gs->avoidgoals[i] = number;
+			gs->avoidgoaltimes[i] = Bot_Time() + avoidtime;
+			return;
+		}
+	}
+
+	for( i = 0; i < MAX_AVOIDGOALS; i++ )
+	{
+		//if this avoid goal has expired
+		if( gs->avoidgoaltimes[i] < Bot_Time() )
+		{
+			gs->avoidgoals[i] = number;
+			gs->avoidgoaltimes[i] = Bot_Time() + avoidtime;
+			return;
+		}
+	}
+}
+
+/*
+=======================
+idBotGoalManager::BotAddToAvoidGoals
+=======================
+*/
+void idBotGoalManager::BotRemoveFromAvoidGoals( int goalstate, int number )
+{
+	int i;
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+
+	//don't use the goals the bot wants to avoid
+	for( i = 0; i < MAX_AVOIDGOALS; i++ )
+	{
+		if( gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= Bot_Time() )
+		{
+			gs->avoidgoaltimes[i] = 0;
+			return;
+		}
+	}
+}
+
+/*
+=======================
+idBotGoalManager::BotAvoidGoalTime
+=======================
+*/
+float idBotGoalManager::BotAvoidGoalTime( int goalstate, int number )
+{
+	int i;
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return 0;
+	}
+
+	//don't use the goals the bot wants to avoid
+	for( i = 0; i < MAX_AVOIDGOALS; i++ )
+	{
+		if( gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= Bot_Time() )
+		{
+			return gs->avoidgoaltimes[i] - Bot_Time();
+		}
+	}
+	return 0;
+}
+
+/*
+=======================
+idBotGoalManager::BotSetAvoidGoalTime
+=======================
+*/
+void idBotGoalManager::BotSetAvoidGoalTime( int goalstate, int number, float avoidtime )
+{
+	bot_goalstate_t* gs;
+	levelitem_t* li;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	if( avoidtime < 0 )
+	{
+		if( !itemconfig )
+		{
+			return;
+		}
+
+		for( li = levelitems; li; li = li->next )
+		{
+			if( li->number == number )
+			{
+				avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime;
+				if( !avoidtime )
+				{
+					avoidtime = AVOID_DEFAULT_TIME;
+				}
+				if( avoidtime < AVOID_MINIMUM_TIME )
+				{
+					avoidtime = AVOID_MINIMUM_TIME;
+				}
+				BotAddToAvoidGoals( gs, number, avoidtime );
+				return;
+			}
+		}
+		return;
+	}
+	else
+	{
+		BotAddToAvoidGoals( gs, number, avoidtime );
+	}
+}
+
+/*
+=======================
+idBotGoalManager::BotGetLevelItemGoal
+=======================
+*/
+int idBotGoalManager::BotGetLevelItemGoal( int index, char* name, bot_goal_t* goal )
+{
+	levelitem_t* li;
+
+	if( !itemconfig )
+	{
+		return -1;
+	}
+	li = levelitems;
+	if( index >= 0 )
+	{
+		for( ; li; li = li->next )
+		{
+			if( li->number == index )
+			{
+				li = li->next;
+				break;
+			}
+		}
+	}
+
+	for( ; li; li = li->next )
+	{
+		if( gameLocal.gameType == GAME_SP )
+		{
+			if( li->flags & IFL_NOTSINGLE )
+			{
+				continue;
+			}
+		}
+		else if( gameLocal.gameType >= GAME_TDM )
+		{
+			if( li->flags & IFL_NOTTEAM )
+			{
+				continue;
+			}
+		}
+		else
+		{
+			if( li->flags & IFL_NOTFREE )
+			{
+				continue;
+			}
+		}
+		if( li->flags & IFL_NOTBOT )
+		{
+			continue;
+		}
+
+		if( itemconfig->iteminfo[li->iteminfo].name == name )
+		{
+//			goal->areanum = li->goalareanum;
+			goal->origin = li->goalorigin;
+			goal->entitynum = li->item ? li->item->entityNumber : 0;
+			goal->mins = itemconfig->iteminfo[li->iteminfo].mins;
+			goal->maxs = itemconfig->iteminfo[li->iteminfo].maxs;
+			goal->number = li->number;
+			goal->flags = GFL_ITEM;
+			if( li->timeout )
+			{
+				goal->flags |= GFL_DROPPED;
+			}
+			//G_Printf( "found li %s\n", itemconfig->iteminfo[li->iteminfo].name);
+			return li->number;
+		}
+	}
+	return -1;
+}
+
+/*
+=========================
+idBotGoalManager::BotGetMapLocationGoal
+=========================
+*/
+int idBotGoalManager::BotGetMapLocationGoal( char* name, bot_goal_t* goal )
+{
+	maplocation_t* ml;
+	idVec3 mins( -8, -8, -8 );
+	idVec3 maxs( 8, 8, 8 );
+
+	for( int i = 0; i < maplocations.Num(); i++ )
+	{
+		ml = &maplocations[i];
+		if( ml->name == name )
+		{
+			goal->areanum = ml->areanum;
+			VectorCopy( ml->origin, goal->origin );
+			goal->entitynum = 0;
+			VectorCopy( mins, goal->mins );
+			VectorCopy( maxs, goal->maxs );
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+=========================
+idBotGoalManager::BotGetNextCampSpotGoal
+=========================
+*/
+int idBotGoalManager::BotGetNextCampSpotGoal( int num, bot_goal_t* goal )
+{
+	int i;
+	campspot_t* cs;
+	idVec3 mins( -8, -8, -8 );
+	idVec3 maxs( 8, 8, 8 );
+
+	if( num < 0 )
+	{
+		num = 0;
+	}
+	i = num;
+	for( int d = 0; d < campspots.Num(); d++ )
+	{
+		cs = &campspots[d];
+		if( --i < 0 )
+		{
+			goal->areanum = cs->areanum;
+			VectorCopy( cs->origin, goal->origin );
+			goal->entitynum = 0;
+			VectorCopy( mins, goal->mins );
+			VectorCopy( maxs, goal->maxs );
+			return num + 1;
+		}
+	}
+	return 0;
+}
+
+/*
+============================
+idBotGoalManager::BotFindEntityForLevelItem
+============================
+*/
+void idBotGoalManager::BotFindEntityForLevelItem( levelitem_t* li )
+{
+	int modelindex;
+	itemconfig_t* ic;
+	//aas_entityinfo_t entinfo;
+
+	ic = itemconfig;
+	if( !itemconfig )
+	{
+		return;
+	}
+	for( int idx = 0; idx < gameLocal.num_entities; idx++ )
+	{
+		idEntity* entity = gameLocal.entities[idx];
+		idItem* ent = entity ? entity->Cast<idItem>() : NULL;
+
+		if( ent == NULL )
+		{
+			continue;
+		}
+
+		//get the model index of the entity
+		modelindex = ent->GetModelIndex();
+
+		if( !modelindex )
+		{
+			continue;
+		}
+
+		// jmarshall - eval moving objects
+		//get info about the entity
+		//AAS_EntityInfo(ent, &entinfo);
+		//if the entity is still moving
+		//if (entinfo.origin[0] != entinfo.lastvisorigin[0] ||
+		//	entinfo.origin[1] != entinfo.lastvisorigin[1] ||
+		//	entinfo.origin[2] != entinfo.lastvisorigin[2]) continue;
+		//
+		// jmarshall end
+		if( ic->iteminfo[li->iteminfo].modelindex == modelindex )
+		{
+			//check if the entity is very close
+			idVec3 dir = li->origin - ent->GetPhysics()->GetOrigin();
+
+			if( dir.Length() < 30 )
+			{
+				//found an entity for this level item
+				li->item = ent;
+				li->origin = ent->GetPhysics()->GetOrigin();
+				li->goalorigin = li->origin;
+			}
+		}
+	}
+}
+
+/*
+========================
+UpdateEntityItems
+========================
+*/
+
+//NOTE: enum entityType_t in bg_public.h
+#define ET_ITEM			2
+
+void idBotGoalManager::UpdateEntityItems( void )
+{
+	int i, modelindex;
+	idVec3 dir;
+	levelitem_t* li, * nextli;
+	itemconfig_t* ic;
+
+	//timeout current entity items if necessary
+	for( li = levelitems; li; li = nextli )
+	{
+		nextli = li->next;
+		//if it is a item that will time out
+		if( li->timeout )
+		{
+			//timeout the item
+			if( li->timeout < Bot_Time() )
+			{
+				RemoveLevelItemFromList( li );
+				FreeLevelItem( li );
+			}
+		}
+	}
+
+	//find new entity items
+	ic = itemconfig;
+	if( !itemconfig )
+	{
+		return;
+	}
+	//
+	for( int idx = 0; idx < gameLocal.num_entities; idx++ )
+	{
+		idEntity* entity = gameLocal.entities[idx];
+		idItem* ent = entity ? entity->Cast<idItem>() : NULL;
+
+		if( ent == nullptr )
+		{
+			continue;
+		}
+
+		//get the model index of the entity
+		modelindex = ent->GetModelIndex();
+
+		if( !modelindex )
+		{
+			continue;
+		}
+		// jmarshall - eval floating items.
+		////get info about the entity
+		//AAS_EntityInfo(ent, &entinfo);
+		////FIXME: don't do this
+		////skip all floating items for now
+		////if (entinfo.groundent != ENTITYNUM_WORLD) continue;
+		////if the entity is still moving
+		//if (entinfo.origin[0] != entinfo.lastvisorigin[0] ||
+		//	entinfo.origin[1] != entinfo.lastvisorigin[1] ||
+		//	entinfo.origin[2] != entinfo.lastvisorigin[2]) continue;
+		// jmarshall end
+
+		//check if the entity is already stored as a level item
+		for( li = levelitems; li; li = li->next )
+		{
+			//if the level item is linked to an entity
+			if( li->item && li->item == ent )
+			{
+				//the entity is re-used if the models are different
+				if( ic->iteminfo[li->iteminfo].modelindex != modelindex )
+				{
+					//remove this level item
+					RemoveLevelItemFromList( li );
+					FreeLevelItem( li );
+					li = NULL;
+					break;
+				}
+				else
+				{
+					//if (entinfo.origin[0] != li->origin[0] ||
+					//	entinfo.origin[1] != li->origin[1] ||
+					//	entinfo.origin[2] != li->origin[2])
+					//{
+					//	VectorCopy(entinfo.origin, li->origin);
+					//	//also update the goal area number
+					//	li->goalareanum = AAS_BestReachableArea(li->origin,
+					//		ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs,
+					//		li->goalorigin);
+					//} //end if
+					li->origin = ent->GetPhysics()->GetOrigin();
+					li->goalorigin = li->origin;
+					break;
+				}
+			}
+		}
+		if( li )
+		{
+			continue;
+		}
+
+		//try to link the entity to a level item
+		for( li = levelitems; li; li = li->next )
+		{
+			//if this level item is already linked
+			if( li->item )
+			{
+				continue;
+			}
+			//
+			if( gameLocal.gameType == GAME_SP )
+			{
+				if( li->flags & IFL_NOTSINGLE )
+				{
+					continue;
+				}
+			}
+			else if( gameLocal.gameType >= GAME_TDM )
+			{
+				if( li->flags & IFL_NOTTEAM )
+				{
+					continue;
+				}
+			}
+			else
+			{
+				if( li->flags & IFL_NOTFREE )
+				{
+					continue;
+				}
+			}
+
+			//if the model of the level item and the entity are the same
+			if( ic->iteminfo[li->iteminfo].modelindex == modelindex )
+			{
+				//check if the entity is very close
+				dir = li->origin - ent->GetPhysics()->GetOrigin();
+				if( dir.Length() < 30 )
+				{
+					//found an entity for this level item
+					li->item = ent;
+					//if the origin is different
+					//if (entinfo.origin[0] != li->origin[0] ||
+					//	entinfo.origin[1] != li->origin[1] ||
+					//	entinfo.origin[2] != li->origin[2])
+					//{
+					//	//update the level item origin
+					//	VectorCopy(ent->r.currentOrigin, li->origin);
+					//	//also update the goal area number
+					//	li->goalareanum = AAS_BestReachableArea(li->origin,
+					//		ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs,
+					//		li->goalorigin);
+					//} //end if
+
+					//update the level item origin
+					//VectorCopy(ent->r.currentOrigin, li->origin);
+					li->origin = ent->GetPhysics()->GetOrigin();
+					li->goalorigin = li->origin;
+					break;
+				}
+			}
+		}
+
+		if( li )
+		{
+			continue;
+		}
+		//check if the model is from a known item
+		for( i = 0; i < ic->numiteminfo; i++ )
+		{
+			if( ic->iteminfo[i].modelindex == modelindex )
+			{
+				break;
+			}
+		}
+
+		//if the model is not from a known item
+		if( i >= ic->numiteminfo )
+		{
+			continue;
+		}
+		//allocate a new level item
+		li = AllocLevelItem();
+
+		if( !li )
+		{
+			continue;
+		}
+
+		//entity number of the level item
+		li->item = ent;
+
+		//number for the level item
+		li->number = ent->entityNumber;
+
+		//set the item info index for the level item
+		li->iteminfo = i;
+
+		//origin of the item
+		//VectorCopy(ent->r.currentOrigin, li->origin);
+		li->origin = ent->GetPhysics()->GetOrigin();
+		li->goalorigin = li->origin;
+
+		// jmarshall - fix this, bots, jump pads, and droppable items, bad combo.
+		//get the item goal area and goal origin
+		//li->goalareanum = AAS_BestReachableArea(li->origin,
+		//	ic->iteminfo[i].mins, ic->iteminfo[i].maxs,
+		//	li->goalorigin);
+		////never go for items dropped into jumppads
+		//if (AAS_AreaJumpPad(li->goalareanum))
+		//{
+		//	FreeLevelItem(li);
+		//	continue;
+		//} //end if
+		// jmarshall end
+		//time this item out after 30 seconds
+		//dropped items disappear after 30 seconds
+		li->timeout = Bot_Time() + 30;
+		//add the level item to the list
+		AddLevelItemToList( li );
+		//G_Printf( "found new level item %s\n", ic->iteminfo[i].classname);
+	}
+}
+
+/*
+====================
+idBotGoalManager::BotDumpGoalStack
+====================
+*/
+void idBotGoalManager::BotDumpGoalStack( int goalstate )
+{
+	int i;
+	bot_goalstate_t* gs;
+	char name[32];
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	for( i = 1; i <= gs->goalstacktop; i++ )
+	{
+		BotGoalName( gs->goalstack[i].number, name, 32 );
+		common->Printf( "%d: %s", i, name );
+	}
+}
+
+/*
+====================
+BotPushGoal
+====================
+*/
+void idBotGoalManager::BotPushGoal( int goalstate, bot_goal_t* goal )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	if( gs->goalstacktop >= MAX_GOALSTACK - 1 )
+	{
+		gameLocal.Error( "goal heap overflow\n" );
+		BotDumpGoalStack( goalstate );
+		return;
+	}
+	gs->goalstacktop++;
+	gs->goalstack[gs->goalstacktop] = *goal;
+}
+
+/*
+====================
+idBotGoalManager::BotPopGoal
+====================
+*/
+void idBotGoalManager::BotPopGoal( int goalstate )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	if( gs->goalstacktop > 0 )
+	{
+		gs->goalstacktop--;
+	}
+}
+
+/*
+====================
+idBotGoalManager::BotEmptyGoalStack
+====================
+*/
+void idBotGoalManager::BotEmptyGoalStack( int goalstate )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	gs->goalstacktop = 0;
+}
+
+/*
+====================
+idBotGoalManager::BotGetTopGoal
+====================
+*/
+int idBotGoalManager::BotGetTopGoal( int goalstate, bot_goal_t* goal )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return false;
+	}
+	if( !gs->goalstacktop )
+	{
+		return false;
+	}
+	memcpy( goal, &gs->goalstack[gs->goalstacktop], sizeof( bot_goal_t ) );
+	return true;
+}
+
+/*
+====================
+idBotGoalManager::BotGetSecondGoal
+====================
+*/
+int idBotGoalManager::BotGetSecondGoal( int goalstate, bot_goal_t* goal )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return false;
+	}
+	if( gs->goalstacktop <= 1 )
+	{
+		return false;
+	}
+	memcpy( goal, &gs->goalstack[gs->goalstacktop - 1], sizeof( bot_goal_t ) );
+	return true;
+}
+
+/*
+====================
+idBotGoalManager::BotChooseLTGItem
+====================
+*/
+int idBotGoalManager::BotChooseLTGItem( int goalstate, idVec3 origin, int* inventory, int travelflags )
+{
+	int t, weightnum;
+	float weight, bestweight, avoidtime;
+	iteminfo_t* iteminfo;
+	itemconfig_t* ic;
+	levelitem_t* li, * bestitem;
+	bot_goal_t goal;
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return false;
+	}
+	if( !gs->itemweightconfig || !gs->itemweightindex )
+	{
+		return false;
+	}
+
+	//get the area the bot is in
+	//areanum = BotReachabilityArea(origin, gs->client);
+	//if the bot is in solid or if the area the bot is in has no reachability links
+	//if (!areanum || !AAS_AreaReachability(areanum))
+	//{
+	//	//use the last valid area the bot was in
+	//	areanum = gs->lastreachabilityarea;
+	//} //end if
+	//remember the last area with reachabilities the bot was in
+	//gs->lastreachabilityarea = areanum;
+	//if still in solid
+	//if (!areanum)
+	//	return qfalse;
+	//the item configuration
+	ic = itemconfig;
+	if( !itemconfig )
+	{
+		return false;
+	}
+	//best weight and item so far
+	bestweight = 0;
+	bestitem = NULL;
+	memset( &goal, 0, sizeof( bot_goal_t ) );
+	//go through the items in the level
+	for( li = levelitems; li; li = li->next )
+	{
+		if( gameLocal.gameType == GAME_SP )
+		{
+			if( li->flags & IFL_NOTSINGLE )
+			{
+				continue;
+			}
+		}
+		else if( gameLocal.gameType >= GAME_TDM )
+		{
+			if( li->flags & IFL_NOTTEAM )
+			{
+				continue;
+			}
+		}
+		else
+		{
+			if( li->flags & IFL_NOTFREE )
+			{
+				continue;
+			}
+		}
+		if( li->flags & IFL_NOTBOT )
+		{
+			continue;
+		}
+		//if the item is not in a possible goal area
+		//if (!li->goalareanum)
+		//	continue;
+		//FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk)
+		if( !li->item && !( li->flags & IFL_ROAM ) )
+		{
+			continue;
+		}
+		//get the fuzzy weight function for this item
+		iteminfo = &ic->iteminfo[li->iteminfo];
+		weightnum = gs->itemweightindex[iteminfo->number];
+		if( weightnum < 0 )
+		{
+			continue;
+		}
+
+#ifdef UNDECIDEDFUZZY
+		weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum );
+#else
+		weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum );
+#endif //UNDECIDEDFUZZY
+#ifdef DROPPEDWEIGHT
+		//HACK: to make dropped items more attractive
+		if( li->timeout )
+		{
+			weight += bot_droppedweight.GetFloat();    //GetValueFromLibVar(droppedweight);
+		}
+#endif //DROPPEDWEIGHT
+		//use weight scale for item_botroam
+		if( li->flags & IFL_ROAM )
+		{
+			weight *= li->weight;
+		}
+		//
+		if( weight > 0 )
+		{
+			//get the travel time towards the goal area
+// jmarshall
+			//t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags);
+			t = gameLocal.TravelTimeToGoal( origin, li->goalorigin, travelflags );
+			// jmarshall end
+
+			//if the goal is reachable
+			if( t > 0 )
+			{
+				//if this item won't respawn before we get there
+				avoidtime = BotAvoidGoalTime( goalstate, li->number );
+				if( avoidtime - t * 0.009 > 0 )
+				{
+					continue;
+				}
+
+				weight /= ( float )t * TRAVELTIME_SCALE;
+
+				if( weight > bestweight )
+				{
+					bestweight = weight;
+					bestitem = li;
+				}
+			}
+		}
+	}
+
+	//if no goal item found
+	if( !bestitem )
+	{
+		/*
+		//if not in lava or slime
+		if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum))
+		{
+			if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin))
+			{
+				VectorSet(goal.mins, -15, -15, -15);
+				VectorSet(goal.maxs, 15, 15, 15);
+				goal.entitynum = 0;
+				goal.number = 0;
+				goal.flags = GFL_ROAM;
+				goal.iteminfo = 0;
+				//push the goal on the stack
+				BotPushGoal(goalstate, &goal);
+				//
+		#ifdef DEBUG
+				G_Printf( "chosen roam goal area %d\n", goal.areanum);
+		#endif //DEBUG
+				return qtrue;
+			} //end if
+		} //end if
+		*/
+		return false;
+	}
+
+	//create a bot goal for this item
+	iteminfo = &ic->iteminfo[bestitem->iteminfo];
+	VectorCopy( bestitem->goalorigin, goal.origin );
+	VectorCopy( iteminfo->mins, goal.mins );
+	VectorCopy( iteminfo->maxs, goal.maxs );
+//	goal.areanum = bestitem->goalareanum;
+	goal.entitynum = bestitem->item ? bestitem->item->entityNumber : 0;
+	goal.number = bestitem->number;
+	goal.flags = GFL_ITEM;
+	if( bestitem->timeout )
+	{
+		goal.flags |= GFL_DROPPED;
+	}
+	if( bestitem->flags & IFL_ROAM )
+	{
+		goal.flags |= GFL_ROAM;
+	}
+	goal.iteminfo = bestitem->iteminfo;
+	//if it's a dropped item
+	if( bestitem->timeout )
+	{
+		avoidtime = AVOID_DROPPED_TIME;
+	}
+	else
+	{
+		avoidtime = iteminfo->respawntime;
+		if( !avoidtime )
+		{
+			avoidtime = AVOID_DEFAULT_TIME;
+		}
+		if( avoidtime < AVOID_MINIMUM_TIME )
+		{
+			avoidtime = AVOID_MINIMUM_TIME;
+		}
+	}
+
+	//add the chosen goal to the goals to avoid for a while
+	BotAddToAvoidGoals( gs, bestitem->number, avoidtime );
+
+	//push the goal on the stack
+	BotPushGoal( goalstate, &goal );
+
+	return true;
+}
+
+/*
+====================
+idBotGoalManager::BotChooseNBGItem
+====================
+*/
+int idBotGoalManager::BotChooseNBGItem( int goalstate, idVec3 origin, int* inventory, int travelflags, bot_goal_t* ltg, float maxtime )
+{
+	int t, weightnum, ltg_time;
+	float weight, bestweight, avoidtime;
+	iteminfo_t* iteminfo;
+	itemconfig_t* ic;
+	levelitem_t* li, * bestitem;
+	bot_goal_t goal;
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return false;
+	}
+	if( !gs->itemweightconfig || !gs->itemweightindex )
+	{
+		return false;
+	}
+	if( maxtime <= 0.0f )
+	{
+		return false;
+	}
+
+	if( ltg )
+	{
+		//ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags);
+		ltg_time = gameLocal.TravelTimeToGoal( origin, ltg->origin, travelflags );
+		if( ltg_time <= 0 )
+		{
+			ltg_time = 99999;
+		}
+	}
+	else
+	{
+		ltg_time = 99999;
+	}
+	//the item configuration
+	ic = itemconfig;
+	if( !itemconfig )
+	{
+		return false;
+	}
+	//best weight and item so far
+	bestweight = 0;
+	bestitem = NULL;
+	memset( &goal, 0, sizeof( bot_goal_t ) );
+	//go through the items in the level
+	for( li = levelitems; li; li = li->next )
+	{
+		if( gameLocal.gameType == GAME_SP )
+		{
+			if( li->flags & IFL_NOTSINGLE )
+			{
+				continue;
+			}
+		}
+		else if( gameLocal.gameType >= GAME_TDM )
+		{
+			if( li->flags & IFL_NOTTEAM )
+			{
+				continue;
+			}
+		}
+		else
+		{
+			if( li->flags & IFL_NOTFREE )
+			{
+				continue;
+			}
+		}
+		if( li->flags & IFL_NOTBOT )
+		{
+			continue;
+		}
+		//if the item is in a possible goal area
+//		if (!li->goalareanum)
+//			continue;
+		//FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk)
+		if( !li->item && !( li->flags & IFL_ROAM ) )
+		{
+			continue;
+		}
+		//get the fuzzy weight function for this item
+		iteminfo = &ic->iteminfo[li->iteminfo];
+		weightnum = gs->itemweightindex[iteminfo->number];
+		if( weightnum < 0 )
+		{
+			continue;
+		}
+		//
+#ifdef UNDECIDEDFUZZY
+		weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum );
+#else
+		weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum );
+#endif //UNDECIDEDFUZZY
+#ifdef DROPPEDWEIGHT
+		//HACK: to make dropped items more attractive
+		if( li->timeout )
+		{
+			weight += bot_droppedweight.GetFloat();
+		}
+#endif //DROPPEDWEIGHT
+		//use weight scale for item_botroam
+		if( li->flags & IFL_ROAM )
+		{
+			weight *= li->weight;
+		}
+		//
+		if( weight > 0 )
+		{
+			//get the travel time towards the goal area
+			//t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags);
+			t = gameLocal.TravelTimeToGoal( origin, li->goalorigin, travelflags );
+
+			//if the goal is reachable
+			if( t > 0 && t < maxtime )
+			{
+				//if this item won't respawn before we get there
+				avoidtime = BotAvoidGoalTime( goalstate, li->number );
+				if( avoidtime - t * 0.009 > 0 )
+				{
+					continue;
+				}
+				//
+				weight /= ( float )t * TRAVELTIME_SCALE;
+				//
+				if( weight > bestweight )
+				{
+					t = 0;
+					if( ltg && !li->timeout )
+					{
+						//get the travel time from the goal to the long term goal
+// jmarshall
+						//t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags);
+						t = gameLocal.TravelTimeToGoal( li->goalorigin, ltg->origin, travelflags );
+						// jmarshall end
+					} //end if
+					//if the travel back is possible and doesn't take too long
+					if( t <= ltg_time )
+					{
+						bestweight = weight;
+						bestitem = li;
+					} //end if
+				} //end if
+			} //end if
+		} //end if
+	} //end for
+	//if no goal item found
+	if( !bestitem )
+	{
+		return false;
+	}
+	//create a bot goal for this item
+	iteminfo = &ic->iteminfo[bestitem->iteminfo];
+	VectorCopy( bestitem->goalorigin, goal.origin );
+	VectorCopy( iteminfo->mins, goal.mins );
+	VectorCopy( iteminfo->maxs, goal.maxs );
+//	goal.areanum = bestitem->goalareanum;
+	goal.entitynum = bestitem->item ? bestitem->item->entityNumber : 0;
+	goal.number = bestitem->number;
+	goal.flags = GFL_ITEM;
+	if( bestitem->timeout )
+	{
+		goal.flags |= GFL_DROPPED;
+	}
+	if( bestitem->flags & IFL_ROAM )
+	{
+		goal.flags |= GFL_ROAM;
+	}
+	goal.iteminfo = bestitem->iteminfo;
+	//if it's a dropped item
+	if( bestitem->timeout )
+	{
+		avoidtime = AVOID_DROPPED_TIME;
+	} //end if
+	else
+	{
+		avoidtime = iteminfo->respawntime;
+		if( !avoidtime )
+		{
+			avoidtime = AVOID_DEFAULT_TIME;
+		}
+		if( avoidtime < AVOID_MINIMUM_TIME )
+		{
+			avoidtime = AVOID_MINIMUM_TIME;
+		}
+	} //end else
+	//add the chosen goal to the goals to avoid for a while
+	BotAddToAvoidGoals( gs, bestitem->number, avoidtime );
+	//push the goal on the stack
+	BotPushGoal( goalstate, &goal );
+	//
+	return true;
+} //end of the function BotChooseNBGItem
+
+
+
+/*
+====================
+idBotGoalManager::BotTouchingGoal
+====================
+*/
+int idBotGoalManager::BotTouchingGoal( idVec3 origin, bot_goal_t* goal )
+{
+	int i;
+	idVec3 boxmins, boxmaxs;
+	idVec3 absmins, absmaxs;
+	idVec3 safety_maxs( 0, 0, 0 ); //{4, 4, 10};
+	idVec3 safety_mins( 0, 0, 0 ); //{-4, -4, 0};
+
+	rvmBot::PresenceTypeBoundingBox( PRESENCE_NORMAL, boxmins, boxmaxs );
+	VectorSubtract( goal->mins, boxmaxs, absmins );
+	VectorSubtract( goal->maxs, boxmins, absmaxs );
+	VectorAdd( absmins, goal->origin, absmins );
+	VectorAdd( absmaxs, goal->origin, absmaxs );
+
+	//make the box a little smaller for safety
+	VectorSubtract( absmaxs, safety_maxs, absmaxs );
+	VectorSubtract( absmins, safety_mins, absmins );
+
+	for( i = 0; i < 3; i++ )
+	{
+		if( origin[i] < absmins[i] || origin[i] > absmaxs[i] )
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+========================
+idBotGoalManager::BotItemGoalInVisButNotVisible
+========================
+*/
+int idBotGoalManager::BotItemGoalInVisButNotVisible( int viewer, idVec3 eye, idAngles viewangles, bot_goal_t* goal )
+{
+	//aas_entityinfo_t entinfo;
+	trace_t trace;
+	idVec3 middle;
+
+	if( !( goal->flags & GFL_ITEM ) )
+	{
+		return false;
+	}
+
+	VectorAdd( goal->mins, goal->mins, middle );
+	VectorScale( middle, 0.5, middle );
+	VectorAdd( goal->origin, middle, middle );
+
+	//trap_Trace(&trace, eye, NULL, NULL, middle, viewer, CONTENTS_SOLID);
+	idMat3 axis;
+	axis.Identity();
+	gameLocal.clip[0]->Translation( trace, eye, middle, NULL, axis, CONTENTS_SOLID, NULL );
+
+	//if the goal middle point is visible
+	if( trace.fraction >= 1 )
+	{
+		//the goal entity number doesn't have to be valid
+		//just assume it's valid
+		if( goal->entitynum <= 0 )
+		{
+			return false;
+		}
+		// jmarshall - time delay thing here?
+		//
+		//if the entity data isn't valid
+		//AAS_EntityInfo(goal->entitynum, &entinfo);
+		//NOTE: for some wacko reason entities are sometimes
+		// not updated
+		//if (!entinfo.valid) return qtrue;
+		//if (entinfo.ltime < AAS_Time() - 0.5)
+		//	return qtrue;
+		// jmarshall end
+	}
+	return false;
+}
+
+/*
+========================
+BotResetGoalState
+========================
+*/
+void idBotGoalManager::BotResetGoalState( int goalstate )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	//memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t));
+	for( int i = 0; i < MAX_GOALSTACK; i++ )
+	{
+		gs->goalstack[i].Reset();
+	}
+	gs->goalstacktop = 0;
+	BotResetAvoidGoals( goalstate );
+}
+
+/*
+========================
+idBotGoalManager::BotResetGoalState
+========================
+*/
+int idBotGoalManager::BotLoadItemWeights( int goalstate, char* filename )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return BLERR_CANNOTLOADITEMWEIGHTS;
+	}
+
+	//load the weight configuration
+	gs->itemweightconfig = botFuzzyWeightManager.ReadWeightConfig( filename );
+	if( !gs->itemweightconfig )
+	{
+		gameLocal.Error( "couldn't load weights\n" );
+		return BLERR_CANNOTLOADITEMWEIGHTS;
+	}
+
+	//if there's no item configuration
+	if( !itemconfig )
+	{
+		return BLERR_CANNOTLOADITEMWEIGHTS;
+	}
+
+	//create the item weight index
+	if( gs->itemweightindex )
+	{
+		delete[] gs->itemweightindex;
+		gs->itemweightindex = NULL;
+	}
+	gs->itemweightindex = ItemWeightIndex( gs->itemweightconfig, itemconfig );
+
+	//everything went ok
+	return BLERR_NOERROR;
+}
+
+/*
+========================
+idBotGoalManager::BotResetGoalState
+========================
+*/
+void idBotGoalManager::BotFreeItemWeights( int goalstate )
+{
+	bot_goalstate_t* gs;
+
+	gs = BotGoalStateFromHandle( goalstate );
+	if( !gs )
+	{
+		return;
+	}
+	if( gs->itemweightconfig )
+	{
+		botFuzzyWeightManager.FreeWeightConfig( gs->itemweightconfig );
+		gs->itemweightconfig = NULL;
+	}
+	if( gs->itemweightindex )
+	{
+		delete[] gs->itemweightindex;
+		gs->itemweightindex = NULL;
+	}
+}
+
+/*
+========================
+idBotGoalManager::BotAllocGoalState
+========================
+*/
+int idBotGoalManager::BotAllocGoalState( int client )
+{
+	int i;
+
+	for( i = 1; i <= MAX_CLIENTS; i++ )
+	{
+		if( !botgoalstates[i].InUse() )
+		{
+			botgoalstates[i].Reset();
+			botgoalstates[i].client = client;
+			return i;
+		}
+	}
+	return 0;
+}
+
+/*
+========================
+idBotGoalManager::BotFreeGoalState
+========================
+*/
+void idBotGoalManager::BotFreeGoalState( int handle )
+{
+	if( handle <= 0 || handle > MAX_CLIENTS )
+	{
+		gameLocal.Error( "goal state handle %d out of range\n", handle );
+		return;
+	}
+
+	if( !botgoalstates[handle].InUse() )
+	{
+		gameLocal.Error( "invalid goal state handle %d\n", handle );
+		return;
+	}
+
+	BotFreeItemWeights( handle );
+	// jmarshall
+	//FreeMemory(botgoalstates[handle]);
+	// jmarshall end
+	botgoalstates[handle].Reset();
+}
+
+/*
+========================
+idBotGoalManager::BotSetupGoalAI
+========================
+*/
+int idBotGoalManager::BotSetupGoalAI( void )
+{
+	//load the item configuration
+	itemconfig = LoadItemConfig( ( char* )bot_itemsfile.GetString() );
+	if( !itemconfig )
+	{
+		gameLocal.Error( "couldn't load item config\n" );
+		return BLERR_CANNOTLOADITEMCONFIG;
+	}
+
+	//everything went ok
+	return BLERR_NOERROR;
+}
+
+/*
+========================
+idBotGoalManager::BotSetupGoalAI
+========================
+*/
+void idBotGoalManager::BotShutdownGoalAI( void )
+{
+	int i;
+	// jmarshall
+	//if (itemconfig)
+	//	FreeMemory(itemconfig);
+	// jmarshall end
+	itemconfig = NULL;
+	// jmarshall
+	//if (levelitemheap)
+	//	FreeMemory(levelitemheap);
+	// jmarshall end
+	//levelitemheap = NULL;
+	for( int i = 0; i < MAX_BOT_LEVEL_ITEMS; i++ )
+	{
+		levelitemheap[i].Reset();
+	}
+
+	freelevelitems = NULL;
+	levelitems = NULL;
+	numlevelitems = 0;
+
+	BotFreeInfoEntities();
+
+	for( i = 1; i <= MAX_CLIENTS; i++ )
+	{
+		if( botgoalstates[i].InUse() )
+		{
+			BotFreeGoalState( i );
+		}
+	}
+}
+
+
+/*
+=======================
+idBotGoalManager::BotNearGoal(
+=======================
+*/
+bool idBotGoalManager::BotNearGoal( idVec3 p1, idVec3 p2 )
+{
+	idVec3 p1_z, p2_z;
+
+	p1_z[0] = p1[0];
+	p1_z[1] = p1[1];
+	p1_z[2] = 0;
+
+	p2_z[0] = p2[0];
+	p2_z[1] = p2[1];
+	p2_z[2] = 0;
+
+	float distToGoal = idMath::Distance( p1_z, p2_z );
+	if( distToGoal <= 50 )
+	{
+		return true;
+	}
+
+	return false;
+}
diff --git a/src/mpgame/bots/Bot_goal.h b/src/mpgame/bots/Bot_goal.h
new file mode 100644
index 0000000..e22bd02
--- /dev/null
+++ b/src/mpgame/bots/Bot_goal.h
@@ -0,0 +1,342 @@
+// Bot_goal.h
+//
+
+#define MAX_BOT_ITEM_INFOS		2
+#define MAX_BOT_ITEM_INFO		256
+#define MAX_BOT_LEVEL_ITEMS		256
+
+//#define DEBUG_AI_GOAL
+#ifdef RANDOMIZE
+	#define UNDECIDEDFUZZY
+#endif //RANDOMIZE
+#define DROPPEDWEIGHT
+//minimum avoid goal time
+#define AVOID_MINIMUM_TIME		10
+//default avoid goal time
+#define AVOID_DEFAULT_TIME		30
+//avoid dropped goal time
+#define AVOID_DROPPED_TIME		10
+//
+#define TRAVELTIME_SCALE		0.01
+//item flags
+#define IFL_NOTFREE				1		//not in free for all
+#define IFL_NOTTEAM				2		//not in team play
+#define IFL_NOTSINGLE			4		//not in single player
+#define IFL_NOTBOT				8		//bot should never go for this
+#define IFL_ROAM				16		//bot roam goal
+
+//a bot goal
+struct bot_goal_t
+{
+	bot_goal_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		origin.Zero();
+		areanum = 0;
+		mins.Zero();
+		maxs.Zero();
+		entitynum = 0;
+		number = 0;
+		flags = 0;
+		iteminfo = 0;
+		framenum = -1;
+	}
+
+	int framenum;
+	idVec3 origin;				//origin of the goal
+	int areanum;				//area number of the goal
+	idVec3 mins, maxs;			//mins and maxs of the goal
+	int entitynum;				//number of the goal entity
+	int number;					//goal number
+	int flags;					//goal flags
+	int iteminfo;				//item information
+};
+
+//location in the map "target_location"
+struct maplocation_t
+{
+	maplocation_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		origin.Zero();
+		areanum = 0;
+		name = "";
+		next = nullptr;
+	}
+
+	idVec3 origin;
+	int areanum;
+	idStr name;
+	maplocation_t* next;
+};
+
+//camp spots "info_camp"
+struct campspot_t
+{
+	campspot_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		origin.Zero();
+		areanum = 0;
+		name = "";
+		range = 0;
+		weight = 0;
+		random = 0;
+	}
+
+	idVec3 origin;
+	int areanum;
+	idStr name;
+	float range;
+	float weight;
+	float wait;
+	float random;
+};
+
+struct levelitem_t
+{
+	levelitem_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		number = 0;
+		iteminfo = 0;
+		flags = 0;
+		weight = 0;
+		origin.Zero();
+		goalorigin.Zero();
+		item = nullptr;
+		timeout = 0;
+		prev = nullptr;
+		next = nullptr;
+	}
+
+	idStr name;
+	int number;							//number of the level item
+	int iteminfo;						//index into the item info
+	int flags;							//item flags
+	float weight;						//fixed roam weight
+	idVec3 origin;						//origin of the item
+	idVec3 goalorigin;					//goal origin within the area
+	//int entitynum;						//entity number
+	idItem* item;
+	float timeout;						//item is removed after this time
+	levelitem_t* prev, * next;
+};
+
+struct iteminfo_t
+{
+	iteminfo_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		classname = "";
+		name = "";
+		model = "";
+		modelindex = 0;
+		type = 0;
+		index = 0;
+		respawntime = 0;
+		mins.Zero();
+		maxs.Zero();
+		number = 0;
+	}
+
+	idStr classname;					//classname of the item
+	idStr name;							//name of the item
+	idStr model;						//model of the item
+	int modelindex;						//model index
+	int type;							//item type
+	int index;							//index in the inventory
+	float respawntime;					//respawn time
+	idVec3 mins;						//mins of the item
+	idVec3 maxs;						//maxs of the item
+	int number;							//number of the item info
+};
+
+//
+// itemconfig_t
+//
+struct itemconfig_t
+{
+	itemconfig_t()
+	{
+		Reset();
+	}
+
+	void Reset( void )
+	{
+		numiteminfo = 0;
+		for( int i = 0; i < MAX_BOT_ITEM_INFO; i++ )
+		{
+			iteminfo[i].Reset();
+		}
+	}
+
+	int numiteminfo;
+	iteminfo_t iteminfo[MAX_BOT_ITEM_INFO];
+};
+
+//goal state
+struct bot_goalstate_t
+{
+	bot_goalstate_t()
+	{
+		itemweightindex = NULL;
+		Reset();
+	}
+
+	void Reset()
+	{
+		itemweightconfig = NULL;
+
+		if( itemweightindex != NULL )
+		{
+			delete[] itemweightindex;
+		}
+		itemweightindex = NULL;
+		client = -1;
+		lastreachabilityarea = 0;
+		goalstacktop = 0;
+
+		for( int i = 0; i < MAX_AVOIDGOALS; i++ )
+		{
+			avoidgoals[i] = 0;
+			avoidgoaltimes[i] = 0;
+		}
+	}
+
+	~bot_goalstate_t()
+	{
+		if( itemweightindex != NULL )
+		{
+			delete[] itemweightindex;
+		}
+
+		itemweightindex = NULL;
+	}
+
+	bool InUse()
+	{
+		return client != -1;
+	}
+
+	weightconfig_t*			itemweightconfig;	//weight config
+	int* itemweightindex;						//index from item to weight
+
+	int client;									//client using this goal state
+	int lastreachabilityarea;					//last area with reachabilities the bot was in
+
+	bot_goal_t goalstack[MAX_GOALSTACK];		//goal stack
+	int goalstacktop;							//the top of the goal stack
+
+	int avoidgoals[MAX_AVOIDGOALS];				//goals to avoid
+	float avoidgoaltimes[MAX_AVOIDGOALS];		//times to avoid the goals
+};
+
+class idBotGoalManager
+{
+public:
+	idBotGoalManager();
+
+	int BotSetupGoalAI( void );
+
+	void InitLevelItems( void );
+	void UpdateEntityItems( void );
+
+	void BotPushGoal( int goalstate, bot_goal_t* goal );
+	void BotPopGoal( int goalstate );
+	void BotEmptyGoalStack( int goalstate );
+
+	int BotLoadItemWeights( int goalstate, char* filename );
+	void BotResetGoalState( int goalstate );
+
+	int BotItemGoalInVisButNotVisible( int viewer, idVec3 eye, idAngles viewangles, bot_goal_t* goal );
+
+	int BotChooseLTGItem( int goalstate, idVec3 origin, int* inventory, int travelflags );
+	int BotChooseNBGItem( int goalstate, idVec3 origin, int* inventory, int travelflags, bot_goal_t* ltg, float maxtime );
+
+	int BotTouchingGoal( idVec3 origin, bot_goal_t* goal );
+
+	int BotAllocGoalState( int client );
+
+	void BotGoalName( int number, char* name, int size );
+
+	void BotFreeGoalState( int handle );
+	void BotShutdownGoalAI( void );
+	void BotFreeItemWeights( int goalstate );
+public:
+	bool BotNearGoal( idVec3 p1, idVec3 p2 );
+	int BotGetTopGoal( int goalstate, bot_goal_t* goal );
+	int BotGetSecondGoal( int goalstate, bot_goal_t* goal );
+public:
+	void BotDumpGoalStack( int goalstate );
+public:
+	int BotGetLevelItemGoal( int index, char* name, bot_goal_t* goal );
+	void BotSetAvoidGoalTime( int goalstate, int number, float avoidtime );
+	float BotAvoidGoalTime( int goalstate, int number );
+	void BotResetAvoidGoals( int goalstate );
+	void BotDumpAvoidGoals( int goalstate );
+	void BotAddToAvoidGoals( bot_goalstate_t* gs, int number, float avoidtime );
+	void BotRemoveFromAvoidGoals( int goalstate, int number );
+	int BotGetMapLocationGoal( char* name, bot_goal_t* goal );
+	int BotGetNextCampSpotGoal( int num, bot_goal_t* goal );
+	void BotFindEntityForLevelItem( levelitem_t* li );
+private:
+
+	levelitem_t* AllocLevelItem( void );
+	void FreeLevelItem( levelitem_t* li );
+
+	void AddLevelItemToList( levelitem_t* li );
+	void RemoveLevelItemFromList( levelitem_t* li );
+
+	void BotFreeInfoEntities( void );
+
+	void BotInitInfoEntities( void );
+
+	void InitLevelItemHeap( void );
+	int* ItemWeightIndex( weightconfig_t* iwc, itemconfig_t* ic );
+	itemconfig_t* LoadItemConfig( char* filename );
+	void BotSaveGoalFuzzyLogic( int goalstate, char* filename );
+	void BotMutateGoalFuzzyLogic( int goalstate, float range );
+	bot_goalstate_t* BotGoalStateFromHandle( int handle );
+	void BotInterbreedGoalFuzzyLogic( int parent1, int parent2, int child );
+private:
+	void ParseItemInfo( idParser& parser, iteminfo_t* itemInfo );
+private:
+	bot_goalstate_t botgoalstates[MAX_CLIENTS + 1];
+
+	//item configuration
+	itemconfig_t		itemconfiglocal;
+	itemconfig_t*		itemconfig;
+
+	//level items
+	levelitem_t levelitemheap[MAX_BOT_LEVEL_ITEMS];
+	levelitem_t* freelevelitems;
+	levelitem_t* levelitems;
+	int numlevelitems;
+
+	//map locations
+	idList<maplocation_t> maplocations;
+	idList<campspot_t> campspots;
+};
+
+extern idBotGoalManager botGoalManager;
diff --git a/src/mpgame/bots/Bot_weapons.cpp b/src/mpgame/bots/Bot_weapons.cpp
new file mode 100644
index 0000000..c9b3a93
--- /dev/null
+++ b/src/mpgame/bots/Bot_weapons.cpp
@@ -0,0 +1,492 @@
+// Bot_weapons.cpp
+//
+
+
+
+
+#include "../Game_local.h"
+
+idCVar bot_weaponsfile( "bot_weaponsfile", "weapons.c", CVAR_GAME | CVAR_CHEAT, "which file to load the weapons weights from" );
+
+idBotWeaponInfoManager botWeaponInfoManager;
+
+/*
+=========================
+idBotWeaponInfoManager::BotValidWeaponNumber
+=========================
+*/
+int idBotWeaponInfoManager::BotValidWeaponNumber( int weaponnum )
+{
+	if( weaponnum <= 0 || weaponnum > BOT_MAX_WEAPONS )
+	{
+		gameLocal.Error( "weapon number out of range\n" );
+		return false;
+	}
+	return true;
+}
+
+/*
+=========================
+idBotWeaponInfoManager::BotWeaponStateFromHandle
+=========================
+*/
+bot_weaponstate_t* idBotWeaponInfoManager::BotWeaponStateFromHandle( int handle )
+{
+	if( handle <= 0 || handle > MAX_CLIENTS )
+	{
+		gameLocal.Error( "move state handle %d out of range\n", handle );
+	}
+
+	return &botweaponstates[handle];
+}
+
+/*
+========================
+idBotWeaponInfoManager::ParseProjectileInfo
+========================
+*/
+void idBotWeaponInfoManager::ParseProjectileInfo( idParser& parser, projectileinfo_t& newProjectileInfo )
+{
+	idToken token;
+	idToken valueToken;
+
+	parser.ExpectTokenString( "{" );
+
+	while( true )
+	{
+		if( !parser.ReadToken( &token ) )
+		{
+			parser.Error( "Unexpected end of file found while parsing weaponinfo!" );
+		}
+
+		if( token == "}" )
+		{
+			break;
+		}
+
+		if( !parser.ReadToken( &valueToken ) )
+		{
+			parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+		}
+
+		if( token == "name" )
+		{
+			newProjectileInfo.name = valueToken; // name of the projectile
+		}
+		else if( token == "model" )
+		{
+			newProjectileInfo.model = valueToken; // model of the projectile
+		}
+		else if( token == "flags" )
+		{
+			newProjectileInfo.flags = valueToken.GetIntValue(); // special flags
+		}
+		else if( token == "gravity" )
+		{
+			newProjectileInfo.gravity = valueToken.GetFloatValue(); // amount of gravity applied to the projectile [0,1]
+		}
+		else if( token == "damage" )
+		{
+			newProjectileInfo.damage = valueToken.GetIntValue(); // damage of the projectile
+		}
+		else if( token == "radius" )
+		{
+			newProjectileInfo.radius = valueToken.GetFloatValue(); // radius of damage
+		}
+		else if( token == "visdamage" )
+		{
+			newProjectileInfo.visdamage = valueToken.GetIntValue(); // damage of the projectile to visible entities
+		}
+		else if( token == "damagetype" )
+		{
+			newProjectileInfo.damagetype = valueToken.GetIntValue(); // type of damage (combination of the DAMAGETYPE_? flags)
+		}
+		else if( token == "healthinc" )
+		{
+			newProjectileInfo.healthinc = valueToken.GetIntValue(); // health increase the owner gets
+		}
+		else if( token == "push" )
+		{
+			newProjectileInfo.push = valueToken.GetFloatValue(); // amount a player is pushed away from the projectile impact
+		}
+		else if( token == "detonation" )
+		{
+			newProjectileInfo.detonation = valueToken.GetFloatValue(); // time before projectile explodes after fire pressed
+		}
+		else if( token == "bounce" )
+		{
+			newProjectileInfo.bounce = valueToken.GetFloatValue(); // amount the projectile bounces
+		}
+		else if( token == "bouncefric" )
+		{
+			newProjectileInfo.bouncefric = valueToken.GetFloatValue(); // amount the bounce decreases per bounce
+		}
+		else if( token == "bouncestop" )
+		{
+			newProjectileInfo.bouncestop = valueToken.GetFloatValue(); // minimum bounce value before bouncing stops
+		}
+		else
+		{
+			parser.Error( "ParseProjectileInfo: Unexpected token %s\n", token.c_str() );
+		}
+	}
+}
+
+/*
+========================
+idBotWeaponInfoManager::ParseWeaponInfo
+========================
+*/
+void idBotWeaponInfoManager::ParseWeaponInfo( idParser& parser, weaponinfo_t& newWeaponInfo )
+{
+	idToken token;
+	idToken valueToken;
+
+	parser.ExpectTokenString( "{" );
+
+	while( true )
+	{
+		if( !parser.ReadToken( &token ) )
+		{
+			parser.Error( "Unexpected end of file found while parsing weaponinfo!" );
+		}
+
+		if( token == "}" )
+		{
+			break;
+		}
+
+		if( !parser.ReadToken( &valueToken ) )
+		{
+			parser.Error( "Failed to read value token, encountered EOF parsing weapon info!" );
+		}
+
+		if( token == "number" )
+		{
+			newWeaponInfo.number = valueToken.GetIntValue(); //weapon number
+		}
+		else if( token == "name" )
+		{
+			newWeaponInfo.name = valueToken; //name of the weapon
+		}
+		else if( token == "level" )
+		{
+			newWeaponInfo.level = valueToken.GetIntValue();
+		}
+		else if( token == "model" )
+		{
+			newWeaponInfo.model = valueToken; //model of the weapon
+		}
+		else if( token == "weaponindex" )
+		{
+			newWeaponInfo.weaponindex = valueToken.GetIntValue(); //index of weapon in inventory
+		}
+		else if( token == "flags" )
+		{
+			newWeaponInfo.flags = valueToken.GetIntValue(); //special flags
+		}
+		else if( token == "projectile" )
+		{
+			newWeaponInfo.projectile = valueToken; //projectile used by the weapon
+		}
+		else if( token == "numprojectiles" )
+		{
+			newWeaponInfo.numprojectiles = valueToken.GetIntValue(); //number of projectiles
+		}
+		else if( token == "hspread" )
+		{
+			newWeaponInfo.hspread = valueToken.GetFloatValue(); //horizontal spread of projectiles (degrees from middle)
+		}
+		else if( token == "vspread" )
+		{
+			newWeaponInfo.vspread = valueToken.GetFloatValue(); //vertical spread of projectiles (degrees from middle)
+		}
+		else if( token == "speed" )
+		{
+			newWeaponInfo.speed = valueToken.GetFloatValue(); //speed of the projectile (0 = instant hit)
+		}
+		else
+		{
+			parser.Error( "ParseWeaponInfo: Unexpected token %s\n", token.c_str() );
+		}
+	}
+}
+
+/*
+========================
+idBotWeaponInfoManager::LoadWeaponConfig
+========================
+*/
+void idBotWeaponInfoManager::LoadWeaponConfig( char* filename )
+{
+	idParser parser;
+	idToken token;
+	int j;
+
+	if( !parser.LoadFile( filename ) )
+	{
+		gameLocal.Error( "Failed to load bot weapon config %s\n", filename );
+	}
+
+	while( parser.ReadToken( &token ) )
+	{
+		if( token == "weaponinfo" )
+		{
+			weaponinfo_t newWeaponInfo;
+			ParseWeaponInfo( parser, newWeaponInfo );
+
+			if( newWeaponInfo.number < 0 || newWeaponInfo.number >= BOT_MAX_WEAPONS )
+			{
+				parser.Error( "weapon info number %d out of range\n", newWeaponInfo.number );
+				return;
+			}
+
+			weaponinfo[newWeaponInfo.number] = newWeaponInfo;
+			weaponinfo[newWeaponInfo.number].valid = true;
+		}
+		else if( token == "projectileinfo" )
+		{
+			projectileinfo_t newProjectileInfo;
+
+			ParseProjectileInfo( parser, newProjectileInfo );
+
+			projectileinfo.Append( newProjectileInfo );
+		}
+		else
+		{
+			parser.Error( "LoadWeaponConfig: Unknown definintion %s", token.c_str() );
+		}
+	}
+
+	// fix up weapons.
+	for( int i = 0; i < BOT_MAX_WEAPONS; i++ )
+	{
+		if( !weaponinfo[i].valid )
+		{
+			continue;
+		}
+
+		if( weaponinfo[i].name.Length() <= 0 )
+		{
+			parser.Error( "weapon %d has no name\n", i );
+			return;
+		}
+
+		if( weaponinfo[i].projectile.Length() <= 0 )
+		{
+			parser.Error( "weapon %s has no projectile\n", weaponinfo[i].name.c_str() );
+			return;
+		}
+
+		//find the projectile info and copy it to the weapon info
+		for( j = 0; j < projectileinfo.Num(); j++ )
+		{
+			if( projectileinfo[j].name == weaponinfo[i].projectile )
+			{
+				weaponinfo[i].proj = projectileinfo[j];
+				break;
+			}
+		}
+		if( j == projectileinfo.Num() )
+		{
+			parser.Error( "weapon %s uses undefined projectile\n", weaponinfo[i].name.c_str() );
+			return;
+		}
+	}
+}
+
+/*
+========================
+idBotWeaponInfoManager::Init
+========================
+*/
+void idBotWeaponInfoManager::Init( void )
+{
+	rvmScopedLexerBaseFolder scopedBaseFolder( BOTFILESBASEFOLDER );
+
+	LoadWeaponConfig( ( char* )bot_weaponsfile.GetString() );
+}
+
+/*
+=====================
+WeaponWeightIndex
+=====================
+*/
+void idBotWeaponInfoManager::WeaponWeightIndex( weightconfig_t* wwc, bot_weaponstate_t* weaponState )
+{
+	for( int i = 0; i < BOT_MAX_WEAPONS; i++ )
+	{
+		weaponState->weaponweightindex[i] = botFuzzyWeightManager.FindFuzzyWeight( wwc, ( char* )weaponinfo[i].name.c_str() );
+	}
+}
+
+/*
+=====================
+idBotWeaponInfoManager::WeaponWeightIndex
+=====================
+*/
+void idBotWeaponInfoManager::BotFreeWeaponWeights( int weaponstate )
+{
+	bot_weaponstate_t* ws;
+
+	ws = BotWeaponStateFromHandle( weaponstate );
+	if( !ws )
+	{
+		return;
+	}
+//	if (ws->weaponweightconfig)
+//		FreeWeightConfig(ws->weaponweightconfig);
+	//	if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex);
+}
+
+/*
+=====================
+BotLoadWeaponWeights
+=====================
+*/
+int idBotWeaponInfoManager::BotLoadWeaponWeights( int weaponstate, char* filename )
+{
+	bot_weaponstate_t* ws;
+
+	ws = BotWeaponStateFromHandle( weaponstate );
+	if( !ws )
+	{
+		return BLERR_CANNOTLOADWEAPONWEIGHTS;
+	}
+
+	BotFreeWeaponWeights( weaponstate );
+
+	ws->weaponweightconfig = botFuzzyWeightManager.ReadWeightConfig( filename );
+	if( !ws->weaponweightconfig )
+	{
+		gameLocal.Error( "couldn't load weapon config %s\n", filename );
+		return BLERR_CANNOTLOADWEAPONWEIGHTS;
+	}
+
+	WeaponWeightIndex( ws->weaponweightconfig, ws );
+	return BLERR_NOERROR;
+}
+
+/*
+=====================
+idBotWeaponInfoManager::BotGetWeaponInfo
+=====================
+*/
+void idBotWeaponInfoManager::BotGetWeaponInfo( int weaponstate, int weapon, weaponinfo_t* weaponinfo )
+{
+	bot_weaponstate_t* ws;
+
+	if( !BotValidWeaponNumber( weapon ) )
+	{
+		return;
+	}
+	ws = BotWeaponStateFromHandle( weaponstate );
+	if( !ws )
+	{
+		return;
+	}
+
+	*weaponinfo = this->weaponinfo[weapon];
+}
+
+/*
+=====================
+idBotWeaponInfoManager::BotChooseBestFightWeapon
+=====================
+*/
+int idBotWeaponInfoManager::BotChooseBestFightWeapon( int weaponstate, int* inventory )
+{
+	int i, index, bestweapon;
+	float weight, bestweight;
+	bot_weaponstate_t* ws;
+
+	ws = BotWeaponStateFromHandle( weaponstate );
+	if( !ws )
+	{
+		return 0;
+	}
+
+	//if the bot has no weapon weight configuration
+	if( !ws->weaponweightconfig )
+	{
+		return 0;
+	}
+
+	bestweight = 0;
+	bestweapon = 0;
+	for( i = 0; i < BOT_MAX_WEAPONS; i++ )
+	{
+		if( !weaponinfo[i].valid )
+		{
+			continue;
+		}
+		index = ws->weaponweightindex[i];
+		if( index < 0 )
+		{
+			continue;
+		}
+		weight = botFuzzyWeightManager.FuzzyWeight( inventory, ws->weaponweightconfig, index );
+		if( weight > bestweight )
+		{
+			bestweight = weight;
+			bestweapon = i;
+		}
+	}
+	return bestweapon;
+}
+
+/*
+=====================
+idBotWeaponInfoManager::BotResetWeaponState
+=====================
+*/
+void idBotWeaponInfoManager::BotResetWeaponState( int weaponstate )
+{
+	weightconfig_t* weaponweightconfig;
+	int* weaponweightindex;
+	bot_weaponstate_t* ws;
+
+	ws = BotWeaponStateFromHandle( weaponstate );
+	if( !ws )
+	{
+		return;
+	}
+	weaponweightconfig = ws->weaponweightconfig;
+	weaponweightindex = ws->weaponweightindex;
+
+	//Com_Memset(ws, 0, sizeof(bot_weaponstate_t));
+	ws->weaponweightconfig = weaponweightconfig;
+	memcpy( ws->weaponweightindex, weaponweightindex, sizeof( int ) * BOT_MAX_WEAPONS );
+}
+
+/*
+=====================
+idBotWeaponInfoManager::BotAllocWeaponState
+=====================
+*/
+int idBotWeaponInfoManager::BotAllocWeaponState( void )
+{
+	int i;
+
+	for( i = 1; i <= MAX_CLIENTS; i++ )
+	{
+		if( !botweaponstates[i].inUse )
+		{
+			botweaponstates[i].Reset();
+			botweaponstates[i].inUse = true;
+			return i;
+		}
+	}
+	return 0;
+}
+
+/*
+=====================
+idBotWeaponInfoManager::BotAllocWeaponState
+=====================
+*/
+void idBotWeaponInfoManager::BotFreeWeaponState( int ws )
+{
+	botweaponstates[ws].inUse = false;
+	botweaponstates[ws].Reset();
+}
diff --git a/src/mpgame/bots/Bot_weapons.h b/src/mpgame/bots/Bot_weapons.h
new file mode 100644
index 0000000..c3b4284
--- /dev/null
+++ b/src/mpgame/bots/Bot_weapons.h
@@ -0,0 +1,161 @@
+// Bot_weapons.h
+//
+
+#pragma once
+
+#define BOT_MAX_WEAPONS				32
+
+//projectile flags
+#define PFL_WINDOWDAMAGE			1		//projectile damages through window
+#define PFL_RETURN					2		//set when projectile returns to owner
+//weapon flags
+#define WFL_FIRERELEASED			1		//set when projectile is fired with key-up event
+//damage types
+#define DAMAGETYPE_IMPACT			1		//damage on impact
+#define DAMAGETYPE_RADIAL			2		//radial damage
+#define DAMAGETYPE_VISIBLE			4		//damage to all entities visible to the projectile
+
+struct projectileinfo_t
+{
+	projectileinfo_t()
+	{
+		name = "";
+		model = "";
+		flags = 0;
+		gravity = 0;
+		damage = 0;
+		radius = 0;
+		visdamage = 0;
+		damagetype = 0;
+		healthinc = 0;
+		push = 0;
+		detonation = 0;
+		bounce = 0;
+		bouncefric = 0;
+		bouncestop = 0;
+	}
+	idStr name;
+	idStr model;
+	int flags;
+	float gravity;
+	int damage;
+	float radius;
+	int visdamage;
+	int damagetype;
+	int healthinc;
+	float push;
+	float detonation;
+	float bounce;
+	float bouncefric;
+	float bouncestop;
+};
+
+struct weaponinfo_t
+{
+	weaponinfo_t()
+	{
+		valid = 0;
+		number = 0;
+		name = "";
+		model = "";
+		level = 0;
+		weaponindex = 0;
+		flags = 0;
+		projectile = "";
+		numprojectiles = 0;
+		hspread = 0;
+		vspread = 0;
+		speed = 0;
+		acceleration = 0;
+		recoil.Zero();
+		offset.Zero();
+		angleoffset.Zero();
+		extrazvelocity = 0;
+		ammoamount = 0;
+		ammoindex = 0;
+		activate = 0;
+		reload = 0;
+		spinup = 0;
+		spindown = 0;
+	}
+
+	int valid;					//true if the weapon info is valid
+	int number;									//number of the weapon
+	idStr name;
+	idStr model;
+	int level;
+	int weaponindex;
+	int flags;
+	idStr projectile;
+	int numprojectiles;
+	float hspread;
+	float vspread;
+	float speed;
+	float acceleration;
+	idVec3 recoil;
+	idVec3 offset;
+	idVec3 angleoffset;
+	float extrazvelocity;
+	int ammoamount;
+	int ammoindex;
+	float activate;
+	float reload;
+	float spinup;
+	float spindown;
+	projectileinfo_t proj;						//pointer to the used projectile
+};
+
+struct bot_weaponstate_t
+{
+	bot_weaponstate_t()
+	{
+		Reset();
+	}
+
+	void Reset()
+	{
+		inUse = false;
+		weaponweightconfig = NULL;
+		for( int i = 0; i < BOT_MAX_WEAPONS; i++ )
+		{
+			weaponweightindex[i] = 0;
+		}
+	}
+
+	bool inUse;
+	weightconfig_t* weaponweightconfig;
+	int weaponweightindex[BOT_MAX_WEAPONS];							//weapon weight index
+};
+
+//
+// idBotWeaponInfoManager
+//
+class idBotWeaponInfoManager
+{
+public:
+	void	Init( void );
+
+	int		BotLoadWeaponWeights( int weaponstate, char* filename );
+	void	BotGetWeaponInfo( int weaponstate, int weapon, weaponinfo_t* weaponinfo );
+	int		BotChooseBestFightWeapon( int weaponstate, int* inventory );
+	void	BotResetWeaponState( int weaponstate );
+	int		BotAllocWeaponState( void );
+	void	BotFreeWeaponState( int ws );
+private:
+	int		BotValidWeaponNumber( int weaponnum );
+	bot_weaponstate_t* BotWeaponStateFromHandle( int handle );
+	void	WeaponWeightIndex( weightconfig_t* wwc, bot_weaponstate_t* weaponState );
+	void	BotFreeWeaponWeights( int weaponstate );
+private:
+	void	LoadWeaponConfig( char* filename );
+
+	void	ParseWeaponInfo( idParser& parser, weaponinfo_t& newWeaponInfo );
+	void	ParseProjectileInfo( idParser& parser, projectileinfo_t& newProjectileInfo );
+private:
+	idList<projectileinfo_t> projectileinfo;
+	bot_weaponstate_t botweaponstates[MAX_CLIENTS + 1];
+
+	weaponinfo_t weaponinfo[BOT_MAX_WEAPONS];
+};
+
+extern idBotWeaponInfoManager botWeaponInfoManager;
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_weights.cpp b/src/mpgame/bots/Bot_weights.cpp
new file mode 100644
index 0000000..77fd4d0
--- /dev/null
+++ b/src/mpgame/bots/Bot_weights.cpp
@@ -0,0 +1,915 @@
+// Bot_wieghts.cpp
+//
+
+
+
+
+#include "../Game_local.h"
+
+idBotFuzzyWeightManager botFuzzyWeightManager;
+
+/*
+========================
+idBotFuzzyWeightManager::Init
+========================
+*/
+void idBotFuzzyWeightManager::Init( void )
+{
+	memset( &fuzzyseperators[0], 0, sizeof( fuzzyseperators ) );
+}
+
+/*
+========================
+idBotFuzzyWeightManager::AllocFuzzyWeight
+========================
+*/
+fuzzyseperator_t* idBotFuzzyWeightManager::AllocFuzzyWeight( void )
+{
+	for( int i = 0; i < MAX_FUZZY_OPERATORS; i++ )
+	{
+		if( fuzzyseperators[i].inUse == false )
+		{
+			memset( &fuzzyseperators[i], 0, sizeof( fuzzyseperator_t ) );
+			fuzzyseperators[i].inUse = true;
+			return &fuzzyseperators[i];
+		}
+	}
+
+	gameLocal.Error( "AllocFuzzyWeight: Not enough fuzzy weights\n" );
+	return NULL;
+}
+
+/*
+========================
+idBotFuzzyWeightManager::ReadValue
+========================
+*/
+bool idBotFuzzyWeightManager::ReadValue( idParser& source, float* value )
+{
+	idToken token;
+
+	if( !source.ReadToken( &token ) )
+	{
+		return false;
+	}
+
+	if( token == "-" )
+	{
+		source.Warning( "negative value set to zero\n" );
+		if( !source.ExpectTokenType( TT_NUMBER, 0, &token ) )
+		{
+			return false;
+		}
+	}
+
+	if( token.type != TT_NUMBER )
+	{
+		source.Error( "invalid return value %s\n", token.c_str() );
+		return false;
+	}
+
+	*value = token.GetFloatValue();
+
+	return true;
+}
+
+/*
+===================
+idBotFuzzyWeightManager::ReadFuzzyWeight
+===================
+*/
+int idBotFuzzyWeightManager::ReadFuzzyWeight( idParser& source, fuzzyseperator_t* fs )
+{
+	if( source.CheckTokenString( "balance" ) )
+	{
+		fs->type = WT_BALANCE;
+
+		if( !source.ExpectTokenString( "(" ) )
+		{
+			return false;
+		}
+
+		if( !ReadValue( source, &fs->weight ) )
+		{
+			return false;
+		}
+
+		if( !source.ExpectTokenString( "," ) )
+		{
+			return false;
+		}
+
+		if( !ReadValue( source, &fs->minweight ) )
+		{
+			return false;
+		}
+
+		if( !source.ExpectTokenString( "," ) )
+		{
+			return false;
+		}
+
+		if( !ReadValue( source, &fs->maxweight ) )
+		{
+			return false;
+		}
+
+		if( !source.ExpectTokenString( ")" ) )
+		{
+			return false;
+		}
+	}
+	else
+	{
+		fs->type = 0;
+
+		if( !ReadValue( source, &fs->weight ) )
+		{
+			return false;
+		}
+		fs->minweight = fs->weight;
+		fs->maxweight = fs->weight;
+	}
+
+	if( !source.ExpectTokenString( ";" ) )
+	{
+		return false;
+	}
+
+	return true;
+}
+
+/*
+===================
+idBotFuzzyWeightManager::FreeFuzzySeperators_r
+===================
+*/
+void idBotFuzzyWeightManager::FreeFuzzySeperators_r( fuzzyseperator_t* fs )
+{
+	if( !fs )
+	{
+		return;
+	}
+	if( fs->child )
+	{
+		FreeFuzzySeperators_r( fs->child );
+	}
+	if( fs->next )
+	{
+		FreeFuzzySeperators_r( fs->next );
+	}
+
+	fs->inUse = false;
+}
+
+/*
+===================
+FreeWeightConfig2
+===================
+*/
+void idBotFuzzyWeightManager::FreeWeightConfig2( weightconfig_t* config )
+{
+	int i;
+
+	for( i = 0; i < config->numweights; i++ )
+	{
+		FreeFuzzySeperators_r( config->weights[i].firstseperator );
+		// jmarshall - todo
+		//if (config->weights[i].name)
+		//	FreeMemory(config->weights[i].name);
+		// jmarshall end
+	}
+
+}
+
+/*
+===================
+idBotFuzzyWeightManager::FreeWeightConfig
+===================
+*/
+void idBotFuzzyWeightManager::FreeWeightConfig( weightconfig_t* config )
+{
+	//if (!LibVarGetValue("bot_reloadcharacters"))
+	//	return;
+
+	FreeWeightConfig2( config );
+}
+
+/*
+===================
+idBotFuzzyWeightManager::ReadFuzzySeperators_r
+===================
+*/
+fuzzyseperator_t* idBotFuzzyWeightManager::ReadFuzzySeperators_r( idParser& source )
+{
+	int newindent, index, founddefault;
+	bool def;
+	idToken token;
+	fuzzyseperator_t* fs, * lastfs, * firstfs;
+
+	founddefault = false;
+	firstfs = NULL;
+	lastfs = NULL;
+	if( !source.ExpectTokenString( "(" ) )
+	{
+		return NULL;
+	}
+
+	if( !source.ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) )
+	{
+		return NULL;
+	}
+
+	index = token.GetIntValue();
+
+	if( !source.ExpectTokenString( ")" ) )
+	{
+		return NULL;
+	}
+
+	if( !source.ExpectTokenString( "{" ) )
+	{
+		return NULL;
+	}
+
+	if( !source.ExpectAnyToken( &token ) )
+	{
+		return NULL;
+	}
+
+	do
+	{
+		def = ( token == "default" ); //!strcmp(token.string, "default");
+		if( def || ( token == "case" ) )
+		{
+			fs = AllocFuzzyWeight();
+			fs->index = index;
+			if( lastfs )
+			{
+				lastfs->next = fs;
+			}
+			else
+			{
+				firstfs = fs;
+			}
+			lastfs = fs;
+			if( def )
+			{
+				if( founddefault )
+				{
+					FreeFuzzySeperators_r( firstfs );
+					gameLocal.Error( "switch already has a default\n" );
+					return NULL;
+				}
+				fs->value = MAX_INVENTORYVALUE;
+				founddefault = true;
+			}
+			else
+			{
+				if( !source.ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) )
+				{
+					FreeFuzzySeperators_r( firstfs );
+					return NULL;
+				}
+
+				fs->value = token.GetIntValue();
+			}
+
+			if( !source.ExpectTokenString( ":" ) || !source.ExpectAnyToken( &token ) )
+			{
+				FreeFuzzySeperators_r( firstfs );
+				return NULL;
+			}
+
+			newindent = false;
+			if( token == "{" )
+			{
+				newindent = true;
+				if( !source.ExpectAnyToken( &token ) )
+				{
+					FreeFuzzySeperators_r( firstfs );
+					return NULL;
+				}
+			}
+
+			if( token == "return" )
+			{
+				if( !ReadFuzzyWeight( source, fs ) )
+				{
+					FreeFuzzySeperators_r( firstfs );
+					return NULL;
+				}
+			}
+			else if( token == "switch" )
+			{
+				fs->child = ReadFuzzySeperators_r( source );
+				if( !fs->child )
+				{
+					FreeFuzzySeperators_r( firstfs );
+					return NULL;
+				}
+			}
+			else
+			{
+				gameLocal.Error( "invalid name %s\n", token.c_str() );
+				return NULL;
+			}
+
+			if( newindent )
+			{
+				if( !source.ExpectTokenString( "}" ) )
+				{
+					FreeFuzzySeperators_r( firstfs );
+					return NULL;
+				}
+			}
+		}
+		else
+		{
+			gameLocal.Error( "invalid name %s\n", token.c_str() );
+			FreeFuzzySeperators_r( firstfs );
+			return NULL;
+		}
+
+		if( !source.ExpectAnyToken( &token ) )
+		{
+			FreeFuzzySeperators_r( firstfs );
+			return NULL;
+		}
+	}
+	while( token != "}" );
+
+	if( !founddefault )
+	{
+		source.Warning( "switch without default\n" );
+		fs = AllocFuzzyWeight();
+		fs->index = index;
+		fs->value = MAX_INVENTORYVALUE;
+		fs->weight = 0;
+		fs->next = NULL;
+		fs->child = NULL;
+		if( lastfs )
+		{
+			lastfs->next = fs;
+		}
+		else
+		{
+			firstfs = fs;
+		}
+		lastfs = fs;
+	}
+
+	return firstfs;
+}
+
+/*
+=====================
+idBotFuzzyWeightManager::ReadWeightConfig
+=====================
+*/
+weightconfig_t* idBotFuzzyWeightManager::ReadWeightConfig( char* filename )
+{
+	int newindent, avail = 0, n;
+	idToken token;
+	idParser source;
+	fuzzyseperator_t* fs;
+	weightconfig_t* config = NULL;
+
+	avail = -1;
+	for( n = 0; n < MAX_WEIGHT_FILES; n++ )
+	{
+		config = &weightFileList[n];
+
+		if( config->inUse )
+		{
+			continue;
+		}
+
+		if( config->filename == filename )
+		{
+			return config;
+		}
+
+		avail = n;
+		break;
+	}
+
+	if( avail == -1 )
+	{
+		gameLocal.Error( "weightFileList was full trying to load %s\n", filename );
+		return NULL;
+	}
+
+	rvmScopedLexerBaseFolder scopedBaseFolder( BOTFILESBASEFOLDER );
+
+	if( !source.LoadFile( filename ) )
+	{
+		gameLocal.Error( "couldn't load %s\n", filename );
+		return NULL;
+	}
+
+	config = &weightFileList[avail];
+	config->Reset();
+
+	config->filename = filename;
+
+	//parse the item config file
+	while( source.ReadToken( &token ) )
+	{
+		if( token == "weight" )
+		{
+			if( config->numweights >= MAX_WEIGHTS )
+			{
+				gameLocal.Error( "too many fuzzy weights\n" );
+				break;
+			}
+
+			if( !source.ExpectTokenType( TT_STRING, 0, &token ) )
+			{
+				FreeWeightConfig( config );
+				return NULL;
+			}
+
+			token.StripDoubleQuotes();
+			config->weights[config->numweights].name = token.c_str();
+
+			if( !source.ExpectAnyToken( &token ) )
+			{
+				FreeWeightConfig( config );
+				return NULL;
+			}
+
+			newindent = false;
+			if( token == "{" )
+			{
+				newindent = true;
+				if( !source.ExpectAnyToken( &token ) )
+				{
+					FreeWeightConfig( config );
+					return NULL;
+				}
+			}
+
+			if( token == "switch" )
+			{
+				fs = ReadFuzzySeperators_r( source );
+				if( !fs )
+				{
+					FreeWeightConfig( config );
+					return NULL;
+				}
+
+				config->weights[config->numweights].firstseperator = fs;
+			}
+			else if( token == "return" )
+			{
+				fs = ( fuzzyseperator_t* )AllocFuzzyWeight();
+				fs->index = 0;
+				fs->value = MAX_INVENTORYVALUE;
+				fs->next = NULL;
+				fs->child = NULL;
+				if( !ReadFuzzyWeight( source, fs ) )
+				{
+					FreeWeightConfig( config );
+					return NULL;
+				}
+				config->weights[config->numweights].firstseperator = fs;
+			}
+			else
+			{
+				gameLocal.Error( "invalid name %s\n", token.c_str() );
+				FreeWeightConfig( config );
+				return NULL;
+			}
+
+			if( newindent )
+			{
+				if( !source.ExpectTokenString( "}" ) )
+				{
+					FreeWeightConfig( config );
+					return NULL;
+				}
+			}
+			config->numweights++;
+		}
+		else
+		{
+			gameLocal.Error( "invalid name %s\n", token.c_str() );
+			FreeWeightConfig( config );
+			return NULL;
+		}
+	}
+
+	//if the file was located in a pak file
+	common->Printf( "idBotFuzzyWeightManager::ReadWeightConfig: loaded %s\n", filename );
+	config->inUse = true;
+	return config;
+}
+
+/*
+==================
+idBotFuzzyWeightManager::FindFuzzyWeight
+==================
+*/
+int idBotFuzzyWeightManager::FindFuzzyWeight( weightconfig_t* wc, char* name )
+{
+	int i;
+
+	for( i = 0; i < wc->numweights; i++ )
+	{
+		if( wc->weights[i].name == name )
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+/*
+==================
+idBotFuzzyWeightManager::FuzzyWeight_r
+==================
+*/
+float idBotFuzzyWeightManager::FuzzyWeight_r( int* inventory, fuzzyseperator_t* fs )
+{
+	float scale, w1, w2;
+
+	if( inventory[fs->index] < fs->value )
+	{
+		if( fs->child )
+		{
+			return FuzzyWeight_r( inventory, fs->child );
+		}
+		else
+		{
+			return fs->weight;
+		}
+	}
+	else if( fs->next )
+	{
+		if( inventory[fs->index] < fs->next->value )
+		{
+			//first weight
+			if( fs->child )
+			{
+				w1 = FuzzyWeight_r( inventory, fs->child );
+			}
+			else
+			{
+				w1 = fs->weight;
+			}
+
+			//second weight
+			if( fs->next->child )
+			{
+				w2 = FuzzyWeight_r( inventory, fs->next->child );
+			}
+			else
+			{
+				w2 = fs->next->weight;
+			}
+
+			//the scale factor
+			scale = ( inventory[fs->index] - fs->value ) / ( fs->next->value - fs->value );
+
+			//scale between the two weights
+			return scale * w1 + ( 1 - scale ) * w2;
+		}
+		return FuzzyWeight_r( inventory, fs->next );
+	}
+	return fs->weight;
+}
+
+/*
+============================
+idBotFuzzyWeightManager::FuzzyWeightUndecided_r
+============================
+*/
+float idBotFuzzyWeightManager::FuzzyWeightUndecided_r( int* inventory, fuzzyseperator_t* fs )
+{
+	float scale, w1, w2;
+
+	if( inventory[fs->index] < fs->value )
+	{
+		if( fs->child )
+		{
+			return FuzzyWeightUndecided_r( inventory, fs->child );
+		}
+		else
+		{
+			return fs->minweight + rvmBotUtil::random() * ( fs->maxweight - fs->minweight );
+		}
+	}
+	else if( fs->next )
+	{
+		if( inventory[fs->index] < fs->next->value )
+		{
+			//first weight
+			if( fs->child )
+			{
+				w1 = FuzzyWeightUndecided_r( inventory, fs->child );
+			}
+			else
+			{
+				w1 = fs->minweight + rvmBotUtil::random() * ( fs->maxweight - fs->minweight );
+			}
+
+			//second weight
+			if( fs->next->child )
+			{
+				w2 = FuzzyWeight_r( inventory, fs->next->child );
+			}
+			else
+			{
+				w2 = fs->next->minweight + rvmBotUtil::random() * ( fs->next->maxweight - fs->next->minweight );
+			}
+
+			//the scale factor
+			scale = ( inventory[fs->index] - fs->value ) / ( fs->next->value - fs->value );
+
+			//scale between the two weights
+			return scale * w1 + ( 1 - scale ) * w2;
+		}
+		return FuzzyWeightUndecided_r( inventory, fs->next );
+	}
+	return fs->weight;
+}
+
+/*
+=================
+idBotFuzzyWeightManager::FuzzyWeight
+=================
+*/
+float idBotFuzzyWeightManager::FuzzyWeight( int* inventory, weightconfig_t* wc, int weightnum )
+{
+	return FuzzyWeight_r( inventory, wc->weights[weightnum].firstseperator );
+}
+
+/*
+=================
+idBotFuzzyWeightManager::FuzzyWeightUndecided
+=================
+*/
+float idBotFuzzyWeightManager::FuzzyWeightUndecided( int* inventory, weightconfig_t* wc, int weightnum )
+{
+	return FuzzyWeightUndecided_r( inventory, wc->weights[weightnum].firstseperator );
+}
+
+/*
+====================
+idBotFuzzyWeightManager::EvolveFuzzySeperator_r
+====================
+*/
+void idBotFuzzyWeightManager::EvolveFuzzySeperator_r( fuzzyseperator_t* fs )
+{
+	if( fs->child )
+	{
+		EvolveFuzzySeperator_r( fs->child );
+	}
+	else if( fs->type == WT_BALANCE )
+	{
+		//every once in a while an evolution leap occurs, mutation
+		if( rvmBotUtil::random() < 0.01 )
+		{
+			fs->weight += rvmBotUtil::crandom() * ( fs->maxweight - fs->minweight );
+		}
+		else
+		{
+			fs->weight += rvmBotUtil::crandom() * ( fs->maxweight - fs->minweight ) * 0.5;
+		}
+
+		//modify bounds if necesary because of mutation
+		if( fs->weight < fs->minweight )
+		{
+			fs->minweight = fs->weight;
+		}
+		else if( fs->weight > fs->maxweight )
+		{
+			fs->maxweight = fs->weight;
+		}
+	}
+	if( fs->next )
+	{
+		EvolveFuzzySeperator_r( fs->next );
+	}
+}
+
+/*
+====================
+idBotFuzzyWeightManager::EvolveWeightConfig
+====================
+*/
+void idBotFuzzyWeightManager::EvolveWeightConfig( weightconfig_t* config )
+{
+	int i;
+
+	for( i = 0; i < config->numweights; i++ )
+	{
+		EvolveFuzzySeperator_r( config->weights[i].firstseperator );
+	}
+}
+
+/*
+====================
+idBotFuzzyWeightManager::ScaleWeight
+====================
+*/
+void idBotFuzzyWeightManager::ScaleFuzzySeperator_r( fuzzyseperator_t* fs, float scale )
+{
+	if( fs->child )
+	{
+		ScaleFuzzySeperator_r( fs->child, scale );
+	}
+	else if( fs->type == WT_BALANCE )
+	{
+		fs->weight = ( fs->maxweight + fs->minweight ) * scale;
+
+		//get the weight between bounds
+		if( fs->weight < fs->minweight )
+		{
+			fs->weight = fs->minweight;
+		}
+		else if( fs->weight > fs->maxweight )
+		{
+			fs->weight = fs->maxweight;
+		}
+	}
+	if( fs->next )
+	{
+		ScaleFuzzySeperator_r( fs->next, scale );
+	}
+}
+
+/*
+====================
+idBotFuzzyWeightManager::ScaleWeight
+====================
+*/
+void idBotFuzzyWeightManager::ScaleWeight( weightconfig_t* config, char* name, float scale )
+{
+	int i;
+
+	if( scale < 0 )
+	{
+		scale = 0;
+	}
+	else if( scale > 1 )
+	{
+		scale = 1;
+	}
+	for( i = 0; i < config->numweights; i++ )
+	{
+		if( config->weights[i].name == name )
+		{
+			ScaleFuzzySeperator_r( config->weights[i].firstseperator, scale );
+			break;
+		}
+	}
+}
+
+/*
+====================
+idBotFuzzyWeightManager::ScaleFuzzySeperatorBalanceRange_r
+====================
+*/
+void idBotFuzzyWeightManager::ScaleFuzzySeperatorBalanceRange_r( fuzzyseperator_t* fs, float scale )
+{
+	if( fs->child )
+	{
+		ScaleFuzzySeperatorBalanceRange_r( fs->child, scale );
+	}
+	else if( fs->type == WT_BALANCE )
+	{
+		float mid = ( fs->minweight + fs->maxweight ) * 0.5;
+		//get the weight between bounds
+		fs->maxweight = mid + ( fs->maxweight - mid ) * scale;
+		fs->minweight = mid + ( fs->minweight - mid ) * scale;
+		if( fs->maxweight < fs->minweight )
+		{
+			fs->maxweight = fs->minweight;
+		}
+	}
+	if( fs->next )
+	{
+		ScaleFuzzySeperatorBalanceRange_r( fs->next, scale );
+	}
+}
+
+/*
+====================
+idBotFuzzyWeightManager::ScaleFuzzyBalanceRange
+====================
+*/
+void idBotFuzzyWeightManager::ScaleFuzzyBalanceRange( weightconfig_t* config, float scale )
+{
+	int i;
+
+	if( scale < 0 )
+	{
+		scale = 0;
+	}
+	else if( scale > 100 )
+	{
+		scale = 100;
+	}
+	for( i = 0; i < config->numweights; i++ )
+	{
+		ScaleFuzzySeperatorBalanceRange_r( config->weights[i].firstseperator, scale );
+	}
+}
+
+/*
+====================
+idBotFuzzyWeightManager::InterbreedFuzzySeperator_r
+====================
+*/
+int idBotFuzzyWeightManager::InterbreedFuzzySeperator_r( fuzzyseperator_t* fs1, fuzzyseperator_t* fs2, fuzzyseperator_t* fsout )
+{
+	if( fs1->child )
+	{
+		if( !fs2->child || !fsout->child )
+		{
+			gameLocal.Error( "cannot interbreed weight configs, unequal child\n" );
+			return false;
+		}
+		if( !InterbreedFuzzySeperator_r( fs2->child, fs2->child, fsout->child ) )
+		{
+			return false;
+		}
+	}
+	else if( fs1->type == WT_BALANCE )
+	{
+		if( fs2->type != WT_BALANCE || fsout->type != WT_BALANCE )
+		{
+			gameLocal.Error( "cannot interbreed weight configs, unequal balance\n" );
+			return false;
+		}
+		fsout->weight = ( fs1->weight + fs2->weight ) / 2;
+		if( fsout->weight > fsout->maxweight )
+		{
+			fsout->maxweight = fsout->weight;
+		}
+		if( fsout->weight > fsout->minweight )
+		{
+			fsout->minweight = fsout->weight;
+		}
+	}
+	if( fs1->next )
+	{
+		if( !fs2->next || !fsout->next )
+		{
+			gameLocal.Error( "cannot interbreed weight configs, unequal next\n" );
+			return false;
+		}
+		if( !InterbreedFuzzySeperator_r( fs1->next, fs2->next, fsout->next ) )
+		{
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+====================
+idBotFuzzyWeightManager::InterbreedWeightConfigs
+====================
+*/
+void idBotFuzzyWeightManager::InterbreedWeightConfigs( weightconfig_t* config1, weightconfig_t* config2, weightconfig_t* configout )
+{
+	int i;
+
+	if( config1->numweights != config2->numweights ||
+			config1->numweights != configout->numweights )
+	{
+		gameLocal.Error( "cannot interbreed weight configs, unequal numweights\n" );
+		return;
+	}
+
+	for( i = 0; i < config1->numweights; i++ )
+	{
+		InterbreedFuzzySeperator_r( config1->weights[i].firstseperator,
+									config2->weights[i].firstseperator,
+									configout->weights[i].firstseperator );
+	}
+}
+
+
+/*
+=======================
+idBotFuzzyWeightManager::BotShutdownWeights
+=======================
+*/
+void idBotFuzzyWeightManager::BotShutdownWeights( void )
+{
+	int i;
+
+	for( i = 0; i < MAX_WEIGHT_FILES; i++ )
+	{
+		weightFileList[i].inUse = false;
+		//if (weightFileList[i])
+		//{
+		//	FreeWeightConfig2(weightFileList[i]);
+		//	weightFileList[i] = NULL;
+		//}
+	}
+}
\ No newline at end of file
diff --git a/src/mpgame/bots/Bot_weights.h b/src/mpgame/bots/Bot_weights.h
new file mode 100644
index 0000000..20a5502
--- /dev/null
+++ b/src/mpgame/bots/Bot_weights.h
@@ -0,0 +1,53 @@
+// Bot_weights.h
+//
+
+#define MAX_INVENTORYVALUE			999999
+#define EVALUATERECURSIVELY
+
+#define MAX_WEIGHT_FILES			128
+#define MAX_FUZZY_OPERATORS			8192
+
+class idBotFuzzyWeightManager
+{
+public:
+	// Init the fuzzy weight manager.
+	void Init( void );
+
+	// Parses a weight config file.
+	weightconfig_t* ReadWeightConfig( char* filename );
+
+	void BotShutdownWeights( void );
+
+	// Fuzzy Weight simulation functions.
+	int FindFuzzyWeight( weightconfig_t* wc, char* name );
+	float FuzzyWeight( int* inventory, weightconfig_t* wc, int weightnum );
+	float FuzzyWeightUndecided( int* inventory, weightconfig_t* wc, int weightnum );
+	void EvolveFuzzySeperator_r( fuzzyseperator_t* fs );
+	void EvolveWeightConfig( weightconfig_t* config );
+	void ScaleWeight( weightconfig_t* config, char* name, float scale );
+	void ScaleFuzzyBalanceRange( weightconfig_t* config, float scale );
+	void InterbreedWeightConfigs( weightconfig_t* config1, weightconfig_t* config2, weightconfig_t* configout );
+	void FreeWeightConfig( weightconfig_t* config );
+private:
+	fuzzyseperator_t* AllocFuzzyWeight( void );
+	bool ReadValue( idParser& source, float* value );
+	int ReadFuzzyWeight( idParser& source, fuzzyseperator_t* fs );
+
+	int InterbreedFuzzySeperator_r( fuzzyseperator_t* fs1, fuzzyseperator_t* fs2, fuzzyseperator_t* fsout );
+
+	fuzzyseperator_t* ReadFuzzySeperators_r( idParser& source );
+
+	void FreeWeightConfig2( weightconfig_t* config );
+	void FreeFuzzySeperators_r( fuzzyseperator_t* fs );
+
+	void ScaleFuzzySeperatorBalanceRange_r( fuzzyseperator_t* fs, float scale );
+
+	void ScaleFuzzySeperator_r( fuzzyseperator_t* fs, float scale );
+	float FuzzyWeight_r( int* inventory, fuzzyseperator_t* fs );
+	float FuzzyWeightUndecided_r( int* inventory, fuzzyseperator_t* fs );
+
+	weightconfig_t weightFileList[MAX_WEIGHT_FILES];
+	fuzzyseperator_t fuzzyseperators[MAX_FUZZY_OPERATORS];
+};
+
+extern idBotFuzzyWeightManager botFuzzyWeightManager;
\ No newline at end of file
diff --git a/src/mpgame/gamesys/Class.cpp b/src/mpgame/gamesys/Class.cpp
index 4f8f0b8..6cd1268 100644
--- a/src/mpgame/gamesys/Class.cpp
+++ b/src/mpgame/gamesys/Class.cpp
@@ -1434,8 +1434,8 @@ void idClass::RegisterClasses( void )
 	REGISTER(riDeadZonePowerup); // ..\..\code\game\Item.cpp
 	REGISTER(WeaponNapalmGun);	// ..\..\code\game\weapon\WeaponNapalmGun.cpp
 // RITUAL END
+	REGISTER(rvmBot);
 #undef REGISTER
 }
 
 // RAVEN END
-
diff --git a/src/mpgame/gamesys/Class.h b/src/mpgame/gamesys/Class.h
index 8d7375d..f4c71fc 100644
--- a/src/mpgame/gamesys/Class.h
+++ b/src/mpgame/gamesys/Class.h
@@ -44,7 +44,7 @@ public:
 	idEventArg( const trace_t *data )			{ type = D_EVENT_TRACE; value = reinterpret_cast<intptr_t>( data ); };
 
 // jmarshall - this can't be called from doomscript!
-#if defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__)
+#if defined(_WIN64) || defined(__x86_64__) || defined(__aarch64__) || defined(__EMSCRIPTEN__)
 	idEventArg(intptr_t data) { type = D_EVENT_INTEGER64bit; value = data; };
 #else
 	idEventArg(long long data) { type = D_EVENT_INTEGER64bit; value = static_cast<intptr_t>(data); };
@@ -89,7 +89,7 @@ public:																	\
 // RAVEN BEGIN
 // jnewquist: Use accessor for static class type 
 #define CLASS_PROTOTYPE( nameofclass )									\
-private:																\
+public:																\
 	static	idTypeInfo						*Type;						\
 public:																	\
 	static	void							RegisterClass( void );		\
@@ -324,6 +324,18 @@ public:
 // jnewquist: Use accessor for static class type 
 	bool						IsType( const idTypeInfo *c ) const { return IsType(*c); }
 // RAVEN END
+
+	template< typename T >
+	T* Cast( void ) {
+		return this ? ( IsType( T::Type ) ? static_cast<T*>( this ) : NULL ) : NULL;
+	}
+
+	template< typename T >
+	const T* Cast( void ) const {
+		return this ? ( IsType( T::Type ) ? static_cast<const T*>( this ) : NULL ) : NULL;
+	}
+
+	virtual void				StateThreadChanged( void ) {}
 	const char *				GetClassname( void ) const;
 	const char *				GetSuperclass( void ) const;
 	void						FindUninitializedMemory( void );
diff --git a/src/mpgame/gamesys/SysCmds.cpp b/src/mpgame/gamesys/SysCmds.cpp
index 916c33a..b477a37 100644
--- a/src/mpgame/gamesys/SysCmds.cpp
+++ b/src/mpgame/gamesys/SysCmds.cpp
@@ -3338,6 +3338,23 @@ void Cmd_ListMaps_f( const idCmdArgs& args ) {
 	gameLocal.mpGame.ListMaps();
 }
 
+void Cmd_AddBot_f( const idCmdArgs& args ) {
+	if ( args.Argc() < 2 ) {
+		common->Warning( "USAGE: addbot <botfile>, for example addbot major\n" );
+		return;
+	}
+	gameLocal.AddBot( args.Argv( 1 ) );
+}
+
+void Cmd_FillBots_f( const idCmdArgs& args ) {
+	const int maxPlayers = idMath::ClampInt( 1, 16, gameLocal.serverInfo.GetInt( "si_maxPlayers", "8" ) );
+	const int target = args.Argc() > 1
+		? idMath::ClampInt( 0, maxPlayers, atoi( args.Argv( 1 ) ) )
+		: Min( maxPlayers, 8 );
+	bot_minplayers.SetInteger( target );
+	common->Printf( "bot_minplayers set to %d bots\n", target );
+}
+
 /*
 =================
 idGameLocal::InitConsoleCommands
@@ -3347,6 +3364,8 @@ so it can perform tab completion
 =================
 */
 void idGameLocal::InitConsoleCommands( void ) {
+	cmdSystem->AddCommand( "addbot", Cmd_AddBot_f, CMD_FL_GAME, "adds a multiplayer bot" );
+	cmdSystem->AddCommand( "fillbots", Cmd_FillBots_f, CMD_FL_GAME, "maintains a minimum bot population", NULL );
 // RAVEN BEGIN
 // jscott: typeinfo gone - didn't work, it was unfinished
 //	cmdSystem->AddCommand( "listTypeInfo",			ListTypeInfo_f,				CMD_FL_GAME,				"list type info" );
diff --git a/src/mpgame/gamesys/SysCvar.cpp b/src/mpgame/gamesys/SysCvar.cpp
index 5e86856..47abf89 100644
--- a/src/mpgame/gamesys/SysCvar.cpp
+++ b/src/mpgame/gamesys/SysCvar.cpp
@@ -62,6 +62,7 @@ idCVar si_mapCycle(					"si_mapCycle",				"",						CVAR_GAME | CVAR_SERVERINFO |
 idCVar si_maxPlayers(				"si_maxPlayers",			"12",			CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "max number of players allowed on the server", 1, 16 );
 // ddynerman: min players to start
 idCVar si_minPlayers(				"si_minPlayers",			"1",			CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "min number of players to start a game (only when warmup is enabled)", 1, 16 );
+idCVar bot_minplayers(				"bot_minplayers",			"0",			CVAR_GAME | CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_INTEGER, "minimum bot population maintained while slots are available", 0, 16 );
 // ddynerman: CTF
 idCVar si_captureLimit(				"si_captureLimit",			"5",			CVAR_GAME | CVAR_SERVERINFO | PC_CVAR_ARCHIVE | CVAR_INTEGER, "score limit for CTF", 1, MP_PLAYER_MAXFRAGS );
 // shouchard:  for tourney
@@ -656,4 +657,3 @@ idCVar pm_powerslide(						"pm_powerslide",					"0.09",			CVAR_GAME | CVAR_FLOAT
 idCVar g_playerLean(						"g_playerLean",						"1",			CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale down or disable client-side player lean" );
 
 idCVar net_clientPredictWeaponSwitch(		"net_clientPredictWeaponSwitch",	"1",			CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "predict weapon switches locally (most noticeable on high ping servers)" );
-
diff --git a/src/mpgame/gamesys/SysCvar.h b/src/mpgame/gamesys/SysCvar.h
index 3b0492e..2a96c1d 100644
--- a/src/mpgame/gamesys/SysCvar.h
+++ b/src/mpgame/gamesys/SysCvar.h
@@ -391,6 +391,7 @@ extern idCVar	si_map;
 extern idCVar	si_mapCycle;
 extern idCVar	si_spectators;
 extern idCVar	si_minPlayers;
+extern idCVar	bot_minplayers;
 // RAVEN BEGIN
 // shouchard:  CTF
 extern idCVar	si_captureLimit;
diff --git a/src/mpgame/physics/Clip.cpp b/src/mpgame/physics/Clip.cpp
index 2802df8..758173d 100644
--- a/src/mpgame/physics/Clip.cpp
+++ b/src/mpgame/physics/Clip.cpp
@@ -2209,3 +2209,11 @@ void idClip::UpdateDynamicContents( idClipModel* clipModel ) {
 	}
 }
 // RAVEN END
+/*
+============
+idClip::PointContents
+============
+*/
+int idClip::PointContents( const idVec3 p ) {
+	return collisionModelManager->Contents( p, NULL, mat3_identity, -1, world, vec3_origin, mat3_identity );
+}
diff --git a/src/mpgame/physics/Clip.h b/src/mpgame/physics/Clip.h
index b7bf837..e0ea722 100644
--- a/src/mpgame/physics/Clip.h
+++ b/src/mpgame/physics/Clip.h
@@ -238,6 +238,7 @@ public:
 
 	void					Init( void );
 	void					Shutdown( void );
+	int						PointContents( const idVec3 p );
 
 	// clip versus the rest of the world
 // RAVEN BEGIN
diff --git a/src/mpgame/vehicle/Vehicle.cpp b/src/mpgame/vehicle/Vehicle.cpp
index 79dc216..332ef6d 100644
--- a/src/mpgame/vehicle/Vehicle.cpp
+++ b/src/mpgame/vehicle/Vehicle.cpp
@@ -558,7 +558,7 @@ void rvVehicle::Think ( void ) {
 
 		// If the vehicle is flipped then kick out all drivers
 		if ( HasDrivers() && IsFlipped ( ) && GetPhysics()->GetLinearVelocity ( ).LengthSqr() < (100.0f * 100.0f)) {
-			if ( spawnArgs.GetBool( "locked_flip_death", false ) ) {
+			if ( spawnArgs.GetBool( "locked_flip_death", "0" ) ) {
 				Killed( this, this, 999999999, GetPhysics()->GetLinearVelocity(), 0 );
 			} else if ( vfl.flipEject ) {
 				for ( i = 0; i < positions.Num(); i ++ ) {
