ddnet/src/game/client/components/menus_browser.cpp

1522 lines
56 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
#include <base/log.h>
#include <engine/favorites.h>
2011-03-23 12:06:35 +00:00
#include <engine/friends.h>
#include <engine/keys.h>
2010-05-29 07:25:38 +00:00
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
2022-12-09 15:15:19 +00:00
#include <engine/shared/localization.h>
2010-05-29 07:25:38 +00:00
#include <engine/textrender.h>
#include <game/client/components/console.h>
#include <game/client/components/countryflags.h>
#include <game/client/render.h>
#include <game/client/ui.h>
#include <game/localization.h>
2021-07-12 09:29:59 +00:00
#include <game/client/gameclient.h>
#include "menus.h"
static const int gs_OffsetColFlagLock = 2;
static const int gs_OffsetColFav = gs_OffsetColFlagLock + 3;
static const int gs_OffsetColOff = gs_OffsetColFav + 3;
static const int gs_OffsetColName = gs_OffsetColOff + 3;
static const int gs_OffsetColGameType = gs_OffsetColName + 3;
static const int gs_OffsetColMap = gs_OffsetColGameType + 3;
static const int gs_OffsetColPlayers = gs_OffsetColMap + 3;
static const int gs_OffsetColPing = gs_OffsetColPlayers + 3;
static const int gs_OffsetColVersion = gs_OffsetColPing + 3;
2020-10-12 10:29:47 +00:00
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
void FormatServerbrowserPing(char *pBuffer, int BufferLength, const CServerInfo *pInfo)
{
if(!pInfo->m_LatencyIsEstimated)
{
str_format(pBuffer, BufferLength, "%d", pInfo->m_Latency);
return;
}
static const char *LOCATION_NAMES[CServerInfo::NUM_LOCS] = {
"", // LOC_UNKNOWN
2022-12-09 15:15:19 +00:00
Localizable("AFR"), // LOC_AFRICA
Localizable("ASI"), // LOC_ASIA
Localizable("AUS"), // LOC_AUSTRALIA
Localizable("EUR"), // LOC_EUROPE
Localizable("NA"), // LOC_NORTH_AMERICA
Localizable("SA"), // LOC_SOUTH_AMERICA
Localizable("CHN"), // LOC_CHINA
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
};
dbg_assert(0 <= pInfo->m_Location && pInfo->m_Location < CServerInfo::NUM_LOCS, "location out of range");
str_copy(pBuffer, Localize(LOCATION_NAMES[pInfo->m_Location]), BufferLength);
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderServerbrowserServerList(CUIRect View)
{
CUIRect Headers;
CUIRect Status;
2010-05-29 07:25:38 +00:00
View.HSplitTop(ms_ListheaderHeight, &Headers, &View);
View.HSplitBottom(65.0f, &View, &Status);
// split of the scrollbar
Headers.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 5.0f);
2010-05-29 07:25:38 +00:00
Headers.VSplitRight(20.0f, &Headers, 0);
struct CColumn
{
int m_ID;
2010-05-29 07:25:38 +00:00
int m_Sort;
CLocConstString m_Caption;
int m_Direction;
float m_Width;
CUIRect m_Rect;
CUIRect m_Spacer;
};
2010-05-29 07:25:38 +00:00
enum
{
COL_FLAG_LOCK = 0,
COL_FLAG_FAV,
COL_FLAG_OFFICIAL,
COL_NAME,
COL_GAMETYPE,
COL_MAP,
COL_PLAYERS,
COL_PING,
COL_VERSION,
};
2010-05-29 07:25:38 +00:00
CColumn s_aCols[] = {
{-1, -1, " ", -1, 2.0f, {0}, {0}},
{COL_FLAG_LOCK, -1, " ", -1, 14.0f, {0}, {0}},
{COL_FLAG_FAV, -1, " ", -1, 14.0f, {0}, {0}},
{COL_FLAG_OFFICIAL, -1, " ", -1, 14.0f, {0}, {0}},
{COL_NAME, IServerBrowser::SORT_NAME, "Name", 0, 50.0f, {0}, {0}}, // Localize - these strings are localized within CLocConstString
2022-12-09 15:15:19 +00:00
{COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, Localizable("Type"), 1, 50.0f, {0}, {0}},
{COL_MAP, IServerBrowser::SORT_MAP, "Map", 1, 120.0f + (Headers.w - 480) / 8, {0}, {0}},
{COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 85.0f, {0}, {0}},
{-1, -1, " ", 1, 10.0f, {0}, {0}},
{COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.0f, {0}, {0}},
};
2010-05-29 07:25:38 +00:00
int NumCols = std::size(s_aCols);
2010-05-29 07:25:38 +00:00
// do layout
2010-05-29 07:25:38 +00:00
for(int i = 0; i < NumCols; i++)
{
2010-05-29 07:25:38 +00:00
if(s_aCols[i].m_Direction == -1)
{
2010-05-29 07:25:38 +00:00
Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers);
if(i + 1 < NumCols)
{
2010-05-29 07:25:38 +00:00
Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers);
}
}
}
2010-05-29 07:25:38 +00:00
for(int i = NumCols - 1; i >= 0; i--)
{
2010-05-29 07:25:38 +00:00
if(s_aCols[i].m_Direction == 1)
{
2010-05-29 07:25:38 +00:00
Headers.VSplitRight(s_aCols[i].m_Width, &Headers, &s_aCols[i].m_Rect);
Headers.VSplitRight(2, &Headers, &s_aCols[i].m_Spacer);
}
}
2010-05-29 07:25:38 +00:00
for(int i = 0; i < NumCols; i++)
{
2010-05-29 07:25:38 +00:00
if(s_aCols[i].m_Direction == 0)
s_aCols[i].m_Rect = Headers;
}
2010-05-29 07:25:38 +00:00
const bool PlayersOrPing = (g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS || g_Config.m_BrSort == IServerBrowser::SORT_PING);
// do headers
2010-05-29 07:25:38 +00:00
for(int i = 0; i < NumCols; i++)
{
int Checked = g_Config.m_BrSort == s_aCols[i].m_Sort;
if(PlayersOrPing && g_Config.m_BrSortOrder == 2 && (s_aCols[i].m_Sort == IServerBrowser::SORT_NUMPLAYERS || s_aCols[i].m_Sort == IServerBrowser::SORT_PING))
Checked = 2;
if(DoButton_GridHeader(s_aCols[i].m_Caption, Localize(s_aCols[i].m_Caption), Checked, &s_aCols[i].m_Rect))
{
2010-05-29 07:25:38 +00:00
if(s_aCols[i].m_Sort != -1)
{
2010-05-29 07:25:38 +00:00
if(g_Config.m_BrSort == s_aCols[i].m_Sort)
{
if(PlayersOrPing)
g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % 3;
else
g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % 2;
}
else
2010-05-29 07:25:38 +00:00
g_Config.m_BrSortOrder = 0;
g_Config.m_BrSort = s_aCols[i].m_Sort;
}
}
}
2010-05-29 07:25:38 +00:00
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0);
2010-05-29 07:25:38 +00:00
CUIRect Scroll;
2021-11-26 20:55:31 +00:00
View.VSplitRight(20.0f, &View, &Scroll);
2010-05-29 07:25:38 +00:00
int NumServers = ServerBrowser()->NumSortedServers();
// display important messages in the middle of the screen so no
// users misses it
{
2010-05-29 07:25:38 +00:00
CUIRect MsgBox = View;
2021-07-08 17:18:01 +00:00
if(!ServerBrowser()->NumServers() && ServerBrowser()->IsGettingServerlist())
UI()->DoLabel(&MsgBox, Localize("Getting server list from master server"), 16.0f, TEXTALIGN_CENTER);
2010-05-29 07:25:38 +00:00
else if(!ServerBrowser()->NumServers())
UI()->DoLabel(&MsgBox, Localize("No servers found"), 16.0f, TEXTALIGN_CENTER);
2010-05-29 07:25:38 +00:00
else if(ServerBrowser()->NumServers() && !NumServers)
UI()->DoLabel(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, TEXTALIGN_CENTER);
}
2008-09-07 21:13:24 +00:00
2010-05-29 07:25:38 +00:00
static float s_ScrollValue = 0;
s_ScrollValue = UI()->DoScrollbarV(&s_ScrollValue, &Scroll, s_ScrollValue);
2010-05-29 07:25:38 +00:00
if(UI()->ConsumeHotkey(CUI::HOTKEY_TAB))
2015-06-28 13:43:34 +00:00
{
const int Direction = Input()->ShiftIsPressed() ? -1 : 1;
g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 + Direction) % 3;
2015-06-28 13:43:34 +00:00
}
if(HandleListInputs(View, s_ScrollValue, 3.0f, &m_ScrollOffset, s_aCols[0].m_Rect.h, m_SelectedIndex, NumServers))
2010-05-29 07:25:38 +00:00
{
const CServerInfo *pItem = ServerBrowser()->SortedGet(m_SelectedIndex);
2022-07-09 16:14:56 +00:00
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
2015-08-27 14:42:21 +00:00
}
// set clipping
2010-05-29 07:25:38 +00:00
UI()->ClipEnable(&View);
CUIRect OriginalView = View;
int Num = (int)(View.h / s_aCols[0].m_Rect.h) + 1;
2020-11-21 17:30:24 +00:00
int ScrollNum = maximum(NumServers - Num + 1, 0);
View.y -= s_ScrollValue * ScrollNum * s_aCols[0].m_Rect.h;
2010-05-29 07:25:38 +00:00
int NewSelected = -1;
bool DoubleClicked = false;
2010-05-29 07:25:38 +00:00
int NumPlayers = 0;
2008-09-04 18:42:26 +00:00
2010-05-29 07:25:38 +00:00
m_SelectedIndex = -1;
2011-06-26 15:10:13 +00:00
// reset friend counter
for(auto &Friend : m_vFriends)
Friend.m_NumFound = 0;
2010-05-29 07:25:38 +00:00
2022-03-19 17:50:33 +00:00
auto RenderBrowserIcons = [this](CUIElement::SUIElementRect &UIRect, CUIRect *pRect, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor, const char *pText, ETextAlignment TextAlign, bool SmallFont = false) {
float FontSize = 14.0f;
2022-03-19 17:50:33 +00:00
if(SmallFont)
FontSize = 6.0f;
2022-03-19 10:25:54 +00:00
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
TextRender()->TextColor(TextColor);
TextRender()->TextOutlineColor(TextOutlineColor);
UI()->DoLabelStreamed(UIRect, pRect, pText, FontSize, TextAlign, -1, 0);
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(nullptr);
};
for(int i = 0; i < NumServers; i++)
{
2010-05-29 07:25:38 +00:00
int ItemIndex = i;
const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
2018-10-29 21:03:57 +00:00
NumPlayers += pItem->m_NumFilteredPlayers;
2010-05-29 07:25:38 +00:00
CUIRect Row;
2021-09-13 21:48:10 +00:00
const int UIRectCount = 2 + (COL_VERSION + 1) * 3;
2020-10-12 10:29:47 +00:00
//initialize
if(pItem->m_pUIElement == NULL)
{
2021-09-13 21:48:10 +00:00
pItem->m_pUIElement = UI()->GetNewUIElement(UIRectCount);
2020-10-12 10:29:47 +00:00
}
2010-05-29 07:25:38 +00:00
int Selected = str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0; //selected_index==ItemIndex;
2014-06-16 11:29:18 +00:00
View.HSplitTop(ms_ListheaderHeight, &Row, &View);
2010-05-29 07:25:38 +00:00
if(Selected)
m_SelectedIndex = i;
2011-06-26 15:10:13 +00:00
// update friend counter
int FriendsOnServer = 0;
2011-06-26 15:10:13 +00:00
if(pItem->m_FriendState != IFriends::FRIEND_NO)
{
for(int j = 0; j < pItem->m_NumReceivedClients; ++j)
2011-06-26 15:10:13 +00:00
{
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
{
FriendsOnServer++;
2011-06-26 15:10:13 +00:00
unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName);
unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan);
for(auto &Friend : m_vFriends)
2011-06-26 15:10:13 +00:00
{
if(((g_Config.m_ClFriendsIgnoreClan && Friend.m_pFriendInfo->m_aName[0]) || (ClanHash == Friend.m_pFriendInfo->m_ClanHash && !str_comp(Friend.m_pFriendInfo->m_aClan, pItem->m_aClients[j].m_aClan))) &&
(!Friend.m_pFriendInfo->m_aName[0] || (NameHash == Friend.m_pFriendInfo->m_NameHash && !str_comp(Friend.m_pFriendInfo->m_aName, pItem->m_aClients[j].m_aName))))
2011-06-26 15:10:13 +00:00
{
Friend.m_NumFound++;
if(Friend.m_pFriendInfo->m_aName[0])
break;
2011-06-26 15:10:13 +00:00
}
}
}
}
}
// make sure that only those in view can be selected
if(Row.y + Row.h > OriginalView.y && Row.y < OriginalView.y + OriginalView.h)
{
2010-05-29 07:25:38 +00:00
if(Selected)
{
CUIRect r = Row;
r.Margin(0.5f, &r);
pItem->m_pUIElement->Rect(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_ALL, 4.0f);
2010-05-29 07:25:38 +00:00
}
else if(UI()->MouseHovered(&Row))
{
CUIRect r = Row;
r.Margin(0.5f, &r);
pItem->m_pUIElement->Rect(0)->Draw(&r, ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
}
2010-05-29 07:25:38 +00:00
if(UI()->DoButtonLogic(pItem, Selected, &Row))
{
2010-05-29 07:25:38 +00:00
NewSelected = ItemIndex;
2014-06-16 11:29:18 +00:00
if(NewSelected == m_DoubleClickIndex)
DoubleClicked = true;
2014-06-16 11:29:18 +00:00
m_DoubleClickIndex = NewSelected;
}
}
2010-05-29 07:25:38 +00:00
else
{
// reset active item, if not visible
if(UI()->CheckActiveItem(pItem))
UI()->SetActiveItem(nullptr);
2010-05-29 07:25:38 +00:00
// don't render invisible items
continue;
}
for(int c = 0; c < NumCols; c++)
{
2010-05-29 07:25:38 +00:00
CUIRect Button;
char aTemp[64];
Button.x = s_aCols[c].m_Rect.x;
Button.y = Row.y;
Button.h = Row.h;
Button.w = s_aCols[c].m_Rect.w;
int ID = s_aCols[c].m_ID;
2010-05-29 07:25:38 +00:00
if(ID == COL_FLAG_LOCK)
{
2010-05-29 07:25:38 +00:00
if(pItem->m_Flags & SERVER_FLAG_PASSWORD)
2022-03-19 10:25:54 +00:00
{
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 0), &Button, {0.75f, 0.75f, 0.75f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\xA3", TEXTALIGN_CENTER);
2022-03-19 10:25:54 +00:00
}
}
else if(ID == COL_FLAG_FAV)
{
if(pItem->m_Favorite != TRISTATE::NONE)
2022-03-19 10:25:54 +00:00
{
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 0), &Button, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\x84", TEXTALIGN_CENTER);
2022-03-19 10:25:54 +00:00
}
}
else if(ID == COL_FLAG_OFFICIAL)
{
if(pItem->m_Official && g_Config.m_UiPage != PAGE_DDNET && g_Config.m_UiPage != PAGE_KOG)
2022-03-19 10:25:54 +00:00
{
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 0), &Button, {0.4f, 0.7f, 0.94f, 1}, {0.0f, 0.0f, 0.0f, 1.0f}, "\xEF\x82\xA3", TEXTALIGN_CENTER);
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 1), &Button, {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, "\xEF\x80\x8C", TEXTALIGN_CENTER, true);
2022-03-19 10:25:54 +00:00
}
}
else if(ID == COL_NAME)
{
float FontSize = 12.0f;
bool Printed = false;
2010-05-29 07:25:38 +00:00
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME))
{
const char *pStrToken = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStrToken = str_next_token(pStrToken, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
if(aFilterStr[0] == '\0')
{
continue;
}
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aName, aFilterStr);
if(pStr)
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(aFilterStr), &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pStr + str_length(aFilterStr), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor);
Printed = true;
break;
}
}
}
if(!Printed)
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else if(ID == COL_MAP)
{
if(g_Config.m_UiPage == PAGE_DDNET)
{
CUIRect Icon;
Button.VMargin(4.0f, &Button);
Button.VSplitLeft(Button.h, &Icon, &Button);
Icon.Margin(2.0f, &Icon);
if(g_Config.m_BrIndicateFinished && pItem->m_HasRank == 1)
2022-03-19 10:25:54 +00:00
{
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 1), &Icon, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), "\xEF\x84\x9E", TEXTALIGN_CENTER);
2022-03-19 10:25:54 +00:00
}
}
float FontSize = 12.0f;
bool Printed = false;
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME))
{
const char *pStrToken = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStrToken = str_next_token(pStrToken, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
// highlight the parts that matches
const char *pStr = str_utf8_find_nocase(pItem->m_aMap, aFilterStr);
if(pStr)
{
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)(pStr - pItem->m_aMap));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pStr, FontSize, TEXTALIGN_LEFT, Button.w, 1, true, (int)str_length(aFilterStr), &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor);
TextRender()->TextColor(1, 1, 1, 1);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pStr + str_length(aFilterStr), FontSize, TEXTALIGN_LEFT, Button.w, 1, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor);
Printed = true;
break;
}
}
}
if(!Printed)
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
else if(ID == COL_PLAYERS)
{
CUIRect Icon, IconText;
Button.VMargin(2.0f, &Button);
2011-06-26 15:10:13 +00:00
if(pItem->m_FriendState != IFriends::FRIEND_NO)
{
Button.VSplitRight(50.0f, &Icon, &Button);
2011-06-26 15:10:13 +00:00
Icon.Margin(2.0f, &Icon);
Icon.HSplitBottom(6.0f, 0, &IconText);
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 1), &Icon, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), "\xEF\x80\x84", TEXTALIGN_CENTER);
if(FriendsOnServer > 1)
{
char aBufFriendsOnServer[64];
str_format(aBufFriendsOnServer, sizeof(aBufFriendsOnServer), "%i", FriendsOnServer);
TextRender()->TextColor(0.94f, 0.8f, 0.8f, 1);
UI()->DoLabel(&IconText, aBufFriendsOnServer, 10.0f, TEXTALIGN_CENTER);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1);
}
2011-06-26 15:10:13 +00:00
}
2018-10-29 21:03:57 +00:00
str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumFilteredPlayers, ServerBrowser()->Max(*pItem));
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_PLAYER))
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
float FontSize = 12.0f;
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_RIGHT, -1, 1, false);
TextRender()->TextColor(1, 1, 1, 1);
}
else if(ID == COL_PING)
{
Button.VMargin(4.0f, &Button);
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
FormatServerbrowserPing(aTemp, sizeof(aTemp), pItem);
if(g_Config.m_UiColorizePing)
{
2019-04-26 12:06:32 +00:00
ColorRGBA rgb = color_cast<ColorRGBA>(ColorHSLA((300.0f - clamp(pItem->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f));
2019-04-26 22:34:20 +00:00
TextRender()->TextColor(rgb);
}
float FontSize = 12.0f;
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_RIGHT, -1, 1, false);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}
else if(ID == COL_VERSION)
{
2010-05-29 07:25:38 +00:00
const char *pVersion = pItem->m_aVersion;
float FontSize = 12.0f;
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_RIGHT, -1, 1, false);
2010-05-29 07:25:38 +00:00
}
else if(ID == COL_GAMETYPE)
{
float FontSize = 12.0f;
2008-10-21 18:50:23 +00:00
if(g_Config.m_UiColorizeGametype)
{
2019-04-26 12:06:32 +00:00
ColorHSLA hsl = ColorHSLA(1.0f, 1.0f, 1.0f);
if(str_comp(pItem->m_aGameType, "DM") == 0 || str_comp(pItem->m_aGameType, "TDM") == 0 || str_comp(pItem->m_aGameType, "CTF") == 0)
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.33f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "catch"))
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.17f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "idm") || str_find_nocase(pItem->m_aGameType, "itdm") || str_find_nocase(pItem->m_aGameType, "ictf"))
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.00f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "fng"))
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.83f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "gores"))
hsl = ColorHSLA(0.525f, 1.0f, 0.75f);
2022-12-07 18:29:40 +00:00
else if(str_find_nocase(pItem->m_aGameType, "BW"))
hsl = ColorHSLA(0.050f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "ddracenet") || str_find_nocase(pItem->m_aGameType, "ddnet"))
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.58f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "ddrace") || str_find_nocase(pItem->m_aGameType, "mkrace"))
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.75f, 1.0f, 0.75f);
else if(str_find_nocase(pItem->m_aGameType, "race") || str_find_nocase(pItem->m_aGameType, "fastcap"))
2019-04-26 12:06:32 +00:00
hsl = ColorHSLA(0.46f, 1.0f, 0.75f);
2019-04-26 12:06:32 +00:00
ColorRGBA rgb = color_cast<ColorRGBA>(hsl);
2019-04-26 22:34:20 +00:00
TextRender()->TextColor(rgb);
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
}
else
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_LEFT, Button.w, 1, true);
}
}
}
2009-10-27 14:38:53 +00:00
UI()->ClipDisable();
2010-05-29 07:25:38 +00:00
if(NewSelected != -1)
{
// select the new server
2010-05-29 07:25:38 +00:00
const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected);
2022-07-09 16:14:56 +00:00
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
if(DoubleClicked && Input()->MouseDoubleClick())
Connect(g_Config.m_UiServerAddress);
}
// Render bar that shows the loading progression.
// The bar is only shown while loading and fades out when it's done.
CUIRect RefreshBar;
Status.HSplitTop(5.0f, &RefreshBar, &Status);
static float s_LoadingProgressionFadeEnd = 0.0f;
if(ServerBrowser()->IsRefreshing() && ServerBrowser()->LoadingProgression() < 100)
{
s_LoadingProgressionFadeEnd = Client()->GlobalTime() + 2.0f;
}
const float LoadingProgressionTimeDiff = s_LoadingProgressionFadeEnd - Client()->GlobalTime();
if(LoadingProgressionTimeDiff > 0.0f)
{
const float RefreshBarAlpha = minimum(LoadingProgressionTimeDiff, 0.8f);
RefreshBar.h = 2.0f;
RefreshBar.w *= ServerBrowser()->LoadingProgression() / 100.0f;
RefreshBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, RefreshBarAlpha), IGraphics::CORNER_NONE, 0.0f);
}
2010-05-29 07:25:38 +00:00
CUIRect SearchInfoAndAddr, ServersAndConnect, Status3;
Status.VSplitRight(250.0f, &SearchInfoAndAddr, &ServersAndConnect);
if(SearchInfoAndAddr.w > 350.0f)
SearchInfoAndAddr.VSplitLeft(350.0f, &SearchInfoAndAddr, NULL);
CUIRect SearchAndInfo, ServerAddr, ConnectButtons;
SearchInfoAndAddr.HSplitTop(40.0f, &SearchAndInfo, &ServerAddr);
ServersAndConnect.HSplitTop(35.0f, &Status3, &ConnectButtons);
CUIRect QuickSearch, QuickExclude;
SearchAndInfo.HSplitTop(20.f, &QuickSearch, &QuickExclude);
QuickSearch.Margin(2.f, &QuickSearch);
QuickExclude.Margin(2.f, &QuickExclude);
float SearchExcludeAddrStrMax = 130.0f;
float SearchIconWidth = 0;
float ExcludeIconWidth = 0;
float ExcludeSearchIconMax = 0;
2022-03-19 17:50:33 +00:00
const char *pSearchLabel = "\xEF\x80\x82";
const char *pExcludeLabel = "\xEF\x81\x9E";
2009-01-21 18:09:06 +00:00
// render quick search
{
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
2020-10-06 10:25:10 +00:00
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
2022-03-11 16:34:48 +00:00
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&QuickSearch, pSearchLabel, 16.0f, TEXTALIGN_LEFT, Props);
SearchIconWidth = TextRender()->TextWidth(0, 16.0f, pSearchLabel, -1, -1.0f);
ExcludeIconWidth = TextRender()->TextWidth(0, 16.0f, pExcludeLabel, -1, -1.0f);
ExcludeSearchIconMax = maximum(SearchIconWidth, ExcludeIconWidth);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
QuickSearch.VSplitLeft(ExcludeSearchIconMax, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
char aBufSearch[64];
str_format(aBufSearch, sizeof(aBufSearch), "%s:", Localize("Search"));
UI()->DoLabel(&QuickSearch, aBufSearch, 14.0f, TEXTALIGN_LEFT);
QuickSearch.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickSearch);
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
2022-03-13 18:09:33 +00:00
SUIExEditBoxProperties EditProps;
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
2022-03-13 18:09:33 +00:00
{
UI()->SetActiveItem(&g_Config.m_BrFilterString);
2022-03-13 18:09:33 +00:00
EditProps.m_SelectText = true;
}
2010-05-29 07:25:38 +00:00
static int s_ClearButton = 0;
static float s_Offset = 0.0f;
if(UI()->DoClearableEditBox(&g_Config.m_BrFilterString, &s_ClearButton, &QuickSearch, g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString), 12.0f, &s_Offset, false, IGraphics::CORNER_ALL, EditProps))
Client()->ServerBrowserUpdate();
2010-05-29 07:25:38 +00:00
}
// render quick exclude
{
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
2020-10-06 10:25:10 +00:00
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
2022-03-11 16:34:48 +00:00
SLabelProperties Props;
Props.m_AlignVertically = 0;
UI()->DoLabel(&QuickExclude, pExcludeLabel, 16.0f, TEXTALIGN_LEFT, Props);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(NULL);
QuickExclude.VSplitLeft(ExcludeSearchIconMax, 0, &QuickExclude);
QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude);
char aBufExclude[64];
str_format(aBufExclude, sizeof(aBufExclude), "%s:", Localize("Exclude"));
UI()->DoLabel(&QuickExclude, aBufExclude, 14.0f, TEXTALIGN_LEFT);
QuickExclude.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickExclude);
QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude);
static int s_ClearButton = 0;
static float s_Offset = 0.0f;
if(Input()->KeyPress(KEY_X) && Input()->ShiftIsPressed() && Input()->ModifierIsPressed())
UI()->SetActiveItem(&g_Config.m_BrExcludeString);
if(UI()->DoClearableEditBox(&g_Config.m_BrExcludeString, &s_ClearButton, &QuickExclude, g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString), 12.0f, &s_Offset, false, IGraphics::CORNER_ALL))
Client()->ServerBrowserUpdate();
}
// render status
char aBufSvr[128];
char aBufPyr[128];
if(ServerBrowser()->NumServers() != 1)
str_format(aBufSvr, sizeof(aBufSvr), Localize("%d of %d servers"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers());
else
str_format(aBufSvr, sizeof(aBufSvr), Localize("%d of %d server"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers());
if(NumPlayers != 1)
str_format(aBufPyr, sizeof(aBufPyr), Localize("%d players"), NumPlayers);
else
str_format(aBufPyr, sizeof(aBufPyr), Localize("%d player"), NumPlayers);
CUIRect SvrsOnline, PlysOnline;
Status3.HSplitTop(20.f, &PlysOnline, &SvrsOnline);
PlysOnline.VSplitRight(TextRender()->TextWidth(0, 12.0f, aBufPyr, -1, -1.0f), 0, &PlysOnline);
UI()->DoLabel(&PlysOnline, aBufPyr, 12.0f, TEXTALIGN_LEFT);
SvrsOnline.VSplitRight(TextRender()->TextWidth(0, 12.0f, aBufSvr, -1, -1.0f), 0, &SvrsOnline);
UI()->DoLabel(&SvrsOnline, aBufSvr, 12.0f, TEXTALIGN_LEFT);
// status box
{
CUIRect ButtonRefresh, ButtonConnect, ButtonArea;
ServerAddr.Margin(2.0f, &ServerAddr);
// address info
UI()->DoLabel(&ServerAddr, Localize("Server address:"), 14.0f, TEXTALIGN_LEFT);
ServerAddr.VSplitLeft(SearchExcludeAddrStrMax + 5.0f + ExcludeSearchIconMax + 5.0f, NULL, &ServerAddr);
static int s_ClearButton = 0;
static float s_Offset = 0.0f;
UI()->DoClearableEditBox(&g_Config.m_UiServerAddress, &s_ClearButton, &ServerAddr, g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress), 12.0f, &s_Offset);
// button area
ButtonArea = ConnectButtons;
ButtonArea.VSplitMid(&ButtonRefresh, &ButtonConnect);
ButtonRefresh.HSplitTop(5.0f, NULL, &ButtonRefresh);
ButtonConnect.HSplitTop(5.0f, NULL, &ButtonConnect);
ButtonConnect.VSplitLeft(5.0f, NULL, &ButtonConnect);
static int s_RefreshButton = 0;
2020-10-12 10:29:47 +00:00
auto Func = [this]() mutable -> const char * {
if(ServerBrowser()->IsRefreshing() || ServerBrowser()->IsGettingServerlist())
2022-07-09 16:14:56 +00:00
str_copy(m_aLocalStringHelper, Localize("Refreshing..."));
2020-10-12 10:29:47 +00:00
else
2022-07-09 16:14:56 +00:00
str_copy(m_aLocalStringHelper, Localize("Refresh"));
2020-10-12 10:29:47 +00:00
return m_aLocalStringHelper;
};
if(DoButtonMenu(m_RefreshButton, &s_RefreshButton, Func, 0, &ButtonRefresh, true, false, IGraphics::CORNER_ALL) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()))
{
RefreshBrowserTab(g_Config.m_UiPage);
}
static int s_JoinButton = 0;
2020-10-12 10:29:47 +00:00
if(DoButtonMenu(
m_ConnectButton, &s_JoinButton, []() -> const char * { return Localize("Connect"); }, 0, &ButtonConnect, false, false, IGraphics::CORNER_ALL, 5, 0, vec4(0.7f, 1, 0.7f, 0.1f), vec4(0.7f, 1, 0.7f, 0.2f)) ||
UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
{
Connect(g_Config.m_UiServerAddress);
}
}
2008-09-04 18:42:26 +00:00
}
void CMenus::Connect(const char *pAddress)
{
if(Client()->State() == IClient::STATE_ONLINE && Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
{
str_copy(m_aNextServer, pAddress);
PopupConfirm(Localize("Disconnect"), Localize("Are you sure that you want to disconnect and switch to a different server?"), Localize("Yes"), Localize("No"), &CMenus::PopupConfirmSwitchServer);
}
else
Client()->Connect(pAddress);
}
void CMenus::PopupConfirmSwitchServer()
{
Client()->Connect(m_aNextServer);
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderServerbrowserFilters(CUIRect View)
2008-09-04 18:42:26 +00:00
{
CUIRect ServerFilter = View, FilterHeader;
const float FontSize = 12.0f;
2014-09-20 09:47:51 +00:00
ServerFilter.HSplitBottom(0.0f, &ServerFilter, 0);
// server filter
ServerFilter.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFilter);
FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
ServerFilter.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f);
UI()->DoLabel(&FilterHeader, Localize("Server filter"), FontSize + 2.0f, TEXTALIGN_CENTER);
2014-12-14 15:45:18 +00:00
CUIRect Button, Button2;
2008-09-04 18:42:26 +00:00
ServerFilter.VSplitLeft(5.0f, 0, &ServerFilter);
ServerFilter.Margin(3.0f, &ServerFilter);
ServerFilter.VMargin(5.0f, &ServerFilter);
2008-09-04 18:42:26 +00:00
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterEmpty, Localize("Has people playing"), g_Config.m_BrFilterEmpty, &Button))
2010-05-29 07:25:38 +00:00
g_Config.m_BrFilterEmpty ^= 1;
2008-09-04 18:42:26 +00:00
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterSpectators, Localize("Count players only"), g_Config.m_BrFilterSpectators, &Button))
g_Config.m_BrFilterSpectators ^= 1;
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterFull, Localize("Server not full"), g_Config.m_BrFilterFull, &Button))
2010-05-29 07:25:38 +00:00
g_Config.m_BrFilterFull ^= 1;
2008-09-04 18:42:26 +00:00
2011-03-23 12:06:35 +00:00
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button))
2011-03-23 12:06:35 +00:00
g_Config.m_BrFilterFriends ^= 1;
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterPw, Localize("No password"), g_Config.m_BrFilterPw, &Button))
2010-05-29 07:25:38 +00:00
g_Config.m_BrFilterPw ^= 1;
2008-09-04 18:42:26 +00:00
2011-04-17 09:57:33 +00:00
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button))
2011-04-17 09:57:33 +00:00
g_Config.m_BrFilterGametypeStrict ^= 1;
ServerFilter.HSplitTop(5.0f, 0, &ServerFilter);
2010-05-29 07:25:38 +00:00
ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter);
UI()->DoLabel(&Button, Localize("Game types:"), FontSize, TEXTALIGN_LEFT);
Button.VSplitRight(60.0f, 0, &Button);
ServerFilter.HSplitTop(3.0f, 0, &ServerFilter);
static float s_OffsetGametype = 0.0f;
if(UI()->DoEditBox(&g_Config.m_BrFilterGametype, &Button, g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype), FontSize, &s_OffsetGametype))
Client()->ServerBrowserUpdate();
2008-09-04 18:42:26 +00:00
// server address
ServerFilter.HSplitTop(3.0f, 0, &ServerFilter);
ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter);
UI()->DoLabel(&Button, Localize("Server address:"), FontSize, TEXTALIGN_LEFT);
Button.VSplitRight(60.0f, 0, &Button);
static float s_OffsetAddr = 0.0f;
if(UI()->DoEditBox(&g_Config.m_BrFilterServerAddress, &Button, g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress), FontSize, &s_OffsetAddr))
Client()->ServerBrowserUpdate();
// player country
{
CUIRect Rect;
ServerFilter.HSplitTop(3.0f, 0, &ServerFilter);
ServerFilter.HSplitTop(26.0f, &Button, &ServerFilter);
Button.VSplitRight(60.0f, &Button, &Rect);
Button.HMargin(3.0f, &Button);
if(DoButton_CheckBox(&g_Config.m_BrFilterCountry, Localize("Player country:"), g_Config.m_BrFilterCountry, &Button))
g_Config.m_BrFilterCountry ^= 1;
2011-08-11 08:59:14 +00:00
float OldWidth = Rect.w;
Rect.w = Rect.h * 2;
Rect.x += (OldWidth - Rect.w) / 2.0f;
ColorRGBA Color(1.0f, 1.0f, 1.0f, g_Config.m_BrFilterCountry ? 1.0f : 0.5f);
2021-07-12 09:43:56 +00:00
m_pClient->m_CountryFlags.Render(g_Config.m_BrFilterCountryIndex, &Color, Rect.x, Rect.y, Rect.w, Rect.h);
if(g_Config.m_BrFilterCountry && UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, 0, &Rect))
m_Popup = POPUP_COUNTRY;
}
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterConnectingPlayers, Localize("Filter connecting players"), g_Config.m_BrFilterConnectingPlayers, &Button))
g_Config.m_BrFilterConnectingPlayers ^= 1;
CUIRect FilterTabs;
ServerFilter.HSplitBottom(23, &ServerFilter, &FilterTabs);
2014-12-14 15:45:18 +00:00
CUIRect ResetButton;
//ServerFilter.HSplitBottom(5.0f, &ServerFilter, 0);
ServerFilter.HSplitBottom(ms_ButtonHeight - 5.0f, &ServerFilter, &ResetButton);
2014-12-14 15:45:18 +00:00
2014-09-19 21:52:09 +00:00
// ddnet country filters
if(g_Config.m_UiPage == PAGE_DDNET)
{
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrIndicateFinished, Localize("Indicate map finish"), g_Config.m_BrIndicateFinished, &Button))
{
g_Config.m_BrIndicateFinished ^= 1;
if(g_Config.m_BrIndicateFinished)
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
}
if(g_Config.m_BrIndicateFinished)
{
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
if(DoButton_CheckBox(&g_Config.m_BrFilterUnfinishedMap, Localize("Unfinished map"), g_Config.m_BrFilterUnfinishedMap, &Button))
g_Config.m_BrFilterUnfinishedMap ^= 1;
}
else
{
g_Config.m_BrFilterUnfinishedMap = 0;
}
}
if(g_Config.m_UiPage == PAGE_DDNET || g_Config.m_UiPage == PAGE_KOG)
{
int Network = g_Config.m_UiPage == PAGE_DDNET ? IServerBrowser::NETWORK_DDNET : IServerBrowser::NETWORK_KOG;
2014-09-19 21:52:09 +00:00
// add more space
ServerFilter.HSplitTop(5.0f, 0, &ServerFilter);
2014-12-14 15:45:18 +00:00
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
ServerFilter.HSplitTop(120.0f, &ServerFilter, 0);
2014-09-19 21:52:09 +00:00
ServerFilter.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
Button.VSplitMid(&Button, &Button2);
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
static int s_ActivePage = 0;
2014-09-19 21:52:09 +00:00
2022-07-16 13:32:06 +00:00
static CButtonContainer s_CountriesButton;
if(DoButton_MenuTab(&s_CountriesButton, Localize("Countries"), s_ActivePage == 0, &Button, IGraphics::CORNER_TL))
2014-12-14 15:45:18 +00:00
{
s_ActivePage = 0;
}
2014-09-19 21:52:09 +00:00
2022-07-16 13:32:06 +00:00
static CButtonContainer s_TypesButton;
if(DoButton_MenuTab(&s_TypesButton, Localize("Types"), s_ActivePage == 1, &Button2, IGraphics::CORNER_TR))
2014-09-19 21:52:09 +00:00
{
2014-12-14 15:45:18 +00:00
s_ActivePage = 1;
}
2014-11-21 13:11:04 +00:00
2014-12-14 15:45:18 +00:00
if(s_ActivePage == 1)
{
char *pFilterExcludeTypes = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeTypes : g_Config.m_BrFilterExcludeTypesKoG;
int MaxTypes = ServerBrowser()->NumTypes(Network);
int NumTypes = ServerBrowser()->NumTypes(Network);
2014-12-14 15:45:18 +00:00
int PerLine = 3;
2014-09-19 21:52:09 +00:00
ServerFilter.HSplitTop(4.0f, 0, &ServerFilter);
ServerFilter.HSplitBottom(4.0f, &ServerFilter, 0);
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
const float TypesWidth = 40.0f;
const float TypesHeight = ServerFilter.h / ceil(MaxTypes / (float)PerLine);
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
CUIRect TypesRect, Left, Right;
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
static int s_aTypeButtons[64];
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
while(NumTypes > 0)
{
ServerFilter.HSplitTop(TypesHeight, &TypesRect, &ServerFilter);
TypesRect.VSplitMid(&Left, &Right);
for(int i = 0; i < PerLine && NumTypes > 0; i++, NumTypes--)
2014-09-19 21:52:09 +00:00
{
2014-12-14 15:45:18 +00:00
int TypeIndex = MaxTypes - NumTypes;
const char *pName = ServerBrowser()->GetType(Network, TypeIndex);
bool Active = !ServerBrowser()->DDNetFiltered(pFilterExcludeTypes, pName);
2014-09-19 21:52:09 +00:00
vec2 Pos = vec2(TypesRect.x + TypesRect.w * ((i + 0.5f) / (float)PerLine), TypesRect.y);
2014-12-14 15:45:18 +00:00
// correct pos
Pos.x -= TypesWidth / 2.0f;
// create button logic
CUIRect Rect;
Rect.x = Pos.x;
Rect.y = Pos.y;
Rect.w = TypesWidth;
Rect.h = TypesHeight;
int Click = UI()->DoButtonLogic(&s_aTypeButtons[TypeIndex], 0, &Rect);
if(Click == 1 || Click == 2)
2014-12-14 15:45:18 +00:00
{
// left/right click to toggle filter
if(pFilterExcludeTypes[0] == '\0')
{
// when all are active, only activate one
for(int j = 0; j < MaxTypes; ++j)
{
if(j != TypeIndex)
ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, ServerBrowser()->GetType(Network, j));
}
}
2014-12-14 15:45:18 +00:00
else
{
bool AllFilteredExceptUs = true;
for(int j = 0; j < MaxTypes; ++j)
{
if(j != TypeIndex && !ServerBrowser()->DDNetFiltered(pFilterExcludeTypes, ServerBrowser()->GetType(Network, j)))
{
AllFilteredExceptUs = false;
break;
}
}
// when last one is removed, reset (re-enable all)
if(AllFilteredExceptUs)
{
pFilterExcludeTypes[0] = '\0';
}
else if(Active)
{
ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, pName);
}
else
{
ServerBrowser()->DDNetFilterRem(pFilterExcludeTypes, pName);
}
}
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
}
else if(Click == 3)
{
2018-07-10 09:29:02 +00:00
// middle click to reset (re-enable all)
pFilterExcludeTypes[0] = '\0';
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
}
2014-12-14 15:45:18 +00:00
2019-04-26 22:11:15 +00:00
TextRender()->TextColor(1.0f, 1.0f, 1.0f, Active ? 1.0f : 0.2f);
UI()->DoLabel(&Rect, pName, FontSize, TEXTALIGN_CENTER);
2014-12-14 15:45:18 +00:00
TextRender()->TextColor(1.0, 1.0, 1.0, 1.0f);
2014-09-19 21:52:09 +00:00
}
2014-12-14 15:45:18 +00:00
}
}
else
{
char *pFilterExcludeCountries = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeCountries : g_Config.m_BrFilterExcludeCountriesKoG;
ServerFilter.HSplitTop(15.0f, &ServerFilter, &ServerFilter);
2014-12-14 15:45:18 +00:00
const float FlagWidth = 40.0f;
const float FlagHeight = 20.0f;
int MaxFlags = ServerBrowser()->NumCountries(Network);
int NumFlags = ServerBrowser()->NumCountries(Network);
int PerLine = MaxFlags > 8 ? 5 : 4;
2014-12-14 15:45:18 +00:00
CUIRect FlagsRect;
static int s_aFlagButtons[64];
while(NumFlags > 0)
{
ServerFilter.HSplitTop(23.0f, &FlagsRect, &ServerFilter);
2014-09-19 21:52:09 +00:00
2014-12-14 15:45:18 +00:00
for(int i = 0; i < PerLine && NumFlags > 0; i++, NumFlags--)
{
int CountryIndex = MaxFlags - NumFlags;
const char *pName = ServerBrowser()->GetCountryName(Network, CountryIndex);
bool Active = !ServerBrowser()->DDNetFiltered(pFilterExcludeCountries, pName);
int FlagID = ServerBrowser()->GetCountryFlag(Network, CountryIndex);
2014-12-14 15:45:18 +00:00
vec2 Pos = vec2(FlagsRect.x + FlagsRect.w * ((i + 0.5f) / (float)PerLine), FlagsRect.y);
2014-12-14 15:45:18 +00:00
// correct pos
Pos.x -= FlagWidth / 2.0f;
Pos.y -= FlagHeight / 2.0f;
// create button logic
CUIRect Rect;
Rect.x = Pos.x;
Rect.y = Pos.y;
Rect.w = FlagWidth;
Rect.h = FlagHeight;
2014-09-19 21:52:09 +00:00
int Click = UI()->DoButtonLogic(&s_aFlagButtons[CountryIndex], 0, &Rect);
if(Click == 1 || Click == 2)
2014-12-14 15:45:18 +00:00
{
// left/right click to toggle filter
if(pFilterExcludeCountries[0] == '\0')
{
// when all are active, only activate one
for(int j = 0; j < MaxFlags; ++j)
{
if(j != CountryIndex)
ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, ServerBrowser()->GetCountryName(Network, j));
}
}
2014-12-14 15:45:18 +00:00
else
{
bool AllFilteredExceptUs = true;
for(int j = 0; j < MaxFlags; ++j)
{
if(j != CountryIndex && !ServerBrowser()->DDNetFiltered(pFilterExcludeCountries, ServerBrowser()->GetCountryName(Network, j)))
{
AllFilteredExceptUs = false;
break;
}
}
// when last one is removed, reset (re-enable all)
if(AllFilteredExceptUs)
{
pFilterExcludeCountries[0] = '\0';
}
else if(Active)
{
ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, pName);
}
else
{
ServerBrowser()->DDNetFilterRem(pFilterExcludeCountries, pName);
}
}
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
}
else if(Click == 3)
{
2018-07-10 09:29:02 +00:00
// middle click to reset (re-enable all)
pFilterExcludeCountries[0] = '\0';
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
}
2014-09-19 21:52:09 +00:00
2019-04-26 22:11:15 +00:00
ColorRGBA Color(1.0f, 1.0f, 1.0f, Active ? 1.0f : 0.2f);
2021-07-12 09:43:56 +00:00
m_pClient->m_CountryFlags.Render(FlagID, &Color, Pos.x, Pos.y, FlagWidth, FlagHeight);
2014-12-14 15:45:18 +00:00
}
2014-09-19 21:52:09 +00:00
}
}
}
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ClearButton;
2014-12-14 15:45:18 +00:00
if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &ResetButton))
2008-09-04 18:42:26 +00:00
{
g_Config.m_BrFilterString[0] = 0;
g_Config.m_BrExcludeString[0] = 0;
2010-05-29 07:25:38 +00:00
g_Config.m_BrFilterFull = 0;
g_Config.m_BrFilterEmpty = 0;
g_Config.m_BrFilterSpectators = 0;
g_Config.m_BrFilterFriends = 0;
g_Config.m_BrFilterCountry = 0;
g_Config.m_BrFilterCountryIndex = -1;
2010-05-29 07:25:38 +00:00
g_Config.m_BrFilterPw = 0;
g_Config.m_BrFilterGametype[0] = 0;
g_Config.m_BrFilterGametypeStrict = 0;
g_Config.m_BrFilterConnectingPlayers = 1;
g_Config.m_BrFilterUnfinishedMap = 0;
g_Config.m_BrFilterServerAddress[0] = 0;
g_Config.m_BrFilterExcludeCountries[0] = 0;
g_Config.m_BrFilterExcludeTypes[0] = 0;
if(g_Config.m_UiPage == PAGE_DDNET || g_Config.m_UiPage == PAGE_KOG)
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
else
Client()->ServerBrowserUpdate();
}
2008-09-04 18:42:26 +00:00
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderServerbrowserServerDetail(CUIRect View)
2008-09-04 18:42:26 +00:00
{
2010-05-29 07:25:38 +00:00
CUIRect ServerDetails = View;
CUIRect ServerScoreBoard, ServerHeader;
const CServerInfo *pSelectedServer = ServerBrowser()->SortedGet(m_SelectedIndex);
2008-09-04 18:42:26 +00:00
// split off a piece to use for scoreboard
2011-03-23 12:06:35 +00:00
ServerDetails.HSplitTop(90.0f, &ServerDetails, &ServerScoreBoard);
ServerDetails.HSplitBottom(2.5f, &ServerDetails, 0x0);
2008-09-04 18:42:26 +00:00
// server details
CTextCursor Cursor;
2010-05-29 07:25:38 +00:00
const float FontSize = 12.0f;
ServerDetails.HSplitTop(ms_ListheaderHeight, &ServerHeader, &ServerDetails);
ServerHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
ServerDetails.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f);
UI()->DoLabel(&ServerHeader, Localize("Server details"), FontSize + 2.0f, TEXTALIGN_CENTER);
if(pSelectedServer)
2008-09-04 18:42:26 +00:00
{
ServerDetails.VSplitLeft(5.0f, 0, &ServerDetails);
ServerDetails.Margin(3.0f, &ServerDetails);
2010-05-29 07:25:38 +00:00
CUIRect Row;
static CLocConstString s_aLabels[] = {
"Version", // Localize - these strings are localized within CLocConstString
2010-05-31 11:07:58 +00:00
"Game type",
"Ping"};
2010-05-29 07:25:38 +00:00
CUIRect LeftColumn;
CUIRect RightColumn;
2010-05-29 07:25:38 +00:00
//
{
2010-05-29 07:25:38 +00:00
CUIRect Button;
ServerDetails.HSplitBottom(20.0f, &ServerDetails, &Button);
CUIRect ButtonAddFav;
CUIRect ButtonLeakIp;
Button.VSplitMid(&ButtonAddFav, &ButtonLeakIp);
ButtonAddFav.VSplitLeft(5.0f, 0, &ButtonAddFav);
2010-05-29 07:25:38 +00:00
static int s_AddFavButton = 0;
static int s_LeakIpButton = 0;
if(DoButton_CheckBox_Tristate(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &ButtonAddFav))
{
if(pSelectedServer->m_Favorite != TRISTATE::NONE)
{
Favorites()->Remove(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses);
}
2008-09-04 18:42:26 +00:00
else
{
Favorites()->Add(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses);
if(g_Config.m_UiPage == PAGE_LAN)
{
Favorites()->AllowPing(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses, true);
}
}
Client()->ServerBrowserUpdate();
}
if(pSelectedServer->m_Favorite != TRISTATE::NONE)
{
if(DoButton_CheckBox_Tristate(&s_LeakIpButton, Localize("Leak IP"), pSelectedServer->m_FavoriteAllowPing, &ButtonLeakIp))
{
Favorites()->AllowPing(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses, pSelectedServer->m_FavoriteAllowPing == TRISTATE::NONE);
}
Client()->ServerBrowserUpdate();
}
2008-09-04 18:42:26 +00:00
}
2010-05-29 07:25:38 +00:00
ServerDetails.VSplitLeft(5.0f, 0x0, &ServerDetails);
ServerDetails.VSplitLeft(80.0f, &LeftColumn, &RightColumn);
2020-10-26 14:14:07 +00:00
for(auto &Label : s_aLabels)
2008-09-04 18:42:26 +00:00
{
2010-05-29 07:25:38 +00:00
LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn);
UI()->DoLabel(&Row, Localize(Label), FontSize, TEXTALIGN_LEFT);
2008-09-04 18:42:26 +00:00
}
2010-05-29 07:25:38 +00:00
RightColumn.HSplitTop(15.0f, &Row, &RightColumn);
TextRender()->SetCursor(&Cursor, Row.x, Row.y + (15.f - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Row.w;
TextRender()->TextEx(&Cursor, pSelectedServer->m_aVersion, -1);
2010-05-29 07:25:38 +00:00
RightColumn.HSplitTop(15.0f, &Row, &RightColumn);
TextRender()->SetCursor(&Cursor, Row.x, Row.y + (15.f - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Row.w;
TextRender()->TextEx(&Cursor, pSelectedServer->m_aGameType, -1);
2010-05-29 07:25:38 +00:00
char aTemp[16];
Add client-side HTTP server info Summary ======= The idea of this is that clients will not have to ping each server for server infos which takes long, leaks the client's IP address even to servers the user does not join and is a DoS vector of the game servers for attackers. For the Internet, DDNet and KoG tab, the server list is entirely fetched from the master server, filtering out servers that don't belong into the list. The favorites tab is also supposed to work that way, except for servers that are marked as "also ping this server if it's not in the master server list". The LAN tab continues to broadcast the server info packet to find servers in the LAN. How does it work? ================= The client ships with a list of master server list URLs. On first start, the client checks which of these work and selects the fastest one. Querying the server list is a HTTP GET request on that URL. The response is a JSON document that contains server infos, server addresses as URLs and an approximate location. It can also contain a legacy server list which is a list of bare IP addresses similar to the functionality the old master servers provided via UDP. This allows us to backtrack on the larger update if it won't work out. Lost functionality ================== (also known as user-visible changes) Since the client doesn't ping each server in the list anymore, it has no way of knowing its latency to the servers. This is alleviated a bit by providing an approximate location for each server (continent) so the client only has to know its own location for approximating pings.
2018-07-11 20:46:04 +00:00
FormatServerbrowserPing(aTemp, sizeof(aTemp), pSelectedServer);
2010-05-29 07:25:38 +00:00
RightColumn.HSplitTop(15.0f, &Row, &RightColumn);
TextRender()->SetCursor(&Cursor, Row.x, Row.y + (15.f - FontSize) / 2.f, FontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Row.w;
TextRender()->TextEx(&Cursor, aTemp, -1);
2008-09-04 18:42:26 +00:00
}
2010-05-29 07:25:38 +00:00
2008-09-04 18:42:26 +00:00
// server scoreboard
ServerScoreBoard.HSplitBottom(23.0f, &ServerScoreBoard, 0x0);
2010-05-29 07:25:38 +00:00
2011-06-26 15:10:13 +00:00
if(pSelectedServer)
2008-09-04 18:42:26 +00:00
{
2014-01-08 05:15:56 +00:00
static int s_VoteList = 0;
static float s_ScrollValue = 0;
UiDoListboxStart(&s_VoteList, &ServerScoreBoard, 26.0f, Localize("Scoreboard"), "", pSelectedServer->m_NumReceivedClients, 1, -1, s_ScrollValue);
2014-01-08 05:15:56 +00:00
for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++)
{
CListboxItem Item = UiDoListboxNextItem(&pSelectedServer->m_aClients[i]);
2014-01-08 05:15:56 +00:00
if(!Item.m_Visible)
continue;
CUIRect Name, Clan, Score, Flag;
2014-01-08 05:15:56 +00:00
Item.m_Rect.HSplitTop(25.0f, &Name, &Item.m_Rect);
if(UiLogicGetCurrentClickedItem() == i)
2011-06-26 15:10:13 +00:00
{
if(pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_PLAYER)
m_pClient->Friends()->RemoveFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
else
m_pClient->Friends()->AddFriend(pSelectedServer->m_aClients[i].m_aName, pSelectedServer->m_aClients[i].m_aClan);
FriendlistOnUpdate();
Client()->ServerBrowserUpdate();
}
ColorRGBA Color = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ?
ColorRGBA(1.0f, 1.0f, 1.0f, (i % 2 + 1) * 0.05f) :
ColorRGBA(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f);
Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f);
Name.VSplitLeft(5.0f, 0, &Name);
Name.VSplitLeft(34.0f, &Score, &Name);
Name.VSplitRight(34.0f, &Name, &Flag);
Flag.HMargin(4.0f, &Flag);
Name.HSplitTop(12.0f, &Name, &Clan);
// score
2014-02-19 13:24:26 +00:00
char aTemp[16];
2014-02-19 13:24:26 +00:00
if(!pSelectedServer->m_aClients[i].m_Player)
2022-07-09 16:14:56 +00:00
str_copy(aTemp, "SPEC");
else if((str_find_nocase(pSelectedServer->m_aGameType, "race") || str_find_nocase(pSelectedServer->m_aGameType, "fastcap")) && g_Config.m_ClDDRaceScoreBoard)
2014-02-19 13:24:26 +00:00
{
if(pSelectedServer->m_aClients[i].m_Score == -9999 || pSelectedServer->m_aClients[i].m_Score == 0)
aTemp[0] = 0;
else
2021-06-23 05:05:49 +00:00
str_time((int64_t)abs(pSelectedServer->m_aClients[i].m_Score) * 100, TIME_HOURS, aTemp, sizeof(aTemp));
}
2014-02-19 13:24:26 +00:00
else
str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_aClients[i].m_Score);
2020-10-18 21:33:45 +00:00
float ScoreFontSize = 12.0f;
while(ScoreFontSize >= 4.0f && TextRender()->TextWidth(0, ScoreFontSize, aTemp, -1, -1.0f) > Score.w)
ScoreFontSize--;
TextRender()->SetCursor(&Cursor, Score.x, Score.y + (Score.h - ScoreFontSize) / 2.0f, ScoreFontSize, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
2014-02-19 13:24:26 +00:00
Cursor.m_LineWidth = Score.w;
TextRender()->TextEx(&Cursor, aTemp, -1);
// name
TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Name.w;
const char *pName = pSelectedServer->m_aClients[i].m_aName;
bool Printed = false;
2010-05-29 07:25:38 +00:00
if(g_Config.m_BrFilterString[0])
2008-09-04 18:42:26 +00:00
{
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
// highlight the parts that matches
const char *pFilteredStr = str_utf8_find_nocase(pName, aFilterStr);
if(pFilteredStr)
{
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredStr + str_length(aFilterStr), -1);
Printed = true;
break;
}
}
}
if(!Printed)
2010-05-29 07:25:38 +00:00
TextRender()->TextEx(&Cursor, pName, -1);
// clan
TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Cursor.m_LineWidth = Clan.w;
const char *pClan = pSelectedServer->m_aClients[i].m_aClan;
Printed = false;
if(g_Config.m_BrFilterString[0])
{
const char *pStr = g_Config.m_BrFilterString;
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
{
// highlight the parts that matches
const char *pFilteredString = str_utf8_find_nocase(pClan, aFilterStr);
if(pFilteredString)
{
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredString - pClan));
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString, str_length(aFilterStr));
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
TextRender()->TextEx(&Cursor, pFilteredString + str_length(aFilterStr), -1);
Printed = true;
break;
}
}
}
if(!Printed)
TextRender()->TextEx(&Cursor, pClan, -1);
// flag
ColorRGBA FColor(1.0f, 1.0f, 1.0f, 0.5f);
2021-07-12 09:43:56 +00:00
m_pClient->m_CountryFlags.Render(pSelectedServer->m_aClients[i].m_Country, &FColor, Flag.x, Flag.y, Flag.w, Flag.h);
}
2014-01-10 11:22:48 +00:00
UiDoListboxEnd(&s_ScrollValue, 0);
}
2008-09-04 18:42:26 +00:00
}
2011-06-26 15:10:13 +00:00
void CMenus::FriendlistOnUpdate()
{
m_vFriends.clear();
2011-06-26 15:10:13 +00:00
for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i)
m_vFriends.emplace_back(m_pClient->Friends()->GetFriend(i));
std::sort(m_vFriends.begin(), m_vFriends.end());
2011-06-26 15:10:13 +00:00
}
2011-03-23 12:06:35 +00:00
void CMenus::RenderServerbrowserFriends(CUIRect View)
2008-09-04 18:42:26 +00:00
{
2011-06-26 15:10:13 +00:00
static int s_Inited = 0;
if(!s_Inited)
{
FriendlistOnUpdate();
s_Inited = 1;
}
2011-03-23 12:06:35 +00:00
CUIRect ServerFriends = View, FilterHeader;
2011-06-26 15:10:13 +00:00
const float FontSize = 10.0f;
2011-03-23 12:06:35 +00:00
ServerFriends.HSplitBottom(18.0f, &ServerFriends, NULL);
2011-03-23 12:06:35 +00:00
// header
ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends);
FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
ServerFriends.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f);
UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_CENTER);
2011-03-23 12:06:35 +00:00
CUIRect Button, List;
2010-05-29 07:25:38 +00:00
2011-03-23 12:06:35 +00:00
ServerFriends.Margin(3.0f, &ServerFriends);
2011-06-26 15:10:13 +00:00
ServerFriends.VMargin(3.0f, &ServerFriends);
2011-03-23 12:06:35 +00:00
ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
2010-05-29 07:25:38 +00:00
2011-03-23 12:06:35 +00:00
// friends list(remove friend)
static float s_ScrollValue = 0;
if(m_FriendlistSelectedIndex >= (int)m_vFriends.size())
m_FriendlistSelectedIndex = m_vFriends.size() - 1;
UiDoListboxStart(&m_vFriends, &List, 30.0f, "", "", m_vFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue);
std::sort(m_vFriends.begin(), m_vFriends.end());
for(auto &Friend : m_vFriends)
2011-03-23 12:06:35 +00:00
{
CListboxItem Item = UiDoListboxNextItem(&Friend.m_NumFound, false, false);
2011-03-23 12:06:35 +00:00
if(Item.m_Visible)
{
2011-06-26 15:10:13 +00:00
Item.m_Rect.Margin(1.5f, &Item.m_Rect);
CUIRect OnState;
Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState);
Item.m_Rect.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.1f), IGraphics::CORNER_L, 4.0f);
2011-06-26 15:10:13 +00:00
Item.m_Rect.VMargin(2.5f, &Item.m_Rect);
Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button);
UI()->DoLabel(&Item.m_Rect, Friend.m_pFriendInfo->m_aName, FontSize, TEXTALIGN_LEFT);
UI()->DoLabel(&Button, Friend.m_pFriendInfo->m_aClan, FontSize, TEXTALIGN_LEFT);
2011-06-26 15:10:13 +00:00
OnState.Draw(Friend.m_NumFound ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(1.0f, 0.0f, 0.0f, 0.25f), IGraphics::CORNER_R, 4.0f);
OnState.HMargin((OnState.h - FontSize) / 3, &OnState);
2011-06-26 15:10:13 +00:00
OnState.VMargin(5.0f, &OnState);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%i", Friend.m_NumFound);
UI()->DoLabel(&OnState, aBuf, FontSize + 2, TEXTALIGN_RIGHT);
2011-03-23 12:06:35 +00:00
}
}
2011-06-26 15:10:13 +00:00
bool Activated = false;
m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated);
// activate found server with friend
if(Activated && m_vFriends[m_FriendlistSelectedIndex].m_NumFound)
2011-06-26 15:10:13 +00:00
{
bool Found = false;
int NumServers = ServerBrowser()->NumSortedServers();
for(int i = 0; i < NumServers && !Found; i++)
2011-06-26 15:10:13 +00:00
{
int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex + i + 1) % NumServers : i;
2011-06-26 15:10:13 +00:00
const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
if(pItem->m_FriendState != IFriends::FRIEND_NO)
{
for(int j = 0; j < pItem->m_NumReceivedClients && !Found; ++j)
2011-06-26 15:10:13 +00:00
{
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO &&
((g_Config.m_ClFriendsIgnoreClan && m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0]) || str_quickhash(pItem->m_aClients[j].m_aClan) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash) &&
(!m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] ||
str_quickhash(pItem->m_aClients[j].m_aName) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash))
2011-06-26 15:10:13 +00:00
{
2022-07-09 16:14:56 +00:00
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
2011-06-26 15:10:13 +00:00
m_ScrollOffset = ItemIndex;
m_SelectedIndex = ItemIndex;
Found = true;
}
}
}
}
}
2011-03-23 12:06:35 +00:00
ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
if(m_FriendlistSelectedIndex != -1)
{
2022-07-16 13:32:06 +00:00
static CButtonContainer s_RemoveButton;
2011-03-23 12:06:35 +00:00
if(DoButton_Menu(&s_RemoveButton, Localize("Remove"), 0, &Button))
{
const CFriendInfo *pRemoveFriend = m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo;
const bool IsPlayer = m_pClient->Friends()->GetFriendState(pRemoveFriend->m_aName, pRemoveFriend->m_aClan) == IFriends::FRIEND_PLAYER;
char aBuf[256];
str_format(aBuf, sizeof(aBuf),
IsPlayer ? Localize("Are you sure that you want to remove the player '%s' from your friends list?") : Localize("Are you sure that you want to remove the clan '%s' from your friends list?"),
IsPlayer ? pRemoveFriend->m_aName : pRemoveFriend->m_aClan);
PopupConfirm(Localize("Remove friend"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmRemoveFriend);
}
2011-03-23 12:06:35 +00:00
}
// add friend
if(m_pClient->Friends()->NumFriends() < IFriends::MAX_FRIENDS)
{
ServerFriends.HSplitTop(10.0f, 0, &ServerFriends);
ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends);
char aBuf[64];
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name"));
UI()->DoLabel(&Button, aBuf, FontSize + 2, TEXTALIGN_LEFT);
2011-03-23 12:06:35 +00:00
Button.VSplitLeft(80.0f, 0, &Button);
static char s_aName[MAX_NAME_LENGTH] = {0};
static float s_OffsetName = 0.0f;
UI()->DoEditBox(&s_aName, &Button, s_aName, sizeof(s_aName), FontSize + 2, &s_OffsetName);
2011-03-23 12:06:35 +00:00
ServerFriends.HSplitTop(3.0f, 0, &ServerFriends);
ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends);
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan"));
UI()->DoLabel(&Button, aBuf, FontSize + 2, TEXTALIGN_LEFT);
2011-03-23 12:06:35 +00:00
Button.VSplitLeft(80.0f, 0, &Button);
static char s_aClan[MAX_CLAN_LENGTH] = {0};
static float s_OffsetClan = 0.0f;
UI()->DoEditBox(&s_aClan, &Button, s_aClan, sizeof(s_aClan), FontSize + 2, &s_OffsetClan);
2011-03-23 12:06:35 +00:00
ServerFriends.HSplitTop(3.0f, 0, &ServerFriends);
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_AddButton;
2011-06-26 15:10:13 +00:00
if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button))
2011-03-23 12:06:35 +00:00
{
m_pClient->Friends()->AddFriend(s_aName, s_aClan);
2011-06-26 15:10:13 +00:00
FriendlistOnUpdate();
2011-03-23 12:06:35 +00:00
Client()->ServerBrowserUpdate();
}
}
}
void CMenus::PopupConfirmRemoveFriend()
{
const CFriendInfo *pRemoveFriend = m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo;
m_pClient->Friends()->RemoveFriend(pRemoveFriend->m_aName, pRemoveFriend->m_aClan);
FriendlistOnUpdate();
Client()->ServerBrowserUpdate();
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderServerbrowser(CUIRect MainView)
2008-09-04 18:42:26 +00:00
{
/*
+-----------------+ +-------+
| | | |
| | | tool |
| server list | | box |
| | | |
| | | |
+-----------------+ | |
status box tab +-------+
2008-09-04 18:42:26 +00:00
*/
2010-05-29 07:25:38 +00:00
CUIRect ServerList, ToolBox;
2010-05-29 07:25:38 +00:00
// background
MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
MainView.Margin(10.0f, &MainView);
// create server list, status box, tab bar and tool box area
MainView.VSplitRight(205.0f, &ServerList, &ToolBox);
ServerList.VSplitRight(5.0f, &ServerList, 0);
2008-09-04 18:42:26 +00:00
// server list
{
RenderServerbrowserServerList(ServerList);
}
2010-05-29 07:25:38 +00:00
int ToolboxPage = g_Config.m_UiToolboxPage;
// tool box
{
ToolBox.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_ALL, 4.0f);
if(ToolboxPage == 0)
RenderServerbrowserFilters(ToolBox);
else if(ToolboxPage == 1)
RenderServerbrowserServerDetail(ToolBox);
else if(ToolboxPage == 2)
RenderServerbrowserFriends(ToolBox);
}
// tab bar
2008-09-04 18:42:26 +00:00
{
CUIRect TabBar;
ToolBox.HSplitBottom(18, &ToolBox, &TabBar);
2011-03-23 12:06:35 +00:00
CUIRect TabButton0, TabButton1, TabButton2;
float CurTabBarWidth = ToolBox.w;
TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton0, &TabBar);
TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton1, &TabBar);
TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton2, &TabBar);
ColorRGBA Active = ms_ColorTabbarActive;
ColorRGBA InActive = ms_ColorTabbarInactive;
ms_ColorTabbarActive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f);
ms_ColorTabbarInactive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f);
2010-05-29 07:25:38 +00:00
2022-07-16 13:32:06 +00:00
static CButtonContainer s_FiltersTab;
if(DoButton_MenuTab(&s_FiltersTab, Localize("Filter"), ToolboxPage == 0, &TabButton0, IGraphics::CORNER_BL, NULL, NULL, NULL, NULL, 4.0f))
2010-05-29 07:25:38 +00:00
ToolboxPage = 0;
2022-07-16 13:32:06 +00:00
static CButtonContainer s_InfoTab;
2020-10-26 01:10:55 +00:00
if(DoButton_MenuTab(&s_InfoTab, Localize("Info"), ToolboxPage == 1, &TabButton1, 0, NULL, NULL, NULL, NULL, 4.0f))
2010-05-29 07:25:38 +00:00
ToolboxPage = 1;
2008-09-04 18:42:26 +00:00
2022-07-16 13:32:06 +00:00
static CButtonContainer s_FriendsTab;
if(DoButton_MenuTab(&s_FriendsTab, Localize("Friends"), ToolboxPage == 2, &TabButton2, IGraphics::CORNER_BR, NULL, NULL, NULL, NULL, 4.0f))
2011-03-23 12:06:35 +00:00
ToolboxPage = 2;
2010-05-29 07:25:38 +00:00
ms_ColorTabbarActive = Active;
ms_ColorTabbarInactive = InActive;
g_Config.m_UiToolboxPage = ToolboxPage;
}
}
2011-06-26 15:10:13 +00:00
void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE)
{
((CMenus *)pUserData)->FriendlistOnUpdate();
((CMenus *)pUserData)->Client()->ServerBrowserUpdate();
}
}
void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
if(pResult->NumArguments() && g_Config.m_UiPage == PAGE_FAVORITES)
((CMenus *)pUserData)->ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
}