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