1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2006-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 // Startup screen for non-parameterized engine start: Network game selection dialog
17 
18 #include "C4Include.h"
19 #include "gui/C4StartupNetDlg.h"
20 
21 #include "game/C4Application.h"
22 #include "graphics/C4Draw.h"
23 #include "graphics/C4GraphicsResource.h"
24 #include "gui/C4ChatDlg.h"
25 #include "gui/C4StartupScenSelDlg.h"
26 #include "gui/C4UpdateDlg.h"
27 #include "network/C4Network2Reference.h"
28 
29 // ----------- C4StartupNetListEntry -----------------------------------------------------------------------
30 
C4StartupNetListEntry(C4GUI::ListBox * pForListBox,C4GUI::Element * pInsertBefore,C4StartupNetDlg * pNetDlg)31 C4StartupNetListEntry::C4StartupNetListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, C4StartupNetDlg *pNetDlg)
32 		: pNetDlg(pNetDlg), pList(pForListBox), pRefClient(nullptr), pRef(nullptr), fError(false), eQueryType(NRQT_Unknown), iTimeout(0), iInfoIconCount(0), iSortOrder(0), fIsSmall(false), fIsCollapsed(false), fIsEnabled(true), fIsImportant(false)
33 {
34 	// calc height
35 	int32_t iLineHgt = ::GraphicsResource.TextFont.GetLineHeight(), iHeight = iLineHgt * 2 + 4;
36 	// add icons - normal icons use small size, only animated netgetref uses full size
37 	rctIconLarge.Set(0, 0, iHeight, iHeight);
38 	int32_t iSmallIcon = iHeight * 2 / 3; rctIconSmall.Set((iHeight - iSmallIcon)/2, (iHeight - iSmallIcon)/2, iSmallIcon, iSmallIcon);
39 	pIcon = new C4GUI::Icon(rctIconSmall, C4GUI::Ico_Host);
40 	AddElement(pIcon);
41 	SetBounds(pIcon->GetBounds());
42 	// add to listbox (will get resized horizontally and moved)
43 	pForListBox->InsertElement(this, pInsertBefore);
44 	// add status icons and text labels now that width is known
45 	CStdFont *pUseFont = &(::GraphicsResource.TextFont);
46 	int32_t iIconSize = pUseFont->GetLineHeight();
47 	C4Rect rcIconRect = GetContainedClientRect();
48 	int32_t iThisWdt = rcIconRect.Wdt;
49 	rcIconRect.x = iThisWdt - iIconSize * (iInfoIconCount + 1);
50 	rcIconRect.Wdt = rcIconRect.Hgt = iIconSize;
51 	for (auto & pInfoIcon : pInfoIcons)
52 	{
53 		AddElement(pInfoIcon = new C4GUI::Icon(rcIconRect, C4GUI::Ico_None));
54 		rcIconRect.x -= rcIconRect.Wdt;
55 	}
56 	C4Rect rcLabelBounds;
57 	rcLabelBounds.x = iHeight+3;
58 	rcLabelBounds.Hgt = iLineHgt;
59 	for (int i=0; i<InfoLabelCount; ++i)
60 	{
61 		C4GUI::Label *pLbl;
62 		rcLabelBounds.y = 1+i*(iLineHgt+2);
63 		rcLabelBounds.Wdt = iThisWdt - rcLabelBounds.x - 1;
64 		if (!i) rcLabelBounds.Wdt -= iLineHgt; // leave space for topright extra icon
65 		AddElement(pLbl = pInfoLbl[i] = new C4GUI::Label("", rcLabelBounds, ALeft, C4GUI_CaptionFontClr));
66 		// label will have collapsed due to no text: Repair it
67 		pLbl->SetAutosize(false);
68 		pLbl->SetBounds(rcLabelBounds);
69 	}
70 	// update small state, which will resize this to a small entry
71 	UpdateSmallState();
72 	// Set*-function will fill icon and text and calculate actual size
73 }
74 
~C4StartupNetListEntry()75 C4StartupNetListEntry::~C4StartupNetListEntry()
76 {
77 	ClearRef();
78 }
79 
DrawElement(C4TargetFacet & cgo)80 void C4StartupNetListEntry::DrawElement(C4TargetFacet &cgo)
81 {
82 	typedef C4GUI::Window ParentClass;
83 	// background if important and not selected
84 	if (fIsImportant && !IsSelectedChild(this))
85 	{
86 		int32_t x1 = cgo.X+cgo.TargetX+rcBounds.x;
87 		int32_t y1 = cgo.Y+cgo.TargetY+rcBounds.y;
88 		pDraw->DrawBoxDw(cgo.Surface, x1,y1, x1+rcBounds.Wdt, y1+rcBounds.Hgt, C4GUI_ImportantBGColor);
89 	}
90 	// inherited
91 	ParentClass::DrawElement(cgo);
92 }
93 
ClearRef()94 void C4StartupNetListEntry::ClearRef()
95 {
96 	// del old ref data
97 	if (pRefClient)
98 	{
99 		C4InteractiveThread &Thread = Application.InteractiveThread;
100 		Thread.RemoveProc(pRefClient);
101 		delete pRefClient; pRefClient = nullptr;
102 	}
103 	if (pRef) { delete pRef; pRef = nullptr; }
104 	eQueryType = NRQT_Unknown;
105 	iTimeout = iRequestTimeout = 0;
106 	fError = false;
107 	sError.Clear();
108 	int32_t i;
109 	for (i=0; i<InfoLabelCount; ++i) sInfoText[i].Clear();
110 	InvalidateStatusIcons();
111 	sRefClientAddress.Clear();
112 	fIsEnabled = true;
113 	fIsImportant = false;
114 }
115 
GetQueryTypeName(QueryType eQueryType)116 const char *C4StartupNetListEntry::GetQueryTypeName(QueryType eQueryType)
117 {
118 	switch (eQueryType)
119 	{
120 	case NRQT_GameDiscovery: return LoadResStr("IDS_NET_QUERY_LOCALNET");
121 	case NRQT_Masterserver:  return LoadResStr("IDS_NET_QUERY_MASTERSRV");
122 	case NRQT_DirectJoin:    return LoadResStr("IDS_NET_QUERY_DIRECTJOIN");
123 	default: return "";
124 	};
125 }
126 
SetRefQuery(const char * szAddress,enum QueryType eQueryType)127 void C4StartupNetListEntry::SetRefQuery(const char *szAddress, enum QueryType eQueryType)
128 {
129 	// safety: clear previous
130 	ClearRef();
131 	// setup layout
132 	const_cast<C4Facet &>(reinterpret_cast<const C4Facet &>(pIcon->GetFacet()))
133 	  = (const C4Facet &) C4Startup::Get()->Graphics.fctNetGetRef;
134 	pIcon->SetAnimated(true, 1);
135 	pIcon->SetBounds(rctIconLarge);
136 	// init a new ref client to query
137 	sRefClientAddress.Copy(szAddress);
138 	this->eQueryType = eQueryType;
139 	pRefClient = new C4Network2RefClient();
140 	if (!pRefClient->Init() || !pRefClient->SetServer(szAddress))
141 	{
142 		// should not happen
143 		sInfoText[0].Clear();
144 		SetError(pRefClient->GetError(), TT_RefReqWait);
145 		return;
146 	}
147 	// set info
148 	sInfoText[0].Format(LoadResStr("IDS_NET_CLIENTONNET"), GetQueryTypeName(eQueryType), pRefClient->getServerName());
149 	sInfoText[1].Copy(LoadResStr("IDS_NET_INFOQUERY"));
150 	UpdateSmallState(); UpdateText();
151 	pRefClient->SetNotify(&Application.InteractiveThread);
152 	// masterserver: always on top
153 	if (eQueryType == NRQT_Masterserver)
154 		iSortOrder = 100;
155 	// register proc
156 	C4InteractiveThread &Thread = Application.InteractiveThread;
157 	Thread.AddProc(pRefClient);
158 	// start querying!
159 	QueryReferences();
160 }
161 
QueryReferences()162 bool C4StartupNetListEntry::QueryReferences()
163 {
164 	// begin querying
165 	if (!pRefClient->QueryReferences())
166 	{
167 		SetError(pRefClient->GetError(), TT_RefReqWait);
168 		return false;
169 	}
170 	// set up timeout
171 	iRequestTimeout = time(nullptr) + C4NetRefRequestTimeout;
172 	return true;
173 }
174 
Execute()175 bool C4StartupNetListEntry::Execute()
176 {
177 	// update entries
178 	// if the return value is false, this entry will be deleted
179 	// timer running?
180 	if (iTimeout) if (time(nullptr) >= iTimeout)
181 		{
182 			// timeout!
183 			// for internet servers, this means refresh needed - search anew!
184 			if (pRefClient && eQueryType == NRQT_Masterserver)
185 			{
186 				fError = false;
187 				sError.Clear();
188 				pIcon->SetFacet(C4Startup::Get()->Graphics.fctNetGetRef);
189 				pIcon->SetAnimated(true, 1);
190 				pIcon->SetBounds(rctIconLarge);
191 				sInfoText[1].Copy(LoadResStr("IDS_NET_INFOQUERY"));
192 				iTimeout = 0;
193 				QueryReferences();
194 				// always keep item even if query failed
195 				return true;
196 			}
197 			// any other item is just removed - return value marks this
198 			return false;
199 		}
200 	// failed without a timer. Nothing to be done about it.
201 	if (fError) return true;
202 	// updates need to be done for references being retrieved only
203 	if (!pRefClient) return true;
204 	// check if it has arrived
205 	if (pRefClient->isBusy())
206 		// still requesting - but do not wait forever
207 		if (time(nullptr) >= iRequestTimeout)
208 		{
209 			SetError(LoadResStr("IDS_NET_ERR_REFREQTIMEOUT"), TT_RefReqWait);
210 			pRefClient->Cancel("Timeout");
211 		}
212 	return true;
213 }
214 
OnReference()215 bool C4StartupNetListEntry::OnReference()
216 {
217 	// wrong type / still busy?
218 	if (!pRefClient || pRefClient->isBusy())
219 		return true;
220 	// successful?
221 	if (!pRefClient->isSuccess())
222 	{
223 		// couldn't get references
224 		SetError(pRefClient->GetError(), TT_RefReqWait);
225 		return true;
226 	}
227 	// Ref getting done!
228 	pIcon->SetAnimated(false, 1);
229 	// Get reference information from client
230 	C4Network2Reference **ppNewRefs=nullptr; int32_t iNewRefCount;
231 	if (!pRefClient->GetReferences(ppNewRefs, iNewRefCount))
232 	{
233 		// References could be retrieved but not read
234 		SetError(LoadResStr("IDS_NET_ERR_REFINVALID"), TT_RefReqWait);
235 		delete [] ppNewRefs;
236 		return true;
237 	}
238 	if (!iNewRefCount)
239 	{
240 		// References retrieved but no game open: Inform user
241 		sInfoText[1].Copy(LoadResStr("IDS_NET_INFONOGAME"));
242 		UpdateText();
243 	}
244 	else
245 	{
246 		// Grab references, count players
247 		C4StartupNetListEntry *pNewRefEntry = this; int iPlayerCount = 0;
248 		for (int i = 0; i < iNewRefCount; i++)
249 		{
250 			pNewRefEntry = AddReference(ppNewRefs[i], pNewRefEntry->GetNextLower(ppNewRefs[i]->getSortOrder()));
251 			iPlayerCount += ppNewRefs[i]->Parameters.PlayerInfos.GetActivePlayerCount(false);
252 		}
253 		// Count player
254 		sInfoText[1].Format(LoadResStr("IDS_NET_INFOGAMES"), (int) iNewRefCount, iPlayerCount);
255 		UpdateText();
256 	}
257 	delete [] ppNewRefs;
258 	// special masterserver handling
259 	if (eQueryType == NRQT_Masterserver)
260 	{
261 		// masterserver: schedule next query
262 		sInfoText[1].Format(LoadResStr("IDS_NET_INFOGAMES"), (int) iNewRefCount);
263 		SetTimeout(TT_Masterserver);
264 		return true;
265 	}
266 	// non-masterserver
267 	if (iNewRefCount)
268 	{
269 		// this item has been "converted" into the references - remove without further feedback
270 		return false;
271 	}
272 	else
273 	{
274 		// no ref found on custom adress: Schedule re-check
275 		SetTimeout(TT_RefReqWait);
276 	}
277 	return true;
278 }
279 
GetNextLower(int32_t sortOrder)280 C4GUI::Element* C4StartupNetListEntry::GetNextLower(int32_t sortOrder)
281 {
282 	// search list for the next element of a lower sort order
283 	for (C4GUI::Element *pElem = pList->GetFirst(); pElem; pElem = pElem->GetNext())
284 	{
285 		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
286 		if (pEntry->iSortOrder < sortOrder)
287 			return pElem;
288 	}
289 	// none found: insert at start
290 	return nullptr;
291 }
292 
293 
UpdateCollapsed(bool fToCollapseValue)294 void C4StartupNetListEntry::UpdateCollapsed(bool fToCollapseValue)
295 {
296 	// if collapsed state changed, update the text
297 	if (fIsCollapsed == fToCollapseValue) return;
298 	fIsCollapsed = fToCollapseValue;
299 	UpdateSmallState();
300 }
301 
UpdateSmallState()302 void C4StartupNetListEntry::UpdateSmallState()
303 {
304 	// small view: Always collapsed if there is no extended text
305 	bool fNewIsSmall = !sInfoText[2].getLength() || fIsCollapsed;
306 	if (fNewIsSmall == fIsSmall) return;
307 	fIsSmall = fNewIsSmall;
308 	for (int i=2; i<InfoLabelCount; ++i) pInfoLbl[i]->SetVisibility(!fIsSmall);
309 	UpdateEntrySize();
310 }
311 
UpdateEntrySize()312 void C4StartupNetListEntry::UpdateEntrySize()
313 {
314 	if(fVisible) {
315 		// restack all labels by their size
316 		int32_t iLblCnt = (fIsSmall ? 2 : InfoLabelCount), iY=1;
317 		for (int i=0; i<iLblCnt; ++i)
318 		{
319 			C4Rect rcBounds = pInfoLbl[i]->GetBounds();
320 			rcBounds.y = iY;
321 				iY += rcBounds.Hgt + 2;
322 		pInfoLbl[i]->SetBounds(rcBounds);
323 		}
324 		// resize this control
325 		GetBounds().Hgt = iY-1;
326 	} else GetBounds().Hgt = 0;
327 	UpdateSize();
328 }
329 
UpdateText()330 void C4StartupNetListEntry::UpdateText()
331 {
332 	bool fRestackElements=false;
333 	CStdFont *pUseFont = &(::GraphicsResource.TextFont);
334 	// adjust icons
335 	int32_t sx=iInfoIconCount*pUseFont->GetLineHeight();
336 	int32_t i;
337 	for (i=iInfoIconCount; i<MaxInfoIconCount; ++i)
338 	{
339 		pInfoIcons[i]->SetIcon(C4GUI::Ico_None);
340 		pInfoIcons[i]->SetToolTip(nullptr);
341 	}
342 	// text to labels
343 	for (i=0; i<InfoLabelCount; ++i)
344 	{
345 		int iAvailableWdt = GetClientRect().Wdt - pInfoLbl[i]->GetBounds().x - 1;
346 		if (!i) iAvailableWdt -= sx;
347 		StdStrBuf BrokenText;
348 		pUseFont->BreakMessage(sInfoText[i].getData(), iAvailableWdt, &BrokenText, true);
349 		int32_t iHgt, iWdt;
350 		if (pUseFont->GetTextExtent(BrokenText.getData(), iWdt, iHgt, true))
351 		{
352 			if ((pInfoLbl[i]->GetBounds().Hgt != iHgt) || (pInfoLbl[i]->GetBounds().Wdt != iAvailableWdt))
353 			{
354 				C4Rect rcBounds = pInfoLbl[i]->GetBounds();
355 				rcBounds.Wdt = iAvailableWdt;
356 				rcBounds.Hgt = iHgt;
357 				pInfoLbl[i]->SetBounds(rcBounds);
358 				fRestackElements = true;
359 			}
360 		}
361 		pInfoLbl[i]->SetText(BrokenText.getData());
362 		pInfoLbl[i]->SetColor(fIsEnabled ? C4GUI_MessageFontClr : C4GUI_InactMessageFontClr);
363 	}
364 	if (fRestackElements) UpdateEntrySize();
365 }
366 
SetVisibility(bool fToValue)367 void C4StartupNetListEntry::SetVisibility(bool fToValue) {
368 	bool fChange = fToValue != fVisible;
369 	C4GUI::Window::SetVisibility(fToValue);
370 	if(fChange) UpdateEntrySize();
371 }
372 
AddStatusIcon(C4GUI::Icons eIcon,const char * szToolTip)373 void C4StartupNetListEntry::AddStatusIcon(C4GUI::Icons eIcon, const char *szToolTip)
374 {
375 	// safety
376 	if (iInfoIconCount==MaxInfoIconCount) return;
377 	// set icon to the left of the existing icons to the desired data
378 	pInfoIcons[iInfoIconCount]->SetIcon(eIcon);
379 	pInfoIcons[iInfoIconCount]->SetToolTip(szToolTip);
380 	++iInfoIconCount;
381 }
382 
SetReference(C4Network2Reference * pRef)383 void C4StartupNetListEntry::SetReference(C4Network2Reference *pRef)
384 {
385 	// safety: clear previous
386 	ClearRef();
387 	// set info
388 	this->pRef = pRef;
389 	int32_t iIcon = pRef->getIcon();
390 	if (!Inside<int32_t>(iIcon, 0, C4StartupScenSel_IconCount-1)) iIcon = C4StartupScenSel_DefaultIcon_Scenario;
391 	pIcon->SetFacet(C4Startup::Get()->Graphics.fctScenSelIcons.GetPhase(iIcon));
392 	pIcon->SetAnimated(false, 0);
393 	pIcon->SetBounds(rctIconSmall);
394 	int32_t iPlrCnt = pRef->isEditor() ? pRef->Parameters.PlayerInfos.GetActivePlayerCount(false) : pRef->Parameters.Clients.getClientCnt();
395 	C4Client *pHost = pRef->Parameters.Clients.getHost();
396 	sInfoText[0].Format(LoadResStr("IDS_NET_REFONCLIENT"), pRef->getTitle(), pHost ? pHost->getName() : "unknown");
397 	if (pRef->isEditor())
398 	{
399 		sInfoText[1].Format(LoadResStr("IDS_NET_INFOEDITOR"),
400 			(int)iPlrCnt,
401 			StdStrBuf(pRef->getGameStatus().getDescription(), true).getData());
402 	}
403 	else
404 	{
405 		sInfoText[1].Format(LoadResStr("IDS_NET_INFOPLRSGOALDESC"),
406 			(int)iPlrCnt,
407 			(int)pRef->Parameters.MaxPlayers,
408 			pRef->getGameGoalString().getData(),
409 			StdStrBuf(pRef->getGameStatus().getDescription(), true).getData());
410 	}
411 	if (pRef->getTime() > 0)
412 	{
413 		StdStrBuf strDuration; strDuration.Format("%02d:%02d:%02d", pRef->getTime()/3600, (pRef->getTime() % 3600) / 60, pRef->getTime() % 60);
414 		sInfoText[1].Append(" - "); sInfoText[1].Append(strDuration);
415 	}
416 	sInfoText[2].Format(LoadResStr("IDS_DESC_VERSION"), pRef->getGameVersion().GetString().getData());
417 	sInfoText[3].Format("%s: %s", LoadResStr("IDS_CTL_COMMENT"), pRef->getComment());
418 	StdStrBuf sAddress;
419 	for (int i=0; i<pRef->getAddrCnt(); ++i)
420 	{
421 		if (i) sAddress.Append(", ");
422 		sAddress.Append(pRef->getAddr(i).toString());
423 	}
424 	// editor reference
425 	if (pRef->isEditor())
426 		AddStatusIcon(C4GUI::Ico_Editor, LoadResStr("IDS_CNS_CONSOLE"));
427 	// password
428 	if (pRef->isPasswordNeeded())
429 		AddStatusIcon(C4GUI::Ico_Ex_LockedFrontal, LoadResStr("IDS_NET_INFOPASSWORD"));
430 	// league
431 	if (pRef->Parameters.isLeague())
432 		AddStatusIcon(C4GUI::Ico_Ex_League, pRef->Parameters.getLeague());
433 	// lobby active
434 	if (pRef->getGameStatus().isLobbyActive())
435 		AddStatusIcon(C4GUI::Ico_Lobby, LoadResStr("IDS_DESC_EXPECTING"));
436 	// game running
437 	if (pRef->getGameStatus().isPastLobby())
438 		AddStatusIcon(C4GUI::Ico_GameRunning, LoadResStr("IDS_NET_INFOINPROGR"));
439 	// runtime join
440 	if (pRef->isJoinAllowed() && pRef->getGameStatus().isPastLobby()) // A little workaround to determine RuntimeJoin...
441 		AddStatusIcon(C4GUI::Ico_RuntimeJoin, LoadResStr("IDS_NET_RUNTIMEJOINFREE"));
442 	// official server
443 	if (pRef->isOfficialServer() && !Config.Network.UseAlternateServer) // Offical server icon is only displayed if references are obtained from official league server
444 	{
445 		fIsImportant = true;
446 		AddStatusIcon(C4GUI::Ico_OfficialServer, LoadResStr("IDS_NET_OFFICIALSERVER"));
447 	}
448 	// list participating player/client names
449 	if (pRef->isEditor())
450 	{
451 		sInfoText[4].Format("%s%s", LoadResStr("IDS_DESC_CLIENTS"), iPlrCnt ? pRef->Parameters.Clients.GetAllClientNames().getData() : LoadResStr("IDS_CTL_NONE"));
452 	}
453 	else
454 	{
455 		sInfoText[4].Format("%s: %s", LoadResStr("IDS_CTL_PLAYER"), iPlrCnt ? pRef->Parameters.PlayerInfos.GetActivePlayerNames(false).getData() : LoadResStr("IDS_CTL_NONE"));
456 	}
457 	// disabled if join is not possible for some reason
458 	C4GameVersion verThis;
459 	if (!pRef->isJoinAllowed() || !(pRef->getGameVersion() == verThis))
460 	{
461 		fIsEnabled = false;
462 	}
463 	// store sort order
464 	iSortOrder = pRef->getSortOrder();
465 	// all references expire after a while
466 	SetTimeout(TT_Reference);
467 	UpdateSmallState(); UpdateText();
468 }
469 
SetError(const char * szErrorText,TimeoutType eTimeout)470 void C4StartupNetListEntry::SetError(const char *szErrorText, TimeoutType eTimeout)
471 {
472 	// set error message
473 	fError = true;
474 	sInfoText[1].Copy(szErrorText);
475 	for (int i=2; i<InfoLabelCount; ++i) sInfoText[i].Clear();
476 	InvalidateStatusIcons();
477 	UpdateSmallState(); UpdateText();
478 	pIcon->SetIcon(C4GUI::Ico_Close);
479 	pIcon->SetAnimated(false, 0);
480 	pIcon->SetBounds(rctIconSmall);
481 	SetTimeout(eTimeout);
482 }
483 
SetTimeout(TimeoutType eTimeout)484 void C4StartupNetListEntry::SetTimeout(TimeoutType eTimeout)
485 {
486 	int iTime = 0;
487 	switch (eTimeout)
488 	{
489 	case TT_RefReqWait: iTime = (eQueryType == NRQT_Masterserver) ? C4NetMasterServerQueryInterval : C4NetErrorRefTimeout; break;
490 	case TT_Reference: iTime = C4NetReferenceTimeout; break;
491 	case TT_Masterserver: iTime = C4NetMasterServerQueryInterval; break;
492 	};
493 	if (!iTime) return;
494 	iTimeout = time(nullptr) + iTime;
495 }
496 
AddReference(C4Network2Reference * pAddRef,C4GUI::Element * pInsertBefore)497 C4StartupNetListEntry *C4StartupNetListEntry::AddReference(C4Network2Reference *pAddRef, C4GUI::Element *pInsertBefore)
498 {
499 	// check list whether the same reference has been added already
500 	for (C4GUI::Element *pElem = pList->GetFirst(); pElem; pElem = pElem->GetNext())
501 	{
502 		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
503 		// match to existing reference entry:
504 		// * same host (checking for same name and nick)
505 		// * at least one match in address and port
506 		// * the incoming reference is newer than (or same as) the current one
507 		if ( pEntry->IsSameHost(pAddRef)
508 		     && pEntry->IsSameAddress(pAddRef)
509 		     && (pEntry->GetReference()->getStartTime() <= pAddRef->getStartTime()) )
510 		{
511 			// update existing entry
512 			pEntry->SetReference(pAddRef);
513 			return pEntry;
514 		}
515 	}
516 	// no update - just add
517 	C4StartupNetListEntry *pNewRefEntry = new C4StartupNetListEntry(pList, pInsertBefore, pNetDlg);
518 	pNewRefEntry->SetReference(pAddRef);
519 	pNetDlg->OnReferenceEntryAdd(pNewRefEntry);
520 	return pNewRefEntry;
521 }
522 
IsSameHost(const C4Network2Reference * pRef2)523 bool C4StartupNetListEntry::IsSameHost(const C4Network2Reference *pRef2)
524 {
525 	// not if ref has not been retrieved yet
526 	if (!pRef) return false;
527 	C4Client *pHost1 = pRef->Parameters.Clients.getHost();
528 	C4Client *pHost2 = pRef2->Parameters.Clients.getHost();
529 	if (!pHost1 || !pHost2) return false;
530 	// check
531 	return SEqual(pHost1->getCUID(), pHost2->getCUID()) && SEqual(pHost1->getName(), pHost2->getName());
532 }
533 
IsSameAddress(const C4Network2Reference * pRef2)534 bool C4StartupNetListEntry::IsSameAddress(const C4Network2Reference *pRef2)
535 {
536 	// not if ref has not been retrieved yet
537 	if (!pRef) return false;
538 	// check all of our addresses
539 	for (int i = 0; i < pRef->getAddrCnt(); i++)
540 		// against all of the other ref's addresses
541 		for (int j = 0; j < pRef2->getAddrCnt(); j++)
542 			// at least one match!
543 			if (pRef->getAddr(i) == pRef2->getAddr(j))
544 				return true;
545 	// no match
546 	return false;
547 }
548 
IsSameRefQueryAddress(const char * szJoinaddress)549 bool C4StartupNetListEntry::IsSameRefQueryAddress(const char *szJoinaddress)
550 {
551 	// only unretrieved references
552 	if (!pRefClient) return false;
553 	// if request failed, create a duplicate anyway in case the game is opened now
554 	// except masterservers, which would re-search some time later anyway
555 	if (fError && eQueryType != NRQT_Masterserver) return false;
556 	// check equality of address
557 	// do it the simple way for now
558 	return SEqualNoCase(sRefClientAddress.getData(), szJoinaddress);
559 }
560 
KeywordMatch(const char * szMatch)561 bool C4StartupNetListEntry::KeywordMatch(const char *szMatch)
562 {
563 	// only finished references
564 	if (!pRef) return false;
565 	if(SSearchNoCase(pRef->getTitle(),szMatch)) return true;
566 	C4Client *pHost = pRef->Parameters.Clients.getHost();
567 	if(pHost && SSearchNoCase(pHost->getName(),szMatch)) return true;
568 	if(SSearchNoCase(pRef->getComment(),szMatch)) return true;
569 	return false;
570 }
571 
GetJoinAddress()572 const char *C4StartupNetListEntry::GetJoinAddress()
573 {
574 	// only unresolved references
575 	if (!pRefClient) return nullptr;
576 	// not masterservers (cannot join directly on clonk.de)
577 	if (eQueryType == NRQT_Masterserver) return nullptr;
578 	// return join address
579 	return pRefClient->getServerName();
580 }
581 
GrabReference()582 C4Network2Reference *C4StartupNetListEntry::GrabReference()
583 {
584 	C4Network2Reference *pOldRef = pRef;
585 	pRef = nullptr;
586 	return pOldRef;
587 }
588 
589 
590 
591 
592 // ----------- C4StartupNetDlg ---------------------------------------------------------------------------------
593 
C4StartupNetDlg()594 C4StartupNetDlg::C4StartupNetDlg() : C4StartupDlg(LoadResStr("IDS_DLG_NETSTART"))
595 {
596 	// ctor
597 	// key bindings
598 	C4CustomKey::CodeList keys;
599 	keys.emplace_back(K_BACK); keys.emplace_back(K_LEFT);
600 	pKeyBack = new C4KeyBinding(keys, "StartupNetBack", KEYSCOPE_Gui,
601 	                            new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyBack), C4CustomKey::PRIO_Dlg);
602 	pKeyRefresh = new C4KeyBinding(C4KeyCodeEx(K_F5), "StartupNetReload", KEYSCOPE_Gui,
603 	                               new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyRefresh), C4CustomKey::PRIO_CtrlOverride);
604 
605 	// screen calculations
606 	UpdateSize();
607 	int32_t iIconSize = C4GUI_IconExWdt;
608 	int32_t iButtonWidth,iCaptionFontHgt, iSideSize = std::max<int32_t>(GetBounds().Wdt/6, iIconSize);
609 	int32_t iButtonHeight = C4GUI_ButtonHgt, iButtonIndent = GetBounds().Wdt/40;
610 	::GraphicsResource.CaptionFont.GetTextExtent("<< BACK", iButtonWidth, iCaptionFontHgt, true);
611 	iButtonWidth *= 3;
612 	C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
613 	C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(caMain.GetHeight()/7),0,0);
614 	int32_t iButtonAreaWdt = caButtonArea.GetWidth()*7/8;
615 	iButtonWidth = std::min<int32_t>(iButtonWidth, (iButtonAreaWdt - 8 * iButtonIndent)/4);
616 	iButtonIndent = (iButtonAreaWdt - 4 * iButtonWidth) / 8;
617 	C4GUI::ComponentAligner caButtons(caButtonArea.GetCentered(iButtonAreaWdt, iButtonHeight),iButtonIndent,0);
618 	C4GUI::ComponentAligner caLeftBtnArea(caMain.GetFromLeft(iSideSize), std::min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);
619 	C4GUI::ComponentAligner caConfigArea(caMain.GetFromRight(iSideSize), std::min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);
620 
621 	// left button area: Switch between chat and game list
622 	if (C4ChatDlg::IsChatEnabled())
623 	{
624 		btnGameList = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_GameList, caLeftBtnArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnGameList);
625 		btnGameList->SetToolTip(LoadResStr("IDS_DESC_SHOWSAVAILABLENETWORKGAME"));
626 		btnGameList->SetText(LoadResStr("IDS_BTN_GAMES"));
627 		AddElement(btnGameList);
628 		btnChat = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Chat, caLeftBtnArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnChat);
629 		btnChat->SetToolTip(LoadResStr("IDS_DESC_CONNECTSTOANIRCCHATSERVER"));
630 		btnChat->SetText(LoadResStr("IDS_BTN_CHAT"));
631 		AddElement(btnChat);
632 	}
633 	else btnChat = nullptr;
634 
635 	// main area: Tabular to switch between game list and chat
636 	pMainTabular = new C4GUI::Tabular(caMain.GetAll(), C4GUI::Tabular::tbNone);
637 	pMainTabular->SetDrawDecoration(false);
638 	pMainTabular->SetSheetMargin(0);
639 	AddElement(pMainTabular);
640 
641 	// main area: game selection sheet
642 	C4GUI::Tabular::Sheet *pSheetGameList = pMainTabular->AddSheet(nullptr);
643 	C4GUI::ComponentAligner caGameList(pSheetGameList->GetContainedClientRect(), 0,0, false);
644 	C4GUI::WoodenLabel *pGameListLbl; int32_t iCaptHgt = C4GUI::WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
645 	pGameListLbl = new C4GUI::WoodenLabel(LoadResStr("IDS_NET_GAMELIST"), caGameList.GetFromTop(iCaptHgt), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont, ALeft);
646 	// search field
647 	C4GUI::WoodenLabel *pSearchLbl;
648 	const char *szSearchLblText = LoadResStr("IDS_NET_MSSEARCH");
649 	int32_t iSearchWdt=100, iSearchHgt;
650 	::GraphicsResource.TextFont.GetTextExtent(szSearchLblText, iSearchWdt, iSearchHgt, true);
651 	C4GUI::ComponentAligner caSearch(caGameList.GetFromTop(iSearchHgt), 0,0);
652 	pSearchLbl = new C4GUI::WoodenLabel(szSearchLblText, caSearch.GetFromLeft(iSearchWdt+10), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont);
653 	const char *szSearchTip = LoadResStr("IDS_NET_MSSEARCH_DESC");
654 	pSearchLbl->SetToolTip(szSearchTip);
655 	pSheetGameList->AddElement(pSearchLbl);
656 	pSearchFieldEdt = new C4GUI::CallbackEdit<C4StartupNetDlg>(caSearch.GetAll(), this, &C4StartupNetDlg::OnSearchFieldEnter);
657 	pSearchFieldEdt->SetToolTip(szSearchTip);
658 	pSheetGameList->AddElement(pSearchFieldEdt);
659 	pSheetGameList->AddElement(pGameListLbl);
660 	pGameSelList = new C4GUI::ListBox(caGameList.GetFromTop(caGameList.GetHeight() - iCaptHgt));
661 	pGameSelList->SetDecoration(true, nullptr, true, true);
662 	pGameSelList->UpdateElementPositions();
663 	pGameSelList->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupNetDlg>(this, &C4StartupNetDlg::OnSelDblClick));
664 	pGameSelList->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupNetDlg>(this, &C4StartupNetDlg::OnSelChange));
665 	pSheetGameList->AddElement(pGameSelList);
666 	C4GUI::ComponentAligner caIP(caGameList.GetAll(), 0,0);
667 	C4GUI::WoodenLabel *pIPLbl;
668 	const char *szIPLblText = LoadResStr("IDS_NET_IP");
669 	int32_t iIPWdt=100, Q;
670 	::GraphicsResource.TextFont.GetTextExtent(szIPLblText, iIPWdt, Q, true);
671 	pIPLbl = new C4GUI::WoodenLabel(szIPLblText, caIP.GetFromLeft(iIPWdt+10), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont);
672 	const char *szIPTip = LoadResStr("IDS_NET_IP_DESC");
673 	pIPLbl->SetToolTip(szIPTip);
674 	pSheetGameList->AddElement(pIPLbl);
675 	pJoinAddressEdt = new C4GUI::CallbackEdit<C4StartupNetDlg>(caIP.GetAll(), this, &C4StartupNetDlg::OnJoinAddressEnter);
676 	pJoinAddressEdt->SetToolTip(szIPTip);
677 	pSheetGameList->AddElement(pJoinAddressEdt);
678 
679 	// main area: chat sheet
680 	if (C4ChatDlg::IsChatEnabled())
681 	{
682 		C4GUI::Tabular::Sheet *pSheetChat = pMainTabular->AddSheet(nullptr);
683 		C4GUI::ComponentAligner caChat(pSheetChat->GetContainedClientRect(), 0,0, false);
684 		pSheetChat->AddElement(pChatTitleLabel = new C4GUI::WoodenLabel("", caChat.GetFromTop(iCaptHgt), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont, ALeft, false));
685 		C4GUI::GroupBox *pChatGroup = new C4GUI::GroupBox(caChat.GetAll());
686 		pChatGroup->SetColors(0u, C4GUI_CaptionFontClr, C4GUI_StandardBGColor);
687 		pChatGroup->SetMargin(2);
688 		pSheetChat->AddElement(pChatGroup);
689 		pChatCtrl = new C4ChatControl(&Application.IRCClient);
690 		pChatCtrl->SetBounds(pChatGroup->GetContainedClientRect());
691 		pChatCtrl->SetTitleChangeCB(new C4GUI::InputCallback<C4StartupNetDlg>(this, &C4StartupNetDlg::OnChatTitleChange));
692 		StdStrBuf sCurrTitle; sCurrTitle.Ref(pChatCtrl->GetTitle()); OnChatTitleChange(sCurrTitle);
693 		pChatGroup->AddElement(pChatCtrl);
694 	}
695 
696 	// config area
697 	btnInternet = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(Config.Network.MasterServerSignUp ? C4GUI::Ico_Ex_InternetOn : C4GUI::Ico_Ex_InternetOff, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnInternet);
698 	btnInternet->SetToolTip(LoadResStr("IDS_DLGTIP_SEARCHINTERNETGAME"));
699 	btnInternet->SetText(LoadResStr("IDS_CTL_INETSERVER"));
700 	AddElement(btnInternet);
701 	btnRecord = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(Game.Record ? C4GUI::Ico_Ex_RecordOn : C4GUI::Ico_Ex_RecordOff, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnRecord);
702 	btnRecord->SetToolTip(LoadResStr("IDS_DLGTIP_RECORD"));
703 	btnRecord->SetText(LoadResStr("IDS_CTL_RECORD"));
704 	AddElement(btnRecord);
705 #ifdef WITH_AUTOMATIC_UPDATE
706 	btnUpdate = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Update, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnUpdate);
707 	btnUpdate->SetVisibility(false); // update only available if masterserver notifies it
708 	btnUpdate->SetToolTip(LoadResStr("IDS_DLGTIP_UPDATE"));
709 	btnUpdate->SetText(LoadResStr("IDS_CTL_UPDATE"));
710 	AddElement(btnUpdate);
711 #endif
712 
713 	// button area
714 	C4GUI::CallbackButton<C4StartupNetDlg> *btn;
715 	AddElement(btn = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_BTN_BACK"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnBackBtn));
716 	btn->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
717 	AddElement(btnRefresh = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_BTN_RELOAD"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnRefreshBtn));
718 	btnRefresh->SetToolTip(LoadResStr("IDS_NET_RELOAD_DESC"));
719 	AddElement(btnJoin = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_NET_JOINGAME_BTN"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnJoinGameBtn));
720 	btnJoin->SetToolTip(LoadResStr("IDS_NET_JOINGAME_DESC"));
721 	AddElement(btn = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_NET_NEWGAME"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnCreateGameBtn));
722 	btn->SetToolTip(LoadResStr("IDS_NET_NEWGAME_DESC"));
723 
724 	// initial dlg mode
725 	UpdateDlgMode();
726 
727 	// initial focus
728 	SetFocus(GetDlgModeFocusControl(), false);
729 
730 	// initialize discovery
731 	DiscoverClient.Init(Config.Network.PortDiscovery);
732 	DiscoverClient.StartDiscovery();
733 	iGameDiscoverInterval = C4NetGameDiscoveryInterval;
734 
735 	// register timer
736 	Application.Add(this);
737 
738 	// register as receiver of reference notifies
739 	Application.InteractiveThread.SetCallback(Ev_HTTP_Response, this);
740 
741 }
742 
~C4StartupNetDlg()743 C4StartupNetDlg::~C4StartupNetDlg()
744 {
745 	// disable notifies
746 	Application.InteractiveThread.ClearCallback(Ev_HTTP_Response, this);
747 	Application.InteractiveThread.RemoveProc(&pUpdateClient);
748 
749 	DiscoverClient.Close();
750 	Application.Remove(this);
751 	if (pMasterserverClient) delete pMasterserverClient;
752 	// dtor
753 	delete pKeyBack;
754 	delete pKeyRefresh;
755 }
756 
DrawElement(C4TargetFacet & cgo)757 void C4StartupNetDlg::DrawElement(C4TargetFacet &cgo)
758 {
759 	// draw background
760 	typedef C4GUI::FullscreenDialog Base;
761 	Base::DrawElement(cgo);
762 }
763 
OnShown()764 void C4StartupNetDlg::OnShown()
765 {
766 	// callback when shown: Start searching for games
767 	C4StartupDlg::OnShown();
768 	CheckVersionUpdate();
769 	UpdateList();
770 	UpdateUpdateButton(); // in case update check was finished before callback registration
771 	UpdateMasterserver();
772 	OnSec1Timer();
773 	tLastRefresh = time(nullptr);
774 	// also update chat
775 	if (pChatCtrl) pChatCtrl->OnShown();
776 }
777 
OnClosed(bool fOK)778 void C4StartupNetDlg::OnClosed(bool fOK)
779 {
780 	// dlg abort: return to main screen
781 	if (pMasterserverClient) { delete pMasterserverClient; pMasterserverClient=nullptr; }
782 	if (!fOK) DoBack();
783 }
784 
GetDefaultControl()785 C4GUI::Control *C4StartupNetDlg::GetDefaultControl()
786 {
787 	// default control depends on whether dlg is in chat or game list mode
788 	if (GetDlgMode() == SNDM_Chat && pChatCtrl)
789 		// chat mode: Chat input edit
790 		return pChatCtrl->GetDefaultControl();
791 	else
792 		// game list mode: No default control, because it would move focus away from IP input edit
793 		return nullptr;
794 }
795 
GetDlgModeFocusControl()796 C4GUI::Control *C4StartupNetDlg::GetDlgModeFocusControl()
797 {
798 	// default control depends on whether dlg is in chat or game list mode
799 	if (GetDlgMode() == SNDM_Chat && pChatCtrl)
800 		// chat mode: Chat input edit
801 		return pChatCtrl->GetDefaultControl();
802 	else
803 		// game list mode: Game list box
804 		return pGameSelList;
805 }
806 
OnBtnGameList(C4GUI::Control * btn)807 void C4StartupNetDlg::OnBtnGameList(C4GUI::Control *btn)
808 {
809 	// switch to game list dialog
810 	pMainTabular->SelectSheet(SNDM_GameList, true);
811 	UpdateDlgMode();
812 }
813 
OnBtnChat(C4GUI::Control * btn)814 void C4StartupNetDlg::OnBtnChat(C4GUI::Control *btn)
815 {
816 	// toggle chat / game list
817 	if (pChatCtrl)
818 	{
819 		if (pMainTabular->GetActiveSheetIndex() == SNDM_GameList)
820 		{
821 			pMainTabular->SelectSheet(SNDM_Chat, true);
822 			pChatCtrl->OnShown();
823 			UpdateDlgMode();
824 		}
825 		else
826 		{
827 			pMainTabular->SelectSheet(SNDM_GameList, true);
828 			UpdateDlgMode();
829 		}
830 	}
831 }
832 
OnBtnInternet(C4GUI::Control * btn)833 void C4StartupNetDlg::OnBtnInternet(C4GUI::Control *btn)
834 {
835 	// toggle masterserver game search
836 	Config.Network.MasterServerSignUp = !Config.Network.MasterServerSignUp;
837 	UpdateMasterserver();
838 }
839 
OnBtnRecord(C4GUI::Control * btn)840 void C4StartupNetDlg::OnBtnRecord(C4GUI::Control *btn)
841 {
842 	// toggle league signup flag
843 	bool fCheck = !Game.Record;
844 	Game.Record = fCheck;
845 	Config.General.DefRec = fCheck;
846 	btnRecord->SetIcon(fCheck ? C4GUI::Ico_Ex_RecordOn : C4GUI::Ico_Ex_RecordOff);
847 }
848 
849 #ifdef WITH_AUTOMATIC_UPDATE
OnBtnUpdate(C4GUI::Control * btn)850 void C4StartupNetDlg::OnBtnUpdate(C4GUI::Control *btn)
851 {
852 	// do update
853 	if (!C4UpdateDlg::DoUpdate(UpdateURL.getData(), GetScreen()))
854 	{
855 		GetScreen()->ShowMessage(LoadResStr("IDS_MSG_UPDATEFAILED"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
856 	}
857 }
858 #endif
859 
UpdateMasterserver()860 void C4StartupNetDlg::UpdateMasterserver()
861 {
862 	// update button icon to current state
863 	btnInternet->SetIcon(Config.Network.MasterServerSignUp ? C4GUI::Ico_Ex_InternetOn : C4GUI::Ico_Ex_InternetOff);
864 	// creates masterserver object if masterserver is enabled; destroy otherwise
865 	if (!Config.Network.MasterServerSignUp == !pMasterserverClient) return;
866 	if (!Config.Network.MasterServerSignUp)
867 	{
868 		delete pMasterserverClient;
869 		pMasterserverClient = nullptr;
870 	}
871 	else
872 	{
873 		pMasterserverClient = new C4StartupNetListEntry(pGameSelList, nullptr, this);
874 		StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
875 		StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s", Config.Network.GetLeagueServerAddress(), strVersion.getData(), C4_OS);
876 		pMasterserverClient->SetRefQuery(strQuery.getData(), C4StartupNetListEntry::NRQT_Masterserver);
877 	}
878 }
879 
UpdateList(bool fGotReference)880 void C4StartupNetDlg::UpdateList(bool fGotReference)
881 {
882 	// recursion check
883 	if (fUpdatingList) return;
884 	fUpdatingList = true;
885 	pGameSelList->FreezeScrolling();
886 	// Games display mask
887 	const char *szGameMask = pSearchFieldEdt->GetText();
888 	if (!szGameMask) szGameMask = "";
889 	// Update all child entries
890 	bool fAnyRemoval = false;
891 	C4GUI::Element *pElem, *pNextElem = pGameSelList->GetFirst();
892 	while ((pElem=pNextElem))
893 	{
894 		pNextElem = pElem->GetNext(); // determine next exec element now - execution
895 		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
896 		// do item updates
897 		if(pEntry->GetReference()) pEntry->SetVisibility(pEntry->KeywordMatch(szGameMask));
898 		bool fKeepEntry = true;
899 		if (fGotReference)
900 			fKeepEntry = pEntry->OnReference();
901 		if (fKeepEntry)
902 			fKeepEntry = pEntry->Execute();
903 		// remove?
904 		if (!fKeepEntry)
905 		{
906 			// entry wishes to be removed
907 			// if the selected entry is being removed, the next entry should be selected (which might be the ref for a finished refquery)
908 			if (pGameSelList->GetSelectedItem() == pEntry)
909 				if (pEntry->GetNext())
910 				{
911 					pGameSelList->SelectEntry(pEntry->GetNext(), false);
912 				}
913 			delete pEntry;
914 			fAnyRemoval = true; // setting any removal will also update collapsed state of all entries; so no need to do updates because of selection change here
915 		}
916 	}
917 
918 	// Add LAN games
919 	C4NetIO::addr_t Discover;
920 	while (DiscoverClient.PopDiscover(Discover))
921 	{
922 		StdStrBuf Address(Discover.ToString());
923 		AddReferenceQuery(Address.getData(), C4StartupNetListEntry::NRQT_GameDiscovery);
924 	}
925 
926 	// check whether view needs to be collapsed or uncollapsed
927 	if (fIsCollapsed && fAnyRemoval)
928 	{
929 		// try uncollapsing
930 		fIsCollapsed = false;
931 		UpdateCollapsed();
932 		// if scrolling is still necessary, the view will be collapsed again immediately
933 	}
934 	if (!fIsCollapsed && pGameSelList->IsScrollingNecessary())
935 	{
936 		fIsCollapsed = true;
937 		UpdateCollapsed();
938 	}
939 
940 	fUpdatingList = false;
941 	// done; selection might have changed
942 	pGameSelList->UnFreezeScrolling();
943 	UpdateSelection(false);
944 }
945 
UpdateCollapsed()946 void C4StartupNetDlg::UpdateCollapsed()
947 {
948 	// update collapsed state for all child entries
949 	for (C4GUI::Element *pElem = pGameSelList->GetFirst(); pElem; pElem = pElem->GetNext())
950 	{
951 		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
952 		pEntry->UpdateCollapsed(fIsCollapsed && pElem != pGameSelList->GetSelectedItem());
953 	}
954 }
955 
UpdateSelection(bool fUpdateCollapsed)956 void C4StartupNetDlg::UpdateSelection(bool fUpdateCollapsed)
957 {
958 	// not during list updates - list update call will do this
959 	if (fUpdatingList) return;
960 	// in collapsed view, updating the selection may uncollapse something
961 	if (fIsCollapsed && fUpdateCollapsed) UpdateCollapsed();
962 }
963 
UpdateDlgMode()964 void C4StartupNetDlg::UpdateDlgMode()
965 {
966 	DlgMode eMode = GetDlgMode();
967 	// buttons for game joining only visible in game list mode
968 	btnInternet->SetVisibility(eMode == SNDM_GameList);
969 	btnRecord->SetVisibility(eMode == SNDM_GameList);
970 	btnJoin->SetVisibility(eMode == SNDM_GameList);
971 	btnRefresh->SetVisibility(eMode == SNDM_GameList);
972 	// focus update
973 	if (!GetFocus()) SetFocus(GetDlgModeFocusControl(), false);
974 }
975 
GetDlgMode()976 C4StartupNetDlg::DlgMode C4StartupNetDlg::GetDlgMode()
977 {
978 	// dlg mode determined by active tabular sheet
979 	if (pMainTabular->GetActiveSheetIndex() == SNDM_Chat) return SNDM_Chat; else return SNDM_GameList;
980 }
981 
OnThreadEvent(C4InteractiveEventType eEvent,void * pEventData)982 void C4StartupNetDlg::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData)
983 {
984 	UpdateUpdateButton();
985 	UpdateList(true);
986 }
987 
UpdateUpdateButton()988 void C4StartupNetDlg::UpdateUpdateButton()
989 {
990 	if (!fUpdateCheckPending) return;
991 	if(!pUpdateClient.isSuccess() || pUpdateClient.isBusy()) return;
992 
993 	pUpdateClient.SetNotify(nullptr);
994 
995 	StdCopyStrBuf versionInfo;
996 
997 	pUpdateClient.GetVersion(&versionInfo);
998 	pUpdateClient.GetUpdateURL(&UpdateURL);
999 
1000 #ifdef WITH_AUTOMATIC_UPDATE
1001 	btnUpdate->SetVisibility(C4UpdateDlg::IsValidUpdate(versionInfo.getData()));
1002 #endif
1003 	fUpdateCheckPending = false;
1004 }
1005 
DoOK()1006 bool C4StartupNetDlg::DoOK()
1007 {
1008 	// OK in chat mode? Forward to chat control
1009 	if (GetDlgMode() == SNDM_Chat) return pChatCtrl->DlgEnter();
1010 	// OK on editbox with text enetered: Add the specified IP for reference retrieval
1011 	if (GetFocus() == pJoinAddressEdt)
1012 	{
1013 		const char *szDirectJoinAddress = pJoinAddressEdt->GetText();
1014 		if (szDirectJoinAddress && *szDirectJoinAddress)
1015 		{
1016 			// First do some acrobatics to avoid trying to resolve addresses with leading
1017 			// or trailing whitespace, which is easily pasted in with an IP address.
1018 			// We can trivially skip whitespace at the beginning, but we need a copy to
1019 			// omit whitespace at the end.
1020 			while (std::isspace(*szDirectJoinAddress))
1021 				// skip whitespace at the beginning
1022 				++szDirectJoinAddress;
1023 			if (!*szDirectJoinAddress)
1024 				// entry empty, apart from whitespace
1025 				return true;
1026 			const char *szDirectJoinAddressEnd = szDirectJoinAddress + std::strlen(szDirectJoinAddress) - 1;
1027 			while (std::isspace(*szDirectJoinAddressEnd))
1028 				// skip whitespace at the end
1029 				--szDirectJoinAddressEnd;
1030 			if (*++szDirectJoinAddressEnd)
1031 			{
1032 				// Make a temporary copy of the part that is not trailing whitespace, if any
1033 				std::string strDirectJoinAddressStripped(szDirectJoinAddress, szDirectJoinAddressEnd - szDirectJoinAddress);
1034 				AddReferenceQuery(strDirectJoinAddressStripped.c_str(), C4StartupNetListEntry::NRQT_DirectJoin);
1035 			}
1036 			else
1037 				AddReferenceQuery(szDirectJoinAddress, C4StartupNetListEntry::NRQT_DirectJoin);
1038 			// Switch focus to list so another OK joins the specified address
1039 			SetFocus(pGameSelList, true);
1040 			return true;
1041 		}
1042 	}
1043 	if (GetFocus() == pSearchFieldEdt)
1044 	{
1045 		UpdateList();
1046 		return true;
1047 	}
1048 	// get currently selected item
1049 	C4GUI::Element *pSelection = pGameSelList->GetSelectedItem();
1050 	StdCopyStrBuf strNoJoin(LoadResStr("IDS_NET_NOJOIN"));
1051 	if (!pSelection)
1052 	{
1053 		// no ref selected: Oh noes!
1054 		::pGUI->ShowMessageModal(
1055 		  LoadResStr("IDS_NET_NOJOIN_NOREF"),
1056 		  strNoJoin.getData(),
1057 		  C4GUI::MessageDialog::btnOK,
1058 		  C4GUI::Ico_Error);
1059 		return true;
1060 	}
1061 	C4StartupNetListEntry *pRefEntry = static_cast<C4StartupNetListEntry *>(pSelection);
1062 	const char *szError;
1063 	if ((szError = pRefEntry->GetError()))
1064 	{
1065 		// erroneous ref selected: Oh noes!
1066 		::pGUI->ShowMessageModal(
1067 		  FormatString(LoadResStr("IDS_NET_NOJOIN_BADREF"), szError).getData(),
1068 		  strNoJoin.getData(),
1069 		  C4GUI::MessageDialog::btnOK,
1070 		  C4GUI::Ico_Error);
1071 		return true;
1072 	}
1073 	C4Network2Reference *pRef = pRefEntry->GetReference();
1074 	const char *szDirectJoinAddress = pRefEntry->GetJoinAddress();
1075 	if (!pRef && !(szDirectJoinAddress && *szDirectJoinAddress))
1076 	{
1077 		// something strange has been selected (e.g., a masterserver entry). Error.
1078 		::pGUI->ShowMessageModal(
1079 		  LoadResStr("IDS_NET_NOJOIN_NOREF"),
1080 		  strNoJoin.getData(),
1081 		  C4GUI::MessageDialog::btnOK,
1082 		  C4GUI::Ico_Error);
1083 		return true;
1084 	}
1085 	// check if join to this reference is possible at all
1086 	if (pRef)
1087 	{
1088 		// version mismatch
1089 		C4GameVersion verThis;
1090 		if (!(pRef->getGameVersion() == verThis))
1091 		{
1092 			::pGUI->ShowMessageModal(
1093 			  FormatString(LoadResStr("IDS_NET_NOJOIN_BADVER"),
1094 			               pRef->getGameVersion().GetString().getData(),
1095 			               verThis.GetString().getData()).getData(),
1096 			  strNoJoin.getData(),
1097 			  C4GUI::MessageDialog::btnOK,
1098 			  C4GUI::Ico_Error);
1099 			return true;
1100 		}
1101 		if (pRef->getGameStatus().isPastLobby())
1102 		{
1103 			// no runtime join
1104 			if (!pRef->isJoinAllowed())
1105 			{
1106 				if (!::pGUI->ShowMessageModal(
1107 					  LoadResStr("IDS_NET_NOJOIN_NORUNTIME"),
1108 					  strNoJoin.getData(),
1109 					  C4GUI::MessageDialog::btnYes | C4GUI::MessageDialog::btnNo,
1110 					  C4GUI::Ico_Error))
1111 				{
1112 					return true;
1113 				}
1114 			}
1115 			else
1116 			{
1117 				if (!::pGUI->ShowMessageModal(
1118 					  LoadResStr("IDS_NET_NOJOIN_RUNTIMEBROKEN"),
1119 					  strNoJoin.getData(),
1120 					  C4GUI::MessageDialog::btnYes | C4GUI::MessageDialog::btnNo,
1121 					  C4GUI::Ico_Error))
1122 				{
1123 					return true;
1124 				}
1125 			}
1126 		}
1127 	}
1128 	// OK; joining!
1129 	if (pRef->isEditor())
1130 	{
1131 		bool success = false;
1132 		// Editor mode join: Serialize reference to temp file and join on that
1133 		// (could pass through environment, but that's hard to do platform-independent
1134 		// (QProcessEnvironment? But then there's a Qt dependency in the network init code))
1135 		StdStrBuf tmpfn(Config.AtTempPath("ocjoin"), true);
1136 		MakeTempFilename(&tmpfn);
1137 		StdStrBuf join_data = DecompileToBuf<StdCompilerINIWrite>(mkNamingAdapt(*pRef, "Reference"));
1138 		if (join_data.getSize())
1139 		{
1140 			if (join_data.SaveToFile(tmpfn.getData()))
1141 			{
1142 				if (RestartApplication({"--editor", FormatString("--join=%s%s", C4Game::DirectJoinFilePrefix, tmpfn.getData()).getData()})) // hope for no " in temp path
1143 				{
1144 					// Application.Quit() has been called. Will quit after returning from this callback.
1145 					// The temp file will be deleted by the new instance
1146 					success = true;
1147 				}
1148 				else
1149 				{
1150 					EraseFile(tmpfn.getData());
1151 				}
1152 			}
1153 		}
1154 		if (!success)
1155 		{
1156 			C4GUI::TheScreen.ShowErrorMessage(LoadResStr("IDS_ERR_STARTEDITOR"));
1157 		}
1158 		return true;
1159 	}
1160 	else
1161 	{
1162 		// Player mode join
1163 		// Take over reference
1164 		pRefEntry->GrabReference();
1165 		// Set join parameters
1166 		*Game.ScenarioFilename = '\0';
1167 		if (szDirectJoinAddress) SCopy(szDirectJoinAddress, Game.DirectJoinAddress, _MAX_PATH); else *Game.DirectJoinAddress = '\0';
1168 		SCopy("Objects.ocd", Game.DefinitionFilenames);
1169 		Game.NetworkActive = true;
1170 		Game.fObserve = false;
1171 		Game.pJoinReference.reset(pRef);
1172 		// start with this set!
1173 		Application.OpenGame();
1174 		return true;
1175 	}
1176 }
1177 
DoBack()1178 bool C4StartupNetDlg::DoBack()
1179 {
1180 	// abort dialog: Back to main
1181 	C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
1182 	return true;
1183 }
1184 
DoRefresh()1185 void C4StartupNetDlg::DoRefresh()
1186 {
1187 	// check min refresh timer
1188 	time_t tNow = time(nullptr);
1189 	if (tLastRefresh && tNow < tLastRefresh + C4NetMinRefreshInterval)
1190 	{
1191 		// avoid hammering on refresh key
1192 		C4GUI::GUISound("UI::Error");
1193 		return;
1194 	}
1195 	tLastRefresh = tNow;
1196 	// empty list of all old entries
1197 	fUpdatingList = true;
1198 	while (pGameSelList->GetFirst()) delete pGameSelList->GetFirst();
1199 	pMasterserverClient=nullptr;
1200 	// (Re-)Start discovery
1201 	if (!DiscoverClient.StartDiscovery())
1202 	{
1203 		StdCopyStrBuf strNoDiscovery(LoadResStr("IDS_NET_NODISCOVERY"));
1204 		::pGUI->ShowMessageModal(
1205 		  FormatString(LoadResStr("IDS_NET_NODISCOVERY_DESC"), DiscoverClient.GetError()).getData(),
1206 		  strNoDiscovery.getData(),
1207 		  C4GUI::MessageDialog::btnAbort,
1208 		  C4GUI::Ico_Error);
1209 	}
1210 	iGameDiscoverInterval = C4NetGameDiscoveryInterval;
1211 	// restart masterserver query
1212 	UpdateMasterserver();
1213 	// done; update stuff
1214 	fUpdatingList = false;
1215 	UpdateList();
1216 }
1217 
CreateGame()1218 void C4StartupNetDlg::CreateGame()
1219 {
1220 	C4Startup::Get()->SwitchDialog(C4Startup::SDID_ScenSelNetwork);
1221 }
1222 
OnSec1Timer()1223 void C4StartupNetDlg::OnSec1Timer()
1224 {
1225 	// no updates if dialog is inactive (e.g., because a join password dlg is shown!)
1226 	if (!IsActive(true))
1227 		return;
1228 
1229 	// Execute discovery
1230 	if (!iGameDiscoverInterval--)
1231 	{
1232 		DiscoverClient.StartDiscovery();
1233 		iGameDiscoverInterval = C4NetGameDiscoveryInterval;
1234 	}
1235 	DiscoverClient.Execute(0);
1236 
1237 	UpdateList(false);
1238 }
1239 
AddReferenceQuery(const char * szAddress,C4StartupNetListEntry::QueryType eQueryType)1240 void C4StartupNetDlg::AddReferenceQuery(const char *szAddress, C4StartupNetListEntry::QueryType eQueryType)
1241 {
1242 	// Check for an active reference query to the same address
1243 	for (C4GUI::Element *pElem = pGameSelList->GetFirst(); pElem; pElem = pElem->GetNext())
1244 	{
1245 		C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
1246 		// same address
1247 		if (pEntry->IsSameRefQueryAddress(szAddress))
1248 		{
1249 			// nothing to do, xcept maybe select it
1250 			if (eQueryType == C4StartupNetListEntry::NRQT_DirectJoin) pGameSelList->SelectEntry(pEntry, true);
1251 			return;
1252 		}
1253 	}
1254 	// No reference from same host found - create a new entry
1255 	C4StartupNetListEntry *pEntry = new C4StartupNetListEntry(pGameSelList, nullptr, this);
1256 	pEntry->SetRefQuery(szAddress, eQueryType);
1257 	if (eQueryType == C4StartupNetListEntry::NRQT_DirectJoin)
1258 		pGameSelList->SelectEntry(pEntry, true);
1259 	else if (fIsCollapsed)
1260 		pEntry->UpdateCollapsed(true);
1261 }
1262 
OnReferenceEntryAdd(C4StartupNetListEntry * pEntry)1263 void C4StartupNetDlg::OnReferenceEntryAdd(C4StartupNetListEntry *pEntry)
1264 {
1265 	// collapse the new entry if desired
1266 	if (fIsCollapsed && pEntry != pGameSelList->GetSelectedItem())
1267 		pEntry->UpdateCollapsed(true);
1268 }
1269 
CheckVersionUpdate()1270 void C4StartupNetDlg::CheckVersionUpdate()
1271 {
1272 #ifdef WITH_AUTOMATIC_UPDATE
1273 	StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
1274 	StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s&action=version", Config.Network.UpdateServerAddress, strVersion.getData(), C4_OS);
1275 
1276 	if (pUpdateClient.Init() && pUpdateClient.SetServer(strQuery.getData()) && pUpdateClient.QueryUpdateURL())
1277 	{
1278 		pUpdateClient.SetNotify(&Application.InteractiveThread);
1279 		Application.InteractiveThread.AddProc(&pUpdateClient);
1280 	}
1281 	fUpdateCheckPending = true;
1282 #endif
1283 }
1284 
OnChatTitleChange(const StdStrBuf & sNewTitle)1285 void C4StartupNetDlg::OnChatTitleChange(const StdStrBuf &sNewTitle)
1286 {
1287 	// update label
1288 	if (pChatTitleLabel) pChatTitleLabel->SetText(FormatString("%s - %s", LoadResStr("IDS_DLG_CHAT"), sNewTitle.getData()).getData());
1289 }
1290