diff --git a/src/g_game.c b/src/g_game.c
index 5f070711128ce8c770d19ceaf1148086e692b7d2..e45afbd3444642dad4482b22439d128fe4cc4dbc 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -4048,6 +4048,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 cb97de6ff753ff3e4abc582f39549004d1f40ba9..1d6ba0cb4724bf688703ee416a192027d194b935 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -237,6 +237,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 4ec5ed8b3685fe985cee08e343aa885637ecd1aa..05d77d9cee08bae1468cb91f3dc1294f40e9805c 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -4174,6 +4174,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);
@@ -4510,6 +4534,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 52c005abdb11c50c39bd8ae1a7dd7a659fc979c9..d4df56f9bb585da149cee5f2162c8f31d52e5ae3 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -10501,44 +10501,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)
@@ -10553,7 +10515,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;