diff --git a/src/framework/async/AsyncServer.cpp b/src/framework/async/AsyncServer.cpp
index 74807767..d27d9d57 100644
--- a/src/framework/async/AsyncServer.cpp
+++ b/src/framework/async/AsyncServer.cpp
@@ -325,6 +325,8 @@ void idAsyncServer::ExecuteMapChange( void ) {
 	idStr		mapName;
 	findFile_t	ff;
 	bool		addonReload = false;
+	bool		restartBots[MAX_ASYNC_CLIENTS] = { false };
+	idDict		botUserInfo[MAX_ASYNC_CLIENTS];
 	//char		bestGameType[ MAX_STRING_CHARS ];
 
 	assert( active );
@@ -416,10 +418,18 @@ void idAsyncServer::ExecuteMapChange( void ) {
 	// re-initialize all connected clients for the new map
 	for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
 		if ( clients[i].clientState >= SCS_PUREWAIT && i != localClientNum ) {
+			restartBots[i] = clients[i].channel.GetRemoteAddress().type == NA_BOT;
+			if ( restartBots[i] ) {
+				botUserInfo[i] = sessLocal.mapSpawnData.userInfo[i];
+			}
 
 			InitClient( i, clients[i].clientId, clients[i].clientRate );
 
-			SendGameInitToClient( i );
+			if ( restartBots[i] ) {
+				sessLocal.mapSpawnData.userInfo[i] = botUserInfo[i];
+			} else {
+				SendGameInitToClient( i );
+			}
 
 			if ( sessLocal.mapSpawnData.serverInfo.GetBool( "si_pure" ) ) {
 				clients[ i ].clientState = SCS_PUREWAIT;
@@ -446,6 +456,17 @@ void idAsyncServer::ExecuteMapChange( void ) {
 		game->SetLocalClient( -1 );
 	}
 
+	// Bot channels have no remote peer to acknowledge gameinit. Recreate their
+	// game entities explicitly after the new map is ready.
+	for ( i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
+		if ( restartBots[i] ) {
+			clients[i].clientState = SCS_INGAME;
+			const char* botCharacter = clients[i].botCharacter.IsEmpty() ? "major" : clients[i].botCharacter.c_str();
+			game->ServerClientBegin( i, true, botCharacter );
+			SendUserInfoBroadcast( i, botUserInfo[i], true );
+		}
+	}
+
 	if ( sessLocal.mapSpawnData.serverInfo.GetInt( "si_pure" ) ) {
 		// lock down the pak list
 		fileSystem->UpdatePureServerChecksums( );
@@ -1114,14 +1135,21 @@ int idAsyncServer::GetNumActiveClients( void ) const {
 int idAsyncServer::GetNumBotClients( void ) const {
 	int count = 0;
 	for ( int i = 0; i < MAX_ASYNC_CLIENTS; i++ ) {
-		if ( clients[i].clientState >= SCS_PUREWAIT &&
-			clients[i].channel.GetRemoteAddress().type == NA_BOT ) {
+		if ( IsBotClient( i ) ) {
 			count++;
 		}
 	}
 	return count;
 }
 
+bool idAsyncServer::IsBotClient( int clientNum ) const {
+	if ( clientNum < 0 || clientNum >= MAX_ASYNC_CLIENTS ) {
+		return false;
+	}
+	return clients[clientNum].clientState >= SCS_PUREWAIT &&
+		clients[clientNum].channel.GetRemoteAddress().type == NA_BOT;
+}
+
 /*
 ==================
 idAsyncServer::GetNumIdleClients
@@ -1188,6 +1216,7 @@ void idAsyncServer::ClearClient( int clientNum ) {
 	client.snapshotSequence = 0;
 	client.acknowledgeSnapshotSequence = 0;
 	client.numDuplicatedUsercmds = 0;
+	client.botCharacter.Clear();
 }
 
 /*
@@ -3602,12 +3631,13 @@ int idAsyncServer::AllocOpenClientSlotForAI(const char* botName, int maxPlayersO
 	}
 
 	idAsyncServer::InitLocalClient(botClientId, true);
+	clients[botClientId].botCharacter = (botName && botName[0]) ? botName : "major";
 
 	// Set all the spawn args for the new bot.
-	spawnArgs.Set("ui_name", botName);
+	spawnArgs.Set("ui_name", clients[botClientId].botCharacter.c_str());
 
 	// Init the new client, and broadcast it to the rest of the players.
-	game->ServerClientBegin(botClientId, true, botName);
+	game->ServerClientBegin(botClientId, true, clients[botClientId].botCharacter.c_str());
 	idAsyncServer::SendUserInfoBroadcast(botClientId, spawnArgs, true);
 
 	return botClientId;
diff --git a/src/framework/async/AsyncServer.h b/src/framework/async/AsyncServer.h
index 0e7ef6b0..b010f4f8 100644
--- a/src/framework/async/AsyncServer.h
+++ b/src/framework/async/AsyncServer.h
@@ -124,6 +124,7 @@ typedef struct serverClient_s {
 	int					numDuplicatedUsercmds;
 
 	char				guid[12];  // Even Balance - M. Quinn
+	idStr				botCharacter;	// canonical bot profile, independent of the display name
 
 } serverClient_t;
 
@@ -157,6 +158,7 @@ public:
 	int					GetNumClients( void ) const;
 	int					GetNumActiveClients( void ) const;
 	int					GetNumBotClients( void ) const;
+	bool				IsBotClient( int clientNum ) const;
 	int					GetNumIdleClients( void ) const;
 	int					GetLocalClientNum( void ) const { return localClientNum; }
 
diff --git a/src/framework/async/NetworkSystem.cpp b/src/framework/async/NetworkSystem.cpp
index c13f00f0..d1fb7010 100644
--- a/src/framework/async/NetworkSystem.cpp
+++ b/src/framework/async/NetworkSystem.cpp
@@ -305,6 +305,10 @@ int idNetworkSystem::ServerGetNumBotClients(void) {
 	return idAsyncNetwork::server.GetNumBotClients();
 }
 
+bool idNetworkSystem::ServerIsBotClient(int clientNum) {
+	return idAsyncNetwork::server.IsBotClient(clientNum);
+}
+
 /*
 ==================
 idNetworkSystem::ServerSetBotUserCommand
diff --git a/src/framework/async/NetworkSystem.h b/src/framework/async/NetworkSystem.h
index 91e4bb35..6b49c8f4 100644
--- a/src/framework/async/NetworkSystem.h
+++ b/src/framework/async/NetworkSystem.h
@@ -95,6 +95,7 @@ public:
 	virtual int				RemoveClientSlotForBot(const char *reason);
 	virtual int				ServerGetNumActiveClients(void);
 	virtual int				ServerGetNumBotClients(void);
+	virtual bool			ServerIsBotClient(int clientNum);
 	virtual int				ServerSetBotUserCommand(int clientNum, int frameNum, const usercmd_t& cmd);
 	virtual int				ServerSetBotUserName(int clientNum, const char* playerName);
 // jmarshall end
