OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4Teams.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2005-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // player team management for teamwork melees
17 
18 #include "C4Include.h"
19 #include "control/C4Teams.h"
20 
21 #include "game/C4Game.h"
22 #include "lib/C4Random.h"
23 #include "c4group/C4Components.h"
24 #include "player/C4Player.h"
25 #include "player/C4PlayerList.h"
26 #include "control/C4GameControl.h"
27 
28 // ---------------------------------------------------------------
29 // C4Team
30 
31 C4Team::C4Team(const C4Team &rCopy)
32  : piPlayers(new int32_t[rCopy.GetPlayerCount()]),
33  iPlayerCount(rCopy.GetPlayerCount()),
34  iPlayerCapacity(rCopy.GetPlayerCount()),
35  iID(rCopy.GetID()), iPlrStartIndex(rCopy.iPlrStartIndex), dwClr(rCopy.dwClr),
36  sIconSpec(rCopy.GetIconSpec()), iMaxPlayer(rCopy.iMaxPlayer)
37 {
38  // copy name
39  SCopy(rCopy.GetName(), Name, C4MaxName);
40  // copy players
41  for (int32_t i = 0; i < iPlayerCount; i++)
42  piPlayers[i] = rCopy.GetIndexedPlayer(i);
43 }
44 
46 {
47  delete [] piPlayers; piPlayers = nullptr;
48  iPlayerCount = iPlayerCapacity = iMaxPlayer = 0;
49  iID = 0; *Name=0;
50  sIconSpec.Clear();
51 }
52 
53 void C4Team::AddPlayer(C4PlayerInfo &rInfo, bool fAdjustPlayer)
54 {
55  // must not happen!
56  assert(rInfo.GetID());
57  if (!rInfo.GetID()) return;
58  // add player; grow vector if necessary
59  if (iPlayerCount >= iPlayerCapacity)
60  {
61  int32_t *piNewPlayers = new int32_t[iPlayerCapacity = (iPlayerCount+4)&~3];
62  if (iPlayerCount) memcpy(piNewPlayers, piPlayers, iPlayerCount*sizeof(int32_t));
63  delete [] piPlayers; piPlayers = piNewPlayers;
64  }
65  // store new player
66  piPlayers[iPlayerCount++] = rInfo.GetID();
67  if (!fAdjustPlayer) return;
68  // set values in info
69  rInfo.SetTeam(GetID());
70  if (Game.Teams.IsTeamColors()) rInfo.SetColor(GetColor());
71  // and in actual player, if it is joined already
72  if (rInfo.IsJoined())
73  {
74  C4Player *pJoinedPlr = ::Players.GetByInfoID(rInfo.GetID());
75  assert(pJoinedPlr || (rInfo.GetType() == C4PT_Script));
76  if (pJoinedPlr)
77  {
78  pJoinedPlr->Team = GetID();
79  if (Game.Teams.IsTeamColors()) pJoinedPlr->SetPlayerColor(GetColor());
80  }
81  }
82 }
83 
84 void C4Team::RemoveIndexedPlayer(int32_t iIndex)
85 {
86  // safety
87  assert(Inside<int32_t>(iIndex, 0, iPlayerCount-1));
88  if (!Inside<int32_t>(iIndex, 0, iPlayerCount-1)) return;
89  // move other players done
90  for (int32_t i = iIndex+1; i < iPlayerCount; ++i)
91  piPlayers[i-1] = piPlayers[i];
92  --iPlayerCount;
93 }
94 
95 void C4Team::RemovePlayerByID(int32_t iID)
96 {
97  // get index
98  int32_t i;
99  for (i=0; i < iPlayerCount; ++i)
100  if (piPlayers[i] == iID) break;
101  if (i == iPlayerCount) { assert(false); return; } // ID not found
102  // remove it
104 }
105 
106 bool C4Team::IsPlayerIDInTeam(int32_t iID)
107 {
108  int32_t i=iPlayerCount, *piPlr = piPlayers;
109  while (i--) if (*piPlr++ == iID) return true;
110  return false;
111 }
112 
114 {
115  // search for a player that does not have the join-flag set
116  int32_t i=iPlayerCount, idPlr, *piPlr = piPlayers;
117  C4PlayerInfo *pInfo;
118  while (i--)
119  if ((pInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlr = *piPlr++)))
120  if (!pInfo->HasJoinIssued())
121  return idPlr;
122  // none found
123  return 0;
124 }
125 
127 {
128  if (pComp->isDeserializer()) Clear();
129  pComp->Value(mkNamingAdapt(iID, "id", 0));
130  pComp->Value(mkNamingAdapt(mkStringAdaptMA(Name), "Name", ""));
131  pComp->Value(mkNamingAdapt(iPlrStartIndex, "PlrStartIndex", 0));
132  pComp->Value(mkNamingAdapt(iPlayerCount, "PlayerCount", 0));
133  if (pComp->isDeserializer()) { delete [] piPlayers; piPlayers = new int32_t [iPlayerCapacity = iPlayerCount]; ZeroMem(piPlayers, sizeof(*piPlayers) * iPlayerCount); }
134  pComp->Value(mkNamingAdapt(mkArrayAdapt(piPlayers, iPlayerCount, -1), "Players"));
135  pComp->Value(mkNamingAdapt(dwClr, "Color", 0u));
137  "IconSpec", StdCopyStrBuf()));
138  pComp->Value(mkNamingAdapt(iMaxPlayer, "MaxPlayer", 0));
139 }
140 
142 {
143  // check all players within the team
144  for (int32_t i=0; i<iPlayerCount; ++i)
145  {
146  bool fIsValid = false; int32_t id; C4PlayerInfo *pInfo;
147  if ((id = piPlayers[i]))
148  if ((pInfo = Game.PlayerInfos.GetPlayerInfoByID(id)))
149  if (pInfo->GetTeam() == GetID())
150  if (pInfo->IsUsingTeam())
151  fIsValid = true;
152  // removal will decrease iPlayerCount, which will abort the loop earlier
153  if (!fIsValid) RemoveIndexedPlayer(i--);
154  }
155  // now check for any new players in the team
156  int32_t id = 0; C4PlayerInfo *pInfo;
157  while ((pInfo = Game.PlayerInfos.GetNextPlayerInfoByID(id)))
158  {
159  id = pInfo->GetID();
160  if (pInfo->GetTeam() == GetID())
161  if (pInfo->IsUsingTeam())
162  if (!IsPlayerIDInTeam(id))
163  AddPlayer(*pInfo, false);
164  }
165 }
166 
167 DWORD GenerateRandomPlayerColor(int32_t iTry); // C4PlayerInfo.cpp
168 bool IsColorConflict(DWORD dwClr1, DWORD dwClr2); // C4PlayerInfo.cpp
169 
171 {
172  // number of times trying new player colors
173  const int32_t C4MaxTeamColorChangeTries = 100;
174  if (!dwClr)
175  {
176  const int defTeamColorCount = 10;
177  DWORD defTeamColorRGB[defTeamColorCount] = { 0xF40000, 0x00C800, 0xFCF41C, 0x2020FF, // red, green, yellow, blue,
178  0xC48444, 0xFFFFFF, 0x848484, 0xFF00EF, // brown, white, grey, pink,
179  0x00FFFF, 0x784830
180  }; // cyan, dk brown
181  // no color assigned yet: Generate by team ID
182  if (iID >=1 && iID <=defTeamColorCount)
183  {
184  // default colors
185  dwClr = defTeamColorRGB[iID-1] | 0xff000000;
186  }
187  else
188  {
189  // find a new, unused color
190  for (int32_t iTry=1; iTry<C4MaxTeamColorChangeTries; ++iTry)
191  {
193  int32_t iIdx=0; C4Team *pTeam; bool fOK=true;
194  while ((pTeam = rForList.GetTeamByIndex(iIdx++)))
195  if (pTeam != this)
196  if (IsColorConflict(pTeam->GetColor(), dwClr))
197  {
198  fOK=false;
199  break;
200  }
201  // color is fine?
202  if (fOK) return;
203  // it's not; try next color
204  }
205  // Giving up: Use last generated color
206  }
207  }
208 }
209 
211 {
212  // compose team name like "Team 1 (boni, GhostBear, Clonko)"
213  // or just "Team 1" for empty team
214  StdStrBuf sTeamName;
215  sTeamName.Copy(GetName());
216  if (GetPlayerCount())
217  {
218  sTeamName.Append(" (");
219  int32_t iTeamPlrCount=0;
220  for (int32_t j=0; j<GetPlayerCount(); ++j)
221  {
222  int32_t iPlr = GetIndexedPlayer(j);
223  C4PlayerInfo *pPlrInfo;
224  if (iPlr) if ((pPlrInfo = Game.PlayerInfos.GetPlayerInfoByID(iPlr)))
225  {
226  if (iTeamPlrCount++) sTeamName.Append(", ");
227  sTeamName.Append(pPlrInfo->GetName());
228  }
229  }
230  sTeamName.AppendChar(')');
231  }
232  return sTeamName;
233 }
234 
235 bool C4Team::HasWon() const
236 {
237  // return true if any member player of the team has won
238  bool fHasWon = false;
239  for (int32_t i=0; i<iPlayerCount; ++i)
240  {
241  int32_t id; C4PlayerInfo *pInfo;
242  if ((id = piPlayers[i]))
243  if ((pInfo = Game.PlayerInfos.GetPlayerInfoByID(id)))
244  if (pInfo->HasWon())
245  {
246  fHasWon = true;
247  break;
248  }
249  }
250  return fHasWon;
251 }
252 
253 // ---------------------------------------------------------------
254 // C4TeamList
255 
257 {
258  // del all teams
259  ClearTeams();
260  // del player team vector
261  delete [] ppList; ppList = nullptr;
262  iTeamCapacity = 0;
263  fAllowHostilityChange = true;
264  fAllowTeamSwitch = false;
265  fCustom = false;
266  fActive = true;
267  fTeamColors = false;
268  eTeamDist = TEAMDIST_Free;
269  fAutoGenerateTeams = false;
270  iMaxScriptPlayers = 0;
271  sScriptPlayerNames.Clear();
272 }
273 
275 {
276  Clear();
277  if ((iTeamCount = iTeamCapacity = rCopy.iTeamCount))
278  ppList = new C4Team *[iTeamCapacity];
279  for (int i = 0; i < iTeamCount; i++)
280  ppList[i] = new C4Team(*rCopy.ppList[i]);
281  iLastTeamID = rCopy.iLastTeamID;
282  fAllowHostilityChange = rCopy.fAllowHostilityChange;
283  fAllowTeamSwitch = rCopy.fAllowTeamSwitch;
284  fCustom = rCopy.fCustom;
285  fActive = rCopy.fActive;
286  eTeamDist = rCopy.eTeamDist;
287  fTeamColors = rCopy.fTeamColors;
288  fAutoGenerateTeams = rCopy.fAutoGenerateTeams;
289  sScriptPlayerNames = rCopy.sScriptPlayerNames;
290  return *this;
291 }
292 
294 {
295  // only if there are any teams
296  if (!fActive) return false;
297  // check by mode
298  switch (eTeamDist)
299  {
300  case TEAMDIST_Free: return true;
302  case TEAMDIST_None:
303  case TEAMDIST_Random:
304  case TEAMDIST_RandomInv:
305  return false;
306  default: assert(false); return false;
307  }
308 }
309 
310 bool C4TeamList::CanLocalChooseTeam(int32_t idPlayer) const
311 {
312  // must be possible at all
313  if (!CanLocalChooseTeam()) return false;
314  // there must be space in a target team
315  // always possible if teams are generated on the fly
316  if (IsAutoGenerateTeams()) return true;
317  // also possible if one of the teams that's not the player's is not full
318  C4Team *pCurrentTeam = nullptr, *pCheck;
319  if (idPlayer) pCurrentTeam = GetTeamByPlayerID(idPlayer);
320  int32_t iCheckTeam=0;
321  while ((pCheck = GetTeamByIndex(iCheckTeam++)))
322  if (pCheck != pCurrentTeam)
323  if (!pCheck->IsFull())
324  break;
325  return !!pCheck;
326 }
327 
329 {
330  if (!fActive) return false;
331  // invisible teams aren't revealed before game start
332  if (eTeamDist != TEAMDIST_RandomInv) return true;
333  return !!Game.IsRunning;
334 }
335 
336 void C4TeamList::AddTeam(C4Team *pNewTeam)
337 {
338  // add team; grow vector if necessary
339  if (iTeamCount >= iTeamCapacity)
340  {
341  // grow up to the nearest multiple of 4 elements
342  // (TODO: Replace the whole thing e.g. with a simple std::vector<C4Team>)
343  C4Team **ppNewTeams = new C4Team*[(iTeamCapacity = ((iTeamCount+4)&~3))];
344  if (iTeamCount) memcpy(ppNewTeams, ppList, iTeamCount*sizeof(C4Team *));
345  delete [] ppList; ppList = ppNewTeams;
346  }
347  // store new team
348  ppList[iTeamCount++] = pNewTeam;
349  // adjust ID
350  iLastTeamID = std::max(pNewTeam->iID, iLastTeamID);
351 }
352 
353 void C4TeamList::ClearTeams()
354 {
355  // delete all teams
356  C4Team **ppTeam=ppList;
357  if (iTeamCount) { while (iTeamCount--) delete *(ppTeam++); iTeamCount = 0; }
358  iLastTeamID = 0;
359 }
360 
361 C4Team *C4TeamList::CreateTeam(const char *szName)
362 {
363  // custom team
364  C4Team *pNewTeam = new C4Team();
365  pNewTeam->iID = iLastTeamID + 1;
366  SCopy(szName, pNewTeam->Name, C4MaxName);
367  AddTeam(pNewTeam);
368  pNewTeam->RecheckColor(*this);
369  return pNewTeam;
370 }
371 
372 bool C4TeamList::GenerateDefaultTeams(int32_t iUpToID)
373 {
374  // generate until last team ID matches given
375  while (iLastTeamID < iUpToID)
376  {
377  char TeamName[C4MaxName+1];
378  sprintf(TeamName, LoadResStr("IDS_MSG_TEAM"), iLastTeamID+1);
379  if (!CreateTeam(TeamName)) return false;
380  }
381  return true;
382 }
383 
384 C4Team *C4TeamList::GetTeamByID(int32_t iID) const
385 {
386  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
387  for (; iCnt--; ++ppCheck) if ((*ppCheck)->GetID() == iID) return *ppCheck;
388  return nullptr;
389 }
390 
392 {
393  // only if enabled
394  if (!IsMultiTeams()) return nullptr;
395  // new team?
396  if (iID == TEAMID_New) iID = GetLargestTeamID()+1;
397  // find in list
398  C4Team *pTeam = GetTeamByID(iID);
399  if (pTeam) return pTeam;
400  // not found: Generate
401  GenerateDefaultTeams(iID);
402  return GetTeamByID(iID);
403 }
404 
405 C4Team *C4TeamList::GetTeamByIndex(int32_t iIndex) const
406 {
407  // safety
408  if (!Inside<int32_t>(iIndex, 0, iTeamCount-1)) return nullptr;
409  // direct list access
410  return ppList[iIndex];
411 }
412 
413 C4Team *C4TeamList::GetTeamByName(const char *szName) const
414 {
415  assert(szName);
416  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
417  for (; iCnt--; ++ppCheck) if (SEqual((*ppCheck)->GetName(), szName)) return *ppCheck;
418  return nullptr;
419 }
420 
422 {
423  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
424  for (; iCnt--; ++ppCheck) if ((*ppCheck)->IsPlayerIDInTeam(iID)) return *ppCheck;
425  return nullptr;
426 }
427 
429 {
430  int32_t iLargest = 0;
431  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
432  for (; iCnt--; ++ppCheck) iLargest = std::max((*ppCheck)->GetID(), iLargest);
433  return iLargest;
434 }
435 
437 {
438  C4Team *pLowestTeam = nullptr; int iLowestTeamCount = 0;
439  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
440  for (; iCnt--; ++ppCheck)
441  {
442  if ((*ppCheck)->IsFull()) continue; // do not join into full teams
443  if (!pLowestTeam || pLowestTeam->GetPlayerCount() > (*ppCheck)->GetPlayerCount())
444  {
445  pLowestTeam = *ppCheck;
446  iLowestTeamCount = 1;
447  }
448  else if (pLowestTeam->GetPlayerCount() == (*ppCheck)->GetPlayerCount())
449  if (!UnsyncedRandom(++iLowestTeamCount))
450  pLowestTeam = *ppCheck;
451  }
452  return pLowestTeam;
453 }
454 
456 {
457  // teams invisible during lobby time if random surprise teams
458  if (eTeamDist == TEAMDIST_RandomInv)
459  if (::Network.isLobbyActive())
460  return false;
461  return true;
462 }
463 
465 {
466  // only if enabled
467  assert(IsMultiTeams());
468  if (!IsMultiTeams()) return false;
469  // check whether a new team is to be assigned first
470  C4Team *pCurrentTeam = GetTeamByPlayerID(rNewJoin.GetID());
471  int32_t idCurrentTeam = pCurrentTeam ? pCurrentTeam->GetID() : 0;
472  if (rNewJoin.GetTeam())
473  {
474  // was that team a change to the current team?
475  // no change anyway: OK, skip this info
476  if (idCurrentTeam == rNewJoin.GetTeam()) return true;
477  // the player had a different team assigned: Check if changes are allowed at all
478  if (eTeamDist == TEAMDIST_Free || (eTeamDist == TEAMDIST_Host && fByHost))
479  // also make sure that selecting this team is allowed, e.g. doesn't break the team limit
480  // 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!
481  if (rNewJoin.GetTeam() != TEAMID_New && IsJoin2TeamAllowed(rNewJoin.GetTeam(), rNewJoin.GetType()))
482  // okay; accept change
483  return true;
484  // Reject change by reassigning the current team
485  rNewJoin.SetTeam(idCurrentTeam);
486  // and determine a new team, if none has been assigned yet
487  if (idCurrentTeam) return true;
488  }
489  // new team assignment
490  // teams are always needed in the lobby, so there's a team preset to change
491  // 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)
492  // but only assign teams in runtime join if the player won't pick it himself
493  bool fWillHaveLobby = ::Network.isEnabled() && !::Network.Status.isPastLobby() && Game.fLobby;
494  bool fHasOrWillHaveLobby = ::Network.isLobbyActive() || fWillHaveLobby;
495  bool fCanPickTeamAtRuntime = !IsRandomTeam() && (rNewJoin.GetType() == C4PT_User) && IsRuntimeJoinTeamChoice();
496  bool fIsTeamNeeded = IsRuntimeJoinTeamChoice() || GetTeamCount();
497  if (!fHasOrWillHaveLobby && (!fIsTeamNeeded || fCanPickTeamAtRuntime)) return false;
498  // get least-used team
499  C4Team *pAssignTeam=nullptr;
500  C4Team *pLowestTeam = GetRandomSmallestTeam();
501  // melee mode
502  if (IsAutoGenerateTeams() && !IsRandomTeam())
503  {
504  // reuse old team only if it's empty
505  if (pLowestTeam && !pLowestTeam->GetPlayerCount())
506  pAssignTeam = pLowestTeam;
507  else
508  {
509  // no empty team: generate new
510  GenerateDefaultTeams(iLastTeamID+1);
511  pAssignTeam = GetTeamByID(iLastTeamID);
512  }
513  }
514  else
515  {
516  if (!pLowestTeam)
517  {
518  // not enough teams defined in teamwork mode?
519  // then create two teams as default
520  if (!GetTeamByIndex(1))
521  GenerateDefaultTeams(2);
522  else
523  // otherwise, all defined teams are full. This is a scenario error, because MaxPlayer should have been adjusted
524  return false;
525  pLowestTeam = GetTeamByIndex(0);
526  }
527  pAssignTeam = pLowestTeam;
528  }
529  // assign it
530  if (!pAssignTeam) return false;
531  pAssignTeam->AddPlayer(rNewJoin, true);
532  return true;
533 }
534 
535 bool C4TeamList::IsJoin2TeamAllowed(int32_t idTeam, C4PlayerType plrType)
536 {
537  // join to new team: Only if new teams can be created
538  if (idTeam == TEAMID_New) return IsAutoGenerateTeams();
539  // team number must be valid
540  C4Team *pTeam = GetTeamByID(idTeam);
541  if (!pTeam) return false;
542  // team player count must not exceed the limit, unless it is a script player
543  return !pTeam->IsFull() || plrType == C4PT_Script;
544 }
545 
547 {
548  // if (pComp->isDeserializer()) Clear(); - do not clear, because this would corrupt the fCustom-flag
549  pComp->Value(mkNamingAdapt(fActive, "Active", true));
550  pComp->Value(mkNamingAdapt(fCustom, "Custom", true));
551  pComp->Value(mkNamingAdapt(fAllowHostilityChange, "AllowHostilityChange", false));
552  pComp->Value(mkNamingAdapt(fAllowTeamSwitch, "AllowTeamSwitch", false));
553  pComp->Value(mkNamingAdapt(fAutoGenerateTeams, "AutoGenerateTeams", false));
554  pComp->Value(mkNamingAdapt(iLastTeamID, "LastTeamID", 0));
555 
556  StdEnumEntry<TeamDist> TeamDistEntries[] =
557  {
558  { "Free", TEAMDIST_Free },
559  { "Host", TEAMDIST_Host },
560  { "None", TEAMDIST_None },
561  { "Random", TEAMDIST_Random },
562  { "RandomInv", TEAMDIST_RandomInv },
563  };
564  pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eTeamDist, TeamDistEntries), "TeamDistribution", TEAMDIST_Free));
565 
566  pComp->Value(mkNamingAdapt(fTeamColors, "TeamColors", false));
567  pComp->Value(mkNamingAdapt(iMaxScriptPlayers, "MaxScriptPlayers", 0));
568  pComp->Value(mkNamingAdapt(mkParAdapt(sScriptPlayerNames, StdCompiler::RCT_All), "ScriptPlayerNames", StdStrBuf()));
569 
570  int32_t iOldTeamCount = iTeamCount;
571  pComp->Value(mkNamingCountAdapt(iTeamCount, "Team"));
572 
573  if (pComp->isDeserializer())
574  {
575  while (iOldTeamCount--) delete ppList[iOldTeamCount];
576  delete [] ppList;
577  if ((iTeamCapacity = iTeamCount))
578  {
579  ppList = new C4Team *[iTeamCapacity];
580  memset(ppList, 0, sizeof(C4Team *)*iTeamCapacity);
581  }
582  else
583  ppList = nullptr;
584  }
585 
586  if (iTeamCount)
587  {
588  // Force compiler to spezialize
589  mkPtrAdaptNoNull(*ppList);
590  // Save team list, using map-function.
591  pComp->Value(mkNamingAdapt(
592  mkArrayAdaptMap(ppList, iTeamCount, mkPtrAdaptNoNull<C4Team>),
593  "Team"));
594  }
595 
596  if (pComp->isDeserializer())
597  {
598  // adjust last team ID, which may not be set properly for player-generated team files
599  iLastTeamID = std::max(GetLargestTeamID(), iLastTeamID);
600  // force automatic generation of teams if none are defined
601  if (!iTeamCount) fAutoGenerateTeams = true;
602  }
603 }
604 
605 bool C4TeamList::Load(C4Group &hGroup, class C4Scenario *pInitDefault, class C4LangStringTable *pLang)
606 {
607  // clear previous
608  Clear();
609  // load file contents
610  StdStrBuf Buf;
611  if (!hGroup.LoadEntryString(C4CFN_Teams, &Buf))
612  {
613  // no teams: Try default init
614  if (!pInitDefault) return false;
615  // no teams defined: Activate default melee teams if a melee rule is found
616  // default: FFA for anything that looks like melee
617  if ( pInitDefault->Game.IsMelee())
618  {
619  fAllowHostilityChange = true;
620  fActive = true;
621  fAutoGenerateTeams = true;
622  }
623  else
624  {
625  // No goals/rules whatsoever: They could be present in the objects.txt, but parsing that would be a bit of
626  // overkill
627  // So just keep the old behaviour here, and disallow teams
628  fAllowHostilityChange = true;
629  fActive = false;
630  }
631  fCustom = false;
632  }
633  else
634  {
635  // team definition file may be localized
636  if (pLang) pLang->ReplaceStrings(Buf);
637  // compile
638  if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(mkNamingAdapt(*this, "Teams"), Buf, C4CFN_Teams)) return false;
639  }
640  // post-initialization: Generate default team colors
641  int32_t iTeam=0; C4Team *pTeam;
642  while ((pTeam = GetTeamByIndex(iTeam++)))
643  pTeam->RecheckColor(*this);
644  return true;
645 }
646 
648 {
649  // remove previous entry from group
650  hGroup.DeleteEntry(C4CFN_Teams);
651  // decompile
652  try
653  {
654  StdStrBuf Buf = DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(*this, "Teams"));
655  // save it
656  hGroup.Add(C4CFN_Teams, Buf, false, true);
657  }
658  catch (StdCompiler::Exception *)
659  { return false; }
660  // done, success
661  return true;
662 }
663 
665 {
666  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
667  for (; iCnt--; ++ppCheck) (*ppCheck)->RecheckPlayers();
668 }
669 
671 {
672  // automatic team distributions only
673  if (!IsRandomTeam()) return;
674  // host decides random teams
675  if (!::Control.isCtrlHost()) return;
676  // random teams in auto generate mode? Make sure there are exactly two teams
677  if (IsAutoGenerateTeams() && GetTeamCount() != 2)
678  {
680  return;
681  }
682  // redistribute players of largest team that has relocatable players left towards smaller teams
683  for (;;)
684  {
685  C4Team *pLowestTeam = GetRandomSmallestTeam();
686  if (!pLowestTeam) break; // no teams: Nothing to re-distribute.
687  // get largest team that has relocateable players
688  C4Team *pLargestTeam = nullptr;
689  C4Team **ppCheck=ppList; int32_t iCnt=iTeamCount;
690  for (; iCnt--; ++ppCheck) if (!pLargestTeam || pLargestTeam->GetPlayerCount() > (*ppCheck)->GetPlayerCount())
691  if ((*ppCheck)->GetFirstUnjoinedPlayerID())
692  pLargestTeam = *ppCheck;
693  // no team can redistribute?
694  if (!pLargestTeam) break;
695  // redistribution won't help much?
696  if (pLargestTeam->GetPlayerCount() - pLowestTeam->GetPlayerCount() <= 1) break;
697  // okay; redistribute one player!
698  int32_t idRedistPlayer = pLargestTeam->GetFirstUnjoinedPlayerID();
699  C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idRedistPlayer);
700  assert(pInfo);
701  if (!pInfo) break; // umn...serious problems
702  pLargestTeam->RemovePlayerByID(idRedistPlayer);
703  pLowestTeam->AddPlayer(*pInfo, true);
704  C4ClientPlayerInfos *pClrInfo = Game.PlayerInfos.GetClientInfoByPlayerID(idRedistPlayer);
705  assert(pClrInfo);
706  // player info change: mark updated to remote clients get information
707  if (pClrInfo)
708  {
709  pClrInfo->SetUpdated();
710  }
711  }
712 }
713 
715 {
716  assert(::Control.isCtrlHost());
717  if (!::Control.isCtrlHost()) return;
718  // go through all player infos; reset team in them
719  int32_t idStart = -1; C4PlayerInfo *pNfo;
720  while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart)))
721  {
722  idStart = pNfo->GetID();
723  if (pNfo->HasJoinIssued()) continue;
724  pNfo->SetTeam(0);
725  // mark changed info as updated
727  assert(pCltInfo);
728  if (pCltInfo)
729  {
730  pCltInfo->SetUpdated();
731  }
732  }
733  // clear players from team lists
734  RecheckPlayers();
735  // in random autogenerate mode, there must be exactly two teams
736  if (IsRandomTeam())
737  if (IsAutoGenerateTeams() && GetTeamCount() != 2)
738  {
739  ClearTeams();
740  GenerateDefaultTeams(2);
741  }
742  // reassign them
743  idStart = -1;
744  while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(idStart)))
745  {
746  idStart = pNfo->GetID();
747  if (pNfo->HasJoinIssued()) continue;
748  assert(!pNfo->GetTeam());
749  RecheckPlayerInfoTeams(*pNfo, true);
750  }
751 }
752 
753 StdStrBuf C4TeamList::GetTeamDistName(TeamDist eTeamDist) const
754 {
755  switch (eTeamDist)
756  {
757  case TEAMDIST_Free: return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_FREE"), true));
758  case TEAMDIST_Host: return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_HOST"), true));
759  case TEAMDIST_None: return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_NONE"), true));
760  case TEAMDIST_Random: return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_RND"), true));
761  case TEAMDIST_RandomInv: return(StdStrBuf(LoadResStr("IDS_MSG_TEAMDIST_RNDINV"), true));
762  default: return(FormatString("TEAMDIST_undefined(%d)", (int) eTeamDist));
763  }
764 }
765 
767 {
768  // no teams if disabled
769  if (!fActive) return;
770  // team distribution options
771  pFiller->AddEntry(GetTeamDistName(TEAMDIST_Free).getData(), TEAMDIST_Free);
772  pFiller->AddEntry(GetTeamDistName(TEAMDIST_Host).getData(), TEAMDIST_Host);
773  if (IsAutoGenerateTeams()) pFiller->AddEntry(GetTeamDistName(TEAMDIST_None).getData(), TEAMDIST_None); // no teams: only for regular melees
774  pFiller->AddEntry(GetTeamDistName(TEAMDIST_Random).getData(), TEAMDIST_Random);
775  pFiller->AddEntry(GetTeamDistName(TEAMDIST_RandomInv).getData(), TEAMDIST_RandomInv);
776  }
777 
779 {
780  assert(::Control.isCtrlHost());
781  // set it for all clients
783 }
784 
786 {
787  // return name of current team distribution setting
788  return GetTeamDistName(eTeamDist);
789 }
790 
792 {
793  // team distribution can be changed if teams are enabled
794  return fActive;
795 }
796 
798 {
799  if (!Inside(eToVal, TEAMDIST_First, TEAMDIST_Last)) { assert(false); return; }
800  eTeamDist = eToVal;
801  // team distribution mode changed: Host may beed to redistribute
802  if (::Control.isCtrlHost())
803  {
804  // if a random team mode was set, reassign all teams so it's really random.
805  // Also reassign in no-team-mode so enough teams for all players exist
806  if (IsRandomTeam() || eTeamDist==TEAMDIST_None)
808  else
809  {
810  // otherwise, it's sufficient to just reassign any teams that are incorrect for the current mode
811  RecheckTeams();
812  }
813  // send updates to other clients and reset flags
814  if (::Network.isEnabled())
815  {
817  }
818  }
819 }
820 
821 void C4TeamList::SendSetTeamColors(bool fEnabled)
822 {
823  // set it for all clients
825 }
826 
827 void C4TeamList::SetTeamColors(bool fEnabled)
828 {
829  // change only
830  if (fEnabled == fTeamColors) return;
831  // reflect change
832  fTeamColors = fEnabled;
833  // update colors of all players
834  if (!::Control.isCtrlHost()) return;
835  // go through all player infos; reset color in them
836  Game.PlayerInfos.UpdatePlayerAttributes(); // sets team and savegame colors
837  if (::Network.isEnabled())
838  {
839  // sends color updates to all clients
841  }
842 }
843 
845 {
846  // enforce some league settings
847  // allow temp hostility switching; often used e.g. to unstick friendly Clonks, but:
848  fAllowTeamSwitch = false; // switching teams in league games? Yeah, sure...
849 }
850 
851 int32_t C4TeamList::GetForcedTeamSelection(int32_t idForPlayer) const
852 {
853  // if there's only one team for the player to join, return that team ID
854  C4Team *pOKTeam = nullptr, *pCheck;
855  if (idForPlayer) pOKTeam = GetTeamByPlayerID(idForPlayer); // curent team is always possible, even if full
856  int32_t iCheckTeam=0;
857  while ((pCheck = GetTeamByIndex(iCheckTeam++)))
858  if (!pCheck->IsFull())
859  {
860  // this team could be joined
861  if (pOKTeam && pOKTeam != pCheck)
862  {
863  // there already was a team that could be joined
864  // two alternatives -> team selection is not forced
865  return 0;
866  }
867  pOKTeam = pCheck;
868  }
869  // was there a team that could be joined?
870  if (pOKTeam)
871  {
872  // if teams are generated on the fly, there would always be the possibility of creating a new team }
873  if (IsAutoGenerateTeams()) return 0;
874  // otherwise, this team is forced!
875  return pOKTeam->GetID();
876  }
877  // no team could be joined: Teams auto generated?
878  if (IsAutoGenerateTeams())
879  {
880  // then the only possible way is to join a new team
881  return TEAMID_New;
882  }
883  // otherwise, nothing can be done...
884  return 0;
885 }
886 
888 {
889  // get a name to assign to a new script player. Try to avoid name conflicts
890  if (!sScriptPlayerNames.getLength()) return StdStrBuf(LoadResStr("IDS_TEXT_COMPUTER")); // default name
891  // test available script names
892  int32_t iNameIdx = 0; StdStrBuf sOut;
893  while (sScriptPlayerNames.GetSection(iNameIdx++, &sOut, '|'))
895  return sOut;
896  // none are available: Return a random name
897  sScriptPlayerNames.GetSection(UnsyncedRandom(iNameIdx-1), &sOut, '|');
898  return sOut;
899 }
900 
901 int32_t C4TeamList::GetStartupTeamCount(int32_t startup_player_count)
902 {
903  // Count non-empty teams
904  int32_t i_team = 0; C4Team *team;
905  int32_t team_count = 0;
906  while ((team = GetTeamByIndex(i_team++)))
907  {
908  if (team->GetPlayerCount() > 0) ++team_count;
909  }
910  // No populated teams found? This can happen in non-network mode when no players are assigned
911  if (!team_count)
912  {
913  // Teams have not been selected yet, but the map script may want to have an estimate
914  // in this case, calculate prospective teams from startup player count
915  if (IsCustom() && !IsAutoGenerateTeams())
916  {
917  // Teams are pre-defined. Assume players will try to distribute broadly on these teams
918  team_count = std::min<int32_t>(startup_player_count, GetTeamCount());
919  }
920  else if (IsRandomTeam())
921  {
922  // Randomized teams: Players will be put into two teams.
923  team_count = std::min<int32_t>(startup_player_count, 2);
924  }
925  else
926  {
927  // Teams are auto-added -> fallback to player count
928  team_count = startup_player_count;
929  }
930  }
931 
932  return team_count;
933 }
void RecheckTeams()
Definition: C4Teams.cpp:670
const char * getData() const
Definition: StdBuf.h:450
int32_t GetPlayerCount() const
Definition: C4Teams.h:64
void RecheckPlayers()
Definition: C4Teams.cpp:664
void SetColor(DWORD dwUseClr)
Definition: C4PlayerInfo.h:118
bool IsRunning
Definition: C4Game.h:141
void SendSetTeamDist(TeamDist eNewDist)
Definition: C4Teams.cpp:778
int32_t GetID() const
Definition: C4Teams.h:66
bool GetSection(size_t idx, StdStrBuf *psOutSection, char cSeparator=';') const
Definition: StdBuf.cpp:376
const char * GetName() const
Definition: C4PlayerInfo.h:160
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:122
int32_t Team
Definition: C4Player.h:90
char Name[C4MaxName+1]
Definition: C4Teams.h:47
Definition: StdAdaptors.h:760
StdNamingCountAdapt< int_t > mkNamingCountAdapt(int_t &iCount, const char *szName)
Definition: StdAdaptors.h:974
bool isCtrlHost() const
Definition: C4GameControl.h:99
#define C4CFN_Teams
Definition: C4Components.h:127
bool IsMelee()
Definition: C4Scenario.cpp:510
C4Game Game
Definition: C4Globals.cpp:52
bool IsRandomTeam() const
Definition: C4Teams.h:171
bool IsTeamColors() const
Definition: C4Teams.h:170
void RecheckColor(C4TeamList &rForList)
Definition: C4Teams.cpp:170
uint32_t UnsyncedRandom()
Definition: C4Random.cpp:58
void Clear()
Definition: StdBuf.h:474
bool HasWon() const
Definition: C4PlayerInfo.h:184
#define sprintf
Definition: Standard.h:171
Definition: C4Teams.h:30
bool isLobbyActive() const
Definition: C4Network2.h:204
void SetTeam(int32_t idToTeam)
Definition: C4PlayerInfo.h:123
uint32_t GetColor() const
Definition: C4Teams.h:70
bool IsAutoGenerateTeams() const
Definition: C4Teams.h:173
StdStrBuf GetNameWithParticipants() const
Definition: C4Teams.cpp:210
C4Team * GetGenerateTeamByID(int32_t iID)
Definition: C4Teams.cpp:391
bool IsTeamVisible() const
Definition: C4Teams.cpp:455
void SendSetTeamColors(bool fEnabled)
Definition: C4Teams.cpp:821
bool IsMultiTeams() const
Definition: C4Teams.h:163
C4Team * GetTeamByID(int32_t iID) const
Definition: C4Teams.cpp:384
C4Team * GetRandomSmallestTeam() const
Definition: C4Teams.cpp:436
void ReassignAllTeams()
Definition: C4Teams.cpp:714
C4PlayerInfo * GetNextPlayerInfoByID(int32_t id) const
C4Team * CreateTeam(const char *szName)
Definition: C4Teams.cpp:361
void UpdatePlayerAttributes(C4ClientPlayerInfos *pForInfo, bool fResolveConflicts)
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:97
bool CanLocalChooseTeam() const
Definition: C4Teams.cpp:293
C4PlayerInfo * GetPlayerInfoByID(int32_t id) const
C4TeamList & Teams
Definition: C4Game.h:72
C4ClientPlayerInfos * GetClientInfoByPlayerID(int32_t id) const
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
StdNamingAdapt< T > mkNamingAdapt(T &&rValue, const char *szName)
Definition: StdAdaptors.h:93
void AppendChar(char cChar)
Definition: StdBuf.h:596
bool Load(C4Group &hGroup, class C4Scenario *pInitDefault, class C4LangStringTable *pLang)
Definition: C4Teams.cpp:605
void RemoveIndexedPlayer(int32_t iIndex)
Definition: C4Teams.cpp:84
bool Save(C4Group &hGroup)
Definition: C4Teams.cpp:647
bool RecheckPlayerInfoTeams(C4PlayerInfo &rNewJoin, bool fByHost)
Definition: C4Teams.cpp:464
int32_t iPlrStartIndex
Definition: C4Teams.h:48
C4Network2 Network
Definition: C4Globals.cpp:53
bool IsFull() const
Definition: C4Teams.h:72
DWORD GenerateRandomPlayerColor(int32_t iTry)
int iCnt
Definition: TstC4NetIO.cpp:35
bool isPastLobby() const
Definition: C4Network2.h:88
C4GameControl Control
int32_t GetForcedTeamSelection(int32_t idForPlayer) const
Definition: C4Teams.cpp:851
StdArrayAdapt< T > mkArrayAdapt(T *pArray, int iSize)
Definition: StdAdaptors.h:308
void SetTeamColors(bool fEnabled)
Definition: C4Teams.cpp:827
bool IsJoined() const
Definition: C4PlayerInfo.h:169
void AddPlayer(class C4PlayerInfo &rInfo, bool fAdjustPlayer)
Definition: C4Teams.cpp:53
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:527
C4Team * GetTeamByIndex(int32_t iIndex) const
Definition: C4Teams.cpp:405
C4PlayerList Players
int32_t GetTeam() const
Definition: C4PlayerInfo.h:198
StdPtrAdapt< T > mkPtrAdaptNoNull(T *&rpObj)
Definition: StdAdaptors.h:604
C4Player * GetByInfoID(int iInfoID) const
bool CanLocalSeeTeam() const
Definition: C4Teams.cpp:328
bool HasJoinIssued() const
Definition: C4PlayerInfo.h:170
bool IsCustom() const
Definition: C4Teams.h:164
void SetPlayerColor(uint32_t dwNewClr)
Definition: C4Player.cpp:1714
C4Network2Status Status
Definition: C4Network2.h:122
const unsigned int C4MaxName
const char * GetName() const
Definition: C4Teams.h:65
C4PlayerInfo * GetActivePlayerInfoByName(const char *szName)
void ReplaceStrings(StdStrBuf &rBuf)
C4Team()
Definition: C4Teams.h:56
void Value(const T &rStruct)
Definition: StdCompiler.h:171
bool IsUsingTeam() const
Definition: C4PlayerInfo.h:176
bool isEnabled() const
Definition: C4Network2.h:203
C4Team * GetTeamByPlayerID(int32_t iID) const
Definition: C4Teams.cpp:421
C4PlayerType
Definition: C4Constants.h:152
const int32_t TEAMID_New
Definition: C4Teams.h:27
bool HasTeamDistOptions() const
Definition: C4Teams.cpp:791
virtual bool isDeserializer()
Definition: StdCompiler.h:63
uint32_t dwClr
Definition: C4Teams.h:49
bool DeleteEntry(const char *szFilename, bool fRecycle=false)
Definition: C4Group.cpp:1384
int32_t GetLargestTeamID() const
Definition: C4Teams.cpp:428
void RemovePlayerByID(int32_t iID)
Definition: C4Teams.cpp:95
C4PlayerInfoList & PlayerInfos
Definition: C4Game.h:73
bool LoadEntryString(const char *szEntryName, StdStrBuf *Buf)
Definition: C4Group.cpp:1932
bool IsColorConflict(DWORD dwClr1, DWORD dwClr2)
void Clear()
Definition: C4Teams.cpp:256
int32_t GetFirstUnjoinedPlayerID() const
Definition: C4Teams.cpp:113
bool IsPlayerIDInTeam(int32_t iID)
Definition: C4Teams.cpp:106
void RecheckPlayers()
Definition: C4Teams.cpp:141
void CompileFunc(StdCompiler *pComp)
Definition: C4Teams.cpp:126
C4Network2Players Players
Definition: C4Network2.h:119
StdCopyStrBuf sIconSpec
Definition: C4Teams.h:50
C4SGame Game
Definition: C4Scenario.h:232
void FillTeamDistOptions(C4GUI::ComboBox_FillCB *pFiller) const
Definition: C4Teams.cpp:766
bool Add(const char *szFile, const char *szAddAs)
Definition: C4Group.cpp:1316
#define mkStringAdaptMA(szString)
Definition: StdAdaptors.h:191
C4Team * GetTeamByName(const char *szName) const
Definition: C4Teams.cpp:413
void AddEntry(const char *szText, int32_t id)
void SetTeamDistribution(TeamDist eToVal)
Definition: C4Teams.cpp:797
StdParameterAdapt< T, P > mkParAdapt(T &&rObj, P &&rPar)
Definition: StdAdaptors.h:456
C4TeamList & operator=(const C4TeamList &rCopy)
Definition: C4Teams.cpp:274
int32_t GetTeamCount() const
Definition: C4Teams.h:159
void CompileFunc(StdCompiler *pComp)
Definition: C4Teams.cpp:546
size_t getLength() const
Definition: StdBuf.h:453
int32_t GetID() const
Definition: C4PlayerInfo.h:197
C4PlayerType GetType() const
Definition: C4PlayerInfo.h:155
int32_t iID
Definition: C4Teams.h:46
bool IsRuntimeJoinTeamChoice() const
Definition: C4Teams.h:174
std::enable_if< std::is_pod< T >::value >::type ZeroMem(T *lpMem, size_t dwSize)
Definition: Standard.h:63
bool IsJoin2TeamAllowed(int32_t idTeam, C4PlayerType plrType)
Definition: C4Teams.cpp:535
bool HasWon() const
Definition: C4Teams.cpp:235
uint32_t DWORD
StdArrayAdapt< T, M > mkArrayAdaptMap(T *pArray, int iSize, M &&map)
Definition: StdAdaptors.h:311
StdStrBuf GetTeamDistString() const
Definition: C4Teams.cpp:785
bool fLobby
Definition: C4Game.h:120
int32_t iMaxPlayer
Definition: C4Teams.h:51
void Copy()
Definition: StdBuf.h:475
void EnforceLeagueRules()
Definition: C4Teams.cpp:844
int32_t GetStartupTeamCount(int32_t startup_player_count)
Definition: C4Teams.cpp:901
bool Inside(T ival, U lbound, V rbound)
Definition: Standard.h:45
StdStrBuf GetScriptPlayerName() const
Definition: C4Teams.cpp:887
void Clear()
Definition: C4Teams.cpp:45
int32_t GetIndexedPlayer(int32_t iIndex) const
Definition: C4Teams.h:76
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:277