int32_t C4TeamList::GetStartupTeamCount(int32_t startup_player_count) { // Count non-empty teams int32_t i_team = 0; C4Team *team; int32_t team_count = 0; while ((team = GetTeamByIndex(i_team++))) { if (team->GetPlayerCount() > 0) ++team_count; } // No populated teams found? This can happen in non-network mode when no players are assigned if (!team_count) { // Teams have not been selected yet, but the map script may want to have an estimate // in this case, calculate prospective teams from startup player count if (IsCustom() && !IsAutoGenerateTeams()) { // Teams are pre-defined. Assume players will try to distribute broadly on these teams team_count = std::min<int32_t>(startup_player_count, GetTeamCount()); } else if (IsRandomTeam()) { // Randomized teams: Players will be put into two teams. team_count = std::min<int32_t>(startup_player_count, 2); } else { // Teams are auto-added -> fallback to player count team_count = startup_player_count; } } return team_count; }
void C4TeamList::EnsureTeamCount() { // in random autogenerate mode, there must be exactly two teams if (IsRandomTeam()) { if (IsAutoGenerateTeams() && GetTeamCount() != 2) { ClearTeams(); GenerateDefaultTeams(2); } } }
void C4TeamList::RecheckTeams() { // automatic team distributions only if (!IsRandomTeam()) return; // host decides random teams if (!::Control.isCtrlHost()) { // Still make sure that we have the right number of teams on the clients as well so that // GetTeamCount() does not report inconsistent values. EnsureTeamCount(); 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 (;;) { C4Team *pLowestTeam = GetRandomSmallestTeam(); if (!pLowestTeam) break; // no teams: Nothing to re-distribute. // get largest team that has relocateable players C4Team *pLargestTeam = nullptr; C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount; for (; iCnt--; ++ppCheck) if (!pLargestTeam || pLargestTeam->GetPlayerCount() > (*ppCheck)->GetPlayerCount()) if ((*ppCheck)->GetFirstUnjoinedPlayerID()) pLargestTeam = *ppCheck; // no team can redistribute? if (!pLargestTeam) break; // redistribution won't help much? if (pLargestTeam->GetPlayerCount() - pLowestTeam->GetPlayerCount() <= 1) break; // okay; redistribute one player! int32_t idRedistPlayer = pLargestTeam->GetFirstUnjoinedPlayerID(); C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idRedistPlayer); assert(pInfo); if (!pInfo) break; // umn...serious problems pLargestTeam->RemovePlayerByID(idRedistPlayer); pLowestTeam->AddPlayer(*pInfo, true); C4ClientPlayerInfos *pClrInfo = Game.PlayerInfos.GetClientInfoByPlayerID(idRedistPlayer); assert(pClrInfo); // player info change: mark updated to remote clients get information if (pClrInfo) { pClrInfo->SetUpdated(); } } }
void C4TeamList::ReassignAllTeams() { assert(::Control.isCtrlHost()); if (!::Control.isCtrlHost()) return; // go through all player infos; reset team in them int32_t idStart = -1; C4PlayerInfo *pNfo; while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart))) { idStart = pNfo->GetID(); if (pNfo->HasJoinIssued()) continue; pNfo->SetTeam(0); // mark changed info as updated C4ClientPlayerInfos *pCltInfo = Game.PlayerInfos.GetClientInfoByPlayerID(idStart); assert(pCltInfo); if (pCltInfo) { pCltInfo->SetUpdated(); } } // 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))) { idStart = pNfo->GetID(); if (pNfo->HasJoinIssued()) continue; assert(!pNfo->GetTeam()); RecheckPlayerInfoTeams(*pNfo, true); } }
void C4TeamList::SetTeamDistribution(TeamDist eToVal) { if (!Inside(eToVal, TEAMDIST_First, TEAMDIST_Last)) { assert(false); return; } eTeamDist = 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. // Also reassign in no-team-mode so enough teams for all players exist if (IsRandomTeam() || eTeamDist==TEAMDIST_None) ReassignAllTeams(); else { // otherwise, it's sufficient to just reassign any teams that are incorrect for the current mode RecheckTeams(); } // send updates to other clients and reset flags if (::Network.isEnabled()) { ::Network.Players.SendUpdatedPlayers(); } } }
bool C4TeamList::RecheckPlayerInfoTeams(C4PlayerInfo &rNewJoin, bool fByHost) { // only if enabled assert(IsMultiTeams()); if (!IsMultiTeams()) return false; // check whether a new team is to be assigned first C4Team *pCurrentTeam = GetTeamByPlayerID(rNewJoin.GetID()); int32_t idCurrentTeam = pCurrentTeam ? pCurrentTeam->GetID() : 0; if (rNewJoin.GetTeam()) { // was that team a change to the current team? // no change anyway: OK, skip this info if (idCurrentTeam == rNewJoin.GetTeam()) return true; // the player had a different team assigned: Check if changes are allowed at all if (eTeamDist == TEAMDIST_Free || (eTeamDist == TEAMDIST_Host && fByHost)) // also make sure that selecting this team is allowed, e.g. doesn't break the team limit // this also checks whether the team number is a valid team - but it would accept TEAMID_New, which shouldn't be used in player infos! if (rNewJoin.GetTeam() != TEAMID_New && IsJoin2TeamAllowed(rNewJoin.GetTeam())) // okay; accept change return true; // Reject change by reassigning the current team rNewJoin.SetTeam(idCurrentTeam); // and determine a new team, if none has been assigned yet if (idCurrentTeam) return true; } // new team assignment // teams are always needed in the lobby, so there's a team preset to change // for runtime joins, teams are needed if specified by teams.txt or if any teams have been created before (to avoid mixed team-noteam-scenarios) // but only assign teams in runtime join if the player won't pick it himself bool fWillHaveLobby = ::Network.isEnabled() && !::Network.Status.isPastLobby() && Game.fLobby; bool fHasOrWillHaveLobby = ::Network.isLobbyActive() || fWillHaveLobby; bool fCanPickTeamAtRuntime = !IsRandomTeam() && (rNewJoin.GetType() == C4PT_User) && IsRuntimeJoinTeamChoice(); bool fIsTeamNeeded = IsRuntimeJoinTeamChoice() || GetTeamCount(); if (!fHasOrWillHaveLobby && (!fIsTeamNeeded || fCanPickTeamAtRuntime)) return false; // get least-used team C4Team *pAssignTeam=NULL; C4Team *pLowestTeam = GetRandomSmallestTeam(); // melee mode if (IsAutoGenerateTeams() && !IsRandomTeam()) { // reuse old team only if it's empty if (pLowestTeam && !pLowestTeam->GetPlayerCount()) pAssignTeam = pLowestTeam; else { // no empty team: generate new GenerateDefaultTeams(iLastTeamID+1); pAssignTeam = GetTeamByID(iLastTeamID); } } else { if (!pLowestTeam) { // not enough teams defined in teamwork mode? // then create two teams as default if (!GetTeamByIndex(1)) GenerateDefaultTeams(2); else // otherwise, all defined teams are full. This is a scenario error, because MaxPlayer should have been adjusted return false; pLowestTeam = GetTeamByIndex(0); } pAssignTeam = pLowestTeam; } // assign it if (!pAssignTeam) return false; pAssignTeam->AddPlayer(rNewJoin, true); return true; }