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