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