OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4PlayerInfoListBox.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2008-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 listbox used in lobby and game over dlg
17 
18 #include "C4Include.h"
20 
21 #include "control/C4PlayerInfo.h"
23 #include "control/C4Teams.h"
24 #include "game/C4Game.h"
25 #include "gui/C4FileSelDlg.h"
27 #include "graphics/C4Draw.h"
28 #include "gui/C4MouseControl.h"
29 #include "network/C4Network2.h"
30 #include "control/C4GameControl.h"
31 #include "control/C4RoundResults.h"
32 #include "gui/C4GameLobby.h"
33 
34 DWORD GenerateRandomPlayerColor(int32_t iTry); // in C4PlayerInfoConflicts.cpp
35 
36 // ----------- ListItem --------------------------------------------------------------------------------
37 
38 // helper
39 C4GameLobby::MainDlg *C4PlayerInfoListBox::ListItem::GetLobby() const
40 {
42 }
43 
44 bool C4PlayerInfoListBox::ListItem::CanLocalChooseTeams(int32_t idPlayer) const
45 {
46  // whether the local client can change any teams
47  // only if teams are available
48  if (!Game.Teams.IsMultiTeams()) return false;
49  // only if global change allowed
50  C4GameLobby::MainDlg *pLobby = GetLobby();
51  if (!pLobby) return false;
52  if (pLobby->IsCountdown()) return false;
53  // only for unjoined players
54  if (idPlayer)
55  {
57  if (pInfo && pInfo->HasJoined()) return false;
58  }
59  // finally, only if team settings permit
60  return Game.Teams.CanLocalChooseTeam(idPlayer);
61 }
62 
63 void C4PlayerInfoListBox::ListItem::DrawElement(C4TargetFacet &cgo)
64 {
65  if (dwBackground) pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, cgo.TargetX+rcBounds.x+rcBounds.Wdt-1, cgo.TargetY+rcBounds.y+rcBounds.Hgt-1, dwBackground);
66  typedef C4GUI::Window BaseClass;
67  BaseClass::DrawElement(cgo);
68 }
69 
70 // ----------- PlayerListItem -----------------------------------------------------------------------
71 
72 C4PlayerInfoListBox::PlayerListItem::PlayerListItem(C4PlayerInfoListBox *pForListBox, int32_t idClient,
73  int32_t idPlayer, bool fSavegamePlayer, C4GUI::Element *pInsertBeforeElement)
74  : ListItem(pForListBox), pScoreLabel(nullptr), pTimeLabel(nullptr), pExtraLabel(nullptr),
75  pRankIcon(nullptr), pTeamCombo(nullptr), pTeamPic(nullptr), fIconSet(false), fJoinedInfoSet(false),
76  dwJoinClr(0), dwPlrClr(0), idClient(idClient), idPlayer(idPlayer), fFreeSavegamePlayer(fSavegamePlayer)
77 {
78  bool fIsEvaluation = pForListBox->IsEvaluation(), fIsLobby = pForListBox->IsLobby();
79  C4PlayerInfo *pInfo = GetPlayerInfo(); assert(pInfo);
80  uint32_t dwTextColor = pForListBox->GetTextColor();
81  CStdFont *pCustomFont = pForListBox->GetCustomFont();
82  uint32_t dwPlayerColor;
83  if (fIsEvaluation)
84  dwPlayerColor = dwTextColor;
85  else
86  dwPlayerColor = pInfo->GetLobbyColor() | C4GUI_MessageFontAlpha;
87  // league account name? Overwrite the shown name
88  StdStrBuf sPlayerName(pInfo->GetLobbyName());
89  // calc height
90  int32_t iHeight = ::GraphicsResource.TextFont.GetLineHeight() + C4GUI::ComboBox::GetDefaultHeight() + 3 * IconLabelSpacing;
91  // create subcomponents
92  pIcon = new C4GUI::Icon(C4Rect(0, 0, iHeight, iHeight), C4GUI::Ico_UnknownPlayer);
93  if (Game.Parameters.isLeague())
95  if (Game.Teams.IsMultiTeams() && !(fIsEvaluation && pList->IsTeamFilter()))
96  {
97  // will be moved when the item is added to the list, and the position moved
98  pTeamCombo = new C4GUI::ComboBox(C4Rect(0,0,10,10));
99  pTeamCombo->SetComboCB(new C4GUI::ComboBox_FillCallback<PlayerListItem>(this, &PlayerListItem::OnTeamComboFill, &PlayerListItem::OnTeamComboSelChange));
100  pTeamCombo->SetSimple(true);
101  if (pCustomFont)
102  {
103  pTeamCombo->SetFont(pCustomFont);
104  pTeamCombo->SetColors(dwTextColor, C4GUI_StandardBGColor, 0);
105  }
106  UpdateTeam();
107  }
108  // Evaluation
109  if (fIsEvaluation)
110  {
111  // Team image if known and if not placed on top of box anyway
112  if (!pList->IsTeamFilter())
113  {
114  C4Team *pTeam = Game.Teams.GetTeamByID(pInfo->GetTeam());
115  if (pTeam && pTeam->GetIconSpec() && *pTeam->GetIconSpec())
116  {
117  pTeamPic = new C4GUI::Picture(C4Rect(iHeight + IconLabelSpacing, 0, iHeight, iHeight), true);
118  Game.DrawTextSpecImage(pTeamPic->GetMFacet(), pTeam->GetIconSpec(), nullptr, pTeam->GetColor());
119  pTeamPic->SetDrawColor(pTeam->GetColor());
120  }
121  }
122  // Total playing time (not in team filter because then the second line is taken by the score label)
123  if (!pList->IsTeamFilter())
124  {
125  StdStrBuf sTimeLabelText;
126  C4RoundResultsPlayer *pRoundResultsPlr = Game.RoundResults.GetPlayers().GetByID(idPlayer);
127  uint32_t iTimeTotal = pRoundResultsPlr ? pRoundResultsPlr->GetTotalPlayingTime() : 0 /* unknown - should not happen */;
128  sTimeLabelText.Format(LoadResStr("IDS_CTL_TOTALPLAYINGTIME"), iTimeTotal/3600, (iTimeTotal/60)%60, iTimeTotal%60);
129  pTimeLabel = new C4GUI::Label(sTimeLabelText.getData(), 0, 0, ARight, dwTextColor, pForListBox->GetCustomFont(), false, true);
130  }
131  // Extra info set by script
132  C4RoundResultsPlayer *pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(idPlayer);;
133  if (pEvaluationPlayer)
134  {
135  const char *szCustomEval = pEvaluationPlayer->GetCustomEvaluationStrings();
136  if (szCustomEval && *szCustomEval)
137  {
138  pExtraLabel = new C4GUI::Label(szCustomEval, 0,0, ARight); // positioned later
140  }
141  }
142  }
143  pNameLabel = new C4GUI::Label(sPlayerName.getData(), (iHeight + IconLabelSpacing) * (1+!!pTeamPic), IconLabelSpacing, ALeft, dwPlayerColor, pCustomFont, !fIsEvaluation, true);
144  // calc own bounds - list box needs height only; width and pos will be moved by list
145  SetBounds(C4Rect(0,0,10,iHeight));
146  // add components
147  AddElement(pIcon); AddElement(pNameLabel);
148  if (pTeamPic) AddElement(pTeamPic);
149  if (pTimeLabel) AddElement(pTimeLabel);
150  if (pTeamCombo) AddElement(pTeamCombo);
151  if (pRankIcon) AddElement(pRankIcon);
152  if (pExtraLabel) AddElement(pExtraLabel);
153  // add to listbox (will get resized horizontally and moved)
154  pForListBox->InsertElement(this, pInsertBeforeElement, PlayerListBoxIndent);
155  // league score update
156  UpdateScoreLabel(pInfo);
157  // set ID
158  if (fFreeSavegamePlayer)
159  idListItemID.idType = ListItem::ID::PLI_SAVEGAMEPLR;
160  else
161  idListItemID.idType = ListItem::ID::PLI_PLAYER;
162  idListItemID.id = idPlayer;
163  UpdateIcon(pInfo, GetJoinedInfo());
164  // context menu for list item
165  if (fIsLobby) SetContextHandler(new C4GUI::CBContextHandler<PlayerListItem>(this, &PlayerListItem::OnContext));
166  // update collapsed/not collapsed
167  fShownCollapsed = false;
168  UpdateCollapsed();
169 }
170 
171 void C4PlayerInfoListBox::PlayerListItem::UpdateOwnPos()
172 {
173  // parent for client rect
174  typedef C4GUI::Window ParentClass;
175  ParentClass::UpdateOwnPos();
176  C4GUI::ComponentAligner caBounds(GetContainedClientRect(), IconLabelSpacing, IconLabelSpacing);
177  // subtract icon(s)
178  caBounds.GetFromLeft(pIcon->GetBounds().Wdt);
179  if (pTeamPic) caBounds.GetFromLeft(pTeamPic->GetBounds().Wdt - IconLabelSpacing);
180  C4Rect rcExtraDataRect;
181  // extra data label area
182  if (pExtraLabel) rcExtraDataRect = caBounds.GetFromBottom(::GraphicsResource.TextFont.GetLineHeight());
183  // second line (team+rank)
184  C4GUI::ComponentAligner caTeamArea(caBounds.GetFromBottom(C4GUI::ComboBox::GetDefaultHeight()), 0,0);
185  C4Rect rcRankIcon;
186  if (pList->IsEvaluation())
187  {
188  if (pRankIcon)
189  {
190  rcRankIcon = caBounds.GetFromRight(caBounds.GetInnerHeight());
191  if (pExtraLabel) rcExtraDataRect.Wdt = caBounds.GetInnerWidth(); // In evaluation view, rank icon has its own coloumn
192  }
193  }
194  else
195  {
196  if (pRankIcon) rcRankIcon = caTeamArea.GetFromRight(caTeamArea.GetInnerHeight());
197  }
198  C4Rect rcTeam = caTeamArea.GetAll();
199  // item to positions: team combo box
200  if (pTeamCombo)
201  {
202  pTeamCombo->SetBounds(rcTeam);
203  }
204  // rank icon
205  if (pRankIcon)
206  {
207  pRankIcon->SetBounds(rcRankIcon);
208  }
209  // time label
210  if (pTimeLabel)
211  {
212  C4Rect rcUpperBounds = caBounds.GetAll();
213  pTimeLabel->SetBounds(rcTeam);
214  pTimeLabel->SetX0(rcUpperBounds.x + rcUpperBounds.Wdt);
215  }
216  // extra label
217  if (pExtraLabel) pExtraLabel->SetBounds(rcExtraDataRect);
218 }
219 
220 int32_t C4PlayerInfoListBox::PlayerListItem::GetListItemTopSpacing()
221 {
222  int32_t iSpacing = C4GUI_DefaultListSpacing;
223  // evaluation: Add some extra spacing between players of different teams
224  if (pList->IsEvaluation())
225  {
226  C4GUI::Element *pPrevItem = GetPrev();
227  if (pPrevItem)
228  {
229  C4PlayerInfoListBox::ListItem *pPrevListItem = static_cast<C4PlayerInfoListBox::ListItem *>(pPrevItem);
230  if (pPrevListItem->idListItemID.idType == ListItem::ID::PLI_PLAYER)
231  {
232  PlayerListItem *pPrevPlayerListItem = static_cast<C4PlayerInfoListBox::PlayerListItem *>(pPrevListItem);
233  C4PlayerInfo *pThisInfo = GetPlayerInfo();
234  C4PlayerInfo *pPrevInfo = pPrevPlayerListItem->GetPlayerInfo();
235  if (pThisInfo && pPrevInfo)
236  {
237  if (pPrevInfo->GetTeam() != pThisInfo->GetTeam())
238  {
239  iSpacing += 10;
240  }
241  }
242  }
243  }
244  }
245  return iSpacing;
246 }
247 
248 void C4PlayerInfoListBox::PlayerListItem::UpdateIcon(C4PlayerInfo *pInfo, C4PlayerInfo *pJoinedInfo)
249 {
250  // check whether icon is known
251  bool fResPresent = false;
252  C4Network2Res *pRes = nullptr;
253  if (pInfo)
254  if ((pRes = pInfo->GetRes()))
255  fResPresent = pRes->isComplete();
256  C4RoundResultsPlayer *pEvaluationPlayer = nullptr;
257  if (pList->IsEvaluation()) pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(idPlayer);
258  bool fHasIcon = fResPresent || pEvaluationPlayer || (!::Network.isEnabled() && pInfo);
259  // check whether joined info is present
260  bool fHasJoinedInfo = !!pJoinedInfo;
261  DWORD dwJoinedInfoClr = pJoinedInfo ? pJoinedInfo->GetLobbyColor() : 0;
262  DWORD dwPlayerClr = pInfo ? pInfo->GetLobbyColor() : 0;
263  // already up-to-date?
264  if (fHasIcon == fIconSet && fJoinedInfoSet == fHasJoinedInfo && dwJoinedInfoClr == dwJoinClr && dwPlayerClr == dwPlrClr) return;
265  // update then
266  // redraw player icon
267  if (fHasIcon)
268  {
269  // custom icon?
270  if (pEvaluationPlayer && pEvaluationPlayer->GetBigIcon().Surface)
271  {
272  pIcon->SetFacet(pEvaluationPlayer->GetBigIcon());
273  fIconSet = true;
274  }
275  else
276  fIconSet = pInfo->LoadBigIcon(pIcon->GetMFacet());
277  if (!fIconSet)
278  {
279  // no custom icon: create default by player color
280  pIcon->GetMFacet().Create(64,64); // the bigicon is bigger than the normal 40x40 icon
281  ::GraphicsResource.fctPlayerClr.DrawClr(pIcon->GetMFacet(), true, dwPlayerClr);
282  }
283  fIconSet = true;
284  }
285  else
286  // no player info known - either res not retrieved yet or script player
287  pIcon->SetIcon((pInfo && pInfo->GetType() == C4PT_Script) ? C4GUI::Ico_Host : C4GUI::Ico_UnknownPlayer);
288  // join
289  if (fHasJoinedInfo)
290  {
291  // make sure we're not drawing on GraphicsResource
292  if (!pIcon->EnsureOwnSurface()) return;
293  // draw join info
294  C4Facet fctDraw = pIcon->GetFacet();
295  int32_t iSizeMax = std::max<int32_t>(fctDraw.Wdt, fctDraw.Hgt);
296  int32_t iCrewClrHgt = iSizeMax/2;
297  fctDraw.Hgt -= iCrewClrHgt; fctDraw.Y += iCrewClrHgt;
298  fctDraw.Wdt = iSizeMax/2;
299  fctDraw.X = 2;
300  // shadow
301  DWORD dwPrevMod; bool fPrevMod = pDraw->GetBlitModulation(dwPrevMod);
303  ::GraphicsResource.fctCrewClr.DrawClr(fctDraw, true, dwJoinedInfoClr);
304  if (fPrevMod) pDraw->ActivateBlitModulation(dwPrevMod); else pDraw->DeactivateBlitModulation();
305  fctDraw.X = 0;
306  // gfx
307  ::GraphicsResource.fctCrewClr.DrawClr(fctDraw, true, dwJoinedInfoClr);
308  }
309  fJoinedInfoSet = fHasJoinedInfo;
310  dwJoinClr = dwJoinedInfoClr;
311  dwPlrClr = dwPlayerClr;
312 }
313 
314 void C4PlayerInfoListBox::PlayerListItem::UpdateTeam()
315 {
316  if (!pTeamCombo) return; // unassigned for no teams
317  const char *szTeamName = ""; bool fReadOnly = true;
318  fReadOnly = !CanLocalChooseTeam();
319  int32_t idTeam; C4Team *pTeam;
320  C4PlayerInfo *pInfo = GetPlayerInfo();
321  if (!Game.Teams.CanLocalSeeTeam())
322  szTeamName = LoadResStr("IDS_MSG_RNDTEAM");
323  else if (pInfo)
324  if ((idTeam = pInfo->GetTeam()))
325  if ((pTeam = Game.Teams.GetTeamByID(idTeam)))
326  szTeamName = pTeam->GetName();
327  pTeamCombo->SetText(szTeamName);
328  pTeamCombo->SetReadOnly(fReadOnly);
329 }
330 
331 void C4PlayerInfoListBox::PlayerListItem::UpdateScoreLabel(C4PlayerInfo *pInfo)
332 {
333  assert(pInfo);
334  C4RoundResultsPlayer *pRoundResultsPlr = nullptr;
335  if (pList->IsEvaluation()) pRoundResultsPlr = Game.RoundResults.GetPlayers().GetByID(idPlayer);
336 
337  if (pInfo->getLeagueScore() || pInfo->IsLeagueProjectedGainValid() || pRoundResultsPlr)
338  {
339  int32_t iScoreRightPos = ((pRankIcon && pList->IsEvaluation()) ? pRankIcon->GetBounds().x : GetBounds().Wdt) - IconLabelSpacing;
340  int32_t iScoreYPos = IconLabelSpacing;
341  // if evaluation and team lists, move score label into second line - TODO: some hack only, still needs to be done right
342  C4RoundResultsPlayer *pEvaluationPlayer = Game.RoundResults.GetPlayers().GetByID(pInfo->GetID());;
343  bool fPlayerHasEvaluationData=false;
344  if (pEvaluationPlayer)
345  {
346  const char *szCustomEval = pEvaluationPlayer->GetCustomEvaluationStrings();
347  if (szCustomEval && *szCustomEval)
348  fPlayerHasEvaluationData=true;
349  }
350  if (pList->IsEvaluation() && pList->IsTeamFilter())
351  iScoreYPos = GetBounds().Hgt - (C4GUI::ComboBox::GetDefaultHeight()*(1+(int32_t)fPlayerHasEvaluationData)) - IconLabelSpacing;
352 
353  // score label visible
354  if (!pScoreLabel)
355  {
356  AddElement(pScoreLabel = new C4GUI::Label("", iScoreRightPos, iScoreYPos, ARight, pList->GetTextColor(), pList->GetCustomFont(), false));
357  if (pList->IsEvaluation())
358  pScoreLabel->SetToolTip(LoadResStr("IDS_DESC_OLDANDNEWSCORE"));
359  else
360  pScoreLabel->SetToolTip(LoadResStr("IDS_DESC_LEAGUESCOREANDPROJECTEDGA"));
361  }
362  StdStrBuf sText;
363  // Evaluation (GameOver)
364  if (pList->IsEvaluation())
365  {
366  if (pInfo->getLeagueScore() || pInfo->IsLeagueProjectedGainValid() || (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid()))
367  {
368  if (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid())
369  {
370  // Show old league score, gain, and new league score
371  // Normally, the league server should make sure that [Old score] + [Gain] == [New score]
372  int32_t iOldScore = pInfo->getLeagueScore(), iScoreGain = pRoundResultsPlr->GetLeagueScoreGain(), iNewScore = pRoundResultsPlr->GetLeagueScoreNew();
373  int32_t iDiscrepancy = iNewScore - (iOldScore + iScoreGain);
374  if (!iDiscrepancy)
375  {
376  sText.Format("{{Ico:League}}<c afafaf>%d (%+d)</c> %d %s", (int)iOldScore, (int)iScoreGain, (int)iNewScore, LoadResStr("IDS_TEXT_SCORE"));
377  }
378  else
379  {
380  // If there's a discrepancy, there must have been some kind of admin intervention during the game - display it in red!
381  sText.Format("{{Ico:League}}<c afafaf>%d (%+d)</c><c ff0000>(%+d)</c> %d %s", (int)iOldScore, (int)iScoreGain, (int)iDiscrepancy, (int)iNewScore, LoadResStr("IDS_TEXT_SCORE"));
382  }
383  }
384  // Show old league score only
385  else
386  {
387  sText.Format("{{Ico:League}}<c afafaf>(%d)</c> %s", (int)pInfo->getLeagueScore(), LoadResStr("IDS_TEXT_SCORE"));
388  }
389  }
390  else if (pRoundResultsPlr && pRoundResultsPlr->IsScoreNewValid() && !Game.RoundResults.SettlementScoreIsHidden())
391  {
392  // new score known
393  sText.Format("{{Ico:Settlement}}<c afafaf>%d (%+d)</c> %d %s", (int)pRoundResultsPlr->GetScoreOld(), (int)(pRoundResultsPlr->GetScoreNew()-pRoundResultsPlr->GetScoreOld()), (int)pRoundResultsPlr->GetScoreNew(), LoadResStr("IDS_TEXT_SCORE"));
394  }
395  else if (pRoundResultsPlr && !pRoundResultsPlr->IsScoreNewValid() && !Game.RoundResults.SettlementScoreIsHidden())
396  {
397  // only old score known (e.g., player disconnected)
398  sText.Format("{{Ico:Settlement}}<c afafaf>(%d)</c> %s", (int)pRoundResultsPlr->GetScoreOld(), LoadResStr("IDS_TEXT_SCORE"));
399  }
400  else
401  {
402  // nothing known. Shouldn't really happen.
403  sText.Ref("");
404  }
405  }
406  // Pre-evaluation (Lobby)
407  else
408  {
409  // Show current league score and projected gain
410  // Don't show if team invisible, so random surprise teams don't get spoiled
412  sText.Format("%d (%+d)", (int)pInfo->getLeagueScore(), (int)pInfo->GetLeagueProjectedGain());
413  // Show current league score only
414  else
415  sText.Format("%d", (int)pInfo->getLeagueScore());
416  }
417  pScoreLabel->SetX0(iScoreRightPos);
418  pScoreLabel->SetText(sText.getData(), false);
419  }
420  else if (pScoreLabel)
421  {
422  // score label invisible
423  delete pScoreLabel;
424  pScoreLabel = nullptr;
425  }
426  if (pRankIcon)
427  {
428  int32_t iSym = 0;
429  if (pRoundResultsPlr && pRoundResultsPlr->IsLeagueScoreNewValid())
430  iSym = pRoundResultsPlr->GetLeagueRankSymbolNew();
431  if (!iSym)
432  iSym = pInfo->getLeagueRankSymbol();
433  if (iSym && !fShownCollapsed)
434  {
435  C4GUI::Icons eRankIcon = (C4GUI::Icons) (C4GUI::Ico_Rank1 + Clamp<int32_t>(iSym-1, 0, C4GUI::Ico_Rank9-C4GUI::Ico_Rank1));
436  pRankIcon->SetVisibility(true);
437  pRankIcon->SetIcon(eRankIcon);
438  }
439  else
440  {
441  pRankIcon->SetVisibility(false);
442  }
443  }
444 }
445 
446 void C4PlayerInfoListBox::PlayerListItem::UpdateCollapsed()
447 {
448  bool fShouldBeCollapsed = pList->IsPlayerItemCollapsed(this);
449  if (fShouldBeCollapsed == fShownCollapsed) return;
450  // so update collapsed state
451  int32_t iHeight; int32_t iNameLblX0;
452  if ((fShownCollapsed = fShouldBeCollapsed))
453  {
454  // calc height
455  iHeight = ::GraphicsResource.TextFont.GetLineHeight() + 2 * IconLabelSpacing;
456  // teamcombo not visible if collapsed
457  if (pTeamCombo) pTeamCombo->SetVisibility(false);
458  }
459  else
460  {
461  // calc height
462  iHeight = ::GraphicsResource.TextFont.GetLineHeight() + C4GUI::ComboBox::GetDefaultHeight() + 3 * IconLabelSpacing;
463  // teamcombo visible if not collapsed
464  if (pTeamCombo) pTeamCombo->SetVisibility(true);
465  }
466  // update subcomponents
467  iNameLblX0 = iHeight + IconLabelSpacing;
468  pIcon->GetBounds() = C4Rect(0, 0, iHeight, iHeight);
469  pIcon->UpdateOwnPos();
470  pNameLabel->SetX0(iNameLblX0);
471  // calc own bounds - use icon bounds only, because only the height is used when the item is added
472  SetBounds(pIcon->GetBounds());
473  // update positions
474  pList->UpdateElementPosition(this, PlayerListBoxIndent);
475 }
476 
477 
478 C4GUI::ContextMenu *C4PlayerInfoListBox::PlayerListItem::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
479 {
480  C4PlayerInfo *pInfo = GetPlayerInfo();
481  assert(pInfo);
482  // no context menu for evaluation
483  if (!GetLobby()) return nullptr;
484  // create context menu
485  C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
486  // if this is a free player, add an option to take it over
487  if (fFreeSavegamePlayer)
488  {
489  if (pInfo->GetType() != C4PT_Script)
490  {
491  StdCopyStrBuf strTakeOver(LoadResStr("IDS_MSG_TAKEOVERPLR"));
492  pMenu->AddItem(strTakeOver.getData(), LoadResStr("IDS_MSG_TAKEOVERPLR_DESC"), C4GUI::Ico_Player, nullptr,
493  new C4GUI::CBContextHandler<PlayerListItem>(this, &PlayerListItem::OnContextTakeOver));
494  }
495  }
496  else
497  {
498  // owned players or host can manipulate players
499  if (::Network.isHost() || IsLocalClientPlayer())
500  {
501  // player removal (except for joined script players)
502  if (pInfo->GetType() != C4PT_Script || !pInfo->GetAssociatedSavegamePlayerID())
503  {
504  StdCopyStrBuf strRemove(LoadResStr("IDS_MSG_REMOVEPLR"));
505  pMenu->AddItem(strRemove.getData(), LoadResStr("IDS_MSG_REMOVEPLR_DESC"), C4GUI::Ico_Close,
506  new C4GUI::CBMenuHandler<PlayerListItem>(this, &PlayerListItem::OnCtxRemove), nullptr);
507  }
508  // color was changed: Add option to assign a new color
509  C4PlayerInfo *pInfo = GetPlayerInfo();
510  assert (pInfo);
511  if (pInfo && pInfo->HasAutoGeneratedColor() && (!Game.Teams.IsTeamColors() || !pInfo->GetTeam()))
512  {
513  StdCopyStrBuf strNewColor(LoadResStr("IDS_MSG_NEWPLRCOLOR"));
514  pMenu->AddItem(strNewColor.getData(), LoadResStr("IDS_MSG_NEWPLRCOLOR_DESC"), C4GUI::Ico_Player,
515  new C4GUI::CBMenuHandler<PlayerListItem>(this, &PlayerListItem::OnCtxNewColor), nullptr);
516  }
517  }
518  }
519  // open it
520  return pMenu;
521 }
522 
523 C4GUI::ContextMenu *C4PlayerInfoListBox::PlayerListItem::OnContextTakeOver(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
524 {
525  // create context menu
526  C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
527  // add options for all own, unassigned players
529  if (pkInfo)
530  {
531  int32_t i=0; C4PlayerInfo *pInfo;
532  while ((pInfo = pkInfo->GetPlayerInfo(i++)))
533  if (!pInfo->HasJoinIssued())
534  if (!pInfo->GetAssociatedSavegamePlayerID())
535  {
536  pMenu->AddItem(FormatString(LoadResStr("IDS_MSG_USINGPLR"), pInfo->GetName()).getData(),
537  LoadResStr("IDS_MSG_USINGPLR_DESC"), C4GUI::Ico_Player,
538  new C4GUI::CBMenuHandlerEx<PlayerListItem, int32_t>(this, &PlayerListItem::OnCtxTakeOver, pInfo->GetID()));
539  }
540  }
541  // add option to use a new one... TODO
542  // add option to take over from savegame player TODO
543  // open it
544  return pMenu;
545 }
546 
547 void C4PlayerInfoListBox::PlayerListItem::OnCtxTakeOver(C4GUI::Element *pListItem, const int32_t &idPlayer)
548 {
549  // use player idPlayer to take over this one
550  // this must be processed as a request by the host
551  // some safety first...
553  if (!fFreeSavegamePlayer || !idPlayer || !pLocalInfo) return;
554  C4ClientPlayerInfos LocalInfoRequest(*pLocalInfo);
555  C4PlayerInfo *pGrabbingInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
556  if (!pGrabbingInfo) return;
557  // now adjust info packet
558  pGrabbingInfo->SetAssociatedSavegamePlayer(this->idPlayer);
559  // and request this update (host processes it directly)
560  ::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
561 }
562 
563 void C4PlayerInfoListBox::PlayerListItem::OnCtxRemove(C4GUI::Element *pListItem)
564 {
565  // only host or own player
566  if (!::Network.isEnabled() || (!::Network.isHost() && !IsLocalClientPlayer())) return;
567  // remove the player
568  // this must be processed as a request by the host
569  // now change it in its own request packet
570  C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
571  if (!pChangeInfo || !idPlayer) return;
572  C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
573  if (!LocalInfoRequest.GetPlayerInfoByID(idPlayer)) return;
574  LocalInfoRequest.RemoveInfo(idPlayer);
575  // and request this update (host processes it directly)
576  ::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
577 }
578 
579 void C4PlayerInfoListBox::PlayerListItem::OnCtxNewColor(C4GUI::Element *pListItem)
580 {
581  // only host or own player
582  if (!::Network.isEnabled() || (!::Network.isHost() && !IsLocalClientPlayer())) return;
583  // just send a request to reclaim the original color to the host
584  // the host will deny this and decide on a new color
585  C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
586  if (!pChangeInfo || !idPlayer) return;
587  C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
588  C4PlayerInfo *pPlrInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
589  if (!pPlrInfo) return;
590  pPlrInfo->SetColor(pPlrInfo->GetOriginalColor());
591  // and request this update (host processes it directly)
592  ::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
593 }
594 
595 void C4PlayerInfoListBox::PlayerListItem::OnTeamComboFill(C4GUI::ComboBox_FillCB *pFiller)
596 {
597  // add all possible teams
598  C4Team *pTeam; int32_t i=0;
599  while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
600  if (!pTeam->IsFull() || GetPlayerInfo()->GetTeam() == pTeam->GetID())
601  pFiller->AddEntry(pTeam->GetName(), pTeam->GetID());
602 }
603 
604 bool C4PlayerInfoListBox::PlayerListItem::OnTeamComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection)
605 {
606  // always return true to mark combo sel as processed, so the GUI won't change the team text
607  // get new team id by name
608  C4Team *pNewTeam = Game.Teams.GetTeamByID(idNewSelection);
609  // some safety first...
610  if (!CanLocalChooseTeam() || !pNewTeam) return true;
611  C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(idClient);
612  if (!pChangeInfo || !idPlayer) return true;
613  // this must be processed as a request by the host
614  // now change it in its own request packet
615  C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
616  C4PlayerInfo *pChangedInfo = LocalInfoRequest.GetPlayerInfoByID(idPlayer);
617  if (!pChangedInfo) return true;
618  pChangedInfo->SetTeam(pNewTeam->GetID());
619  // and request this update (host processes it directly)
620  ::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
621  // next update will change the combo box text
622  return true;
623 }
624 
625 void C4PlayerInfoListBox::PlayerListItem::Update()
626 {
627  UpdateCollapsed();
628  UpdateIcon(GetPlayerInfo(), GetJoinedInfo());
629  UpdateTeam();
630  C4PlayerInfo *pNfo = GetPlayerInfo();
631  if (pNfo)
632  {
633  UpdateScoreLabel(pNfo);
634  // update name + color
635  StdStrBuf sShowName(pNfo->GetLobbyName());
636  if (pList->IsEvaluation())
637  {
638  bool fShowWinners = (pList->GetMode() != PILBM_EvaluationNoWinners);
639  bool fHasWon = fShowWinners && pNfo->HasTeamWon();
640  // Append "winner" or "loser" to player name
641  if (fShowWinners)
642  {
643  sShowName.Take(FormatString("%s (%s)", sShowName.getData(), LoadResStr(fHasWon ? "IDS_CTL_WON" : "IDS_CTL_LOST")));
644  }
645  // evaluation: Golden color+background for winners; gray for losers or no winner show
646  if (fHasWon)
647  {
648  pNameLabel->SetColor(C4GUI_WinningTextColor, false);
649  dwBackground = C4GUI_WinningBackgroundColor;
650  }
651  else
652  {
653  pNameLabel->SetColor(C4GUI_LosingTextColor, false);
654  dwBackground = C4GUI_LosingBackgroundColor;
655  }
656  }
657  else
658  {
659  // lobby: Label color by player color
660  pNameLabel->SetColor(pNfo->GetLobbyColor());
661  }
662  pNameLabel->SetText(sShowName.getData(), false);
663  }
664 }
665 
666 C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetPlayerInfo() const
667 {
668  return fFreeSavegamePlayer ? Game.RestorePlayerInfos.GetPlayerInfoByID(idPlayer) : Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
669 }
670 
671 C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetJoinedInfo() const
672 {
673  // safety
674  C4PlayerInfo *pInfo = GetPlayerInfo();
675  if (!pInfo) return nullptr;
676  // is it a joined savegame player?
677  if (fFreeSavegamePlayer)
678  // then this is the joined player
679  return pInfo;
680  // otherwise, does it have a savegame association?
681  int32_t idSavegameInfo;
682  if ((idSavegameInfo = pInfo->GetAssociatedSavegamePlayerID()))
683  // then return the respective info from savegame recreation list
684  return Game.RestorePlayerInfos.GetPlayerInfoByID(idSavegameInfo);
685  // not joined
686  return nullptr;
687 }
688 
689 bool C4PlayerInfoListBox::PlayerListItem::CanLocalChooseTeam() const
690 {
691  // never on savegame players
692  if (fFreeSavegamePlayer || GetJoinedInfo()) return false;
693  // only host or own player
694  if (!::Network.isHost() && !IsLocalClientPlayer()) return false;
695  // finally, only if team settings permit
696  return CanLocalChooseTeams(idPlayer);
697 }
698 
699 bool C4PlayerInfoListBox::PlayerListItem::IsLocalClientPlayer() const
700 {
701  // check whether client is local
702  // if no client can be found, assume network disconnect and everythign local then
703  C4Network2Client *pClient = GetNetClient();
704  return !pClient || pClient->isLocal();
705 }
706 
707 C4Network2Client *C4PlayerInfoListBox::PlayerListItem::GetNetClient() const
708 {
710 }
711 
712 
713 // ----------- ClientListItem -----------------------------------------------------------------
714 
715 C4PlayerInfoListBox::ClientListItem::ClientListItem(C4PlayerInfoListBox *pForListBox, const C4ClientCore &rClientInfo, ListItem *pInsertBefore) // ctor
716  : ListItem(pForListBox), idClient(rClientInfo.getID()), dwClientClr(0xffffff), tLastSoundTime(0)
717 {
718  // set current active-flag (not really needed until player info is retrieved)
719  fIsShownActive = rClientInfo.isActivated();
720  // set ID
721  idListItemID.idType = ListItem::ID::PLI_CLIENT;
722  idListItemID.id = idClient;
723  // get height
724  int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
725  // create subcomponents
726  pStatusIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), GetCurrentStatusIcon());
727  pNameLabel = new C4GUI::Label(rClientInfo.getName(), iIconSize + IconLabelSpacing,0, ALeft, dwClientClr | C4GUI_MessageFontAlpha, nullptr, true, false);
728  pPingLabel = nullptr;
730  if (IsLocalClientPlayer())
731  {
732  // this computer: add player button
733  btnAddPlayer = new C4GUI::CallbackButton<ClientListItem, C4GUI::IconButton>(C4GUI::Ico_AddPlr, C4Rect(0, 0, iIconSize, iIconSize), 'P' /* 2do TODO */, &ClientListItem::OnBtnAddPlr, this);
734  }
735  // calc own bounds
736  C4Rect rcOwnBounds = pNameLabel->GetBounds();
737  rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
738  rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
739  SetBounds(rcOwnBounds);
740  // add components
741  AddElement(pStatusIcon); AddElement(pNameLabel);
742  if (btnAddPlayer) AddElement(btnAddPlayer);
743  // tooltip (same for all components for now. separate tooltip for status icon later?)
744  SetToolTip(FormatString("Client %s (%s)", rClientInfo.getName(), rClientInfo.getNick()).getData());
745  // insert into listbox at correct order
746  // (will eventually get resized horizontally and moved)
747  pForListBox->InsertElement(this, pInsertBefore);
748  // after move: update add player button pos
749  if (btnAddPlayer)
750  {
751  int32_t iHgt = GetClientRect().Hgt;
752  btnAddPlayer->GetBounds() = GetToprightCornerRect(iHgt,iHgt,2,0);
753  }
754  // context menu for list item
755  SetContextHandler(new C4GUI::CBContextHandler<ClientListItem>(this, &ClientListItem::OnContext));
756  // update (also sets color)
757  Update();
758 }
759 
760 void C4PlayerInfoListBox::ClientListItem::SetPing(int32_t iToPing)
761 {
762  // no ping?
763  if (iToPing == -1)
764  {
765  // remove any ping label
766  if (pPingLabel) { delete pPingLabel; pPingLabel = nullptr; }
767  return;
768  }
769  // get ping as text
770  StdStrBuf ping;
771  ping.Format("%d ms", iToPing);
772  // create ping label if necessary
773  if (!pPingLabel)
774  {
775  pPingLabel = new C4GUI::Label(ping.getData(), GetBounds().Wdt, 0, ARight, C4GUI_MessageFontClr);
776  pPingLabel->SetToolTip(LoadResStr("IDS_DLGTIP_PING"));
777  AddElement(pPingLabel);
778  }
779  else
780  // or just set updated text
781  pPingLabel->SetText(ping.getData());
782 }
783 
784 void C4PlayerInfoListBox::ClientListItem::UpdateInfo()
785 {
786  // update color (always, because it can change silently)
787  SetColor(::Network.Players.GetClientChatColor(idClient, true));
788  // update activation status
789  fIsShownActive = GetClient() && GetClient()->isActivated();
790  // update status icon
791  SetStatus(GetCurrentStatusIcon());
792 }
793 
794 C4Client *C4PlayerInfoListBox::ClientListItem::GetClient() const
795 {
796  // search (let's hope it exists)
797  return Game.Clients.getClientByID(idClient);
798 }
799 
800 bool C4PlayerInfoListBox::ClientListItem::IsLocalClientPlayer() const
801 {
802  // check whether client is local
803  // if no client can be found, something is wrong - assume network disconnect and everything local then
804  C4Network2Client *pClient = GetNetClient();
805  assert(pClient);
806  return !pClient || pClient->isLocal();
807 }
808 
809 C4Network2Client *C4PlayerInfoListBox::ClientListItem::GetNetClient() const
810 {
812 }
813 
814 bool C4PlayerInfoListBox::ClientListItem::IsLocal() const
815 {
816  // it's local if client ID matches local ID
817  return idClient == Game.Clients.getLocalID();
818 }
819 
820 C4GUI::Icons C4PlayerInfoListBox::ClientListItem::GetCurrentStatusIcon()
821 {
822  if (GetClient()->IsIgnored()) return C4GUI::Ico_Ignored;
823 
824  // sound icon?
825  if (tLastSoundTime)
826  {
827  time_t dt = time(nullptr) - tLastSoundTime;
828  if (dt >= SoundIconShowTime)
829  {
830  // stop showing sound icon
831  tLastSoundTime = 0;
832  }
833  else
834  {
835  // time not up yet: show sound icon
836  return C4GUI::Ico_Sound;
837  }
838  }
839  // info present?
840  C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(idClient);
841  if (!pInfoPacket || !GetClient())
842  // unknown status
844  // host?
845  if (GetClient()->isHost()) return C4GUI::Ico_Host;
846  // active client?
847  if (GetClient()->isActivated())
848  {
849  if (GetClient()->isLobbyReady())
850  {
851  return C4GUI::Ico_Ready;
852  }
853  else
854  {
855  return C4GUI::Ico_Client;
856  }
857  }
858  // observer
860 }
861 
862 void C4PlayerInfoListBox::ClientListItem::UpdatePing()
863 {
864  // safety for removed clients
865  if (!GetClient()) return;
866  // default value indicating no ping
867  int32_t iPing = -1;
868  C4Network2Client *pClient = GetNetClient();
869  C4Network2IOConnection *pConn;
870  // must be a remote client
871  if (pClient && !pClient->isLocal())
872  {
873  // must have a connection
874  if ((pConn = pClient->getMsgConn()))
875  // get ping of that connection
876  iPing = pConn->getLag();
877  // check data connection if msg conn gave no value
878  // what's the meaning of those two connections anyway? o_O
879  if (iPing<=0)
880  if ((pConn = pClient->getDataConn()))
881  iPing = pConn->getLag();
882  }
883  // set that ping in label
884  SetPing(iPing);
885 }
886 
887 void C4PlayerInfoListBox::ClientListItem::SetSoundIcon()
888 {
889  // remember time for reset
890  tLastSoundTime = time(nullptr);
891  // force icon
892  SetStatus(GetCurrentStatusIcon());
893 }
894 
895 C4GUI::ContextMenu *C4PlayerInfoListBox::ClientListItem::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
896 {
897  // safety
898  if (!::Network.isEnabled()) return nullptr;
899  // get associated client
900  C4Client *pClient = GetClient();
901  // create context menu
902  C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
903  // host options
904  if (::Network.isHost() && GetNetClient())
905  {
906  StdCopyStrBuf strKickDesc(LoadResStr("IDS_NET_KICKCLIENT_DESC"));
907  pMenu->AddItem(LoadResStr("IDS_NET_KICKCLIENT"), strKickDesc.getData(), C4GUI::Ico_None,
908  new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxKick));
909  StdCopyStrBuf strActivateDesc(LoadResStr("IDS_NET_ACTIVATECLIENT_DESC"));
910  pMenu->AddItem(LoadResStr(pClient->isActivated() ? "IDS_NET_DEACTIVATECLIENT" : "IDS_NET_ACTIVATECLIENT"),
911  strActivateDesc.getData(), C4GUI::Ico_None,
912  new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxActivate));
913  }
914  // info
915  StdCopyStrBuf strClientInfoDesc(LoadResStr("IDS_NET_CLIENTINFO_DESC"));
916  pMenu->AddItem(LoadResStr("IDS_NET_CLIENTINFO"), strClientInfoDesc.getData(), C4GUI::Ico_None,
917  new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxInfo));
918  //Ignore button
919  if(!pClient->isLocal())
920  {
921  StdCopyStrBuf strNewColor(LoadResStr(pClient->IsIgnored() ? "IDS_NET_CLIENT_UNIGNORE" : "IDS_NET_CLIENT_IGNORE"));
922  pMenu->AddItem(strNewColor.getData(), FormatString(LoadResStr("IDS_NET_CLIENT_IGNORE_DESC"), pClient->getName()).getData(),
923  C4GUI::Ico_None, new C4GUI::CBMenuHandler<ClientListItem>(this, &ClientListItem::OnCtxIgnore), nullptr);
924  }
925 
926  // open it
927  return pMenu;
928 }
929 
930 void C4PlayerInfoListBox::ClientListItem::OnCtxIgnore(C4GUI::Element *pListItem)
931 {
932  GetClient()->ToggleIgnore();
933 }
934 
935 void C4PlayerInfoListBox::ClientListItem::OnCtxKick(C4GUI::Element *pListItem)
936 {
937  // host only
938  if (!::Network.isEnabled() || !::Network.isHost()) return;
939  // add control
940  Game.Clients.CtrlRemove(GetClient(), LoadResStr("IDS_MSG_KICKFROMLOBBY"));
941 }
942 
943 void C4PlayerInfoListBox::ClientListItem::OnCtxActivate(C4GUI::Element *pListItem)
944 {
945  // host only
946  C4Client *pClient = GetClient();
947  if (!::Network.isEnabled() || !::Network.isHost() || !pClient) return;
948  // add control
950 }
951 
952 void C4PlayerInfoListBox::ClientListItem::OnCtxInfo(C4GUI::Element *pListItem)
953 {
954  // show client info dialog
955  ::pGUI->ShowRemoveDlg(new C4Network2ClientDlg(idClient));
956 }
957 
958 void C4PlayerInfoListBox::ClientListItem::OnBtnAddPlr(C4GUI::Control *btn)
959 {
960  // show player add dialog
961  GetScreen()->ShowRemoveDlg(new C4PlayerSelDlg(new C4FileSel_CBEx<C4GameLobby::MainDlg>(GetLobby(), &C4GameLobby::MainDlg::OnClientAddPlayer, idClient)));
962 }
963 
964 
965 // ----------- TeamListItem ---------------------------------------------
966 
967 C4PlayerInfoListBox::TeamListItem::TeamListItem(C4PlayerInfoListBox *pForListBox, int32_t idTeam, ListItem *pInsertBefore)
968  : ListItem(pForListBox), idTeam(idTeam)
969 {
970  bool fEvaluation = pList->IsEvaluation();
971  // get team data
972  const char *szTeamName;
973  C4Team *pTeam = nullptr;
974  if (idTeam == TEAMID_Unknown)
975  szTeamName = LoadResStr("IDS_MSG_RNDTEAM");
976  else
977  {
978  pTeam = Game.Teams.GetTeamByID(idTeam); assert(pTeam);
979  if (pTeam) szTeamName = pTeam->GetName(); else szTeamName = "INTERNAL TEAM ERROR";
980  }
981  // set ID
982  idListItemID.idType = ListItem::ID::PLI_TEAM;
983  idListItemID.id = idTeam;
984  // get height
985  int32_t iIconSize; CStdFont *pFont;
986  if (!fEvaluation)
987  {
988  pFont = &::GraphicsResource.TextFont;
989  iIconSize = pFont->GetLineHeight();
990  }
991  else
992  {
993  pFont = &::GraphicsResource.TitleFont;
994  iIconSize = C4SymbolSize; // C4PictureSize doesn't fit...
995  }
996  // create subcomponents
997  pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Team);
998  pNameLabel = new C4GUI::Label(szTeamName, iIconSize + IconLabelSpacing, (iIconSize - pFont->GetLineHeight())/2, ALeft, pList->GetTextColor(), pFont, false);
999  if (fEvaluation && pTeam && pTeam->GetIconSpec() && *pTeam->GetIconSpec())
1000  {
1001  C4FacetSurface fctSymbol;
1002  fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1003  Game.DrawTextSpecImage(fctSymbol, pTeam->GetIconSpec(), nullptr, pTeam->GetColor());
1004  pIcon->GetMFacet().GrabFrom(fctSymbol);
1005  }
1006  // calc own bounds
1007  C4Rect rcOwnBounds = pNameLabel->GetBounds();
1008  rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
1009  rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
1010  SetBounds(rcOwnBounds);
1011  // add components
1012  AddElement(pIcon); AddElement(pNameLabel);
1013  // tooltip
1014  SetToolTip(FormatString(LoadResStr("IDS_DESC_TEAM"), szTeamName).getData());
1015  // insert into listbox at correct order
1016  // (will eventually get resized horizontally and moved)
1017  pForListBox->InsertElement(this, pInsertBefore);
1018 }
1019 
1020 void C4PlayerInfoListBox::TeamListItem::MouseInput(C4GUI::CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
1021 {
1022  // double click on team to enter it with all local players
1023  if (iButton == C4MC_Button_LeftDouble)
1024  {
1025  MoveLocalPlayersIntoTeam();
1026  }
1027  else
1028  ListItem::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
1029 }
1030 
1031 void C4PlayerInfoListBox::TeamListItem::UpdateOwnPos()
1032 {
1033  // parent for client rect
1034  typedef C4GUI::Window ParentClass;
1035  ParentClass::UpdateOwnPos();
1036  // evaluation: Center team label
1037  if (pList->IsEvaluation())
1038  {
1039  int32_t iTotalWdt = pIcon->GetBounds().Wdt + IconLabelSpacing + ::GraphicsResource.TitleFont.GetTextWidth(pNameLabel->GetText());
1040  C4GUI::ComponentAligner caAll(GetContainedClientRect(), 0,0);
1041  C4GUI::ComponentAligner caBounds(caAll.GetCentered(iTotalWdt, caAll.GetInnerHeight()), 0,0);
1042  pIcon->SetBounds(caBounds.GetFromLeft(pIcon->GetBounds().Wdt, pIcon->GetBounds().Hgt));
1043  pNameLabel->SetBounds(caBounds.GetCentered(caBounds.GetInnerWidth(), ::GraphicsResource.TitleFont.GetLineHeight()));
1044  }
1045 }
1046 
1047 void C4PlayerInfoListBox::TeamListItem::MoveLocalPlayersIntoTeam()
1048 {
1049  // check if changing teams is allowed
1050  if (!CanLocalChooseTeams()) return;
1051  // safety: Clicked team must exist
1052  if (!Game.Teams.GetTeamByID(idTeam)) return;
1053  // get local client to change teams of
1054  bool fAnyChange = false;
1056  if (!pChangeInfo) return;
1057  // this must be processed as a request by the host
1058  // now change it in its own request packet
1059  C4ClientPlayerInfos LocalInfoRequest(*pChangeInfo);
1060  C4PlayerInfo *pInfo; int32_t i=0;
1061  while ((pInfo = LocalInfoRequest.GetPlayerInfo(i++)))
1062  if (pInfo->GetTeam() != idTeam)
1063  if (pInfo->GetType() == C4PT_User)
1064  {
1065  pInfo->SetTeam(idTeam);
1066  fAnyChange = true;
1067  }
1068  if (!fAnyChange) return;
1069  // and request this update (host processes it directly)
1070  ::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
1071  // next update will move the player labels
1072 }
1073 
1074 void C4PlayerInfoListBox::TeamListItem::Update()
1075 {
1076  // evaluation: update color by team winning status
1077  if (pList->IsEvaluation())
1078  {
1079  C4Team *pTeam = Game.Teams.GetTeamByID(idTeam);
1080  if (pTeam && pTeam->HasWon())
1081  {
1082  pNameLabel->SetColor(C4GUI_WinningTextColor, false);
1083  }
1084  else
1085  {
1086  pNameLabel->SetColor(C4GUI_LosingTextColor, false);
1087  }
1088  }
1089 }
1090 
1091 
1092 
1093 // ----------- FreeSavegamePlayersListItem ---------------------------------------------
1094 
1095 C4PlayerInfoListBox::FreeSavegamePlayersListItem::FreeSavegamePlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
1096  : ListItem(pForListBox)
1097 {
1098  // set ID
1099  idListItemID.idType = ListItem::ID::PLI_SAVEGAMEPLR;
1100  idListItemID.id = 0;
1101  // get height
1102  int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
1103  // create subcomponents
1104  pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_SavegamePlayer);
1105  pNameLabel = new C4GUI::Label(LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"), iIconSize + IconLabelSpacing,0, ALeft);
1106  // calc own bounds
1107  C4Rect rcOwnBounds = pNameLabel->GetBounds();
1108  rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
1109  rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
1110  SetBounds(rcOwnBounds);
1111  // add components
1112  AddElement(pIcon); AddElement(pNameLabel);
1113  // tooltip
1114  SetToolTip(LoadResStr("IDS_DESC_UNASSOCIATEDSAVEGAMEPLAYE"));
1115  // insert into listbox at correct order
1116  // (will eventually get resized horizontally and moved)
1117  pForListBox->InsertElement(this, pInsertBefore);
1118  // initial update
1119  Update();
1120 }
1121 
1122 void C4PlayerInfoListBox::FreeSavegamePlayersListItem::Update()
1123 {
1124  // 2do: none-label
1125 }
1126 
1127 
1128 // ----------- ScriptPlayersListItem ---------------------------------------------
1129 
1130 C4PlayerInfoListBox::ScriptPlayersListItem::ScriptPlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
1131  : ListItem(pForListBox)
1132 {
1133  // set ID
1134  idListItemID.idType = ListItem::ID::PLI_SCRIPTPLR;
1135  idListItemID.id = 0;
1136  // get height
1137  int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
1138  // create subcomponents
1139  pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Record);
1140  pNameLabel = new C4GUI::Label(LoadResStr("IDS_CTL_SCRIPTPLAYERS"), iIconSize + IconLabelSpacing,0, ALeft);
1141  btnAddPlayer = nullptr;
1142  if (::Control.isCtrlHost())
1143  {
1144  btnAddPlayer = new C4GUI::CallbackButton<ScriptPlayersListItem, C4GUI::IconButton>(C4GUI::Ico_AddPlr, C4Rect(0, 0, iIconSize, iIconSize), 'A' /* 2do TODO */, &ScriptPlayersListItem::OnBtnAddPlr, this);
1145  }
1146  // calc own bounds
1147  C4Rect rcOwnBounds = pNameLabel->GetBounds();
1148  rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
1149  rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
1150  SetBounds(rcOwnBounds);
1151  // add components
1152  AddElement(pIcon); AddElement(pNameLabel);
1153  if (btnAddPlayer) AddElement(btnAddPlayer);
1154  // tooltip
1155  SetToolTip(LoadResStr("IDS_DESC_PLAYERSCONTROLLEDBYCOMPUT"));
1156  // insert into listbox at correct order
1157  // (will eventually get resized horizontally and moved)
1158  pForListBox->InsertElement(this, pInsertBefore);
1159  // after move: update add player button pos
1160  if (btnAddPlayer)
1161  {
1162  int32_t iHgt = GetClientRect().Hgt;
1163  btnAddPlayer->GetBounds() = GetToprightCornerRect(iHgt,iHgt,2,0);
1164  }
1165  // initial update
1166  Update();
1167 }
1168 
1169 void C4PlayerInfoListBox::ScriptPlayersListItem::Update()
1170 {
1171  // player join button: Visible if there's still some room for script players
1172  if (btnAddPlayer)
1173  {
1174  bool fCanJoinScriptPlayers = (Game.Teams.GetMaxScriptPlayers() - Game.PlayerInfos.GetActiveScriptPlayerCount(true, true) > 0);
1175  btnAddPlayer->SetVisibility(fCanJoinScriptPlayers);
1176  }
1177 }
1178 
1179 void C4PlayerInfoListBox::ScriptPlayersListItem::OnBtnAddPlr(C4GUI::Control *btn)
1180 {
1181  // safety
1182  int32_t iCurrScriptPlrCount = Game.PlayerInfos.GetActiveScriptPlayerCount(true, true);
1183  bool fCanJoinScriptPlayers = (Game.Teams.GetMaxScriptPlayers() - iCurrScriptPlrCount > 0);
1184  if (!fCanJoinScriptPlayers) return;
1185  if (!::Control.isCtrlHost()) return;
1186  // request a script player join
1187  C4PlayerInfo *pScriptPlrInfo = new C4PlayerInfo();
1188  pScriptPlrInfo->SetAsScriptPlayer(Game.Teams.GetScriptPlayerName().getData(), GenerateRandomPlayerColor(iCurrScriptPlrCount), 0, C4ID::None);
1189  C4ClientPlayerInfos JoinPkt(nullptr, true, pScriptPlrInfo);
1190  // add to queue!
1192 }
1193 
1194 
1195 // ----------- ReplayPlayersListItem ---------------------------------------------
1196 
1197 C4PlayerInfoListBox::ReplayPlayersListItem::ReplayPlayersListItem(C4PlayerInfoListBox *pForListBox, ListItem *pInsertBefore)
1198  : ListItem(pForListBox)
1199 {
1200  // set ID
1201  idListItemID.idType = ListItem::ID::PLI_REPLAY;
1202  idListItemID.id = 0;
1203  // get height
1204  int32_t iIconSize = ::GraphicsResource.TextFont.GetLineHeight();
1205  // create subcomponents
1206  pIcon = new C4GUI::Icon(C4Rect(0, 0, iIconSize, iIconSize), C4GUI::Ico_Record);
1207  pNameLabel = new C4GUI::Label(LoadResStr("IDS_MSG_REPLAYPLRS"), iIconSize + IconLabelSpacing,0, ALeft);
1208  // calc own bounds
1209  C4Rect rcOwnBounds = pNameLabel->GetBounds();
1210  rcOwnBounds.Wdt += rcOwnBounds.x; rcOwnBounds.x = 0;
1211  rcOwnBounds.Hgt += rcOwnBounds.y; rcOwnBounds.y = 0;
1212  SetBounds(rcOwnBounds);
1213  // add components
1214  AddElement(pIcon); AddElement(pNameLabel);
1215  // tooltip
1216  SetToolTip(LoadResStr("IDS_MSG_REPLAYPLRS_DESC"));
1217  // insert into listbox at correct order
1218  // (will eventually get resized horizontally and moved)
1219  pForListBox->InsertElement(this, pInsertBefore);
1220 }
1221 
1222 
1223 
1224 // ------------------- C4PlayerInfoListBox ------------------------
1225 
1226 C4PlayerInfoListBox::C4PlayerInfoListBox(const C4Rect &rcBounds, Mode eMode, int32_t iTeamFilter)
1227  : C4GUI::ListBox(rcBounds), eMode(eMode), iMaxUncollapsedPlayers(10), fIsCollapsed(false), iTeamFilter(iTeamFilter), dwTextColor(C4GUI_MessageFontClr), pCustomFont(nullptr)
1228 {
1229  // update if client listbox selection changes
1231  // initial update
1232  Update();
1233 }
1234 
1236 {
1237  // get client element
1238  ListItem *pItem = GetPlayerListItem(ListItem::ID::PLI_CLIENT, iForClientID);
1239  if (pItem)
1240  {
1241  ClientListItem *pClientItem = static_cast<ClientListItem *>(pItem);
1242  pClientItem->SetSoundIcon();
1243  }
1244 }
1245 
1246 C4PlayerInfoListBox::ListItem *C4PlayerInfoListBox::GetPlayerListItem(ListItem::ID::IDType eType, int32_t id)
1247 {
1248  ListItem::ID idSearch(eType, id);
1249  // search through listbox
1250  for (C4GUI::Element *pEItem = GetFirst(); pEItem; pEItem = pEItem->GetNext())
1251  {
1252  // only playerlistitems in this box
1253  ListItem *pItem = static_cast<ListItem *>(pEItem);
1254  if (pItem->idListItemID == idSearch) return pItem;
1255  }
1256  // nothing found
1257  return nullptr;
1258 }
1259 
1260 bool C4PlayerInfoListBox::PlrListItemUpdate(ListItem::ID::IDType eType, int32_t id, class ListItem **pEnsurePos)
1261 {
1262  assert(pEnsurePos);
1263  // search item
1264  ListItem *pItem = GetPlayerListItem(eType, id);
1265  if (!pItem) return false;
1266  // ensure its position is correct
1267  if (pItem != *pEnsurePos)
1268  {
1269  RemoveElement(pItem);
1270  InsertElement(pItem, *pEnsurePos);
1271  }
1272  else
1273  {
1274  // pos correct; advance past it
1275  *pEnsurePos = static_cast<ListItem *>(pItem->GetNext());
1276  }
1277  // update item
1278  pItem->Update();
1279  // done, success
1280  return true;
1281 }
1282 
1283 // static safety var to prevent recusive updates
1284 static bool fPlayerListUpdating=false;
1285 
1287 {
1288  if (fPlayerListUpdating) return;
1289  fPlayerListUpdating = true;
1290 
1291  // synchronize current list with what it should be
1292  // call update on all other list items
1293  ListItem *pCurrInList = static_cast<ListItem *>(GetFirst()); // list item being compared with the searched item
1294 
1295  // free savegame players first
1296  UpdateSavegamePlayers(&pCurrInList);
1297 
1298  // next comes the regular players, sorted either by clients or teams, or special sort for evaluation mode
1299  switch (eMode)
1300  {
1301  case PILBM_LobbyTeamSort:
1302  // sort by team
1303  if (Game.Teams.CanLocalSeeTeam())
1304  UpdatePlayersByTeam(&pCurrInList);
1305  else
1306  UpdatePlayersByRandomTeam(&pCurrInList);
1307  break;
1308 
1309  case PILBM_LobbyClientSort:
1310  // sort by client
1311  // replay players first?
1312  if (Game.C4S.Head.Replay) UpdateReplayPlayers(&pCurrInList);
1313  // script controlled players from the main list
1314  UpdateScriptPlayers(&pCurrInList);
1315  // regular players
1316  UpdatePlayersByClient(&pCurrInList);
1317  break;
1318 
1319  case PILBM_Evaluation:
1321  UpdatePlayersByEvaluation(&pCurrInList, eMode == PILBM_Evaluation);
1322  break;
1323  }
1324 
1325  // finally: remove any remaining list items at the end
1326  while (pCurrInList)
1327  {
1328  ListItem *pDel = pCurrInList;
1329  pCurrInList = static_cast<ListItem *>(pCurrInList->GetNext());
1330  delete pDel;
1331  }
1332 
1333  // update done
1334  fPlayerListUpdating = false;
1335 
1336  // check whether view needs to be collapsed
1337  if (!fIsCollapsed && IsScrollingActive() && !IsEvaluation())
1338  {
1339  // then collapse it, and update window
1340  iMaxUncollapsedPlayers = Game.PlayerInfos.GetPlayerCount()-1;
1341  fIsCollapsed = true;
1342  Update(); // recursive call!
1343  }
1344  else if (fIsCollapsed && Game.PlayerInfos.GetPlayerCount() <= iMaxUncollapsedPlayers)
1345  {
1346  // player count dropped below collapse-limit: uncollapse
1347  // note that this may again cause a collapse after that update, if scrolling was still necessary
1348  // however, it will then not recurse any further, because iMaxUncollapsedPlayers will have been updated
1349  fIsCollapsed = false;
1350  Update();
1351  }
1352 }
1353 
1354 void C4PlayerInfoListBox::UpdateSavegamePlayers(ListItem **ppCurrInList)
1355 {
1356  // add unassociated savegame players (script players excluded)
1358  {
1359  // caption
1360  if (!PlrListItemUpdate(ListItem::ID::PLI_SAVEGAMEPLR, 0, ppCurrInList))
1361  new FreeSavegamePlayersListItem(this, *ppCurrInList);
1362  // the players
1363  bool fAnyPlayers = false;
1364  C4PlayerInfo *pInfo; int32_t iInfoID=0;
1365  while ((pInfo = Game.RestorePlayerInfos.GetNextPlayerInfoByID(iInfoID)))
1366  {
1367  iInfoID = pInfo->GetID();
1368  // skip assigned
1369  if (Game.PlayerInfos.GetPlayerInfoBySavegameID(iInfoID)) continue;
1370  // skip script controlled - those are put into the script controlled player list
1371  if (pInfo->GetType() == C4PT_Script) continue;
1372  // players are in the list
1373  fAnyPlayers = true;
1374  // show them
1375  if (!PlrListItemUpdate(ListItem::ID::PLI_SAVEGAMEPLR, iInfoID, ppCurrInList))
1376  new PlayerListItem(this, -1, iInfoID, true, *ppCurrInList);
1377  }
1378  // 2do: none-label
1379  (void) fAnyPlayers;
1380  }
1381 
1382 }
1383 
1384 void C4PlayerInfoListBox::UpdateReplayPlayers(ListItem **ppCurrInList)
1385 {
1386  // header
1387  if (!PlrListItemUpdate(ListItem::ID::PLI_REPLAY, 0, ppCurrInList))
1388  new ReplayPlayersListItem(this, *ppCurrInList);
1389  // players
1390  C4PlayerInfo *pInfo; int32_t iInfoID=0;
1391  while ((pInfo = Game.PlayerInfos.GetNextPlayerInfoByID(iInfoID)))
1392  {
1393  if (pInfo->IsInvisible()) continue;
1394  iInfoID = pInfo->GetID();
1395  // show them
1396  if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, iInfoID, ppCurrInList))
1397  new PlayerListItem(this, -1, iInfoID, false, *ppCurrInList);
1398  }
1399  // 2do: none-label
1400 }
1401 
1402 void C4PlayerInfoListBox::UpdateScriptPlayers(ListItem **ppCurrInList)
1403 {
1404  // script controlled players from the main list
1405  // processing the restore list would be redundant, because all script players should have been taken over by a new script player join automatically
1406  // also show the label if script players can be joined
1408  {
1409  // header
1410  if (!PlrListItemUpdate(ListItem::ID::PLI_SCRIPTPLR, 0, ppCurrInList))
1411  new ScriptPlayersListItem(this, *ppCurrInList);
1412  // players
1413  C4PlayerInfo *pInfo; int32_t iClientIdx=0; C4ClientPlayerInfos *pInfos;
1414  while ((pInfos = Game.PlayerInfos.GetIndexedInfo(iClientIdx++)))
1415  {
1416  int32_t iInfoIdx=0;
1417  while ((pInfo = pInfos->GetPlayerInfo(iInfoIdx++)))
1418  {
1419  if (pInfo->GetType() != C4PT_Script) continue;
1420  if (pInfo->IsRemoved()) continue;
1421  if (pInfo->IsInvisible()) continue;
1422  // show them
1423  int32_t iInfoID = pInfo->GetID();
1424  if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, iInfoID, ppCurrInList))
1425  new PlayerListItem(this, pInfos->GetClientID(), iInfoID, false, *ppCurrInList);
1426  }
1427  }
1428  }
1429 }
1430 
1431 void C4PlayerInfoListBox::UpdatePlayersByTeam(ListItem **ppCurrInList)
1432 {
1433  // sort by team
1434  C4Team *pTeam; int32_t i=0;
1435  while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
1436  {
1437  // no empty teams that are not used
1438  if (Game.Teams.IsAutoGenerateTeams() && !pTeam->GetPlayerCount()) continue;
1439  // the team label
1440  if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, pTeam->GetID(), ppCurrInList))
1441  new TeamListItem(this, pTeam->GetID(), *ppCurrInList);
1442  // players for this team
1443  int32_t idPlr, j=0; int32_t idClient; C4Client *pClient; C4PlayerInfo *pPlrInfo;
1444  while ((idPlr = pTeam->GetIndexedPlayer(j++)))
1445  if ((pPlrInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlr, &idClient)))
1446  if (!pPlrInfo->IsInvisible())
1447  if ((pClient=Game.Clients.getClientByID(idClient)) && pClient->isActivated())
1448  if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, idPlr, ppCurrInList))
1449  new PlayerListItem(this, idClient, idPlr, false, *ppCurrInList);
1450  }
1451 }
1452 
1453 void C4PlayerInfoListBox::UpdatePlayersByRandomTeam(ListItem **ppCurrInList)
1454 {
1455  // team sort but teams set to random and invisible: Show all players within one "Random Team"-label
1456  bool fTeamLabelPut = false;
1457  C4Client *pClient = nullptr;
1458  while ((pClient = Game.Clients.getClient(pClient)))
1459  {
1460  // player infos for this client - not for deactivated, and never in replays
1461  if (Game.C4S.Head.Replay || !pClient->isActivated()) continue;
1462  C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(pClient->getID());
1463  if (pInfoPacket)
1464  {
1465  C4PlayerInfo *pPlrInfo; int32_t i=0;
1466  while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
1467  {
1468  if (pPlrInfo->IsInvisible()) continue;
1469  if (!fTeamLabelPut)
1470  {
1471  if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, TEAMID_Unknown, ppCurrInList))
1472  new TeamListItem(this, TEAMID_Unknown, *ppCurrInList);
1473  fTeamLabelPut = true;
1474  }
1475  if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
1476  new PlayerListItem(this, pClient->getID(), pPlrInfo->GetID(), false, *ppCurrInList);
1477  }
1478  }
1479  }
1480 }
1481 
1482 void C4PlayerInfoListBox::UpdatePlayersByClient(ListItem **ppCurrInList)
1483 {
1484  // regular players
1485  C4Client *pClient = nullptr;
1486  while ((pClient = Game.Clients.getClient(pClient)))
1487  {
1488  // the client label
1489  if (!PlrListItemUpdate(ListItem::ID::PLI_CLIENT, pClient->getID(), ppCurrInList))
1490  new ClientListItem(this, pClient->getCore(), *ppCurrInList);
1491  // player infos for this client - not for observers, and never in replays
1492  // could also check for activated here. However, non-observers will usually be activated later and thus be using their players
1493  if (Game.C4S.Head.Replay || pClient->isObserver()) continue;
1494  C4ClientPlayerInfos *pInfoPacket = Game.PlayerInfos.GetInfoByClientID(pClient->getID());
1495  if (pInfoPacket)
1496  {
1497  C4PlayerInfo *pPlrInfo; int32_t i=0;
1498  while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
1499  {
1500  if (pPlrInfo->GetType() == C4PT_Script) continue;
1501  if (pPlrInfo->IsRemoved()) continue;
1502  if (pPlrInfo->IsInvisible()) continue;
1503  if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
1504  new PlayerListItem(this, pClient->getID(), pPlrInfo->GetID(), false, *ppCurrInList);
1505  }
1506  }
1507  }
1508 }
1509 
1510 void C4PlayerInfoListBox::UpdatePlayersByEvaluation(ListItem **ppCurrInList, bool fShowWinners)
1511 {
1512  // if a team filter is provided, add team label first
1513  if (iTeamFilter)
1514  if (!PlrListItemUpdate(ListItem::ID::PLI_TEAM, iTeamFilter, ppCurrInList))
1515  new TeamListItem(this, iTeamFilter, *ppCurrInList);
1516  // Add by teams: In show-winner-mode winning teams first
1517  // Otherwise, just add all
1518  AddMode pShowWinnersAddModes[] = { AM_Winners, AM_Losers };
1519  AddMode pHideWinnersAddModes[] = { AM_All };
1520  AddMode *pAddModes; int32_t iAddModeCount;
1521  if (fShowWinners)
1522  {
1523  pAddModes = pShowWinnersAddModes; iAddModeCount = 2;
1524  }
1525  else
1526  {
1527  pAddModes = pHideWinnersAddModes; iAddModeCount = 1;
1528  }
1529  for (int32_t iAddMode = 0; iAddMode < iAddModeCount; ++iAddMode)
1530  {
1531  AddMode eAddMode = pAddModes[iAddMode];
1532  if (iTeamFilter)
1533  {
1534  // Team filter mode: Add only players of specified team
1535  UpdatePlayersByEvaluation(ppCurrInList, Game.Teams.GetTeamByID(iTeamFilter), eAddMode);
1536  }
1537  else
1538  {
1539  // Normal mode: Add all teams of winning status
1540  C4Team *pTeam; int32_t i=0;
1541  while ((pTeam = Game.Teams.GetTeamByIndex(i++)))
1542  {
1543  UpdatePlayersByEvaluation(ppCurrInList, pTeam, eAddMode);
1544  }
1545  // Add teamless players of winning status
1546  UpdatePlayersByEvaluation(ppCurrInList, nullptr, eAddMode);
1547  }
1548  }
1549 }
1550 
1551 void C4PlayerInfoListBox::UpdatePlayersByEvaluation(ListItem **ppCurrInList, C4Team *pTeam, C4PlayerInfoListBox::AddMode eWinMode)
1552 {
1553  // check winning status of team first
1554  if (pTeam && eWinMode != AM_All) if (pTeam->HasWon() != (eWinMode == AM_Winners)) return;
1555  // now add all matching players
1556  int32_t iTeamID = pTeam ? pTeam->GetID() : 0;
1557  C4ClientPlayerInfos *pInfoPacket; int32_t iClient=0;
1558  while ((pInfoPacket = Game.PlayerInfos.GetIndexedInfo(iClient++)))
1559  {
1560  C4PlayerInfo *pPlrInfo; int32_t i=0;
1561  while ((pPlrInfo = pInfoPacket->GetPlayerInfo(i++)))
1562  {
1563  if (!pPlrInfo->HasJoined()) continue;
1564  if (pPlrInfo->GetTeam() != iTeamID) continue;
1565  if (pPlrInfo->IsInvisible()) continue;
1566  if (!pTeam && eWinMode != AM_All && pPlrInfo->HasWon() != (eWinMode == AM_Winners)) continue;
1567  if (!PlrListItemUpdate(ListItem::ID::PLI_PLAYER, pPlrInfo->GetID(), ppCurrInList))
1568  new PlayerListItem(this, pInfoPacket->GetClientID(), pPlrInfo->GetID(), false, *ppCurrInList);
1569  }
1570  }
1571 }
1572 
1574 {
1575  // never if view is not collapsed
1576  if (!fIsCollapsed) return false;
1577  // collapsed if not selected
1578  return GetSelectedItem() != pItem;
1579 }
1580 
1582 {
1583  if (eMode != eNewMode)
1584  {
1585  eMode = eNewMode;
1586  Update();
1587  }
1588 }
1589 
1590 void C4PlayerInfoListBox::SetCustomFont(CStdFont *pNewFont, uint32_t dwTextColor)
1591 {
1592  pCustomFont = pNewFont;
1593  this->dwTextColor = dwTextColor;
1594  // update done later by caller anyway
1595 }
const char * getData() const
Definition: StdBuf.h:450
int32_t GetPlayerCount() const
Definition: C4Teams.h:64
class C4GameLobby::MainDlg * GetLobby() const
Definition: C4Network2.h:216
#define C4GUI_DefaultListSpacing
Definition: C4Gui.h:123
void SetColor(DWORD dwUseClr)
Definition: C4PlayerInfo.h:118
C4Network2Res * GetRes() const
Definition: C4PlayerInfo.h:166
void AddItem(const char *szText, const char *szToolTip=nullptr, Icons icoIcon=Ico_None, MenuHandler *pMenuHandler=nullptr, ContextHandler *pSubmenuHandler=nullptr)
Definition: C4Gui.h:1873
const char * GetIconSpec() const
Definition: C4Teams.h:71
C4PlayerInfoList & RestorePlayerInfos
Definition: C4Game.h:74
#define C4GUI_StandardBGColor
Definition: C4Gui.h:66
int32_t GetID() const
Definition: C4Teams.h:66
int GetLineHeight() const
Definition: C4FontLoader.h:132
const char * GetName() const
Definition: C4PlayerInfo.h:160
float Y
Definition: C4Facet.h:120
C4Network2Client * GetClientByID(int32_t iID) const
int32_t getLeagueRankSymbol() const
Definition: C4PlayerInfo.h:190
bool isHost() const
Definition: C4Network2.h:209
#define C4GUI_LosingTextColor
Definition: C4Gui.h:79
#define C4GUI_MessageFontClr
Definition: C4Gui.h:43
void RequestPlayerInfoUpdate(const class C4ClientPlayerInfos &rRequest)
int32_t GetPlayerCount() const
bool isCtrlHost() const
Definition: C4GameControl.h:99
C4Game Game
Definition: C4Globals.cpp:52
bool IsTeamColors() const
Definition: C4Teams.h:170
void SetCustomFont(CStdFont *pNewFont, uint32_t dwTextColor)
C4Scenario C4S
Definition: C4Game.h:76
bool HasWon() const
Definition: C4PlayerInfo.h:184
C4Network2IOConnection * getMsgConn() const
Definition: C4Teams.h:30
void SetTeam(int32_t idToTeam)
Definition: C4PlayerInfo.h:123
uint32_t GetColor() const
Definition: C4Teams.h:70
bool Replay
Definition: C4Scenario.h:72
bool isComplete() const
bool IsAutoGenerateTeams() const
Definition: C4Teams.h:173
uint32_t GetLobbyColor() const
void SetToolTip(const char *szNewTooltip, bool is_immediate=false)
Definition: C4Gui.cpp:410
C4ClientPlayerInfos * GetLocalPlayerInfoPacket() const
const int32_t TEAMID_Unknown
Definition: C4Teams.h:24
#define C4GUI_WinningBackgroundColor
Definition: C4Gui.h:78
bool isActivated() const
Definition: C4Client.h:110
bool InsertElement(Element *pChild, Element *pInsertBefore, int32_t iIndent=0)
virtual void RemoveElement(Element *pChild)
bool IsInvisible() const
Definition: C4PlayerInfo.h:178
int32_t GetMaxScriptPlayers() const
Definition: C4Teams.h:175
const int ARight
Definition: C4Surface.h:43
void OnPlrListSelChange(class C4GUI::Element *pEl)
bool ShowRemoveDlg(Dialog *pDlg)
void DrawClr(C4Facet &cgo, bool fAspect=true, DWORD dwClr=0)
Definition: C4Facet.cpp:213
Definition: C4Rect.h:29
bool HasAutoGeneratedColor()
Definition: C4PlayerInfo.h:183
uint32_t GetTotalPlayingTime() const
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:181
bool SetAsScriptPlayer(const char *szName, uint32_t dwColor, uint32_t dwFlags, C4ID idExtra)
bool IsTeamVisible() const
Definition: C4Teams.cpp:455
bool IsMultiTeams() const
Definition: C4Teams.h:163
Element * GetFirst()
Definition: C4Gui.h:1571
C4Team * GetTeamByID(int32_t iID) const
Definition: C4Teams.cpp:384
uint32_t GetOriginalColor() const
Definition: C4PlayerInfo.h:158
bool IsPlayerItemCollapsed(PlayerListItem *pItem)
C4PlayerInfo * GetNextPlayerInfoByID(int32_t id) const
void SetSelectionChangeCallbackFn(BaseCallbackHandler *pToHandler)
Definition: C4Gui.h:1548
int32_t GetTextWidth(const char *szText, bool fCheckMarkup=true)
Definition: C4FontLoader.h:147
C4GraphicsResource GraphicsResource
void CtrlRemove(const C4Client *pClient, const char *szReason)
Definition: C4Client.cpp:350
C4GameParameters & Parameters
Definition: C4Game.h:69
StdStrBuf GetLobbyName() const
bool CanLocalChooseTeam() const
Definition: C4Teams.cpp:293
C4PlayerInfoListBox(const C4Rect &rcBounds, Mode eMode, int32_t iTeamFilter=0)
C4ClientPlayerInfos * GetIndexedInfo(int32_t iIndex) const
Definition: C4PlayerInfo.h:361
C4PlayerInfo * GetPlayerInfoByID(int32_t id) const
C4TeamList & Teams
Definition: C4Game.h:72
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
Element * GetSelectedItem()
Definition: C4Gui.h:1580
C4Network2IOConnection * getDataConn() const
void OnClientAddPlayer(const char *szFilename, int32_t idClient)
const int32_t C4MC_Button_LeftDouble
int32_t getID() const
Definition: C4Client.h:105
int32_t Wdt
Definition: C4Rect.h:32
bool HasTeamWon() const
int32_t GetLeagueRankSymbolNew() const
C4GUIScreen * pGUI
Definition: C4Gui.cpp:1194
C4PlayerInfo * GetPlayerInfo(int32_t iIndex) const
bool isObserver() const
Definition: C4Client.h:111
bool isActivated() const
Definition: C4Client.h:59
C4Network2 Network
Definition: C4Globals.cpp:53
bool IsFull() const
Definition: C4Teams.h:72
int32_t y
Definition: C4Rect.h:32
int32_t GetAssociatedSavegamePlayerID() const
Definition: C4PlayerInfo.h:129
DWORD GetClientChatColor(int idForClient, bool fLobby) const
int32_t getLocalID() const
Definition: C4Client.h:171
const char * GetCustomEvaluationStrings()
C4GameControl Control
bool DoPlayerInfoUpdate(C4ClientPlayerInfos *pUpdate)
void DrawBoxDw(C4Surface *sfcDest, int iX1, int iY1, int iX2, int iY2, DWORD dwClr)
Definition: C4Draw.cpp:849
int32_t GetScoreOld() const
const C4ClientCore & getCore() const
Definition: C4Client.h:104
static const C4ID None
Definition: C4Id.h:42
int32_t GetClientID() const
Definition: C4PlayerInfo.h:260
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
C4Team * GetTeamByIndex(int32_t iIndex) const
Definition: C4Teams.cpp:405
int32_t GetTeam() const
Definition: C4PlayerInfo.h:198
bool IsRemoved() const
Definition: C4PlayerInfo.h:167
bool CanLocalSeeTeam() const
Definition: C4Teams.cpp:328
static int32_t GetDefaultHeight()
bool HasJoinIssued() const
Definition: C4PlayerInfo.h:170
const char * GetName() const
Definition: C4Teams.h:65
C4Draw * pDraw
Definition: C4Draw.cpp:45
#define C4GUI_MessageFontAlpha
Definition: C4Gui.h:44
int32_t GetLeagueScoreNew() const
int32_t GetScoreNew() const
int32_t getLeagueScore() const
Definition: C4PlayerInfo.h:188
bool isEnabled() const
Definition: C4Network2.h:203
C4RoundResults & RoundResults
Definition: C4Game.h:75
C4ClientPlayerInfos * GetInfoByClientID(int32_t iClientID) const
Definition: C4PlayerInfo.h:364
Icons
Definition: C4Gui.h:637
const char * getNick() const
Definition: C4Client.h:71
int32_t GetLeagueProjectedGain() const
Definition: C4PlayerInfo.h:194
int32_t x
Definition: C4Rect.h:32
void Ref(const char *pnData)
Definition: StdBuf.h:463
const C4RoundResultsPlayers & GetPlayers() const
DWORD GenerateRandomPlayerColor(int32_t iTry)
bool IsLeagueProjectedGainValid() const
Definition: C4PlayerInfo.h:193
C4Network2ClientList Clients
Definition: C4Network2.h:116
Element * GetNext() const
Definition: C4Gui.h:449
bool SettlementScoreIsHidden()
float TargetX
Definition: C4Facet.h:167
C4PlayerInfoList & PlayerInfos
Definition: C4Game.h:73
C4Client * getClientByID(int32_t iID) const
Definition: C4Client.cpp:217
#define C4GUI_WinningTextColor
Definition: C4Gui.h:77
const int C4SymbolSize
Definition: C4Constants.h:58
float Hgt
Definition: C4Facet.h:120
const char * getName() const
Definition: C4Client.h:107
bool GetBlitModulation(DWORD &rdwColor)
Definition: C4Draw.h:190
C4Facet & GetBigIcon()
bool IsIgnored() const
Definition: C4Client.h:114
bool isLocal() const
Definition: C4Client.h:116
C4RoundResultsPlayer * GetByID(int32_t id) const
bool Create(int iWdt, int iHgt, int iWdt2=C4FCT_Full, int iHgt2=C4FCT_Full)
Definition: C4FacetEx.cpp:54
bool IsLeagueScoreNewValid() const
const int ALeft
Definition: C4Surface.h:43
C4Network2Players Players
Definition: C4Network2.h:119
bool isLocal() const
C4ClientList & Clients
Definition: C4Game.h:71
void SetMode(Mode eNewMode)
int32_t GetActiveScriptPlayerCount(bool fCountSavegameResumes, bool fCountInvisible) const
bool isLeague() const
void DeactivateBlitModulation()
Definition: C4Draw.h:189
void AddEntry(const char *szText, int32_t id)
void SetAssociatedSavegamePlayer(int32_t aidSavegamePlayer)
Definition: C4PlayerInfo.h:127
int32_t GetID() const
Definition: C4PlayerInfo.h:197
C4PlayerType GetType() const
Definition: C4PlayerInfo.h:155
void ActivateBlitModulation(DWORD dwWithClr)
Definition: C4Draw.h:188
int32_t Hgt
Definition: C4Rect.h:32
C4SHead Head
Definition: C4Scenario.h:230
bool HasWon() const
Definition: C4Teams.cpp:235
int32_t GetActivePlayerCount(bool fCountInvisible) const
const char * getName() const
Definition: C4Client.h:69
C4Surface * Surface
Definition: C4Facet.h:119
float TargetY
Definition: C4Facet.h:167
uint32_t DWORD
C4Client * getClient(const C4Client *pAfter=nullptr) const
Definition: C4Client.h:160
C4PlayerInfo * GetPlayerInfoBySavegameID(int32_t id) const
bool HasJoined() const
Definition: C4PlayerInfo.h:168
float Wdt
Definition: C4Facet.h:120
float X
Definition: C4Facet.h:120
bool IsScrollingActive()
Definition: C4Gui.h:1581
bool IsScoreNewValid() const
StdStrBuf GetScriptPlayerName() const
Definition: C4Teams.cpp:887
bool LoadBigIcon(C4FacetSurface &fctTarget)
void SetClientSoundIcon(int32_t iForClientID)
int32_t GetLeagueScoreGain() const
void SetText(const char *szToText, bool fAllowHotkey=true)
Definition: C4GuiLabels.cpp:74
CStdFont * GetCustomFont() const
bool DrawTextSpecImage(C4Facet &fctTarget, const char *szSpec, class C4DrawTransform *pTransform, uint32_t dwClr=0xff)
Definition: C4Game.cpp:3741
int32_t GetIndexedPlayer(int32_t iIndex) const
Definition: C4Teams.h:76
#define C4GUI_LosingBackgroundColor
Definition: C4Gui.h:80
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:277
uint32_t GetTextColor() const