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"
19 #include "gui/C4PlayerInfoListBox.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"
26 #include "graphics/C4GraphicsResource.h"
27 #include "gui/C4FileSelDlg.h"
28 #include "gui/C4GameLobby.h"
29 #include "gui/C4MouseControl.h"
30 #include "network/C4Network2.h"
31 #include "network/C4Network2Dialogs.h"
32
33 DWORD GenerateRandomPlayerColor(int32_t iTry); // in C4PlayerInfoConflicts.cpp
34
35 // ----------- ListItem --------------------------------------------------------------------------------
36
37 // helper
GetLobby() const38 C4GameLobby::MainDlg *C4PlayerInfoListBox::ListItem::GetLobby() const
39 {
40 return ::Network.GetLobby();
41 }
42
CanLocalChooseTeams(int32_t idPlayer) const43 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 {
55 C4PlayerInfo *pInfo = Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
56 if (pInfo && pInfo->HasJoined()) return false;
57 }
58 // finally, only if team settings permit
59 return Game.Teams.CanLocalChooseTeam(idPlayer);
60 }
61
DrawElement(C4TargetFacet & cgo)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
PlayerListItem(C4PlayerInfoListBox * pForListBox,int32_t idClient,int32_t idPlayer,bool fSavegamePlayer,C4GUI::Element * pInsertBeforeElement)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())
93 pRankIcon = new C4GUI::Icon(C4Rect(0, 0, C4GUI::ComboBox::GetDefaultHeight(), C4GUI::ComboBox::GetDefaultHeight()), C4GUI::Ico_None);
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
138 iHeight += ::GraphicsResource.TextFont.GetLineHeight();
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
UpdateOwnPos()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
GetListItemTopSpacing()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
UpdateIcon(C4PlayerInfo * pInfo,C4PlayerInfo * pJoinedInfo)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);
301 pDraw->ActivateBlitModulation(1);
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
UpdateTeam()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
UpdateScoreLabel(C4PlayerInfo * pInfo)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
410 if (pInfo->IsLeagueProjectedGainValid() && Game.Teams.IsTeamVisible())
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
UpdateCollapsed()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
OnContext(C4GUI::Element * pListItem,int32_t iX,int32_t iY)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
OnContextTakeOver(C4GUI::Element * pListItem,int32_t iX,int32_t iY)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
527 C4ClientPlayerInfos *pkInfo = ::Network.Players.GetLocalPlayerInfoPacket();
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
OnCtxTakeOver(C4GUI::Element * pListItem,const int32_t & idPlayer)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...
551 C4ClientPlayerInfos *pLocalInfo = ::Network.Players.GetLocalPlayerInfoPacket();
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
OnCtxRemove(C4GUI::Element * pListItem)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
OnCtxNewColor(C4GUI::Element * pListItem)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
OnTeamComboFill(C4GUI::ComboBox_FillCB * pFiller)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
OnTeamComboSelChange(C4GUI::ComboBox * pForCombo,int32_t idNewSelection)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
Update()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
GetPlayerInfo() const665 C4PlayerInfo *C4PlayerInfoListBox::PlayerListItem::GetPlayerInfo() const
666 {
667 return fFreeSavegamePlayer ? Game.RestorePlayerInfos.GetPlayerInfoByID(idPlayer) : Game.PlayerInfos.GetPlayerInfoByID(idPlayer);
668 }
669
GetJoinedInfo() const670 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
CanLocalChooseTeam() const688 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
IsLocalClientPlayer() const698 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
GetNetClient() const706 C4Network2Client *C4PlayerInfoListBox::PlayerListItem::GetNetClient() const
707 {
708 return ::Network.Clients.GetClientByID(idClient);
709 }
710
711
712 // ----------- ClientListItem -----------------------------------------------------------------
713
ClientListItem(C4PlayerInfoListBox * pForListBox,const C4ClientCore & rClientInfo,ListItem * pInsertBefore)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;
728 C4GUI::CallbackButton<ClientListItem, C4GUI::IconButton> *btnAddPlayer = 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
SetPing(int32_t iToPing)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
UpdateInfo()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
GetClient() const793 C4Client *C4PlayerInfoListBox::ClientListItem::GetClient() const
794 {
795 // search (let's hope it exists)
796 return Game.Clients.getClientByID(idClient);
797 }
798
IsLocalClientPlayer() const799 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
GetNetClient() const808 C4Network2Client *C4PlayerInfoListBox::ClientListItem::GetNetClient() const
809 {
810 return ::Network.Clients.GetClientByID(idClient);
811 }
812
IsLocal() const813 bool C4PlayerInfoListBox::ClientListItem::IsLocal() const
814 {
815 // it's local if client ID matches local ID
816 return idClient == Game.Clients.getLocalID();
817 }
818
GetCurrentStatusIcon()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
842 return C4GUI::Ico_UnknownClient;
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
858 return C4GUI::Ico_ObserverClient;
859 }
860
UpdatePing()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
SetSoundIcon()886 void C4PlayerInfoListBox::ClientListItem::SetSoundIcon()
887 {
888 // remember time for reset
889 tLastSoundTime = time(nullptr);
890 // force icon
891 SetStatus(GetCurrentStatusIcon());
892 }
893
OnContext(C4GUI::Element * pListItem,int32_t iX,int32_t iY)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
OnCtxIgnore(C4GUI::Element * pListItem)929 void C4PlayerInfoListBox::ClientListItem::OnCtxIgnore(C4GUI::Element *pListItem)
930 {
931 GetClient()->ToggleIgnore();
932 }
933
OnCtxKick(C4GUI::Element * pListItem)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
OnCtxActivate(C4GUI::Element * pListItem)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
948 ::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(idClient, CUT_Activate, !pClient->isActivated()), CDT_Sync);
949 }
950
OnCtxInfo(C4GUI::Element * pListItem)951 void C4PlayerInfoListBox::ClientListItem::OnCtxInfo(C4GUI::Element *pListItem)
952 {
953 // show client info dialog
954 ::pGUI->ShowRemoveDlg(new C4Network2ClientDlg(idClient));
955 }
956
OnBtnAddPlr(C4GUI::Control * btn)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
TeamListItem(C4PlayerInfoListBox * pForListBox,int32_t idTeam,ListItem * pInsertBefore)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
MouseInput(C4GUI::CMouse & rMouse,int32_t iButton,int32_t iX,int32_t iY,DWORD dwKeyParam)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
UpdateOwnPos()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
MoveLocalPlayersIntoTeam()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;
1054 C4ClientPlayerInfos *pChangeInfo = Game.PlayerInfos.GetInfoByClientID(Game.Clients.getLocalID());
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
Update()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
FreeSavegamePlayersListItem(C4PlayerInfoListBox * pForListBox,ListItem * pInsertBefore)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
Update()1121 void C4PlayerInfoListBox::FreeSavegamePlayersListItem::Update()
1122 {
1123 // 2do: none-label
1124 }
1125
1126
1127 // ----------- ScriptPlayersListItem ---------------------------------------------
1128
ScriptPlayersListItem(C4PlayerInfoListBox * pForListBox,ListItem * pInsertBefore)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
Update()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
OnBtnAddPlr(C4GUI::Control * btn)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!
1190 Game.PlayerInfos.DoPlayerInfoUpdate(&JoinPkt);
1191 }
1192
1193
1194 // ----------- ReplayPlayersListItem ---------------------------------------------
1195
ReplayPlayersListItem(C4PlayerInfoListBox * pForListBox,ListItem * pInsertBefore)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
C4PlayerInfoListBox(const C4Rect & rcBounds,Mode eMode,int32_t iTeamFilter)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
1229 SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4PlayerInfoListBox>(this, &C4PlayerInfoListBox::OnPlrListSelChange));
1230 // initial update
1231 Update();
1232 }
1233
SetClientSoundIcon(int32_t iForClientID)1234 void C4PlayerInfoListBox::SetClientSoundIcon(int32_t iForClientID)
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
GetPlayerListItem(ListItem::ID::IDType eType,int32_t id)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
PlrListItemUpdate(ListItem::ID::IDType eType,int32_t id,class ListItem ** pEnsurePos)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
Update()1285 void C4PlayerInfoListBox::Update()
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:
1319 case PILBM_EvaluationNoWinners:
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
UpdateSavegamePlayers(ListItem ** ppCurrInList)1353 void C4PlayerInfoListBox::UpdateSavegamePlayers(ListItem **ppCurrInList)
1354 {
1355 // add unassociated savegame players (script players excluded)
1356 if (Game.RestorePlayerInfos.GetActivePlayerCount(true) - Game.RestorePlayerInfos.GetActiveScriptPlayerCount(true, true))
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
UpdateReplayPlayers(ListItem ** ppCurrInList)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
UpdateScriptPlayers(ListItem ** ppCurrInList)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
1406 if (Game.Teams.GetMaxScriptPlayers() || Game.PlayerInfos.GetActiveScriptPlayerCount(true, false))
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
UpdatePlayersByTeam(ListItem ** ppCurrInList)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
UpdatePlayersByRandomTeam(ListItem ** ppCurrInList)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
UpdatePlayersByClient(ListItem ** ppCurrInList)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
UpdatePlayersByEvaluation(ListItem ** ppCurrInList,bool fShowWinners)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
UpdatePlayersByEvaluation(ListItem ** ppCurrInList,C4Team * pTeam,C4PlayerInfoListBox::AddMode eWinMode)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
IsPlayerItemCollapsed(PlayerListItem * pItem)1572 bool C4PlayerInfoListBox::IsPlayerItemCollapsed(PlayerListItem *pItem)
1573 {
1574 // never if view is not collapsed
1575 if (!fIsCollapsed) return false;
1576 // collapsed if not selected
1577 return GetSelectedItem() != pItem;
1578 }
1579
SetMode(Mode eNewMode)1580 void C4PlayerInfoListBox::SetMode(Mode eNewMode)
1581 {
1582 if (eMode != eNewMode)
1583 {
1584 eMode = eNewMode;
1585 Update();
1586 }
1587 }
1588
SetCustomFont(CStdFont * pNewFont,uint32_t dwTextColor)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 }
1595