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