From 666782f6cc79391024b375f896ca369001f52cf7 Mon Sep 17 00:00:00 2001
From: Sven Eberhardt <none@none>
Date: Tue, 8 Nov 2011 17:27:32 +0000
Subject: [PATCH 05/10] Fix random team distribution when a player leaves
 during lobby time

Also allow random/random surprise teams in AutoGenerateTeam mode
(always creates two teams)
---
 src/control/C4Teams.cpp           |   51 ++++++++++++++++++++++++++----------
 src/control/C4Teams.h             |   10 ++++---
 src/network/C4Network2Players.cpp |    5 +++-
 3 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/src/control/C4Teams.cpp b/src/control/C4Teams.cpp
index 12e22af..48d12b1 100644
--- a/src/control/C4Teams.cpp
+++ b/src/control/C4Teams.cpp
@@ -262,12 +262,10 @@ bool C4Team::HasWon() const
 void C4TeamList::Clear()
 {
 	// del all teams
-	C4Team **ppTeam=ppList;
-	if (iTeamCount) { while (iTeamCount--) delete *(ppTeam++); iTeamCount = 0; }
+	ClearTeams();
 	// del player team vector
 	delete [] ppList; ppList = NULL;
 	iTeamCapacity = 0;
-	iLastTeamID = 0;
 	fAllowHostilityChange = true;
 	fAllowTeamSwitch = false;
 	fCustom = false;
@@ -307,6 +305,7 @@ bool C4TeamList::CanLocalChooseTeam() const
 	{
 	case TEAMDIST_Free: return true;
 	case TEAMDIST_Host: return ::Control.isCtrlHost();
+	case TEAMDIST_None:
 	case TEAMDIST_Random:
 	case TEAMDIST_RandomInv:
 		return false;
@@ -355,6 +354,14 @@ void C4TeamList::AddTeam(C4Team *pNewTeam)
 	iLastTeamID = Max(pNewTeam->iID, iLastTeamID);
 }
 
+void C4TeamList::ClearTeams()
+{
+	// delete all teams
+	C4Team **ppTeam=ppList;
+	if (iTeamCount) { while (iTeamCount--) delete *(ppTeam++); iTeamCount = 0; }
+	iLastTeamID = 0;
+}
+
 C4Team *C4TeamList::CreateTeam(const char *szName)
 {
 	// custom team
@@ -498,7 +505,7 @@ bool C4TeamList::RecheckPlayerInfoTeams(C4PlayerInfo &rNewJoin, bool fByHost)
 	C4Team *pAssignTeam=NULL;
 	C4Team *pLowestTeam = GetRandomSmallestTeam();
 	// melee mode
-	if (IsAutoGenerateTeams())
+	if (IsAutoGenerateTeams() && !IsRandomTeam())
 	{
 		// reuse old team only if it's empty
 		if (pLowestTeam && !pLowestTeam->GetPlayerCount())
@@ -514,9 +521,9 @@ bool C4TeamList::RecheckPlayerInfoTeams(C4PlayerInfo &rNewJoin, bool fByHost)
 	{
 		if (!pLowestTeam)
 		{
-			// no teams defined in teamwork mode?
+			// not enough teams defined in teamwork mode?
 			// then create two teams as default
-			if (!GetTeamByIndex(0))
+			if (!GetTeamByIndex(1))
 				GenerateDefaultTeams(2);
 			else
 				// otherwise, all defined teams are full. This is a scenario error, because MaxPlayer should have been adjusted
@@ -556,6 +563,7 @@ void C4TeamList::CompileFunc(StdCompiler *pComp)
 	{
 		{ "Free", TEAMDIST_Free },
 		{ "Host", TEAMDIST_Host },
+		{ "None", TEAMDIST_None },
 		{ "Random", TEAMDIST_Random },
 		{ "RandomInv", TEAMDIST_RandomInv },
 	};
@@ -668,9 +676,15 @@ void C4TeamList::RecheckPlayers()
 void C4TeamList::RecheckTeams()
 {
 	// automatic team distributions only
-	if (eTeamDist != TEAMDIST_Random && eTeamDist != TEAMDIST_RandomInv) return;
+	if (!IsRandomTeam()) return;
 	// host decides random teams
 	if (!::Control.isCtrlHost()) return;
+	// random teams in auto generate mode? Make sure there are exactly two teams
+	if (IsAutoGenerateTeams() && GetTeamCount() != 2)
+		{
+		ReassignAllTeams();
+		return;
+		}
 	// redistribute players of largest team that has relocatable players left towards smaller teams
 	for (;;)
 	{
@@ -724,6 +738,13 @@ void C4TeamList::ReassignAllTeams()
 	}
 	// clear players from team lists
 	RecheckPlayers();
+	// in random autogenerate mode, there must be exactly two teams
+	if (IsRandomTeam())
+		if (IsAutoGenerateTeams() && GetTeamCount() != 2)
+			{
+			ClearTeams();
+			GenerateDefaultTeams(2);
+			}
 	// reassign them
 	idStart = -1;
 	while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart)))
@@ -741,7 +762,8 @@ StdStrBuf C4TeamList::GetTeamDistName(TeamDist eTeamDist) const
 	{
 	case TEAMDIST_Free:      return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_FREE"), true));
 	case TEAMDIST_Host:      return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_HOST"), true));
-	case TEAMDIST_Random:    return(StdStrBuf(LoadResStr(IsAutoGenerateTeams() ? "IDS_MSG_TEAMDIST_NONE" : "IDS_MSG_TEAMDIST_RND"), true));
+	case TEAMDIST_None:      return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_NONE"), true));
+	case TEAMDIST_Random:    return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_RND"), true));
 	case TEAMDIST_RandomInv: return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_RNDINV"), true));
 	default: return(FormatString("TEAMDIST_undefined(%d)", (int) eTeamDist));
 	}
@@ -751,13 +773,13 @@ void C4TeamList::FillTeamDistOptions(C4GUI::ComboBox_FillCB *pFiller) const
 {
 	// no teams if disabled
 	if (!fActive) return;
-	// some options that are always valid
+	// team distribution options
 	pFiller->AddEntry(GetTeamDistName(TEAMDIST_Free).getData(), TEAMDIST_Free);
 	pFiller->AddEntry(GetTeamDistName(TEAMDIST_Host).getData(), TEAMDIST_Host);
+	if (IsAutoGenerateTeams()) pFiller->AddEntry(GetTeamDistName(TEAMDIST_None).getData(), TEAMDIST_None); // no teams: only for regular melees
 	pFiller->AddEntry(GetTeamDistName(TEAMDIST_Random).getData(), TEAMDIST_Random);
-	// random invisible teams not allowed for automatically created teams
-	if (!IsAutoGenerateTeams()) pFiller->AddEntry(GetTeamDistName(TEAMDIST_RandomInv).getData(), TEAMDIST_RandomInv);
-}
+	pFiller->AddEntry(GetTeamDistName(TEAMDIST_RandomInv).getData(), TEAMDIST_RandomInv);
+	}
 
 void C4TeamList::SendSetTeamDist(TeamDist eNewTeamDist)
 {
@@ -785,8 +807,9 @@ void C4TeamList::SetTeamDistribution(TeamDist eToVal)
 	// team distribution mode changed: Host may beed to redistribute
 	if (::Control.isCtrlHost())
 	{
-		// if a random team mode was set, reassign all teams so it's really random
-		if (eTeamDist == TEAMDIST_Random || eTeamDist == TEAMDIST_RandomInv)
+		// if a random team mode was set, reassign all teams so it's really random.
+		// Also reassign in no-team-mode so enough teams for all players exist
+		if (IsRandomTeam() || eTeamDist==TEAMDIST_None)
 			ReassignAllTeams();
 		else
 		{
diff --git a/src/control/C4Teams.h b/src/control/C4Teams.h
index 39c45fe..915ceb9 100644
--- a/src/control/C4Teams.h
+++ b/src/control/C4Teams.h
@@ -116,9 +116,10 @@ public:
 		TEAMDIST_First     = 0,
 		TEAMDIST_Free      = 0, // anyone can choose teams
 		TEAMDIST_Host      = 1, // host decides teams
-		TEAMDIST_Random    = 2, // fixed random teams
-		TEAMDIST_RandomInv = 3, // fixed random teams invisible in lobby
-		TEAMDIST_Last      = 3
+		TEAMDIST_None      = 2, // no teams
+		TEAMDIST_Random    = 3, // fixed random teams
+		TEAMDIST_RandomInv = 4, // fixed random teams invisible in lobby
+		TEAMDIST_Last      = 4,
 	};
 
 private:
@@ -152,6 +153,7 @@ private:
 
 private:
 	void AddTeam(C4Team *pNewTeam); // add a team; grow list if necessary
+	void ClearTeams(); // delete all teams
 	int32_t GetFreeTeamID();
 	bool GenerateDefaultTeams(int32_t iUpToID); // generate Team 1, Team 2, etc.
 
@@ -175,7 +177,7 @@ public:
 	bool CanLocalChooseTeam(int32_t idPlayer) const; // whether the local host can determine teams (e.g., not if teams are random, or if all except the player's current team are full)
 	bool CanLocalSeeTeam() const;
 	bool IsTeamColors() const { return fTeamColors; } // whether team colors are enabled
-	bool IsRandomTeam() const { return eTeamDist==TEAMDIST_Random ||eTeamDist==TEAMDIST_RandomInv; } // whether team colors are enabled
+	bool IsRandomTeam() const { return eTeamDist==TEAMDIST_Random ||eTeamDist==TEAMDIST_RandomInv; } // whether a random team mode is selected
 	bool IsJoin2TeamAllowed(int32_t idTeam); // checks whether a team ID is valid and still available for new joins
 	bool IsAutoGenerateTeams() const { return fAutoGenerateTeams; }
 	bool IsRuntimeJoinTeamChoice() const { return IsCustom() && IsMultiTeams(); } // whether players joining at runtime must select a team first
diff --git a/src/network/C4Network2Players.cpp b/src/network/C4Network2Players.cpp
index 82cfee3..e0cc108 100644
--- a/src/network/C4Network2Players.cpp
+++ b/src/network/C4Network2Players.cpp
@@ -231,6 +231,7 @@ void C4Network2Players::HandlePlayerInfo(const class C4ClientPlayerInfos &rInfoP
 	pClientInfo = rInfoList.AddInfo(pClientInfo);
 	// make sure team list reflects teams set in player infos
 	Game.Teams.RecheckPlayers();
+	Game.Teams.RecheckTeams(); // recheck random teams - if a player left, teams may need to be rebalanced
 	// make sure resources are loaded for those players
 	rInfoList.LoadResources();
 	// get associated client - note that pClientInfo might be NULL for empty packets that got discarded
@@ -244,6 +245,8 @@ void C4Network2Players::HandlePlayerInfo(const class C4ClientPlayerInfos &rInfoP
 			JoinUnjoinedPlayersInControlQueue(pClientInfo);
 		}
 	}
+	// adding the player may have invalidated other players (through team settings). Send them.
+	SendUpdatedPlayers();
 	// lobby: update players
 	C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
 	if (pLobby) pLobby->OnPlayersChange();
@@ -258,11 +261,11 @@ void C4Network2Players::SendUpdatedPlayers()
 	while ((pUpdInfo = rInfoList.GetIndexedInfo(i++)))
 		if (pUpdInfo->IsUpdated())
 		{
+			pUpdInfo->ResetUpdated();
 			C4ControlPlayerInfo *pkSend = new C4ControlPlayerInfo(*pUpdInfo);
 			// send info to all
 			::Control.DoInput(CID_PlrInfo, pkSend, CDT_Direct);
 		}
-	ResetUpdatedPlayers();
 }
 
 void C4Network2Players::UpdateSavegameAssignments(C4ClientPlayerInfos *pNewInfo)
-- 
1.7.7.1

