| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2005, Sven2 |
| 6 | * Copyright (c) 2017-2021, The LegacyClonk Team and contributors |
| 7 | * |
| 8 | * Distributed under the terms of the ISC license; see accompanying file |
| 9 | * "COPYING" for details. |
| 10 | * |
| 11 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 12 | * See accompanying file "TRADEMARK" for details. |
| 13 | * |
| 14 | * To redistribute this file separately, substitute the full license texts |
| 15 | * for the above references. |
| 16 | */ |
| 17 | |
| 18 | // player team management for teamwork melees |
| 19 | |
| 20 | #pragma once |
| 21 | |
| 22 | #include "C4Constants.h" |
| 23 | #include "C4ForwardDeclarations.h" |
| 24 | #include "StdBuf.h" |
| 25 | |
| 26 | #include <cstdint> |
| 27 | |
| 28 | class C4TeamList; |
| 29 | |
| 30 | // class predec |
| 31 | namespace C4GUI { class ComboBox_FillCB; } |
| 32 | |
| 33 | // constant used by lobby to indicate invisible, random team |
| 34 | const int32_t TEAMID_Unknown = -1; |
| 35 | |
| 36 | // constant used by InitScenarioPlayer() to indicate creation of a new team |
| 37 | const int32_t TEAMID_New = -1; |
| 38 | |
| 39 | // one player team |
| 40 | class C4Team |
| 41 | { |
| 42 | private: |
| 43 | // std::vector... |
| 44 | // containing player info IDs |
| 45 | int32_t *piPlayers; |
| 46 | int32_t iPlayerCount; |
| 47 | int32_t iPlayerCapacity; |
| 48 | |
| 49 | public: |
| 50 | // copying |
| 51 | C4Team(const C4Team &rCopy); |
| 52 | C4Team &operator=(const C4Team &rCopy); |
| 53 | |
| 54 | protected: |
| 55 | // team identification; usually > 0 for a valid team |
| 56 | int32_t iID; |
| 57 | char Name[C4MaxName + 1]; |
| 58 | int32_t iPlrStartIndex; // 0 for unassigned; 1 to 4 if all players of that team shall be assigned a specific [Player*]-section in the Scenario.txt |
| 59 | uint32_t dwClr; // team color |
| 60 | StdStrBuf sIconSpec; // icon drawing specification for offline or runtime team selection dialog |
| 61 | int32_t iMaxPlayer; // maximum number of players allowed in this team - 0 for infinite |
| 62 | |
| 63 | friend class C4TeamList; |
| 64 | |
| 65 | public: |
| 66 | C4Team() : piPlayers(nullptr), iPlayerCount(0), iPlayerCapacity(0), iID(0), iPlrStartIndex(0), dwClr(0), iMaxPlayer(0) { *Name = 0; } |
| 67 | ~C4Team() { Clear(); } |
| 68 | |
| 69 | void Clear(); |
| 70 | void AddPlayer(class C4PlayerInfo &rInfo, bool fAdjustPlayer); // add player by info; adjusts ID in info and at any joined player |
| 71 | void RemoveIndexedPlayer(int32_t iIndex); // remove info at index; this changes the local list only |
| 72 | void RemovePlayerByID(int32_t iID); |
| 73 | |
| 74 | int32_t GetPlayerCount() const { return iPlayerCount; } |
| 75 | const char *GetName() const { return Name; } |
| 76 | int32_t GetID() const { return iID; } |
| 77 | bool IsPlayerIDInTeam(int32_t iID); // search list for a player with the given ID |
| 78 | int32_t GetFirstUnjoinedPlayerID() const; // search for a player that does not have the join-flag set |
| 79 | int32_t GetFirstActivePlayerID() const; // search for a player that is still in the game |
| 80 | int32_t GetPlrStartIndex() const { return iPlrStartIndex; } |
| 81 | uint32_t GetColor() const { return dwClr; } |
| 82 | const char *GetIconSpec() const { return sIconSpec.getData(); } |
| 83 | bool IsFull() const { return iMaxPlayer && (iPlayerCount >= iMaxPlayer); } // whether no more players may join this team |
| 84 | StdStrBuf GetNameWithParticipants() const; // compose team name like "Team 1 (boni, GhostBear, Clonko)" |
| 85 | bool HasWon() const; // return true if any member player of the team has won |
| 86 | |
| 87 | int32_t GetIndexedPlayer(int32_t iIndex) const { return Inside<int32_t>(ival: iIndex, lbound: 0, rbound: iPlayerCount - 1) ? piPlayers[iIndex] : 0; } |
| 88 | |
| 89 | void CompileFunc(StdCompiler *pComp); |
| 90 | |
| 91 | // this rechecks teams for all (not removed) players; sets players here by team selection in player infos |
| 92 | void RecheckPlayers(); |
| 93 | |
| 94 | // this assigns a team color if it's still zero |
| 95 | void RecheckColor(C4TeamList &rForList); |
| 96 | }; |
| 97 | |
| 98 | // global team list |
| 99 | class C4TeamList |
| 100 | { |
| 101 | public: |
| 102 | // team config constants |
| 103 | enum ConfigValue |
| 104 | { |
| 105 | TEAM_None = 0, |
| 106 | TEAM_Custom = 1, |
| 107 | TEAM_Active = 2, |
| 108 | TEAM_AllowHostilityChange = 3, |
| 109 | TEAM_Dist = 4, |
| 110 | TEAM_AllowTeamSwitch = 5, |
| 111 | TEAM_AutoGenerateTeams = 6, |
| 112 | TEAM_TeamColors = 7, |
| 113 | }; |
| 114 | |
| 115 | // team distribution configuration |
| 116 | enum TeamDist |
| 117 | { |
| 118 | TEAMDIST_First = 0, |
| 119 | TEAMDIST_Free = 0, // anyone can choose teams |
| 120 | TEAMDIST_Host = 1, // host decides teams |
| 121 | TEAMDIST_None = 2, // no teams |
| 122 | TEAMDIST_Random = 3, // fixed random teams |
| 123 | TEAMDIST_RandomInv = 4, // fixed random teams invisible in lobby |
| 124 | TEAMDIST_Last = 4, |
| 125 | }; |
| 126 | |
| 127 | private: |
| 128 | // std::vector... |
| 129 | C4Team **ppList; |
| 130 | int32_t iTeamCount; |
| 131 | int32_t iTeamCapacity; |
| 132 | |
| 133 | int32_t iLastTeamID; |
| 134 | bool fAllowHostilityChange; // hostility not fixed |
| 135 | bool fAllowTeamSwitch; // teams not fixed |
| 136 | bool fActive; |
| 137 | bool fCustom; // set if read from team file or changed in scenario |
| 138 | bool fTeamColors; // if set, player colors are determined by team colors |
| 139 | bool fAutoGenerateTeams; // teams are generated automatically so there's enough teams for everyone |
| 140 | TeamDist eTeamDist; |
| 141 | int32_t iMaxScriptPlayers; // maximum number of script players to be added in the lobby |
| 142 | StdStrBuf sScriptPlayerNames; // default script player names |
| 143 | int32_t randomTeamCount; // count of teams used when generating random teams; unlimited if less than 2 |
| 144 | |
| 145 | public: |
| 146 | C4TeamList() : ppList(nullptr), iTeamCount(0), iTeamCapacity(0), iLastTeamID(0), fAllowHostilityChange(true), fAllowTeamSwitch(false), |
| 147 | fActive(true), fCustom(false), eTeamDist(TEAMDIST_Free), fTeamColors(false), fAutoGenerateTeams(false), iMaxScriptPlayers(0) {} |
| 148 | ~C4TeamList() { Clear(); } |
| 149 | void Clear(); |
| 150 | |
| 151 | C4TeamList &operator=(const C4TeamList &rCopy); |
| 152 | |
| 153 | private: |
| 154 | // no copying |
| 155 | C4TeamList(const C4TeamList &rCopy); |
| 156 | |
| 157 | private: |
| 158 | void AddTeam(C4Team *pNewTeam); // add a team; grow list if necessary |
| 159 | void ClearTeams(); // delete all teams |
| 160 | bool GenerateDefaultTeams(int32_t iUpToID); // generate Team 1, Team 2, etc. |
| 161 | int32_t GetGenerateTeamCount() const; |
| 162 | |
| 163 | public: |
| 164 | C4Team *GetTeamByID(int32_t iID) const; // get team by ID |
| 165 | C4Team *GetGenerateTeamByID(int32_t iID); // get team by ID; generate if not existent. Always generate for TEAMID_New. |
| 166 | C4Team *GetTeamByIndex(int32_t iIndex) const; // get team by list index, to enumerate all teams |
| 167 | C4Team *GetTeamByPlayerID(int32_t iID) const; // get team by player ID (not number!) |
| 168 | int32_t GetLargestTeamID() const; |
| 169 | C4Team *GetRandomSmallestTeam(bool limitRandomTeamCount = false) const; // get team with least amount of players in it |
| 170 | int32_t GetTeamCount() const { return iTeamCount; } |
| 171 | int32_t GetRandomTeamCount() const { return randomTeamCount; } |
| 172 | |
| 173 | C4Team *CreateTeam(const char *szName); // create a custom team |
| 174 | |
| 175 | bool IsMultiTeams() const { return fActive; } // teams can be selected |
| 176 | bool IsCustom() const { return fCustom; } // whether teams are not generated as default |
| 177 | bool IsHostilityChangeAllowed() const { return fAllowHostilityChange; } // allow (temporary) hostility changes at runtime |
| 178 | bool IsTeamSwitchAllowed() const { return fAllowTeamSwitch; } // allow permanent team changes at runtime |
| 179 | bool CanLocalChooseTeam() 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) |
| 180 | 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) |
| 181 | bool CanLocalSeeTeam() const; |
| 182 | bool IsTeamColors() const { return fTeamColors; } // whether team colors are enabled |
| 183 | bool IsRandomTeam() const { return eTeamDist == TEAMDIST_Random || eTeamDist == TEAMDIST_RandomInv; } // whether a random team mode is selected |
| 184 | bool IsJoin2TeamAllowed(int32_t idTeam); // checks whether a team ID is valid and still available for new joins |
| 185 | bool IsAutoGenerateTeams() const { return fAutoGenerateTeams; } |
| 186 | bool IsRuntimeJoinTeamChoice() const { return IsCustom() && IsMultiTeams(); } // whether players joining at runtime must select a team first |
| 187 | int32_t GetMaxScriptPlayers() const { return iMaxScriptPlayers; } // return max number of script players to be added inthe lobby |
| 188 | int32_t GetForcedTeamSelection(int32_t idForPlayer) const; // if there's only one team for the player to join, return that team ID |
| 189 | StdStrBuf GetScriptPlayerName() const; // get a name to assign to a new script player. Try to avoid name conflicts |
| 190 | bool IsTeamVisible() const; // teams invisible during lobby time if TEAMDIST_RandomInv |
| 191 | |
| 192 | void EnforceLeagueRules(); // enforce some league settings, such as disallowing runtime team change |
| 193 | |
| 194 | // assign a team ID to player info; recheck if any user-set team infos are valid within the current team options |
| 195 | // creates a new team for melee; assigns the least-used team for teamwork melee |
| 196 | // host/single-call only; using SafeRandom! |
| 197 | bool RecheckPlayerInfoTeams(C4PlayerInfo &rNewJoin, bool fByHost); |
| 198 | |
| 199 | // compiler |
| 200 | void CompileFunc(StdCompiler *pComp); |
| 201 | bool Load(C4Group &hGroup, class C4Scenario *pInitDefault, class C4LangStringTable *pLang); // clear self and load from group file (C4CFN_Teams); init default by scen if no team fiel is present |
| 202 | bool Save(C4Group &hGroup); // save to group file (C4CFN_Teams) |
| 203 | |
| 204 | // this rechecks teams for all (not removed) players; sets players here by team selection in player infos |
| 205 | void RecheckPlayers(); |
| 206 | |
| 207 | // this reorders any unjoined players to teams if they are unevenly filled and team distribution is random |
| 208 | // any changed players will be flagged "updated" |
| 209 | void RecheckTeams(); |
| 210 | |
| 211 | // marks all unjoined players as not-in-team and reassigns a team for them |
| 212 | // also automatically flags all affected player infos as updated |
| 213 | void ReassignAllTeams(); |
| 214 | |
| 215 | private: |
| 216 | // team distribution configuration |
| 217 | std::string GetTeamDistName(TeamDist eTeamDist) const; |
| 218 | |
| 219 | public: |
| 220 | void FillTeamDistOptions(C4GUI::ComboBox_FillCB *pFiller) const; |
| 221 | void SendSetTeamDist(TeamDist eNewDist); |
| 222 | TeamDist GetTeamDist() const { return eTeamDist; } |
| 223 | std::string GetTeamDistString() const; |
| 224 | bool HasTeamDistOptions() const; |
| 225 | void SetTeamDistribution(TeamDist eToVal); |
| 226 | void SendSetTeamColors(bool fEnabled); |
| 227 | void SetTeamColors(bool fEnabled); |
| 228 | void SetRandomTeamCount(int32_t count); |
| 229 | }; |
| 230 | |