diff --git a/src/g_game.c b/src/g_game.c
index e57402683f92c14dd9eec7bf0b713619dbf50495..18b7ea4a81bc70f3d0b05066fb5a944a928f7e37 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -4054,6 +4054,134 @@ void G_FreeTeamData(UINT8 team)
 	team_ptr->flag_name = NULL;
 }
 
+static boolean G_AreTeamScoresTied(void)
+{
+	for (UINT8 i = 1; i < teamsingame - 1; i++)
+	{
+		if (teamscores[G_GetTeam(i)] != teamscores[G_GetTeam(i + 1)])
+			return false;
+	}
+
+	return true;
+}
+
+static boolean G_AreTeamPlayerCountsTied(void)
+{
+	INT32 numplayers[MAXTEAMS];
+
+	for (INT32 i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i])
+			numplayers[players[i].ctfteam]++;
+	}
+
+	for (UINT8 i = 1; i < teamsingame - 1; i++)
+	{
+		if (numplayers[G_GetTeam(i)] != numplayers[G_GetTeam(i + 1)])
+			return false;
+	}
+
+	return true;
+}
+
+UINT8 G_GetBestPerformingTeam(void)
+{
+	if (teamsingame < 2 || G_AreTeamScoresTied())
+		return TEAM_NONE;
+
+	UINT8 mostscore = TEAM_NONE;
+
+	for (UINT8 i = 1; i < teamsingame; i++)
+	{
+		UINT8 team = G_GetTeam(i);
+		if (mostscore == TEAM_NONE || teamscores[team] > teamscores[mostscore])
+			mostscore = team;
+	}
+
+	return mostscore;
+}
+
+UINT8 G_GetWorstPerformingTeam(void)
+{
+	if (teamsingame < 2 || G_AreTeamScoresTied())
+		return TEAM_NONE;
+
+	UINT8 leastscore = TEAM_NONE;
+
+	for (UINT8 i = 1; i < teamsingame; i++)
+	{
+		UINT8 team = G_GetTeam(i);
+		if (leastscore == TEAM_NONE || teamscores[team] < teamscores[leastscore])
+			leastscore = team;
+	}
+
+	return leastscore;
+}
+
+UINT8 G_GetMostAdvantagedTeam(void)
+{
+	if (teamsingame < 2)
+		return TEAM_NONE;
+
+	INT32 numplayers[MAXTEAMS];
+	UINT8 mostscore = TEAM_NONE;
+	UINT8 mostplayers = TEAM_NONE;
+
+	for (INT32 i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i])
+			numplayers[players[i].ctfteam]++;
+	}
+
+	for (UINT8 i = 1; i < teamsingame; i++)
+	{
+		UINT8 team = G_GetTeam(i);
+		if (mostscore == TEAM_NONE || teamscores[team] > teamscores[mostscore])
+			mostscore = team;
+		if (mostplayers == TEAM_NONE || numplayers[team] > numplayers[mostplayers])
+			mostplayers = team;
+	}
+
+	if (mostplayers != TEAM_NONE && !G_AreTeamPlayerCountsTied())
+		return mostplayers;
+	else if (mostscore != TEAM_NONE && !G_AreTeamScoresTied())
+		return mostscore;
+
+	return TEAM_NONE;
+}
+
+UINT8 G_GetMostDisadvantagedTeam(void)
+{
+	if (teamsingame < 2)
+		return TEAM_NONE;
+
+	INT32 numplayers[MAXTEAMS];
+	UINT8 leastscore = TEAM_NONE;
+	UINT8 leastplayers = TEAM_NONE;
+
+	for (INT32 i = 0; i < MAXPLAYERS; i++)
+	{
+		if (playeringame[i])
+			numplayers[players[i].ctfteam]++;
+	}
+
+	for (UINT8 i = 1; i < teamsingame; i++)
+	{
+		UINT8 team = G_GetTeam(i);
+		if (leastscore == TEAM_NONE || teamscores[team] < teamscores[leastscore])
+			leastscore = team;
+		if (leastplayers == TEAM_NONE || numplayers[team] < numplayers[leastplayers])
+			leastplayers = team;
+	}
+
+	if (leastplayers != TEAM_NONE && !G_AreTeamPlayerCountsTied())
+		return leastplayers;
+	else if (leastscore != TEAM_NONE && !G_AreTeamScoresTied())
+		return leastscore;
+
+	return TEAM_NONE;
+}
+
 /** Select a random map with the given typeoflevel flags.
   * If no map has those flags, this arbitrarily gives you map 1.
   * \param tolflags The typeoflevel flags to insist on. Other bits may
diff --git a/src/g_game.h b/src/g_game.h
index 035b4541e310e6d9ae11b49bef0f29aa1c0293e2..fa7936747b558b4a1815c50722e65f6d22b2c6a9 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -236,6 +236,11 @@ boolean G_HasTeamIcon(UINT8 team, UINT8 icon_type);
 void G_SetTeamIcon(UINT8 team, UINT8 icon_type, const char *icon);
 void G_FreeTeamData(UINT8 team);
 
+UINT8 G_GetBestPerformingTeam(void);
+UINT8 G_GetWorstPerformingTeam(void);
+UINT8 G_GetMostAdvantagedTeam(void);
+UINT8 G_GetMostDisadvantagedTeam(void);
+
 void G_Ticker(boolean run);
 boolean G_Responder(event_t *ev);
 boolean G_LuaResponder(event_t *ev);
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index af64d0823e07965505bf68d796c44b8ba7d435fa..c8b237bf6b89dd26111e93afa5ecbb869514db1c 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -4031,6 +4031,30 @@ static int lib_gCompetitionGametype(lua_State *L)
 	return 1;
 }
 
+static int lib_gGetBestPerformingTeam(lua_State *L)
+{
+	lua_pushinteger(L, G_GetBestPerformingTeam());
+	return 1;
+}
+
+static int lib_gGetWorstPerformingTeam(lua_State *L)
+{
+	lua_pushinteger(L, G_GetWorstPerformingTeam());
+	return 1;
+}
+
+static int lib_gGetMostAdvantagedTeam(lua_State *L)
+{
+	lua_pushinteger(L, G_GetMostAdvantagedTeam());
+	return 1;
+}
+
+static int lib_gGetMostDisadvantagedTeam(lua_State *L)
+{
+	lua_pushinteger(L, G_GetMostDisadvantagedTeam());
+	return 1;
+}
+
 static int lib_gTicsToHours(lua_State *L)
 {
 	tic_t rtic = luaL_checkinteger(L, 1);
@@ -4357,6 +4381,10 @@ static luaL_Reg lib[] = {
 	{"G_CoopGametype",lib_gCoopGametype},
 	{"G_TagGametype",lib_gTagGametype},
 	{"G_CompetitionGametype",lib_gCompetitionGametype},
+	{"G_GetBestPerformingTeam",lib_gGetBestPerformingTeam},
+	{"G_GetWorstPerformingTeam",lib_gGetWorstPerformingTeam},
+	{"G_GetMostAdvantagedTeam",lib_gGetMostAdvantagedTeam},
+	{"G_GetMostDisadvantagedTeam",lib_gGetMostDisadvantagedTeam},
 	{"G_TicsToHours",lib_gTicsToHours},
 	{"G_TicsToMinutes",lib_gTicsToMinutes},
 	{"G_TicsToSeconds",lib_gTicsToSeconds},
diff --git a/src/p_user.c b/src/p_user.c
index 358addb1367068b68a6aac6626f847c39f2374cd..7e2ef1bf5bd3e219f10959a2ab337a57fbb9d841 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -10495,44 +10495,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	return (x == thiscam->x && y == thiscam->y && z == thiscam->z && angle == thiscam->aiming);
 }
 
-static UINT8 P_GetWorstPerformingTeam(void)
-{
-	INT32 numplayers[MAXTEAMS];
-	INT32 leastscore = TEAM_NONE;
-	INT32 leastplayers = TEAM_NONE;
-
-	for (INT32 i = 0; i < MAXPLAYERS; i++)
-	{
-		if (playeringame[i])
-			numplayers[players[i].ctfteam]++;
-	}
-
-	for (INT32 i = 1; i < teamsingame; i++)
-	{
-		UINT8 team = G_GetTeam(i);
-		UINT8 compareto = leastscore == TEAM_NONE ? G_GetTeam(1) : leastscore;
-
-		if (teamscores[team] < teamscores[compareto])
-		{
-			leastscore = i;
-		}
-
-		compareto = leastplayers == TEAM_NONE ? G_GetTeam(1) : leastplayers;
-
-		if (numplayers[team] < numplayers[compareto])
-		{
-			leastplayers = i;
-		}
-	}
-
-	if (leastplayers != TEAM_NONE)
-		return leastplayers;
-	else if (leastscore != TEAM_NONE)
-		return leastscore;
-
-	return G_GetTeam(P_RandomRange(1, teamsingame - 1));
-}
-
 boolean P_SpectatorJoinGame(player_t *player)
 {
 	if (!G_CoopGametype() && !cv_allowteamchange.value)
@@ -10547,7 +10509,9 @@ boolean P_SpectatorJoinGame(player_t *player)
 	// Partial code reproduction from p_tick.c autobalance code.
 	else if (G_GametypeHasTeams())
 	{
-		UINT8 changeto = P_GetWorstPerformingTeam();
+		UINT8 changeto = G_GetMostDisadvantagedTeam();
+		if (changeto == TEAM_NONE)
+			changeto = G_GetTeam(P_RandomRange(1, teamsingame - 1));
 
 		if (!LUA_HookTeamSwitch(player, changeto, true, false, false))
 			return false;