forked from Mirrors/openclonk
1277 lines
44 KiB
C++
1277 lines
44 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2006-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
// Startup screen for non-parameterized engine start: Network game selection dialog
|
|
|
|
#include "C4Include.h"
|
|
#include "gui/C4StartupNetDlg.h"
|
|
|
|
#include "game/C4Application.h"
|
|
#include "gui/C4UpdateDlg.h"
|
|
#include "gui/C4StartupScenSelDlg.h"
|
|
#include "game/C4Game.h"
|
|
#include "gui/C4ChatDlg.h"
|
|
#include "graphics/C4GraphicsResource.h"
|
|
#include "graphics/C4Draw.h"
|
|
#include "network/C4Network2Reference.h"
|
|
|
|
// ----------- C4StartupNetListEntry -----------------------------------------------------------------------
|
|
|
|
C4StartupNetListEntry::C4StartupNetListEntry(C4GUI::ListBox *pForListBox, C4GUI::Element *pInsertBefore, C4StartupNetDlg *pNetDlg)
|
|
: 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)
|
|
{
|
|
// calc height
|
|
int32_t iLineHgt = ::GraphicsResource.TextFont.GetLineHeight(), iHeight = iLineHgt * 2 + 4;
|
|
// add icons - normal icons use small size, only animated netgetref uses full size
|
|
rctIconLarge.Set(0, 0, iHeight, iHeight);
|
|
int32_t iSmallIcon = iHeight * 2 / 3; rctIconSmall.Set((iHeight - iSmallIcon)/2, (iHeight - iSmallIcon)/2, iSmallIcon, iSmallIcon);
|
|
pIcon = new C4GUI::Icon(rctIconSmall, C4GUI::Ico_Host);
|
|
AddElement(pIcon);
|
|
SetBounds(pIcon->GetBounds());
|
|
// add to listbox (will get resized horizontally and moved)
|
|
pForListBox->InsertElement(this, pInsertBefore);
|
|
// add status icons and text labels now that width is known
|
|
CStdFont *pUseFont = &(::GraphicsResource.TextFont);
|
|
int32_t iIconSize = pUseFont->GetLineHeight();
|
|
C4Rect rcIconRect = GetContainedClientRect();
|
|
int32_t iThisWdt = rcIconRect.Wdt;
|
|
rcIconRect.x = iThisWdt - iIconSize * (iInfoIconCount + 1);
|
|
rcIconRect.Wdt = rcIconRect.Hgt = iIconSize;
|
|
for (int32_t iIcon = 0; iIcon<MaxInfoIconCount; ++iIcon)
|
|
{
|
|
AddElement(pInfoIcons[iIcon] = new C4GUI::Icon(rcIconRect, C4GUI::Ico_None));
|
|
rcIconRect.x -= rcIconRect.Wdt;
|
|
}
|
|
C4Rect rcLabelBounds;
|
|
rcLabelBounds.x = iHeight+3;
|
|
rcLabelBounds.Hgt = iLineHgt;
|
|
for (int i=0; i<InfoLabelCount; ++i)
|
|
{
|
|
C4GUI::Label *pLbl;
|
|
rcLabelBounds.y = 1+i*(iLineHgt+2);
|
|
rcLabelBounds.Wdt = iThisWdt - rcLabelBounds.x - 1;
|
|
if (!i) rcLabelBounds.Wdt -= iLineHgt; // leave space for topright extra icon
|
|
AddElement(pLbl = pInfoLbl[i] = new C4GUI::Label("", rcLabelBounds, ALeft, C4GUI_CaptionFontClr));
|
|
// label will have collapsed due to no text: Repair it
|
|
pLbl->SetAutosize(false);
|
|
pLbl->SetBounds(rcLabelBounds);
|
|
}
|
|
// update small state, which will resize this to a small entry
|
|
UpdateSmallState();
|
|
// Set*-function will fill icon and text and calculate actual size
|
|
}
|
|
|
|
C4StartupNetListEntry::~C4StartupNetListEntry()
|
|
{
|
|
ClearRef();
|
|
}
|
|
|
|
void C4StartupNetListEntry::DrawElement(C4TargetFacet &cgo)
|
|
{
|
|
typedef C4GUI::Window ParentClass;
|
|
// background if important and not selected
|
|
if (fIsImportant && !IsSelectedChild(this))
|
|
{
|
|
int32_t x1 = cgo.X+cgo.TargetX+rcBounds.x;
|
|
int32_t y1 = cgo.Y+cgo.TargetY+rcBounds.y;
|
|
pDraw->DrawBoxDw(cgo.Surface, x1,y1, x1+rcBounds.Wdt, y1+rcBounds.Hgt, C4GUI_ImportantBGColor);
|
|
}
|
|
// inherited
|
|
ParentClass::DrawElement(cgo);
|
|
}
|
|
|
|
void C4StartupNetListEntry::ClearRef()
|
|
{
|
|
// del old ref data
|
|
if (pRefClient)
|
|
{
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
Thread.RemoveProc(pRefClient);
|
|
delete pRefClient; pRefClient = nullptr;
|
|
}
|
|
if (pRef) { delete pRef; pRef = nullptr; }
|
|
eQueryType = NRQT_Unknown;
|
|
iTimeout = iRequestTimeout = 0;
|
|
fError = false;
|
|
sError.Clear();
|
|
int32_t i;
|
|
for (i=0; i<InfoLabelCount; ++i) sInfoText[i].Clear();
|
|
InvalidateStatusIcons();
|
|
sRefClientAddress.Clear();
|
|
fIsEnabled = true;
|
|
fIsImportant = false;
|
|
}
|
|
|
|
const char *C4StartupNetListEntry::GetQueryTypeName(QueryType eQueryType)
|
|
{
|
|
switch (eQueryType)
|
|
{
|
|
case NRQT_GameDiscovery: return LoadResStr("IDS_NET_QUERY_LOCALNET");
|
|
case NRQT_Masterserver: return LoadResStr("IDS_NET_QUERY_MASTERSRV");
|
|
case NRQT_DirectJoin: return LoadResStr("IDS_NET_QUERY_DIRECTJOIN");
|
|
default: return "";
|
|
};
|
|
}
|
|
|
|
void C4StartupNetListEntry::SetRefQuery(const char *szAddress, enum QueryType eQueryType)
|
|
{
|
|
// safety: clear previous
|
|
ClearRef();
|
|
// setup layout
|
|
const_cast<C4Facet &>(reinterpret_cast<const C4Facet &>(pIcon->GetFacet()))
|
|
= (const C4Facet &) C4Startup::Get()->Graphics.fctNetGetRef;
|
|
pIcon->SetAnimated(true, 1);
|
|
pIcon->SetBounds(rctIconLarge);
|
|
// init a new ref client to query
|
|
sRefClientAddress.Copy(szAddress);
|
|
this->eQueryType = eQueryType;
|
|
pRefClient = new C4Network2RefClient();
|
|
if (!pRefClient->Init() || !pRefClient->SetServer(szAddress))
|
|
{
|
|
// should not happen
|
|
sInfoText[0].Clear();
|
|
SetError(pRefClient->GetError(), TT_RefReqWait);
|
|
return;
|
|
}
|
|
// set info
|
|
sInfoText[0].Format(LoadResStr("IDS_NET_CLIENTONNET"), GetQueryTypeName(eQueryType), pRefClient->getServerName());
|
|
sInfoText[1].Copy(LoadResStr("IDS_NET_INFOQUERY"));
|
|
UpdateSmallState(); UpdateText();
|
|
pRefClient->SetNotify(&Application.InteractiveThread);
|
|
// masterserver: always on top
|
|
if (eQueryType == NRQT_Masterserver)
|
|
iSortOrder = 100;
|
|
// register proc
|
|
C4InteractiveThread &Thread = Application.InteractiveThread;
|
|
Thread.AddProc(pRefClient);
|
|
// start querying!
|
|
QueryReferences();
|
|
}
|
|
|
|
bool C4StartupNetListEntry::QueryReferences()
|
|
{
|
|
// begin querying
|
|
if (!pRefClient->QueryReferences())
|
|
{
|
|
SetError(pRefClient->GetError(), TT_RefReqWait);
|
|
return false;
|
|
}
|
|
// set up timeout
|
|
iRequestTimeout = time(nullptr) + C4NetRefRequestTimeout;
|
|
return true;
|
|
}
|
|
|
|
bool C4StartupNetListEntry::Execute()
|
|
{
|
|
// update entries
|
|
// if the return value is false, this entry will be deleted
|
|
// timer running?
|
|
if (iTimeout) if (time(nullptr) >= iTimeout)
|
|
{
|
|
// timeout!
|
|
// for internet servers, this means refresh needed - search anew!
|
|
if (pRefClient && eQueryType == NRQT_Masterserver)
|
|
{
|
|
fError = false;
|
|
sError.Clear();
|
|
pIcon->SetFacet(C4Startup::Get()->Graphics.fctNetGetRef);
|
|
pIcon->SetAnimated(true, 1);
|
|
pIcon->SetBounds(rctIconLarge);
|
|
sInfoText[1].Copy(LoadResStr("IDS_NET_INFOQUERY"));
|
|
iTimeout = 0;
|
|
QueryReferences();
|
|
// always keep item even if query failed
|
|
return true;
|
|
}
|
|
// any other item is just removed - return value marks this
|
|
return false;
|
|
}
|
|
// failed without a timer. Nothing to be done about it.
|
|
if (fError) return true;
|
|
// updates need to be done for references being retrieved only
|
|
if (!pRefClient) return true;
|
|
// check if it has arrived
|
|
if (pRefClient->isBusy())
|
|
// still requesting - but do not wait forever
|
|
if (time(nullptr) >= iRequestTimeout)
|
|
{
|
|
SetError(LoadResStr("IDS_NET_ERR_REFREQTIMEOUT"), TT_RefReqWait);
|
|
pRefClient->Cancel("Timeout");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool C4StartupNetListEntry::OnReference()
|
|
{
|
|
// wrong type / still busy?
|
|
if (!pRefClient || pRefClient->isBusy())
|
|
return true;
|
|
// successful?
|
|
if (!pRefClient->isSuccess())
|
|
{
|
|
// couldn't get references
|
|
SetError(pRefClient->GetError(), TT_RefReqWait);
|
|
return true;
|
|
}
|
|
// Ref getting done!
|
|
pIcon->SetAnimated(false, 1);
|
|
// Get reference information from client
|
|
C4Network2Reference **ppNewRefs=nullptr; int32_t iNewRefCount;
|
|
if (!pRefClient->GetReferences(ppNewRefs, iNewRefCount))
|
|
{
|
|
// References could be retrieved but not read
|
|
SetError(LoadResStr("IDS_NET_ERR_REFINVALID"), TT_RefReqWait);
|
|
delete [] ppNewRefs;
|
|
return true;
|
|
}
|
|
if (!iNewRefCount)
|
|
{
|
|
// References retrieved but no game open: Inform user
|
|
sInfoText[1].Copy(LoadResStr("IDS_NET_INFONOGAME"));
|
|
UpdateText();
|
|
}
|
|
else
|
|
{
|
|
// Grab references, count players
|
|
C4StartupNetListEntry *pNewRefEntry = this; int iPlayerCount = 0;
|
|
for (int i = 0; i < iNewRefCount; i++)
|
|
{
|
|
pNewRefEntry = AddReference(ppNewRefs[i], pNewRefEntry->GetNextLower(ppNewRefs[i]->getSortOrder()));
|
|
iPlayerCount += ppNewRefs[i]->Parameters.PlayerInfos.GetActivePlayerCount(false);
|
|
}
|
|
// Count player
|
|
sInfoText[1].Format(LoadResStr("IDS_NET_INFOGAMES"), (int) iNewRefCount, iPlayerCount);
|
|
UpdateText();
|
|
}
|
|
delete [] ppNewRefs;
|
|
// special masterserver handling
|
|
if (eQueryType == NRQT_Masterserver)
|
|
{
|
|
// masterserver: schedule next query
|
|
sInfoText[1].Format(LoadResStr("IDS_NET_INFOGAMES"), (int) iNewRefCount);
|
|
SetTimeout(TT_Masterserver);
|
|
return true;
|
|
}
|
|
// non-masterserver
|
|
if (iNewRefCount)
|
|
{
|
|
// this item has been "converted" into the references - remove without further feedback
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// no ref found on custom adress: Schedule re-check
|
|
SetTimeout(TT_RefReqWait);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
C4GUI::Element* C4StartupNetListEntry::GetNextLower(int32_t sortOrder)
|
|
{
|
|
// search list for the next element of a lower sort order
|
|
for (C4GUI::Element *pElem = pList->GetFirst(); pElem; pElem = pElem->GetNext())
|
|
{
|
|
C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
|
|
if (pEntry->iSortOrder < sortOrder)
|
|
return pElem;
|
|
}
|
|
// none found: insert at start
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void C4StartupNetListEntry::UpdateCollapsed(bool fToCollapseValue)
|
|
{
|
|
// if collapsed state changed, update the text
|
|
if (fIsCollapsed == fToCollapseValue) return;
|
|
fIsCollapsed = fToCollapseValue;
|
|
UpdateSmallState();
|
|
}
|
|
|
|
void C4StartupNetListEntry::UpdateSmallState()
|
|
{
|
|
// small view: Always collapsed if there is no extended text
|
|
bool fNewIsSmall = !sInfoText[2].getLength() || fIsCollapsed;
|
|
if (fNewIsSmall == fIsSmall) return;
|
|
fIsSmall = fNewIsSmall;
|
|
for (int i=2; i<InfoLabelCount; ++i) pInfoLbl[i]->SetVisibility(!fIsSmall);
|
|
UpdateEntrySize();
|
|
}
|
|
|
|
void C4StartupNetListEntry::UpdateEntrySize()
|
|
{
|
|
if(fVisible) {
|
|
// restack all labels by their size
|
|
int32_t iLblCnt = (fIsSmall ? 2 : InfoLabelCount), iY=1;
|
|
for (int i=0; i<iLblCnt; ++i)
|
|
{
|
|
C4Rect rcBounds = pInfoLbl[i]->GetBounds();
|
|
rcBounds.y = iY;
|
|
iY += rcBounds.Hgt + 2;
|
|
pInfoLbl[i]->SetBounds(rcBounds);
|
|
}
|
|
// resize this control
|
|
GetBounds().Hgt = iY-1;
|
|
} else GetBounds().Hgt = 0;
|
|
UpdateSize();
|
|
}
|
|
|
|
void C4StartupNetListEntry::UpdateText()
|
|
{
|
|
bool fRestackElements=false;
|
|
CStdFont *pUseFont = &(::GraphicsResource.TextFont);
|
|
// adjust icons
|
|
int32_t sx=iInfoIconCount*pUseFont->GetLineHeight();
|
|
int32_t i;
|
|
for (i=iInfoIconCount; i<MaxInfoIconCount; ++i)
|
|
{
|
|
pInfoIcons[i]->SetIcon(C4GUI::Ico_None);
|
|
pInfoIcons[i]->SetToolTip(nullptr);
|
|
}
|
|
// text to labels
|
|
for (i=0; i<InfoLabelCount; ++i)
|
|
{
|
|
int iAvailableWdt = GetClientRect().Wdt - pInfoLbl[i]->GetBounds().x - 1;
|
|
if (!i) iAvailableWdt -= sx;
|
|
StdStrBuf BrokenText;
|
|
pUseFont->BreakMessage(sInfoText[i].getData(), iAvailableWdt, &BrokenText, true);
|
|
int32_t iHgt, iWdt;
|
|
if (pUseFont->GetTextExtent(BrokenText.getData(), iWdt, iHgt, true))
|
|
{
|
|
if ((pInfoLbl[i]->GetBounds().Hgt != iHgt) || (pInfoLbl[i]->GetBounds().Wdt != iAvailableWdt))
|
|
{
|
|
C4Rect rcBounds = pInfoLbl[i]->GetBounds();
|
|
rcBounds.Wdt = iAvailableWdt;
|
|
rcBounds.Hgt = iHgt;
|
|
pInfoLbl[i]->SetBounds(rcBounds);
|
|
fRestackElements = true;
|
|
}
|
|
}
|
|
pInfoLbl[i]->SetText(BrokenText.getData());
|
|
pInfoLbl[i]->SetColor(fIsEnabled ? C4GUI_MessageFontClr : C4GUI_InactMessageFontClr);
|
|
}
|
|
if (fRestackElements) UpdateEntrySize();
|
|
}
|
|
|
|
void C4StartupNetListEntry::SetVisibility(bool fToValue) {
|
|
bool fChange = fToValue != fVisible;
|
|
C4GUI::Window::SetVisibility(fToValue);
|
|
if(fChange) UpdateEntrySize();
|
|
}
|
|
|
|
void C4StartupNetListEntry::AddStatusIcon(C4GUI::Icons eIcon, const char *szToolTip)
|
|
{
|
|
// safety
|
|
if (iInfoIconCount==MaxInfoIconCount) return;
|
|
// set icon to the left of the existing icons to the desired data
|
|
pInfoIcons[iInfoIconCount]->SetIcon(eIcon);
|
|
pInfoIcons[iInfoIconCount]->SetToolTip(szToolTip);
|
|
++iInfoIconCount;
|
|
}
|
|
|
|
void C4StartupNetListEntry::SetReference(C4Network2Reference *pRef)
|
|
{
|
|
// safety: clear previous
|
|
ClearRef();
|
|
// set info
|
|
this->pRef = pRef;
|
|
int32_t iIcon = pRef->getIcon();
|
|
if (!Inside<int32_t>(iIcon, 0, C4StartupScenSel_IconCount-1)) iIcon = C4StartupScenSel_DefaultIcon_Scenario;
|
|
pIcon->SetFacet(C4Startup::Get()->Graphics.fctScenSelIcons.GetPhase(iIcon));
|
|
pIcon->SetAnimated(false, 0);
|
|
pIcon->SetBounds(rctIconSmall);
|
|
int32_t iPlrCnt = pRef->isEditor() ? pRef->Parameters.PlayerInfos.GetActivePlayerCount(false) : pRef->Parameters.Clients.getClientCnt();
|
|
C4Client *pHost = pRef->Parameters.Clients.getHost();
|
|
sInfoText[0].Format(LoadResStr("IDS_NET_REFONCLIENT"), pRef->getTitle(), pHost ? pHost->getName() : "unknown");
|
|
if (pRef->isEditor())
|
|
{
|
|
sInfoText[1].Format(LoadResStr("IDS_NET_INFOEDITOR"),
|
|
(int)iPlrCnt,
|
|
StdStrBuf(pRef->getGameStatus().getDescription(), true).getData());
|
|
}
|
|
else
|
|
{
|
|
sInfoText[1].Format(LoadResStr("IDS_NET_INFOPLRSGOALDESC"),
|
|
(int)iPlrCnt,
|
|
(int)pRef->Parameters.MaxPlayers,
|
|
pRef->getGameGoalString().getData(),
|
|
StdStrBuf(pRef->getGameStatus().getDescription(), true).getData());
|
|
}
|
|
if (pRef->getTime() > 0)
|
|
{
|
|
StdStrBuf strDuration; strDuration.Format("%02d:%02d:%02d", pRef->getTime()/3600, (pRef->getTime() % 3600) / 60, pRef->getTime() % 60);
|
|
sInfoText[1].Append(" - "); sInfoText[1].Append(strDuration);
|
|
}
|
|
sInfoText[2].Format(LoadResStr("IDS_DESC_VERSION"), pRef->getGameVersion().GetString().getData());
|
|
sInfoText[3].Format("%s: %s", LoadResStr("IDS_CTL_COMMENT"), pRef->getComment());
|
|
StdStrBuf sAddress;
|
|
for (int i=0; i<pRef->getAddrCnt(); ++i)
|
|
{
|
|
if (i) sAddress.Append(", ");
|
|
sAddress.Append(pRef->getAddr(i).toString());
|
|
}
|
|
// editor reference
|
|
if (pRef->isEditor())
|
|
AddStatusIcon(C4GUI::Ico_Editor, LoadResStr("IDS_CNS_CONSOLE"));
|
|
// password
|
|
if (pRef->isPasswordNeeded())
|
|
AddStatusIcon(C4GUI::Ico_Ex_LockedFrontal, LoadResStr("IDS_NET_INFOPASSWORD"));
|
|
// league
|
|
if (pRef->Parameters.isLeague())
|
|
AddStatusIcon(C4GUI::Ico_Ex_League, pRef->Parameters.getLeague());
|
|
// lobby active
|
|
if (pRef->getGameStatus().isLobbyActive())
|
|
AddStatusIcon(C4GUI::Ico_Lobby, LoadResStr("IDS_DESC_EXPECTING"));
|
|
// game running
|
|
if (pRef->getGameStatus().isPastLobby())
|
|
AddStatusIcon(C4GUI::Ico_GameRunning, LoadResStr("IDS_NET_INFOINPROGR"));
|
|
// runtime join
|
|
if (pRef->isJoinAllowed() && pRef->getGameStatus().isPastLobby()) // A little workaround to determine RuntimeJoin...
|
|
AddStatusIcon(C4GUI::Ico_RuntimeJoin, LoadResStr("IDS_NET_RUNTIMEJOINFREE"));
|
|
// official server
|
|
if (pRef->isOfficialServer() && !Config.Network.UseAlternateServer) // Offical server icon is only displayed if references are obtained from official league server
|
|
{
|
|
fIsImportant = true;
|
|
AddStatusIcon(C4GUI::Ico_OfficialServer, LoadResStr("IDS_NET_OFFICIALSERVER"));
|
|
}
|
|
// list participating player/client names
|
|
if (pRef->isEditor())
|
|
{
|
|
sInfoText[4].Format("%s%s", LoadResStr("IDS_DESC_CLIENTS"), iPlrCnt ? pRef->Parameters.Clients.GetAllClientNames().getData() : LoadResStr("IDS_CTL_NONE"));
|
|
}
|
|
else
|
|
{
|
|
sInfoText[4].Format("%s: %s", LoadResStr("IDS_CTL_PLAYER"), iPlrCnt ? pRef->Parameters.PlayerInfos.GetActivePlayerNames(false).getData() : LoadResStr("IDS_CTL_NONE"));
|
|
}
|
|
// disabled if join is not possible for some reason
|
|
C4GameVersion verThis;
|
|
if (!pRef->isJoinAllowed() || !(pRef->getGameVersion() == verThis))
|
|
{
|
|
fIsEnabled = false;
|
|
}
|
|
// store sort order
|
|
iSortOrder = pRef->getSortOrder();
|
|
// all references expire after a while
|
|
SetTimeout(TT_Reference);
|
|
UpdateSmallState(); UpdateText();
|
|
}
|
|
|
|
void C4StartupNetListEntry::SetError(const char *szErrorText, TimeoutType eTimeout)
|
|
{
|
|
// set error message
|
|
fError = true;
|
|
sInfoText[1].Copy(szErrorText);
|
|
for (int i=2; i<InfoLabelCount; ++i) sInfoText[i].Clear();
|
|
InvalidateStatusIcons();
|
|
UpdateSmallState(); UpdateText();
|
|
pIcon->SetIcon(C4GUI::Ico_Close);
|
|
pIcon->SetAnimated(false, 0);
|
|
pIcon->SetBounds(rctIconSmall);
|
|
SetTimeout(eTimeout);
|
|
}
|
|
|
|
void C4StartupNetListEntry::SetTimeout(TimeoutType eTimeout)
|
|
{
|
|
int iTime = 0;
|
|
switch (eTimeout)
|
|
{
|
|
case TT_RefReqWait: iTime = (eQueryType == NRQT_Masterserver) ? C4NetMasterServerQueryInterval : C4NetErrorRefTimeout; break;
|
|
case TT_Reference: iTime = C4NetReferenceTimeout; break;
|
|
case TT_Masterserver: iTime = C4NetMasterServerQueryInterval; break;
|
|
};
|
|
if (!iTime) return;
|
|
iTimeout = time(nullptr) + iTime;
|
|
}
|
|
|
|
C4StartupNetListEntry *C4StartupNetListEntry::AddReference(C4Network2Reference *pAddRef, C4GUI::Element *pInsertBefore)
|
|
{
|
|
// check list whether the same reference has been added already
|
|
for (C4GUI::Element *pElem = pList->GetFirst(); pElem; pElem = pElem->GetNext())
|
|
{
|
|
C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
|
|
// match to existing reference entry:
|
|
// * same host (checking for same name and nick)
|
|
// * at least one match in address and port
|
|
// * the incoming reference is newer than (or same as) the current one
|
|
if ( pEntry->IsSameHost(pAddRef)
|
|
&& pEntry->IsSameAddress(pAddRef)
|
|
&& (pEntry->GetReference()->getStartTime() <= pAddRef->getStartTime()) )
|
|
{
|
|
// update existing entry
|
|
pEntry->SetReference(pAddRef);
|
|
return pEntry;
|
|
}
|
|
}
|
|
// no update - just add
|
|
C4StartupNetListEntry *pNewRefEntry = new C4StartupNetListEntry(pList, pInsertBefore, pNetDlg);
|
|
pNewRefEntry->SetReference(pAddRef);
|
|
pNetDlg->OnReferenceEntryAdd(pNewRefEntry);
|
|
return pNewRefEntry;
|
|
}
|
|
|
|
bool C4StartupNetListEntry::IsSameHost(const C4Network2Reference *pRef2)
|
|
{
|
|
// not if ref has not been retrieved yet
|
|
if (!pRef) return false;
|
|
C4Client *pHost1 = pRef->Parameters.Clients.getHost();
|
|
C4Client *pHost2 = pRef2->Parameters.Clients.getHost();
|
|
if (!pHost1 || !pHost2) return false;
|
|
// check
|
|
return SEqual(pHost1->getCUID(), pHost2->getCUID()) && SEqual(pHost1->getName(), pHost2->getName());
|
|
}
|
|
|
|
bool C4StartupNetListEntry::IsSameAddress(const C4Network2Reference *pRef2)
|
|
{
|
|
// not if ref has not been retrieved yet
|
|
if (!pRef) return false;
|
|
// check all of our addresses
|
|
for (int i = 0; i < pRef->getAddrCnt(); i++)
|
|
// against all of the other ref's addresses
|
|
for (int j = 0; j < pRef2->getAddrCnt(); j++)
|
|
// at least one match!
|
|
if (pRef->getAddr(i) == pRef2->getAddr(j))
|
|
return true;
|
|
// no match
|
|
return false;
|
|
}
|
|
|
|
bool C4StartupNetListEntry::IsSameRefQueryAddress(const char *szJoinaddress)
|
|
{
|
|
// only unretrieved references
|
|
if (!pRefClient) return false;
|
|
// if request failed, create a duplicate anyway in case the game is opened now
|
|
// except masterservers, which would re-search some time later anyway
|
|
if (fError && eQueryType != NRQT_Masterserver) return false;
|
|
// check equality of address
|
|
// do it the simple way for now
|
|
return SEqualNoCase(sRefClientAddress.getData(), szJoinaddress);
|
|
}
|
|
|
|
bool C4StartupNetListEntry::KeywordMatch(const char *szMatch)
|
|
{
|
|
// only finished references
|
|
if (!pRef) return false;
|
|
if(SSearchNoCase(pRef->getTitle(),szMatch)) return true;
|
|
C4Client *pHost = pRef->Parameters.Clients.getHost();
|
|
if(pHost && SSearchNoCase(pHost->getName(),szMatch)) return true;
|
|
if(SSearchNoCase(pRef->getComment(),szMatch)) return true;
|
|
return false;
|
|
}
|
|
|
|
const char *C4StartupNetListEntry::GetJoinAddress()
|
|
{
|
|
// only unresolved references
|
|
if (!pRefClient) return nullptr;
|
|
// not masterservers (cannot join directly on clonk.de)
|
|
if (eQueryType == NRQT_Masterserver) return nullptr;
|
|
// return join address
|
|
return pRefClient->getServerName();
|
|
}
|
|
|
|
C4Network2Reference *C4StartupNetListEntry::GrabReference()
|
|
{
|
|
C4Network2Reference *pOldRef = pRef;
|
|
pRef = nullptr;
|
|
return pOldRef;
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----------- C4StartupNetDlg ---------------------------------------------------------------------------------
|
|
|
|
C4StartupNetDlg::C4StartupNetDlg() : C4StartupDlg(LoadResStr("IDS_DLG_NETSTART")), pChatTitleLabel(nullptr), pMasterserverClient(nullptr), fIsCollapsed(false), fUpdatingList(false), iGameDiscoverInterval(0), tLastRefresh(0), fUpdateCheckPending(false)
|
|
{
|
|
// ctor
|
|
// key bindings
|
|
C4CustomKey::CodeList keys;
|
|
keys.push_back(C4KeyCodeEx(K_BACK)); keys.push_back(C4KeyCodeEx(K_LEFT));
|
|
pKeyBack = new C4KeyBinding(keys, "StartupNetBack", KEYSCOPE_Gui,
|
|
new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyBack), C4CustomKey::PRIO_Dlg);
|
|
pKeyRefresh = new C4KeyBinding(C4KeyCodeEx(K_F5), "StartupNetReload", KEYSCOPE_Gui,
|
|
new C4GUI::DlgKeyCB<C4StartupNetDlg>(*this, &C4StartupNetDlg::KeyRefresh), C4CustomKey::PRIO_CtrlOverride);
|
|
|
|
// screen calculations
|
|
UpdateSize();
|
|
int32_t iIconSize = C4GUI_IconExWdt;
|
|
int32_t iButtonWidth,iCaptionFontHgt, iSideSize = std::max<int32_t>(GetBounds().Wdt/6, iIconSize);
|
|
int32_t iButtonHeight = C4GUI_ButtonHgt, iButtonIndent = GetBounds().Wdt/40;
|
|
::GraphicsResource.CaptionFont.GetTextExtent("<< BACK", iButtonWidth, iCaptionFontHgt, true);
|
|
iButtonWidth *= 3;
|
|
C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
|
|
C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(caMain.GetHeight()/7),0,0);
|
|
int32_t iButtonAreaWdt = caButtonArea.GetWidth()*7/8;
|
|
iButtonWidth = std::min<int32_t>(iButtonWidth, (iButtonAreaWdt - 8 * iButtonIndent)/4);
|
|
iButtonIndent = (iButtonAreaWdt - 4 * iButtonWidth) / 8;
|
|
C4GUI::ComponentAligner caButtons(caButtonArea.GetCentered(iButtonAreaWdt, iButtonHeight),iButtonIndent,0);
|
|
C4GUI::ComponentAligner caLeftBtnArea(caMain.GetFromLeft(iSideSize), std::min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);
|
|
C4GUI::ComponentAligner caConfigArea(caMain.GetFromRight(iSideSize), std::min<int32_t>(caMain.GetWidth()/20, (iSideSize-C4GUI_IconExWdt)/2), caMain.GetHeight()/40);
|
|
|
|
// left button area: Switch between chat and game list
|
|
if (C4ChatDlg::IsChatEnabled())
|
|
{
|
|
btnGameList = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_GameList, caLeftBtnArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnGameList);
|
|
btnGameList->SetToolTip(LoadResStr("IDS_DESC_SHOWSAVAILABLENETWORKGAME"));
|
|
btnGameList->SetText(LoadResStr("IDS_BTN_GAMES"));
|
|
AddElement(btnGameList);
|
|
btnChat = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Chat, caLeftBtnArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnChat);
|
|
btnChat->SetToolTip(LoadResStr("IDS_DESC_CONNECTSTOANIRCCHATSERVER"));
|
|
btnChat->SetText(LoadResStr("IDS_BTN_CHAT"));
|
|
AddElement(btnChat);
|
|
}
|
|
else btnChat = nullptr;
|
|
|
|
// main area: Tabular to switch between game list and chat
|
|
pMainTabular = new C4GUI::Tabular(caMain.GetAll(), C4GUI::Tabular::tbNone);
|
|
pMainTabular->SetDrawDecoration(false);
|
|
pMainTabular->SetSheetMargin(0);
|
|
AddElement(pMainTabular);
|
|
|
|
// main area: game selection sheet
|
|
C4GUI::Tabular::Sheet *pSheetGameList = pMainTabular->AddSheet(nullptr);
|
|
C4GUI::ComponentAligner caGameList(pSheetGameList->GetContainedClientRect(), 0,0, false);
|
|
C4GUI::WoodenLabel *pGameListLbl; int32_t iCaptHgt = C4GUI::WoodenLabel::GetDefaultHeight(&::GraphicsResource.TextFont);
|
|
pGameListLbl = new C4GUI::WoodenLabel(LoadResStr("IDS_NET_GAMELIST"), caGameList.GetFromTop(iCaptHgt), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont, ALeft);
|
|
// search field
|
|
C4GUI::WoodenLabel *pSearchLbl;
|
|
const char *szSearchLblText = LoadResStr("IDS_NET_MSSEARCH");
|
|
int32_t iSearchWdt=100, iSearchHgt;
|
|
::GraphicsResource.TextFont.GetTextExtent(szSearchLblText, iSearchWdt, iSearchHgt, true);
|
|
C4GUI::ComponentAligner caSearch(caGameList.GetFromTop(iSearchHgt), 0,0);
|
|
pSearchLbl = new C4GUI::WoodenLabel(szSearchLblText, caSearch.GetFromLeft(iSearchWdt+10), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont);
|
|
const char *szSearchTip = LoadResStr("IDS_NET_MSSEARCH_DESC");
|
|
pSearchLbl->SetToolTip(szSearchTip);
|
|
pSheetGameList->AddElement(pSearchLbl);
|
|
pSearchFieldEdt = new C4GUI::CallbackEdit<C4StartupNetDlg>(caSearch.GetAll(), this, &C4StartupNetDlg::OnSearchFieldEnter);
|
|
pSearchFieldEdt->SetToolTip(szSearchTip);
|
|
pSheetGameList->AddElement(pSearchFieldEdt);
|
|
pSheetGameList->AddElement(pGameListLbl);
|
|
pGameSelList = new C4GUI::ListBox(caGameList.GetFromTop(caGameList.GetHeight() - iCaptHgt));
|
|
pGameSelList->SetDecoration(true, nullptr, true, true);
|
|
pGameSelList->UpdateElementPositions();
|
|
pGameSelList->SetSelectionDblClickFn(new C4GUI::CallbackHandler<C4StartupNetDlg>(this, &C4StartupNetDlg::OnSelDblClick));
|
|
pGameSelList->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<C4StartupNetDlg>(this, &C4StartupNetDlg::OnSelChange));
|
|
pSheetGameList->AddElement(pGameSelList);
|
|
C4GUI::ComponentAligner caIP(caGameList.GetAll(), 0,0);
|
|
C4GUI::WoodenLabel *pIPLbl;
|
|
const char *szIPLblText = LoadResStr("IDS_NET_IP");
|
|
int32_t iIPWdt=100, Q;
|
|
::GraphicsResource.TextFont.GetTextExtent(szIPLblText, iIPWdt, Q, true);
|
|
pIPLbl = new C4GUI::WoodenLabel(szIPLblText, caIP.GetFromLeft(iIPWdt+10), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont);
|
|
const char *szIPTip = LoadResStr("IDS_NET_IP_DESC");
|
|
pIPLbl->SetToolTip(szIPTip);
|
|
pSheetGameList->AddElement(pIPLbl);
|
|
pJoinAddressEdt = new C4GUI::CallbackEdit<C4StartupNetDlg>(caIP.GetAll(), this, &C4StartupNetDlg::OnJoinAddressEnter);
|
|
pJoinAddressEdt->SetToolTip(szIPTip);
|
|
pSheetGameList->AddElement(pJoinAddressEdt);
|
|
|
|
// main area: chat sheet
|
|
if (C4ChatDlg::IsChatEnabled())
|
|
{
|
|
C4GUI::Tabular::Sheet *pSheetChat = pMainTabular->AddSheet(nullptr);
|
|
C4GUI::ComponentAligner caChat(pSheetChat->GetContainedClientRect(), 0,0, false);
|
|
pSheetChat->AddElement(pChatTitleLabel = new C4GUI::WoodenLabel("", caChat.GetFromTop(iCaptHgt), C4GUI_Caption2FontClr, &::GraphicsResource.TextFont, ALeft, false));
|
|
C4GUI::GroupBox *pChatGroup = new C4GUI::GroupBox(caChat.GetAll());
|
|
pChatGroup->SetColors(0u, C4GUI_CaptionFontClr, C4GUI_StandardBGColor);
|
|
pChatGroup->SetMargin(2);
|
|
pSheetChat->AddElement(pChatGroup);
|
|
pChatCtrl = new C4ChatControl(&Application.IRCClient);
|
|
pChatCtrl->SetBounds(pChatGroup->GetContainedClientRect());
|
|
pChatCtrl->SetTitleChangeCB(new C4GUI::InputCallback<C4StartupNetDlg>(this, &C4StartupNetDlg::OnChatTitleChange));
|
|
StdStrBuf sCurrTitle; sCurrTitle.Ref(pChatCtrl->GetTitle()); OnChatTitleChange(sCurrTitle);
|
|
pChatGroup->AddElement(pChatCtrl);
|
|
}
|
|
|
|
// config area
|
|
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);
|
|
btnInternet->SetToolTip(LoadResStr("IDS_DLGTIP_SEARCHINTERNETGAME"));
|
|
btnInternet->SetText(LoadResStr("IDS_CTL_INETSERVER"));
|
|
AddElement(btnInternet);
|
|
btnRecord = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(Game.Record ? C4GUI::Ico_Ex_RecordOn : C4GUI::Ico_Ex_RecordOff, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnRecord);
|
|
btnRecord->SetToolTip(LoadResStr("IDS_DLGTIP_RECORD"));
|
|
btnRecord->SetText(LoadResStr("IDS_CTL_RECORD"));
|
|
AddElement(btnRecord);
|
|
#ifdef WITH_AUTOMATIC_UPDATE
|
|
btnUpdate = new C4GUI::CallbackButton<C4StartupNetDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Update, caConfigArea.GetFromTop(iIconSize, iIconSize), '\0', &C4StartupNetDlg::OnBtnUpdate);
|
|
btnUpdate->SetVisibility(false); // update only available if masterserver notifies it
|
|
btnUpdate->SetToolTip(LoadResStr("IDS_DLGTIP_UPDATE"));
|
|
btnUpdate->SetText(LoadResStr("IDS_CTL_UPDATE"));
|
|
AddElement(btnUpdate);
|
|
#endif
|
|
|
|
// button area
|
|
C4GUI::CallbackButton<C4StartupNetDlg> *btn;
|
|
AddElement(btn = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_BTN_BACK"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnBackBtn));
|
|
btn->SetToolTip(LoadResStr("IDS_DLGTIP_BACKMAIN"));
|
|
AddElement(btnRefresh = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_BTN_RELOAD"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnRefreshBtn));
|
|
btnRefresh->SetToolTip(LoadResStr("IDS_NET_RELOAD_DESC"));
|
|
AddElement(btnJoin = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_NET_JOINGAME_BTN"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnJoinGameBtn));
|
|
btnJoin->SetToolTip(LoadResStr("IDS_NET_JOINGAME_DESC"));
|
|
AddElement(btn = new C4GUI::CallbackButton<C4StartupNetDlg>(LoadResStr("IDS_NET_NEWGAME"), caButtons.GetFromLeft(iButtonWidth), &C4StartupNetDlg::OnCreateGameBtn));
|
|
btn->SetToolTip(LoadResStr("IDS_NET_NEWGAME_DESC"));
|
|
|
|
// initial dlg mode
|
|
UpdateDlgMode();
|
|
|
|
// initial focus
|
|
SetFocus(GetDlgModeFocusControl(), false);
|
|
|
|
// initialize discovery
|
|
DiscoverClient.Init(Config.Network.PortDiscovery);
|
|
DiscoverClient.StartDiscovery();
|
|
iGameDiscoverInterval = C4NetGameDiscoveryInterval;
|
|
|
|
// register timer
|
|
Application.Add(this);
|
|
|
|
// register as receiver of reference notifies
|
|
Application.InteractiveThread.SetCallback(Ev_HTTP_Response, this);
|
|
|
|
}
|
|
|
|
C4StartupNetDlg::~C4StartupNetDlg()
|
|
{
|
|
// disable notifies
|
|
Application.InteractiveThread.ClearCallback(Ev_HTTP_Response, this);
|
|
Application.InteractiveThread.RemoveProc(&pUpdateClient);
|
|
|
|
DiscoverClient.Close();
|
|
Application.Remove(this);
|
|
if (pMasterserverClient) delete pMasterserverClient;
|
|
// dtor
|
|
delete pKeyBack;
|
|
delete pKeyRefresh;
|
|
}
|
|
|
|
void C4StartupNetDlg::DrawElement(C4TargetFacet &cgo)
|
|
{
|
|
// draw background
|
|
typedef C4GUI::FullscreenDialog Base;
|
|
Base::DrawElement(cgo);
|
|
}
|
|
|
|
void C4StartupNetDlg::OnShown()
|
|
{
|
|
// callback when shown: Start searching for games
|
|
C4StartupDlg::OnShown();
|
|
CheckVersionUpdate();
|
|
UpdateList();
|
|
UpdateUpdateButton(); // in case update check was finished before callback registration
|
|
UpdateMasterserver();
|
|
OnSec1Timer();
|
|
tLastRefresh = time(0);
|
|
// also update chat
|
|
if (pChatCtrl) pChatCtrl->OnShown();
|
|
}
|
|
|
|
void C4StartupNetDlg::OnClosed(bool fOK)
|
|
{
|
|
// dlg abort: return to main screen
|
|
if (pMasterserverClient) { delete pMasterserverClient; pMasterserverClient=nullptr; }
|
|
if (!fOK) DoBack();
|
|
}
|
|
|
|
C4GUI::Control *C4StartupNetDlg::GetDefaultControl()
|
|
{
|
|
// default control depends on whether dlg is in chat or game list mode
|
|
if (GetDlgMode() == SNDM_Chat && pChatCtrl)
|
|
// chat mode: Chat input edit
|
|
return pChatCtrl->GetDefaultControl();
|
|
else
|
|
// game list mode: No default control, because it would move focus away from IP input edit
|
|
return nullptr;
|
|
}
|
|
|
|
C4GUI::Control *C4StartupNetDlg::GetDlgModeFocusControl()
|
|
{
|
|
// default control depends on whether dlg is in chat or game list mode
|
|
if (GetDlgMode() == SNDM_Chat && pChatCtrl)
|
|
// chat mode: Chat input edit
|
|
return pChatCtrl->GetDefaultControl();
|
|
else
|
|
// game list mode: Game list box
|
|
return pGameSelList;
|
|
}
|
|
|
|
void C4StartupNetDlg::OnBtnGameList(C4GUI::Control *btn)
|
|
{
|
|
// switch to game list dialog
|
|
pMainTabular->SelectSheet(SNDM_GameList, true);
|
|
UpdateDlgMode();
|
|
}
|
|
|
|
void C4StartupNetDlg::OnBtnChat(C4GUI::Control *btn)
|
|
{
|
|
// toggle chat / game list
|
|
if (pChatCtrl)
|
|
{
|
|
if (pMainTabular->GetActiveSheetIndex() == SNDM_GameList)
|
|
{
|
|
pMainTabular->SelectSheet(SNDM_Chat, true);
|
|
pChatCtrl->OnShown();
|
|
UpdateDlgMode();
|
|
}
|
|
else
|
|
{
|
|
pMainTabular->SelectSheet(SNDM_GameList, true);
|
|
UpdateDlgMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4StartupNetDlg::OnBtnInternet(C4GUI::Control *btn)
|
|
{
|
|
// toggle masterserver game search
|
|
Config.Network.MasterServerSignUp = !Config.Network.MasterServerSignUp;
|
|
UpdateMasterserver();
|
|
}
|
|
|
|
void C4StartupNetDlg::OnBtnRecord(C4GUI::Control *btn)
|
|
{
|
|
// toggle league signup flag
|
|
bool fCheck = !Game.Record;
|
|
Game.Record = fCheck;
|
|
Config.General.DefRec = fCheck;
|
|
btnRecord->SetIcon(fCheck ? C4GUI::Ico_Ex_RecordOn : C4GUI::Ico_Ex_RecordOff);
|
|
}
|
|
|
|
#ifdef WITH_AUTOMATIC_UPDATE
|
|
void C4StartupNetDlg::OnBtnUpdate(C4GUI::Control *btn)
|
|
{
|
|
// do update
|
|
if (!C4UpdateDlg::DoUpdate(UpdateURL.getData(), GetScreen()))
|
|
{
|
|
GetScreen()->ShowMessage(LoadResStr("IDS_MSG_UPDATEFAILED"), LoadResStr("IDS_TYPE_UPDATE"), C4GUI::Ico_Ex_Update);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void C4StartupNetDlg::UpdateMasterserver()
|
|
{
|
|
// update button icon to current state
|
|
btnInternet->SetIcon(Config.Network.MasterServerSignUp ? C4GUI::Ico_Ex_InternetOn : C4GUI::Ico_Ex_InternetOff);
|
|
// creates masterserver object if masterserver is enabled; destroy otherwise
|
|
if (!Config.Network.MasterServerSignUp == !pMasterserverClient) return;
|
|
if (!Config.Network.MasterServerSignUp)
|
|
{
|
|
delete pMasterserverClient;
|
|
pMasterserverClient = nullptr;
|
|
}
|
|
else
|
|
{
|
|
pMasterserverClient = new C4StartupNetListEntry(pGameSelList, nullptr, this);
|
|
StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
|
|
StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s", Config.Network.GetLeagueServerAddress(), strVersion.getData(), C4_OS);
|
|
pMasterserverClient->SetRefQuery(strQuery.getData(), C4StartupNetListEntry::NRQT_Masterserver);
|
|
}
|
|
}
|
|
|
|
void C4StartupNetDlg::UpdateList(bool fGotReference)
|
|
{
|
|
// recursion check
|
|
if (fUpdatingList) return;
|
|
fUpdatingList = true;
|
|
pGameSelList->FreezeScrolling();
|
|
// Games display mask
|
|
const char *szGameMask = pSearchFieldEdt->GetText();
|
|
if (!szGameMask) szGameMask = "";
|
|
// Update all child entries
|
|
bool fAnyRemoval = false;
|
|
C4GUI::Element *pElem, *pNextElem = pGameSelList->GetFirst();
|
|
while ((pElem=pNextElem))
|
|
{
|
|
pNextElem = pElem->GetNext(); // determine next exec element now - execution
|
|
C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
|
|
// do item updates
|
|
if(pEntry->GetReference()) pEntry->SetVisibility(pEntry->KeywordMatch(szGameMask));
|
|
bool fKeepEntry = true;
|
|
if (fGotReference)
|
|
fKeepEntry = pEntry->OnReference();
|
|
if (fKeepEntry)
|
|
fKeepEntry = pEntry->Execute();
|
|
// remove?
|
|
if (!fKeepEntry)
|
|
{
|
|
// entry wishes to be removed
|
|
// if the selected entry is being removed, the next entry should be selected (which might be the ref for a finished refquery)
|
|
if (pGameSelList->GetSelectedItem() == pEntry)
|
|
if (pEntry->GetNext())
|
|
{
|
|
pGameSelList->SelectEntry(pEntry->GetNext(), false);
|
|
}
|
|
delete pEntry;
|
|
fAnyRemoval = true; // setting any removal will also update collapsed state of all entries; so no need to do updates because of selection change here
|
|
}
|
|
}
|
|
|
|
// Add LAN games
|
|
C4NetIO::addr_t Discover;
|
|
while (DiscoverClient.PopDiscover(Discover))
|
|
{
|
|
StdStrBuf Address(Discover.ToString());
|
|
AddReferenceQuery(Address.getData(), C4StartupNetListEntry::NRQT_GameDiscovery);
|
|
}
|
|
|
|
// check whether view needs to be collapsed or uncollapsed
|
|
if (fIsCollapsed && fAnyRemoval)
|
|
{
|
|
// try uncollapsing
|
|
fIsCollapsed = false;
|
|
UpdateCollapsed();
|
|
// if scrolling is still necessary, the view will be collapsed again immediately
|
|
}
|
|
if (!fIsCollapsed && pGameSelList->IsScrollingNecessary())
|
|
{
|
|
fIsCollapsed = true;
|
|
UpdateCollapsed();
|
|
}
|
|
|
|
fUpdatingList = false;
|
|
// done; selection might have changed
|
|
pGameSelList->UnFreezeScrolling();
|
|
UpdateSelection(false);
|
|
}
|
|
|
|
void C4StartupNetDlg::UpdateCollapsed()
|
|
{
|
|
// update collapsed state for all child entries
|
|
for (C4GUI::Element *pElem = pGameSelList->GetFirst(); pElem; pElem = pElem->GetNext())
|
|
{
|
|
C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
|
|
pEntry->UpdateCollapsed(fIsCollapsed && pElem != pGameSelList->GetSelectedItem());
|
|
}
|
|
}
|
|
|
|
void C4StartupNetDlg::UpdateSelection(bool fUpdateCollapsed)
|
|
{
|
|
// not during list updates - list update call will do this
|
|
if (fUpdatingList) return;
|
|
// in collapsed view, updating the selection may uncollapse something
|
|
if (fIsCollapsed && fUpdateCollapsed) UpdateCollapsed();
|
|
}
|
|
|
|
void C4StartupNetDlg::UpdateDlgMode()
|
|
{
|
|
DlgMode eMode = GetDlgMode();
|
|
// buttons for game joining only visible in game list mode
|
|
btnInternet->SetVisibility(eMode == SNDM_GameList);
|
|
btnRecord->SetVisibility(eMode == SNDM_GameList);
|
|
btnJoin->SetVisibility(eMode == SNDM_GameList);
|
|
btnRefresh->SetVisibility(eMode == SNDM_GameList);
|
|
// focus update
|
|
if (!GetFocus()) SetFocus(GetDlgModeFocusControl(), false);
|
|
}
|
|
|
|
C4StartupNetDlg::DlgMode C4StartupNetDlg::GetDlgMode()
|
|
{
|
|
// dlg mode determined by active tabular sheet
|
|
if (pMainTabular->GetActiveSheetIndex() == SNDM_Chat) return SNDM_Chat; else return SNDM_GameList;
|
|
}
|
|
|
|
void C4StartupNetDlg::OnThreadEvent(C4InteractiveEventType eEvent, void *pEventData)
|
|
{
|
|
UpdateUpdateButton();
|
|
UpdateList(true);
|
|
}
|
|
|
|
void C4StartupNetDlg::UpdateUpdateButton()
|
|
{
|
|
if (!fUpdateCheckPending) return;
|
|
if(!pUpdateClient.isSuccess() || pUpdateClient.isBusy()) return;
|
|
|
|
pUpdateClient.SetNotify(nullptr);
|
|
|
|
StdCopyStrBuf versionInfo;
|
|
|
|
pUpdateClient.GetVersion(&versionInfo);
|
|
pUpdateClient.GetUpdateURL(&UpdateURL);
|
|
|
|
#ifdef WITH_AUTOMATIC_UPDATE
|
|
btnUpdate->SetVisibility(C4UpdateDlg::IsValidUpdate(versionInfo.getData()));
|
|
#endif
|
|
fUpdateCheckPending = false;
|
|
}
|
|
|
|
bool C4StartupNetDlg::DoOK()
|
|
{
|
|
// OK in chat mode? Forward to chat control
|
|
if (GetDlgMode() == SNDM_Chat) return pChatCtrl->DlgEnter();
|
|
// OK on editbox with text enetered: Add the specified IP for reference retrieval
|
|
if (GetFocus() == pJoinAddressEdt)
|
|
{
|
|
const char *szDirectJoinAddress = pJoinAddressEdt->GetText();
|
|
if (szDirectJoinAddress && *szDirectJoinAddress)
|
|
{
|
|
// First do some acrobatics to avoid trying to resolve addresses with leading
|
|
// or trailing whitespace, which is easily pasted in with an IP address.
|
|
// We can trivially skip whitespace at the beginning, but we need a copy to
|
|
// omit whitespace at the end.
|
|
while (std::isspace(*szDirectJoinAddress))
|
|
// skip whitespace at the beginning
|
|
++szDirectJoinAddress;
|
|
if (!*szDirectJoinAddress)
|
|
// entry empty, apart from whitespace
|
|
return true;
|
|
const char *szDirectJoinAddressEnd = szDirectJoinAddress + std::strlen(szDirectJoinAddress) - 1;
|
|
while (std::isspace(*szDirectJoinAddressEnd))
|
|
// skip whitespace at the end
|
|
--szDirectJoinAddressEnd;
|
|
if (*++szDirectJoinAddressEnd)
|
|
{
|
|
// Make a temporary copy of the part that is not trailing whitespace, if any
|
|
std::string strDirectJoinAddressStripped(szDirectJoinAddress, szDirectJoinAddressEnd - szDirectJoinAddress);
|
|
AddReferenceQuery(strDirectJoinAddressStripped.c_str(), C4StartupNetListEntry::NRQT_DirectJoin);
|
|
}
|
|
else
|
|
AddReferenceQuery(szDirectJoinAddress, C4StartupNetListEntry::NRQT_DirectJoin);
|
|
// Switch focus to list so another OK joins the specified address
|
|
SetFocus(pGameSelList, true);
|
|
return true;
|
|
}
|
|
}
|
|
if (GetFocus() == pSearchFieldEdt)
|
|
{
|
|
UpdateList();
|
|
return true;
|
|
}
|
|
// get currently selected item
|
|
C4GUI::Element *pSelection = pGameSelList->GetSelectedItem();
|
|
StdCopyStrBuf strNoJoin(LoadResStr("IDS_NET_NOJOIN"));
|
|
if (!pSelection)
|
|
{
|
|
// no ref selected: Oh noes!
|
|
::pGUI->ShowMessageModal(
|
|
LoadResStr("IDS_NET_NOJOIN_NOREF"),
|
|
strNoJoin.getData(),
|
|
C4GUI::MessageDialog::btnOK,
|
|
C4GUI::Ico_Error);
|
|
return true;
|
|
}
|
|
C4StartupNetListEntry *pRefEntry = static_cast<C4StartupNetListEntry *>(pSelection);
|
|
const char *szError;
|
|
if ((szError = pRefEntry->GetError()))
|
|
{
|
|
// erroneous ref selected: Oh noes!
|
|
::pGUI->ShowMessageModal(
|
|
FormatString(LoadResStr("IDS_NET_NOJOIN_BADREF"), szError).getData(),
|
|
strNoJoin.getData(),
|
|
C4GUI::MessageDialog::btnOK,
|
|
C4GUI::Ico_Error);
|
|
return true;
|
|
}
|
|
C4Network2Reference *pRef = pRefEntry->GetReference();
|
|
const char *szDirectJoinAddress = pRefEntry->GetJoinAddress();
|
|
if (!pRef && !(szDirectJoinAddress && *szDirectJoinAddress))
|
|
{
|
|
// something strange has been selected (e.g., a masterserver entry). Error.
|
|
::pGUI->ShowMessageModal(
|
|
LoadResStr("IDS_NET_NOJOIN_NOREF"),
|
|
strNoJoin.getData(),
|
|
C4GUI::MessageDialog::btnOK,
|
|
C4GUI::Ico_Error);
|
|
return true;
|
|
}
|
|
// check if join to this reference is possible at all
|
|
if (pRef)
|
|
{
|
|
// version mismatch
|
|
C4GameVersion verThis;
|
|
if (!(pRef->getGameVersion() == verThis))
|
|
{
|
|
::pGUI->ShowMessageModal(
|
|
FormatString(LoadResStr("IDS_NET_NOJOIN_BADVER"),
|
|
pRef->getGameVersion().GetString().getData(),
|
|
verThis.GetString().getData()).getData(),
|
|
strNoJoin.getData(),
|
|
C4GUI::MessageDialog::btnOK,
|
|
C4GUI::Ico_Error);
|
|
return true;
|
|
}
|
|
// no runtime join
|
|
if (!pRef->isJoinAllowed())
|
|
{
|
|
if (!::pGUI->ShowMessageModal(
|
|
LoadResStr("IDS_NET_NOJOIN_NORUNTIME"),
|
|
strNoJoin.getData(),
|
|
C4GUI::MessageDialog::btnYes | C4GUI::MessageDialog::btnNo,
|
|
C4GUI::Ico_Error))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// OK; joining!
|
|
if (pRef->isEditor())
|
|
{
|
|
bool success = false;
|
|
// Editor mode join: Serialize reference to temp file and join on that
|
|
// (could pass through environment, but that's hard to do platform-independent
|
|
// (QProcessEnvironment? But then there's a Qt dependency in the network init code))
|
|
StdStrBuf tmpfn(Config.AtTempPath("ocjoin"), true);
|
|
MakeTempFilename(&tmpfn);
|
|
StdBuf join_data = DecompileToBuf<StdCompilerBinWrite>(*pRef);
|
|
if (join_data.getSize())
|
|
{
|
|
if (join_data.SaveToFile(tmpfn.getData()))
|
|
{
|
|
if (RestartApplication({"--editor", FormatString("--join=%s%s", C4Game::DirectJoinFilePrefix, tmpfn.getData()).getData()})) // hope for no " in temp path
|
|
{
|
|
// Application.Quit() has been called. Will quit after returning from this callback.
|
|
// The temp file will be deleted by the new instance
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
EraseFile(tmpfn.getData());
|
|
}
|
|
}
|
|
}
|
|
if (!success)
|
|
{
|
|
C4GUI::TheScreen.ShowErrorMessage(LoadResStr("IDS_ERR_STARTEDITOR"));
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Player mode join
|
|
// Take over reference
|
|
pRefEntry->GrabReference();
|
|
// Set join parameters
|
|
*Game.ScenarioFilename = '\0';
|
|
if (szDirectJoinAddress) SCopy(szDirectJoinAddress, Game.DirectJoinAddress, _MAX_PATH); else *Game.DirectJoinAddress = '\0';
|
|
SCopy("Objects.ocd", Game.DefinitionFilenames);
|
|
Game.NetworkActive = true;
|
|
Game.fObserve = false;
|
|
Game.pJoinReference.reset(pRef);
|
|
// start with this set!
|
|
Application.OpenGame();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool C4StartupNetDlg::DoBack()
|
|
{
|
|
// abort dialog: Back to main
|
|
C4Startup::Get()->SwitchDialog(C4Startup::SDID_Back);
|
|
return true;
|
|
}
|
|
|
|
void C4StartupNetDlg::DoRefresh()
|
|
{
|
|
// check min refresh timer
|
|
time_t tNow = time(0);
|
|
if (tLastRefresh && tNow < tLastRefresh + C4NetMinRefreshInterval)
|
|
{
|
|
// avoid hammering on refresh key
|
|
C4GUI::GUISound("UI::Error");
|
|
return;
|
|
}
|
|
tLastRefresh = tNow;
|
|
// empty list of all old entries
|
|
fUpdatingList = true;
|
|
while (pGameSelList->GetFirst()) delete pGameSelList->GetFirst();
|
|
pMasterserverClient=nullptr;
|
|
// (Re-)Start discovery
|
|
if (!DiscoverClient.StartDiscovery())
|
|
{
|
|
StdCopyStrBuf strNoDiscovery(LoadResStr("IDS_NET_NODISCOVERY"));
|
|
::pGUI->ShowMessageModal(
|
|
FormatString(LoadResStr("IDS_NET_NODISCOVERY_DESC"), DiscoverClient.GetError()).getData(),
|
|
strNoDiscovery.getData(),
|
|
C4GUI::MessageDialog::btnAbort,
|
|
C4GUI::Ico_Error);
|
|
}
|
|
iGameDiscoverInterval = C4NetGameDiscoveryInterval;
|
|
// restart masterserver query
|
|
UpdateMasterserver();
|
|
// done; update stuff
|
|
fUpdatingList = false;
|
|
UpdateList();
|
|
}
|
|
|
|
void C4StartupNetDlg::CreateGame()
|
|
{
|
|
C4Startup::Get()->SwitchDialog(C4Startup::SDID_ScenSelNetwork);
|
|
}
|
|
|
|
void C4StartupNetDlg::OnSec1Timer()
|
|
{
|
|
// no updates if dialog is inactive (e.g., because a join password dlg is shown!)
|
|
if (!IsActive(true))
|
|
return;
|
|
|
|
// Execute discovery
|
|
if (!iGameDiscoverInterval--)
|
|
{
|
|
DiscoverClient.StartDiscovery();
|
|
iGameDiscoverInterval = C4NetGameDiscoveryInterval;
|
|
}
|
|
DiscoverClient.Execute(0);
|
|
|
|
UpdateList(false);
|
|
}
|
|
|
|
void C4StartupNetDlg::AddReferenceQuery(const char *szAddress, C4StartupNetListEntry::QueryType eQueryType)
|
|
{
|
|
// Check for an active reference query to the same address
|
|
for (C4GUI::Element *pElem = pGameSelList->GetFirst(); pElem; pElem = pElem->GetNext())
|
|
{
|
|
C4StartupNetListEntry *pEntry = static_cast<C4StartupNetListEntry *>(pElem);
|
|
// same address
|
|
if (pEntry->IsSameRefQueryAddress(szAddress))
|
|
{
|
|
// nothing to do, xcept maybe select it
|
|
if (eQueryType == C4StartupNetListEntry::NRQT_DirectJoin) pGameSelList->SelectEntry(pEntry, true);
|
|
return;
|
|
}
|
|
}
|
|
// No reference from same host found - create a new entry
|
|
C4StartupNetListEntry *pEntry = new C4StartupNetListEntry(pGameSelList, nullptr, this);
|
|
pEntry->SetRefQuery(szAddress, eQueryType);
|
|
if (eQueryType == C4StartupNetListEntry::NRQT_DirectJoin)
|
|
pGameSelList->SelectEntry(pEntry, true);
|
|
else if (fIsCollapsed)
|
|
pEntry->UpdateCollapsed(true);
|
|
}
|
|
|
|
void C4StartupNetDlg::OnReferenceEntryAdd(C4StartupNetListEntry *pEntry)
|
|
{
|
|
// collapse the new entry if desired
|
|
if (fIsCollapsed && pEntry != pGameSelList->GetSelectedItem())
|
|
pEntry->UpdateCollapsed(true);
|
|
}
|
|
|
|
void C4StartupNetDlg::CheckVersionUpdate()
|
|
{
|
|
#ifdef WITH_AUTOMATIC_UPDATE
|
|
StdStrBuf strVersion; strVersion.Format("%d.%d", C4XVER1, C4XVER2);
|
|
StdStrBuf strQuery; strQuery.Format("%s?version=%s&platform=%s&action=version", Config.Network.UpdateServerAddress, strVersion.getData(), C4_OS);
|
|
|
|
if (pUpdateClient.Init() && pUpdateClient.SetServer(strQuery.getData()) && pUpdateClient.QueryUpdateURL())
|
|
{
|
|
pUpdateClient.SetNotify(&Application.InteractiveThread);
|
|
Application.InteractiveThread.AddProc(&pUpdateClient);
|
|
}
|
|
fUpdateCheckPending = true;
|
|
#endif
|
|
}
|
|
|
|
void C4StartupNetDlg::OnChatTitleChange(const StdStrBuf &sNewTitle)
|
|
{
|
|
// update label
|
|
if (pChatTitleLabel) pChatTitleLabel->SetText(FormatString("%s - %s", LoadResStr("IDS_DLG_CHAT"), sNewTitle.getData()).getData());
|
|
}
|