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 C4GameSave::WriteDescPlayers(StdStrBuf &sBuf) { // New style using Game.PlayerInfos if (Game.PlayerInfos.GetPlayerCount()) { sBuf.Append(LoadResStr("IDS_DESC_PLRS")); if (Game.Teams.IsMultiTeams() && !Game.Teams.IsAutoGenerateTeams()) { // Teams defined: Print players sorted by teams WriteDescLineFeed(sBuf); C4Team *pTeam; int32_t i=0; while ((pTeam = Game.Teams.GetTeamByIndex(i++))) { WriteDescPlayers(sBuf, true, pTeam->GetID()); } // Finally, print out players outside known teams (those can only be achieved by script using SetPlayerTeam) WriteDescPlayers(sBuf, true, 0); } else { // No teams defined: Print all players that have ever joined WriteDescPlayers(sBuf, false, 0); } } }
int32_t C4TeamList::GetForcedTeamSelection(int32_t idForPlayer) const { // if there's only one team for the player to join, return that team ID C4Team *pOKTeam = NULL, *pCheck; if (idForPlayer) pOKTeam = GetTeamByPlayerID(idForPlayer); // curent team is always possible, even if full int32_t iCheckTeam=0; while ((pCheck = GetTeamByIndex(iCheckTeam++))) if (!pCheck->IsFull()) { // this team could be joined if (pOKTeam && pOKTeam != pCheck) { // there already was a team that could be joined // two alternatives -> team selection is not forced return 0; } pOKTeam = pCheck; } // was there a team that could be joined? if (pOKTeam) { // if teams are generated on the fly, there would always be the possibility of creating a new team } if (IsAutoGenerateTeams()) return 0; // otherwise, this team is forced! return pOKTeam->GetID(); } // no team could be joined: Teams auto generated? if (IsAutoGenerateTeams()) { // then the only possible way is to join a new team return TEAMID_New; } // otherwise, nothing can be done... return 0; }
void C4GameSave::WriteDescPlayers(StdStrBuf &sBuf, bool fByTeam, int32_t idTeam) { // write out all players; only if they match the given team if specified C4PlayerInfo *pPlr; bool fAnyPlrWritten = false; for (int i = 0; (pPlr = Game.PlayerInfos.GetPlayerInfoByIndex(i)); i++) if (pPlr->HasJoined() && !pPlr->IsRemoved() && !pPlr->IsInvisible()) { if (fByTeam) { if (idTeam) { // match team if (pPlr->GetTeam() != idTeam) continue; } else { // must be in no known team if (Game.Teams.GetTeamByID(pPlr->GetTeam())) continue; } } if (fAnyPlrWritten) sBuf.Append(", "); else if (fByTeam && idTeam) { C4Team *pTeam = Game.Teams.GetTeamByID(idTeam); if (pTeam) sBuf.AppendFormat("%s: ", pTeam->GetName()); } sBuf.Append(pPlr->GetName()); fAnyPlrWritten = true; } if (fAnyPlrWritten) WriteDescLineFeed(sBuf); }
bool C4TeamList::IsJoin2TeamAllowed(int32_t idTeam) { // join to new team: Only if new teams can be created if (idTeam == TEAMID_New) return IsAutoGenerateTeams(); // team number must be valid C4Team *pTeam = GetTeamByID(idTeam); if (!pTeam) return false; // team player count must not exceed the limit return !pTeam->IsFull(); }
C4Team *C4TeamList::CreateTeam(const char *szName) { // custom team C4Team *pNewTeam = new C4Team(); pNewTeam->iID = iLastTeamID + 1; SCopy(szName, pNewTeam->Name, C4MaxName); AddTeam(pNewTeam); pNewTeam->RecheckColor(*this); return pNewTeam; }
bool C4TeamList::IsJoin2TeamAllowed(int32_t idTeam, C4PlayerType plrType) { // join to new team: Only if new teams can be created if (idTeam == TEAMID_New) return IsAutoGenerateTeams(); // team number must be valid C4Team *pTeam = GetTeamByID(idTeam); if (!pTeam) return false; // team player count must not exceed the limit, unless it is a script player return !pTeam->IsFull() || plrType == C4PT_Script; }
C4Team *C4TeamList::GetRandomSmallestTeam() const { C4Team *pLowestTeam = NULL; int iLowestTeamCount = 0; C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount; for (; iCnt--; ++ppCheck) { if ((*ppCheck)->IsFull()) continue; // do not join into full teams if (!pLowestTeam || pLowestTeam->GetPlayerCount() > (*ppCheck)->GetPlayerCount()) { pLowestTeam = *ppCheck; iLowestTeamCount = 1; } else if (pLowestTeam->GetPlayerCount() == (*ppCheck)->GetPlayerCount()) if (!SafeRandom(++iLowestTeamCount)) pLowestTeam = *ppCheck; } return pLowestTeam; }
bool C4TeamList::Load(C4Group &hGroup, class C4Scenario *pInitDefault, class C4LangStringTable *pLang) { // clear previous Clear(); // load file contents StdStrBuf Buf; if (!hGroup.LoadEntryString(C4CFN_Teams, &Buf)) { // no teams: Try default init if (!pInitDefault) return false; // no teams defined: Activate default melee teams if a melee rule is found // default: FFA for anything that looks like melee if ( pInitDefault->Game.Goals.GetIDCount(C4ID::Melee, 1)) { fAllowHostilityChange = true; fActive = true; fAutoGenerateTeams = true; } else { // No goals/rules whatsoever: They could be present in the objects.txt, but parsing that would be a bit of // overkill // So just keep the old behaviour here, and disallow teams fAllowHostilityChange = true; fActive = false; } fCustom = false; } else { // team definition file may be localized if (pLang) pLang->ReplaceStrings(Buf); // compile if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(mkNamingAdapt(*this, "Teams"), Buf, C4CFN_Teams)) return false; } // post-initialization: Generate default team colors int32_t iTeam=0; C4Team *pTeam; while ((pTeam = GetTeamByIndex(iTeam++))) pTeam->RecheckColor(*this); return true; }
void C4Team::RecheckColor(C4TeamList &rForList) { // number of times trying new player colors const int32_t C4MaxTeamColorChangeTries = 100; if (!dwClr) { const int defTeamColorCount = 10; DWORD defTeamColorRGB[defTeamColorCount] = { 0xF40000, 0x00C800, 0xFCF41C, 0x2020FF, // red, green, yellow, blue, 0xC48444, 0xFFFFFF, 0x848484, 0xFF00EF, // brown, white, grey, pink, 0x00FFFF, 0x784830 }; // cyan, dk brown // no color assigned yet: Generate by team ID if (iID >=1 && iID <=defTeamColorCount) { // default colors dwClr = defTeamColorRGB[iID-1] | 0xff000000; } else { // find a new, unused color for (int32_t iTry=1; iTry<C4MaxTeamColorChangeTries; ++iTry) { dwClr = GenerateRandomPlayerColor(iTry); int32_t iIdx=0; C4Team *pTeam; bool fOK=true; while ((pTeam = rForList.GetTeamByIndex(iIdx++))) if (pTeam != this) if (IsColorConflict(pTeam->GetColor(), dwClr)) { fOK=false; break; } // color is fine? if (fOK) return; // it's not; try next color } // Giving up: Use last generated color } } }
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(); } } }
C4Team::C4Team(const C4Team &rCopy) : piPlayers(new int32_t[rCopy.GetPlayerCount()]), iPlayerCount(rCopy.GetPlayerCount()), iPlayerCapacity(rCopy.GetPlayerCount()), iID(rCopy.GetID()), iPlrStartIndex(rCopy.iPlrStartIndex), dwClr(rCopy.dwClr), sIconSpec(rCopy.GetIconSpec()), iMaxPlayer(rCopy.iMaxPlayer) { // copy name SCopy(rCopy.GetName(), Name, C4MaxName); // copy players for (int32_t i = 0; i < iPlayerCount; i++) piPlayers[i] = rCopy.GetIndexedPlayer(i); }
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; }
bool C4MainMenu::DoRefillInternal(bool &rfRefilled) { // Variables C4FacetSurface fctSymbol; C4Player *pPlayer; C4IDList ListItems; C4Facet fctTarget; bool fWasEmpty = !GetItemCount(); // Refill switch (Identification) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - case C4MN_Hostility: { // Clear items ClearItems(); // Refill player if (!(pPlayer = ::Players.Get(Player))) return false; // Refill items C4Player *pPlr; int32_t iIndex; for (iIndex=0; (pPlr = ::Players.GetByIndex(iIndex)); iIndex++) // Ignore player self and invisible if (pPlr != pPlayer) if (!pPlr->IsInvisible()) { // Symbol fctSymbol.Create(C4SymbolSize,C4SymbolSize); pPlayer->DrawHostility(fctSymbol,iIndex); // Message StdStrBuf sMsg; bool isFriendly = pPlayer->Hostility.find(pPlr) == pPlayer->Hostility.end(); if (isFriendly) sMsg.Format(LoadResStr("IDS_MENU_ATTACK"),pPlr->GetName()); else sMsg.Format(LoadResStr("IDS_MENU_NOATTACK"),pPlr->GetName()); // Command char szCommand[1000]; sprintf(szCommand,"SetHostility:%i",pPlr->Number); // Info caption char szInfoCaption[C4MaxTitle+1],szFriendly[50],szNot[30]=""; SCopy(LoadResStr(isFriendly ? "IDS_MENU_ATTACKHOSTILE" : "IDS_MENU_ATTACKFRIENDLY"),szFriendly); if (!isFriendly) SCopy(LoadResStr("IDS_MENU_ATTACKNOT"),szNot); sprintf(szInfoCaption,LoadResStr("IDS_MENU_ATTACKINFO"),pPlr->GetName(),szFriendly,szNot); if (iIndex==pPlayer->Number) SCopy(LoadResStr("IDS_MENU_ATTACKSELF"),szInfoCaption); // Add item Add(sMsg.getData(),fctSymbol,szCommand,C4MN_Item_NoCount,NULL,szInfoCaption); fctSymbol.Default(); } break; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - case C4MN_TeamSelection: case C4MN_TeamSwitch: { // Clear items ClearItems(); // add all teams as menu items // 2do: Icon C4Team *pTeam; int32_t i=0; bool fAddNewTeam=Game.Teams.IsAutoGenerateTeams(); for (;;) { pTeam = Game.Teams.GetTeamByIndex(i); if (pTeam) { // next regular team ++i; // do not add a new team if an empty team exists if (!pTeam->GetPlayerCount()) fAddNewTeam = false; } else if (fAddNewTeam) { // join new team fAddNewTeam = false; } else { // all teams done break; } // create team symbol: Icon spec if specified; otherwise flag for empty and crew for nonempty team fctSymbol.Create(C4SymbolSize,C4SymbolSize); const char *szIconSpec = pTeam ? pTeam->GetIconSpec() : NULL; bool fHasIcon = false; if (szIconSpec && *szIconSpec) { fHasIcon = Game.DrawTextSpecImage(fctSymbol, szIconSpec, NULL, pTeam->GetColor()); } if (!fHasIcon) { if (pTeam && pTeam->GetPlayerCount()) ::GraphicsResource.fctCrewClr.DrawClr(fctSymbol, true, pTeam->GetColor()); else C4GUI::Icon::GetIconFacet(C4GUI::Ico_Team).Draw(fctSymbol, true); } StdStrBuf sTeamName; if (pTeam) { sTeamName.Take(pTeam->GetNameWithParticipants()); } else sTeamName.Ref(LoadResStr("IDS_PRC_NEWTEAM")); const char *szOperation = (Identification == C4MN_TeamSwitch) ? "TeamSwitch" : "TeamSel"; Add(sTeamName.getData(), fctSymbol,FormatString("%s:%d", szOperation, pTeam ? pTeam->GetID() : TEAMID_New).getData(), C4MN_Item_NoCount,NULL,FormatString(LoadResStr("IDS_MSG_JOINTEAM"), sTeamName.getData()).getData(), C4ID(pTeam ? pTeam->GetID() : 0)); fctSymbol.Default(); } break; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - case C4MN_Observer: // observer menu { // Clear items ClearItems(); // Check validity C4Viewport *pVP = ::Viewports.GetViewport(NO_OWNER); if (!pVP) return false; int32_t iInitialSelection = 0; // Add free view AddRefSym(LoadResStr("IDS_MSG_FREEVIEW"), C4GUI::Icon::GetIconFacet(C4GUI::Ico_Star), "Observe:Free", C4MN_Item_NoCount, NULL, LoadResStr("IDS_MSG_FREELYSCROLLAROUNDTHEMAP")); // Add players C4Player *pPlr; int32_t iIndex; for (iIndex=0; (pPlr = ::Players.GetByIndex(iIndex)); iIndex++) { // Ignore invisible if (!pPlr->IsInvisible()) { // Symbol fctSymbol.Create(C4SymbolSize,C4SymbolSize); ::GraphicsResource.fctPlayerClr.DrawClr(fctSymbol, true, pPlr->ColorDw); // Message StdStrBuf sMsg; DWORD dwClr = pPlr->ColorDw; sMsg.Format("<c %x>%s</c>", (unsigned int)C4GUI::MakeColorReadableOnBlack(dwClr), pPlr->GetName()); // Command StdStrBuf sCommand; sCommand.Format("Observe:%d", (int)pPlr->Number); // Info caption StdStrBuf sInfo; sInfo.Format(LoadResStr("IDS_TEXT_FOLLOWVIEWOFPLAYER"), pPlr->GetName()); // Add item Add(sMsg.getData(),fctSymbol,sCommand.getData(),C4MN_Item_NoCount,NULL,sInfo.getData()); fctSymbol.Default(); // check if this is the currently selected player if (pVP->GetPlayer() == pPlr->Number) iInitialSelection = GetItemCount()-1; } // Initial selection on followed player if (fWasEmpty) SetSelection(iInitialSelection, false, true); } } break; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - default: // No internal refill needed return true; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } // Successfull internal refill rfRefilled = true; return true; }