/* (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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "menus.h" void CMenus::RenderServerbrowserServerList(CUIRect View) { CUIRect Headers; CUIRect Status; View.HSplitTop(ms_ListheaderHeight, &Headers, &View); View.HSplitBottom(28.0f, &View, &Status); // split of the scrollbar RenderTools()->DrawUIRect(&Headers, vec4(1,1,1,0.25f), CUI::CORNER_T, 5.0f); Headers.VSplitRight(20.0f, &Headers, 0); struct CColumn { int m_ID; int m_Sort; CLocConstString m_Caption; int m_Direction; float m_Width; int m_Flags; CUIRect m_Rect; CUIRect m_Spacer; }; enum { FIXED=1, SPACER=2, COL_FLAG_LOCK=0, COL_FLAG_PURE, COL_FLAG_FAV, COL_NAME, COL_GAMETYPE, COL_MAP, COL_PLAYERS, COL_PING, COL_VERSION, }; static CColumn s_aCols[] = { {-1, -1, " ", -1, 2.0f, 0, {0}, {0}}, {COL_FLAG_LOCK, -1, " ", -1, 14.0f, 0, {0}, {0}}, {COL_FLAG_PURE, -1, " ", -1, 14.0f, 0, {0}, {0}}, {COL_FLAG_FAV, -1, " ", -1, 14.0f, 0, {0}, {0}}, {COL_NAME, IServerBrowser::SORT_NAME, "Name", 0, 300.0f, 0, {0}, {0}}, // Localize - these strings are localized within CLocConstString {COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, "Type", 1, 50.0f, 0, {0}, {0}}, {COL_MAP, IServerBrowser::SORT_MAP, "Map", 1, 100.0f, 0, {0}, {0}}, {COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 60.0f, 0, {0}, {0}}, {-1, -1, " ", 1, 10.0f, 0, {0}, {0}}, {COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.0f, FIXED, {0}, {0}}, }; // This is just for scripts/update_localization.py to work correctly (all other strings are already Localize()'d somewhere else). Don't remove! // Localize("Type"); int NumCols = sizeof(s_aCols)/sizeof(CColumn); // do layout for(int i = 0; i < NumCols; i++) { if(s_aCols[i].m_Direction == -1) { Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers); if(i+1 < NumCols) { //Cols[i].flags |= SPACER; Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers); } } } for(int i = NumCols-1; i >= 0; i--) { if(s_aCols[i].m_Direction == 1) { Headers.VSplitRight(s_aCols[i].m_Width, &Headers, &s_aCols[i].m_Rect); Headers.VSplitRight(2, &Headers, &s_aCols[i].m_Spacer); } } for(int i = 0; i < NumCols; i++) { if(s_aCols[i].m_Direction == 0) s_aCols[i].m_Rect = Headers; } // do headers for(int i = 0; i < NumCols; i++) { if(DoButton_GridHeader(s_aCols[i].m_Caption, s_aCols[i].m_Caption, g_Config.m_BrSort == s_aCols[i].m_Sort, &s_aCols[i].m_Rect)) { if(s_aCols[i].m_Sort != -1) { if(g_Config.m_BrSort == s_aCols[i].m_Sort) g_Config.m_BrSortOrder ^= 1; else g_Config.m_BrSortOrder = 0; g_Config.m_BrSort = s_aCols[i].m_Sort; } } } RenderTools()->DrawUIRect(&View, vec4(0,0,0,0.15f), 0, 0); CUIRect Scroll; View.VSplitRight(15, &View, &Scroll); int NumServers = ServerBrowser()->NumSortedServers(); // display important messages in the middle of the screen so no // users misses it { CUIRect MsgBox = View; MsgBox.y += View.h/3; if(m_ActivePage == PAGE_INTERNET && ServerBrowser()->IsRefreshingMasters()) UI()->DoLabelScaled(&MsgBox, Localize("Refreshing master servers"), 16.0f, 0); else if(!ServerBrowser()->NumServers()) UI()->DoLabelScaled(&MsgBox, Localize("No servers found"), 16.0f, 0); else if(ServerBrowser()->NumServers() && !NumServers) UI()->DoLabelScaled(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, 0); } int Num = (int)(View.h/s_aCols[0].m_Rect.h) + 1; static int s_ScrollBar = 0; static float s_ScrollValue = 0; Scroll.HMargin(5.0f, &Scroll); s_ScrollValue = DoScrollbarV(&s_ScrollBar, &Scroll, s_ScrollValue); int ScrollNum = NumServers-Num+1; if(ScrollNum > 0) { if(m_ScrollOffset) { s_ScrollValue = (float)(m_ScrollOffset)/ScrollNum; m_ScrollOffset = 0; } if(Input()->KeyPresses(KEY_MOUSE_WHEEL_UP) && UI()->MouseInside(&View)) s_ScrollValue -= 3.0f/ScrollNum; if(Input()->KeyPresses(KEY_MOUSE_WHEEL_DOWN) && UI()->MouseInside(&View)) s_ScrollValue += 3.0f/ScrollNum; } else ScrollNum = 0; if(m_SelectedIndex > -1) { for(int i = 0; i < m_NumInputEvents; i++) { int NewIndex = -1; if(m_aInputEvents[i].m_Flags&IInput::FLAG_PRESS) { if(m_aInputEvents[i].m_Key == KEY_DOWN) NewIndex = m_SelectedIndex + 1; if(m_aInputEvents[i].m_Key == KEY_UP) NewIndex = m_SelectedIndex - 1; } if(NewIndex > -1 && NewIndex < NumServers) { //scroll float IndexY = View.y - s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h + NewIndex*s_aCols[0].m_Rect.h; int Scroll = View.y > IndexY ? -1 : View.y+View.h < IndexY+s_aCols[0].m_Rect.h ? 1 : 0; if(Scroll) { if(Scroll < 0) { int NumScrolls = (View.y-IndexY+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; s_ScrollValue -= (1.0f/ScrollNum)*NumScrolls; } else { int NumScrolls = (IndexY+s_aCols[0].m_Rect.h-(View.y+View.h)+s_aCols[0].m_Rect.h-1.0f)/s_aCols[0].m_Rect.h; s_ScrollValue += (1.0f/ScrollNum)*NumScrolls; } } m_SelectedIndex = NewIndex; const CServerInfo *pItem = ServerBrowser()->SortedGet(m_SelectedIndex); str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); } } } if(s_ScrollValue < 0) s_ScrollValue = 0; if(s_ScrollValue > 1) s_ScrollValue = 1; // set clipping UI()->ClipEnable(&View); CUIRect OriginalView = View; View.y -= s_ScrollValue*ScrollNum*s_aCols[0].m_Rect.h; int NewSelected = -1; int NumPlayers = 0; m_SelectedIndex = -1; // reset friend counter for(int i = 0; i < m_lFriends.size(); m_lFriends[i++].m_NumFound = 0); for (int i = 0; i < NumServers; i++) { int ItemIndex = i; const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex); NumPlayers += g_Config.m_BrFilterSpectators ? pItem->m_NumPlayers : pItem->m_NumClients; CUIRect Row; CUIRect SelectHitBox; int Selected = str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0; //selected_index==ItemIndex; View.HSplitTop(17.0f, &Row, &View); SelectHitBox = Row; if(Selected) m_SelectedIndex = i; // update friend counter if(pItem->m_FriendState != IFriends::FRIEND_NO) { for(int j = 0; j < pItem->m_NumClients; ++j) { if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO) { unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName); unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan); for(int f = 0; f < m_lFriends.size(); ++f) { if(ClanHash == m_lFriends[f].m_pFriendInfo->m_ClanHash && (!m_lFriends[f].m_pFriendInfo->m_aName[0] || NameHash == m_lFriends[f].m_pFriendInfo->m_NameHash)) { m_lFriends[f].m_NumFound++; if(m_lFriends[f].m_pFriendInfo->m_aName[0]) break; } } } } } // make sure that only those in view can be selected if(Row.y+Row.h > OriginalView.y && Row.y < OriginalView.y+OriginalView.h) { if(Selected) { CUIRect r = Row; r.Margin(1.5f, &r); RenderTools()->DrawUIRect(&r, vec4(1,1,1,0.5f), CUI::CORNER_ALL, 4.0f); } // clip the selection if(SelectHitBox.y < OriginalView.y) // top { SelectHitBox.h -= OriginalView.y-SelectHitBox.y; SelectHitBox.y = OriginalView.y; } else if(SelectHitBox.y+SelectHitBox.h > OriginalView.y+OriginalView.h) // bottom SelectHitBox.h = OriginalView.y+OriginalView.h-SelectHitBox.y; if(UI()->DoButtonLogic(pItem, "", Selected, &SelectHitBox)) { NewSelected = ItemIndex; } } else { // reset active item, if not visible if(UI()->ActiveItem() == pItem) UI()->SetActiveItem(0); // don't render invisible items continue; } for(int c = 0; c < NumCols; c++) { 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; if(ID == COL_FLAG_LOCK) { if(pItem->m_Flags & SERVER_FLAG_PASSWORD) DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_LOCK, &Button); } else if(ID == COL_FLAG_PURE) { if( str_comp(pItem->m_aGameType, "DM") == 0 || str_comp(pItem->m_aGameType, "TDM") == 0 || str_comp(pItem->m_aGameType, "CTF") == 0) { // pure server } else { // unpure DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_UNPURE, &Button); } } else if(ID == COL_FLAG_FAV) { if(pItem->m_Favorite) DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Button); } else if(ID == COL_NAME) { CTextCursor Cursor; TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Button.w; if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit&IServerBrowser::QUICK_SERVERNAME)) { // highlight the parts that matches const char *pStr = str_find_nocase(pItem->m_aName, g_Config.m_BrFilterString); if(pStr) { TextRender()->TextEx(&Cursor, pItem->m_aName, (int)(pStr-pItem->m_aName)); TextRender()->TextColor(0.4f,0.4f,1.0f,1); TextRender()->TextEx(&Cursor, pStr, str_length(g_Config.m_BrFilterString)); TextRender()->TextColor(1,1,1,1); TextRender()->TextEx(&Cursor, pStr+str_length(g_Config.m_BrFilterString), -1); } else TextRender()->TextEx(&Cursor, pItem->m_aName, -1); } else TextRender()->TextEx(&Cursor, pItem->m_aName, -1); } else if(ID == COL_MAP) { CTextCursor Cursor; TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f * UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Button.w; if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit&IServerBrowser::QUICK_MAPNAME)) { // highlight the parts that matches const char *pStr = str_find_nocase(pItem->m_aMap, g_Config.m_BrFilterString); if(pStr) { TextRender()->TextEx(&Cursor, pItem->m_aMap, (int)(pStr-pItem->m_aMap)); TextRender()->TextColor(0.4f,0.4f,1.0f,1); TextRender()->TextEx(&Cursor, pStr, str_length(g_Config.m_BrFilterString)); TextRender()->TextColor(1,1,1,1); TextRender()->TextEx(&Cursor, pStr+str_length(g_Config.m_BrFilterString), -1); } else TextRender()->TextEx(&Cursor, pItem->m_aMap, -1); } else TextRender()->TextEx(&Cursor, pItem->m_aMap, -1); } else if(ID == COL_PLAYERS) { CUIRect Icon; Button.VMargin(4.0f, &Button); if(pItem->m_FriendState != IFriends::FRIEND_NO) { Button.VSplitLeft(Button.h, &Icon, &Button); Icon.Margin(2.0f, &Icon); DoButton_Icon(IMAGE_BROWSEICONS, SPRITE_BROWSE_HEART, &Icon); } if(g_Config.m_BrFilterSpectators) str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumPlayers, pItem->m_MaxPlayers); else str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumClients, pItem->m_MaxClients); if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit&IServerBrowser::QUICK_PLAYER)) TextRender()->TextColor(0.4f,0.4f,1.0f,1); UI()->DoLabelScaled(&Button, aTemp, 12.0f, 1); TextRender()->TextColor(1,1,1,1); } else if(ID == COL_PING) { str_format(aTemp, sizeof(aTemp), "%i", pItem->m_Latency); UI()->DoLabelScaled(&Button, aTemp, 12.0f, 1); } else if(ID == COL_VERSION) { const char *pVersion = pItem->m_aVersion; UI()->DoLabelScaled(&Button, pVersion, 12.0f, 1); } else if(ID == COL_GAMETYPE) { CTextCursor Cursor; TextRender()->SetCursor(&Cursor, Button.x, Button.y, 12.0f*UI()->Scale(), TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Button.w; TextRender()->TextEx(&Cursor, pItem->m_aGameType, -1); } } } UI()->ClipDisable(); if(NewSelected != -1) { // select the new server const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected); str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); if(Input()->MouseDoubleClick()) Client()->Connect(g_Config.m_UiServerAddress); } RenderTools()->DrawUIRect(&Status, vec4(1,1,1,0.25f), CUI::CORNER_B, 5.0f); Status.Margin(5.0f, &Status); // render quick search CUIRect QuickSearch, Button; Status.VSplitLeft(240.0f, &QuickSearch, &Status); const char *pLabel = Localize("Quick search:"); UI()->DoLabelScaled(&QuickSearch, pLabel, 12.0f, -1); float w = TextRender()->TextWidth(0, 12.0f, pLabel, -1); QuickSearch.VSplitLeft(w, 0, &QuickSearch); QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch); QuickSearch.VSplitLeft(240.0f-w-22.0f, &QuickSearch, &Button); static float Offset = 0.0f; if(DoEditBox(&g_Config.m_BrFilterString, &QuickSearch, g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString), 12.0f, &Offset, false, CUI::CORNER_L)) Client()->ServerBrowserUpdate(); // clear button { static int s_ClearButton = 0; RenderTools()->DrawUIRect(&Button, vec4(1,1,1,0.33f)*ButtonColorMul(&s_ClearButton), CUI::CORNER_R, 3.0f); UI()->DoLabel(&Button, "x", Button.h*ms_FontmodHeight, 0); if(UI()->DoButtonLogic(&s_ClearButton, "x", 0, &Button)) { g_Config.m_BrFilterString[0] = 0; UI()->SetActiveItem(&g_Config.m_BrFilterString); Client()->ServerBrowserUpdate(); } } // render status char aBuf[128]; if(ServerBrowser()->IsRefreshing()) { char aBuf2[64]; char aBuf3[64]; str_format(aBuf3, sizeof(aBuf3), Localize("%d%% loaded"), ServerBrowser()->LoadingProgression()); str_format(aBuf2, sizeof(aBuf2), Localize("%d of %d servers, %d players"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers(), NumPlayers); str_format(aBuf, sizeof(aBuf), "%s, %s", aBuf3, aBuf2); } else str_format(aBuf, sizeof(aBuf), Localize("%d of %d servers, %d players"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers(), NumPlayers); Status.VSplitRight(TextRender()->TextWidth(0, 14.0f, aBuf, -1), 0, &Status); UI()->DoLabelScaled(&Status, aBuf, 14.0f, -1); } void CMenus::RenderServerbrowserFilters(CUIRect View) { CUIRect ServerFilter = View, FilterHeader; const float FontSize = 12.0f; ServerFilter.HSplitBottom(42.5f, &ServerFilter, 0); // server filter ServerFilter.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFilter); RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); RenderTools()->DrawUIRect(&ServerFilter, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f); UI()->DoLabelScaled(&FilterHeader, Localize("Server filter"), FontSize+2.0f, 0); CUIRect Button; ServerFilter.VSplitLeft(5.0f, 0, &ServerFilter); ServerFilter.Margin(3.0f, &ServerFilter); ServerFilter.VMargin(5.0f, &ServerFilter); ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox(&g_Config.m_BrFilterEmpty, Localize("Has people playing"), g_Config.m_BrFilterEmpty, &Button)) g_Config.m_BrFilterEmpty ^= 1; 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)) g_Config.m_BrFilterFull ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button)) 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)) g_Config.m_BrFilterPw ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox((char *)&g_Config.m_BrFilterCompatversion, Localize("Compatible version"), g_Config.m_BrFilterCompatversion, &Button)) g_Config.m_BrFilterCompatversion ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox((char *)&g_Config.m_BrFilterPure, Localize("Standard gametype"), g_Config.m_BrFilterPure, &Button)) g_Config.m_BrFilterPure ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox((char *)&g_Config.m_BrFilterPureMap, Localize("Standard map"), g_Config.m_BrFilterPureMap, &Button)) g_Config.m_BrFilterPureMap ^= 1; ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter); if (DoButton_CheckBox((char *)&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button)) g_Config.m_BrFilterGametypeStrict ^= 1; ServerFilter.HSplitTop(5.0f, 0, &ServerFilter); ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); UI()->DoLabelScaled(&Button, Localize("Game types:"), FontSize, -1); Button.VSplitRight(60.0f, 0, &Button); ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); static float Offset = 0.0f; if(DoEditBox(&g_Config.m_BrFilterGametype, &Button, g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype), FontSize, &Offset)) Client()->ServerBrowserUpdate(); { ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); CUIRect EditBox; Button.VSplitRight(60.0f, &Button, &EditBox); UI()->DoLabelScaled(&Button, Localize("Maximum ping:"), FontSize, -1); char aBuf[5]; str_format(aBuf, sizeof(aBuf), "%d", g_Config.m_BrFilterPing); static float Offset = 0.0f; DoEditBox(&g_Config.m_BrFilterPing, &EditBox, aBuf, sizeof(aBuf), FontSize, &Offset); g_Config.m_BrFilterPing = clamp(str_toint(aBuf), 0, 999); } // server address ServerFilter.HSplitTop(3.0f, 0, &ServerFilter); ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter); UI()->DoLabelScaled(&Button, Localize("Server address:"), FontSize, -1); Button.VSplitRight(60.0f, 0, &Button); static float OffsetAddr = 0.0f; if(DoEditBox(&g_Config.m_BrFilterServerAddress, &Button, g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress), FontSize, &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; float OldWidth = Rect.w; Rect.w = Rect.h*2; Rect.x += (OldWidth-Rect.w)/2.0f; vec4 Color(1.0f, 1.0f, 1.0f, g_Config.m_BrFilterCountry?1.0f: 0.5f); m_pClient->m_pCountryFlags->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.HSplitBottom(5.0f, &ServerFilter, 0); ServerFilter.HSplitBottom(ms_ButtonHeight-2.0f, &ServerFilter, &Button); static int s_ClearButton = 0; if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &Button)) { g_Config.m_BrFilterString[0] = 0; 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; g_Config.m_BrFilterPw = 0; g_Config.m_BrFilterPing = 999; g_Config.m_BrFilterGametype[0] = 0; g_Config.m_BrFilterGametypeStrict = 0; g_Config.m_BrFilterServerAddress[0] = 0; g_Config.m_BrFilterPure = 0; g_Config.m_BrFilterPureMap = 0; g_Config.m_BrFilterCompatversion = 0; Client()->ServerBrowserUpdate(); } } void CMenus::RenderServerbrowserServerDetail(CUIRect View) { CUIRect ServerDetails = View; CUIRect ServerScoreBoard, ServerHeader; const CServerInfo *pSelectedServer = ServerBrowser()->SortedGet(m_SelectedIndex); // split off a piece to use for scoreboard ServerDetails.HSplitTop(90.0f, &ServerDetails, &ServerScoreBoard); ServerDetails.HSplitBottom(2.5f, &ServerDetails, 0x0); // server details CTextCursor Cursor; const float FontSize = 12.0f; ServerDetails.HSplitTop(ms_ListheaderHeight, &ServerHeader, &ServerDetails); RenderTools()->DrawUIRect(&ServerHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); RenderTools()->DrawUIRect(&ServerDetails, vec4(0,0,0,0.15f), CUI::CORNER_B, 4.0f); UI()->DoLabelScaled(&ServerHeader, Localize("Server details"), FontSize+2.0f, 0); if (pSelectedServer) { ServerDetails.VSplitLeft(5.0f, 0, &ServerDetails); ServerDetails.Margin(3.0f, &ServerDetails); CUIRect Row; static CLocConstString s_aLabels[] = { "Version", // Localize - these strings are localized within CLocConstString "Game type", "Ping"}; CUIRect LeftColumn; CUIRect RightColumn; // { CUIRect Button; ServerDetails.HSplitBottom(20.0f, &ServerDetails, &Button); Button.VSplitLeft(5.0f, 0, &Button); static int s_AddFavButton = 0; if(DoButton_CheckBox(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &Button)) { if(pSelectedServer->m_Favorite) ServerBrowser()->RemoveFavorite(pSelectedServer->m_NetAddr); else ServerBrowser()->AddFavorite(pSelectedServer->m_NetAddr); } } ServerDetails.VSplitLeft(5.0f, 0x0, &ServerDetails); ServerDetails.VSplitLeft(80.0f, &LeftColumn, &RightColumn); for (unsigned int i = 0; i < sizeof(s_aLabels) / sizeof(s_aLabels[0]); i++) { LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn); UI()->DoLabelScaled(&Row, s_aLabels[i], FontSize, -1); } RightColumn.HSplitTop(15.0f, &Row, &RightColumn); TextRender()->SetCursor(&Cursor, Row.x, Row.y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Row.w; TextRender()->TextEx(&Cursor, pSelectedServer->m_aVersion, -1); RightColumn.HSplitTop(15.0f, &Row, &RightColumn); TextRender()->SetCursor(&Cursor, Row.x, Row.y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Row.w; TextRender()->TextEx(&Cursor, pSelectedServer->m_aGameType, -1); char aTemp[16]; str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_Latency); RightColumn.HSplitTop(15.0f, &Row, &RightColumn); TextRender()->SetCursor(&Cursor, Row.x, Row.y, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Row.w; TextRender()->TextEx(&Cursor, aTemp, -1); } // server scoreboard //ServerScoreBoard.HSplitBottom(20.0f, &ServerScoreBoard, 0x0); if(pSelectedServer) { static int s_VoteList = 0; static float s_ScrollValue = 0; UiDoListboxStart(&s_VoteList, &ServerScoreBoard, 26.0f, Localize("Scoreboard"), "", pSelectedServer->m_NumClients, 1, -1, s_ScrollValue); for (int i = 0; i < pSelectedServer->m_NumClients; i++) { CListboxItem Item = UiDoListboxNextItem(&i); if(!Item.m_Visible) continue; CUIRect Name, Clan, Score, Flag; Item.m_Rect.HSplitTop(25.0f, &Name, &Item.m_Rect); if(UI()->DoButtonLogic(&pSelectedServer->m_aClients[i], "", 0, &Name)) { 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(); } vec4 Colour = pSelectedServer->m_aClients[i].m_FriendState == IFriends::FRIEND_NO ? vec4(1.0f, 1.0f, 1.0f, (i%2+1)*0.05f) : vec4(0.5f, 1.0f, 0.5f, 0.15f+(i%2+1)*0.05f); RenderTools()->DrawUIRect(&Name, Colour, CUI::CORNER_ALL, 4.0f); Name.VSplitLeft(5.0f, 0, &Name); Name.VSplitLeft(30.0f, &Score, &Name); Name.VSplitRight(34.0f, &Name, &Flag); Flag.HMargin(4.0f, &Flag); Name.HSplitTop(11.0f, &Name, &Clan); // score if(pSelectedServer->m_aClients[i].m_Player) { char aTemp[16]; str_format(aTemp, sizeof(aTemp), "%d", pSelectedServer->m_aClients[i].m_Score); TextRender()->SetCursor(&Cursor, Score.x, Score.y+(Score.h-FontSize)/4.0f, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Score.w; TextRender()->TextEx(&Cursor, aTemp, -1); } // name TextRender()->SetCursor(&Cursor, Name.x, Name.y, FontSize-2, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Name.w; const char *pName = pSelectedServer->m_aClients[i].m_aName; if(g_Config.m_BrFilterString[0]) { // highlight the parts that matches const char *s = str_find_nocase(pName, g_Config.m_BrFilterString); if(s) { TextRender()->TextEx(&Cursor, pName, (int)(s-pName)); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); TextRender()->TextEx(&Cursor, s, str_length(g_Config.m_BrFilterString)); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); TextRender()->TextEx(&Cursor, s+str_length(g_Config.m_BrFilterString), -1); } else TextRender()->TextEx(&Cursor, pName, -1); } else TextRender()->TextEx(&Cursor, pName, -1); // clan TextRender()->SetCursor(&Cursor, Clan.x, Clan.y, FontSize-2, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = Clan.w; const char *pClan = pSelectedServer->m_aClients[i].m_aClan; if(g_Config.m_BrFilterString[0]) { // highlight the parts that matches const char *s = str_find_nocase(pClan, g_Config.m_BrFilterString); if(s) { TextRender()->TextEx(&Cursor, pClan, (int)(s-pClan)); TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f); TextRender()->TextEx(&Cursor, s, str_length(g_Config.m_BrFilterString)); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); TextRender()->TextEx(&Cursor, s+str_length(g_Config.m_BrFilterString), -1); } else TextRender()->TextEx(&Cursor, pClan, -1); } else TextRender()->TextEx(&Cursor, pClan, -1); // flag vec4 Color(1.0f, 1.0f, 1.0f, 0.5f); m_pClient->m_pCountryFlags->Render(pSelectedServer->m_aClients[i].m_Country, &Color, Flag.x, Flag.y, Flag.w, Flag.h); } UiDoListboxEnd(&s_ScrollValue, 0); } } void CMenus::FriendlistOnUpdate() { m_lFriends.clear(); for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i) { CFriendItem Item; Item.m_pFriendInfo = m_pClient->Friends()->GetFriend(i); Item.m_NumFound = 0; m_lFriends.add_unsorted(Item); } m_lFriends.sort_range(); } void CMenus::RenderServerbrowserFriends(CUIRect View) { static int s_Inited = 0; if(!s_Inited) { FriendlistOnUpdate(); s_Inited = 1; } CUIRect ServerFriends = View, FilterHeader; const float FontSize = 10.0f; // header ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends); RenderTools()->DrawUIRect(&FilterHeader, vec4(1,1,1,0.25f), CUI::CORNER_T, 4.0f); RenderTools()->DrawUIRect(&ServerFriends, vec4(0,0,0,0.15f), 0, 4.0f); UI()->DoLabelScaled(&FilterHeader, Localize("Friends"), FontSize+4.0f, 0); CUIRect Button, List; ServerFriends.Margin(3.0f, &ServerFriends); ServerFriends.VMargin(3.0f, &ServerFriends); ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends); // friends list(remove friend) static float s_ScrollValue = 0; if(m_FriendlistSelectedIndex >= m_lFriends.size()) m_FriendlistSelectedIndex = m_lFriends.size()-1; UiDoListboxStart(&m_lFriends, &List, 30.0f, "", "", m_lFriends.size(), 1, m_FriendlistSelectedIndex, s_ScrollValue); m_lFriends.sort_range(); for(int i = 0; i < m_lFriends.size(); ++i) { CListboxItem Item = UiDoListboxNextItem(&m_lFriends[i]); if(Item.m_Visible) { Item.m_Rect.Margin(1.5f, &Item.m_Rect); CUIRect OnState; Item.m_Rect.VSplitRight(30.0f, &Item.m_Rect, &OnState); RenderTools()->DrawUIRect(&Item.m_Rect, vec4(1.0f, 1.0f, 1.0f, 0.1f), CUI::CORNER_L, 4.0f); Item.m_Rect.VMargin(2.5f, &Item.m_Rect); Item.m_Rect.HSplitTop(12.0f, &Item.m_Rect, &Button); UI()->DoLabelScaled(&Item.m_Rect, m_lFriends[i].m_pFriendInfo->m_aName, FontSize, -1); UI()->DoLabelScaled(&Button, m_lFriends[i].m_pFriendInfo->m_aClan, FontSize, -1); RenderTools()->DrawUIRect(&OnState, m_lFriends[i].m_NumFound ? vec4(0.0f, 1.0f, 0.0f, 0.25f) : vec4(1.0f, 0.0f, 0.0f, 0.25f), CUI::CORNER_R, 4.0f); OnState.HMargin((OnState.h-FontSize)/3, &OnState); OnState.VMargin(5.0f, &OnState); char aBuf[64]; str_format(aBuf, sizeof(aBuf), "%i", m_lFriends[i].m_NumFound); UI()->DoLabelScaled(&OnState, aBuf, FontSize+2, 1); } } bool Activated = false; m_FriendlistSelectedIndex = UiDoListboxEnd(&s_ScrollValue, &Activated); // activate found server with friend if(Activated && !m_EnterPressed && m_lFriends[m_FriendlistSelectedIndex].m_NumFound) { bool Found = false; int NumServers = ServerBrowser()->NumSortedServers(); for (int i = 0; i < NumServers && !Found; i++) { int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex+i+1)%NumServers : i; const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex); if(pItem->m_FriendState != IFriends::FRIEND_NO) { for(int j = 0; j < pItem->m_NumClients && !Found; ++j) { if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO && str_quickhash(pItem->m_aClients[j].m_aClan) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash && (!m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] || str_quickhash(pItem->m_aClients[j].m_aName) == m_lFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash)) { str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress, sizeof(g_Config.m_UiServerAddress)); m_ScrollOffset = ItemIndex; m_SelectedIndex = ItemIndex; Found = true; } } } } } ServerFriends.HSplitTop(2.5f, 0, &ServerFriends); ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); if(m_FriendlistSelectedIndex != -1) { static int s_RemoveButton = 0; if(DoButton_Menu(&s_RemoveButton, Localize("Remove"), 0, &Button)) m_Popup = POPUP_REMOVE_FRIEND; } // 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()->DoLabelScaled(&Button, aBuf, FontSize, -1); Button.VSplitLeft(80.0f, 0, &Button); static char s_aName[MAX_NAME_LENGTH] = {0}; static float s_OffsetName = 0.0f; DoEditBox(&s_aName, &Button, s_aName, sizeof(s_aName), FontSize, &s_OffsetName); ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends); str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan")); UI()->DoLabelScaled(&Button, aBuf, FontSize, -1); Button.VSplitLeft(80.0f, 0, &Button); static char s_aClan[MAX_CLAN_LENGTH] = {0}; static float s_OffsetClan = 0.0f; DoEditBox(&s_aClan, &Button, s_aClan, sizeof(s_aClan), FontSize, &s_OffsetClan); ServerFriends.HSplitTop(3.0f, 0, &ServerFriends); ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends); static int s_AddButton = 0; if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button)) { m_pClient->Friends()->AddFriend(s_aName, s_aClan); FriendlistOnUpdate(); Client()->ServerBrowserUpdate(); } } } void CMenus::RenderServerbrowser(CUIRect MainView) { /* +-----------------+ +-------+ | | | | | | | tool | | server list | | box | | | | | | | | | +-----------------+ | | status box tab +-------+ */ CUIRect ServerList, ToolBox, StatusBox, TabBar; // background RenderTools()->DrawUIRect(&MainView, ms_ColorTabbarActive, CUI::CORNER_ALL, 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.HSplitBottom(70.0f, &ServerList, &StatusBox); StatusBox.VSplitRight(100.0f, &StatusBox, &TabBar); ServerList.VSplitRight(5.0f, &ServerList, 0); // server list { RenderServerbrowserServerList(ServerList); } int ToolboxPage = g_Config.m_UiToolboxPage; // tab bar { CUIRect TabButton0, TabButton1, TabButton2; TabBar.HSplitTop(5.0f, 0, &TabBar); TabBar.HSplitTop(20.0f, &TabButton0, &TabBar); TabBar.HSplitTop(2.5f, 0, &TabBar); TabBar.HSplitTop(20.0f, &TabButton1, &TabBar); TabBar.HSplitTop(2.5f, 0, &TabBar); TabBar.HSplitTop(20.0f, &TabButton2, 0); vec4 Active = ms_ColorTabbarActive; vec4 InActive = ms_ColorTabbarInactive; ms_ColorTabbarActive = vec4(0.0f, 0.0f, 0.0f, 0.3f); ms_ColorTabbarInactive = vec4(0.0f, 0.0f, 0.0f, 0.15f); static int s_FiltersTab = 0; if (DoButton_MenuTab(&s_FiltersTab, Localize("Filter"), ToolboxPage==0, &TabButton0, CUI::CORNER_L)) ToolboxPage = 0; static int s_InfoTab = 0; if (DoButton_MenuTab(&s_InfoTab, Localize("Info"), ToolboxPage==1, &TabButton1, CUI::CORNER_L)) ToolboxPage = 1; static int s_FriendsTab = 0; if (DoButton_MenuTab(&s_FriendsTab, Localize("Friends"), ToolboxPage==2, &TabButton2, CUI::CORNER_L)) ToolboxPage = 2; ms_ColorTabbarActive = Active; ms_ColorTabbarInactive = InActive; g_Config.m_UiToolboxPage = ToolboxPage; } // tool box { RenderTools()->DrawUIRect(&ToolBox, vec4(0.0f, 0.0f, 0.0f, 0.15f), CUI::CORNER_T, 4.0f); if(ToolboxPage == 0) RenderServerbrowserFilters(ToolBox); else if(ToolboxPage == 1) RenderServerbrowserServerDetail(ToolBox); else if(ToolboxPage == 2) RenderServerbrowserFriends(ToolBox); } // status box { CUIRect Button, ButtonArea; StatusBox.HSplitTop(5.0f, 0, &StatusBox); // version note StatusBox.HSplitBottom(15.0f, &StatusBox, &Button); char aBuf[64]; if(str_comp(Client()->LatestVersion(), "0") != 0) { str_format(aBuf, sizeof(aBuf), Localize("DDNet %s is out! Download it at ddnet.tw!"), Client()->LatestVersion()); TextRender()->TextColor(1.0f, 0.4f, 0.4f, 1.0f); } else str_format(aBuf, sizeof(aBuf), Localize("Current version: %s"), GAME_VERSION); UI()->DoLabelScaled(&Button, aBuf, 14.0f, -1); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); // button area StatusBox.VSplitRight(80.0f, &StatusBox, 0); StatusBox.VSplitRight(170.0f, &StatusBox, &ButtonArea); ButtonArea.VSplitRight(150.0f, 0, &ButtonArea); ButtonArea.HSplitTop(20.0f, &Button, &ButtonArea); Button.VMargin(2.0f, &Button); static int s_RefreshButton = 0; if(DoButton_Menu(&s_RefreshButton, Localize("Refresh"), 0, &Button)) { if(g_Config.m_UiPage == PAGE_INTERNET) ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); else if(g_Config.m_UiPage == PAGE_LAN) ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); else if(g_Config.m_UiPage == PAGE_FAVORITES) ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); } ButtonArea.HSplitTop(5.0f, 0, &ButtonArea); ButtonArea.HSplitTop(20.0f, &Button, &ButtonArea); Button.VMargin(2.0f, &Button); static int s_JoinButton = 0; if(DoButton_Menu(&s_JoinButton, Localize("Connect"), 0, &Button) || m_EnterPressed) { Client()->Connect(g_Config.m_UiServerAddress); m_EnterPressed = false; } // address info StatusBox.VSplitLeft(20.0f, 0, &StatusBox); StatusBox.HSplitTop(20.0f, &Button, &StatusBox); UI()->DoLabelScaled(&Button, Localize("Host address"), 14.0f, -1); StatusBox.HSplitTop(20.0f, &Button, 0); static float Offset = 0.0f; DoEditBox(&g_Config.m_UiServerAddress, &Button, g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress), 14.0f, &Offset); } } 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)->Client()->State() == IClient::STATE_OFFLINE) ((CMenus *)pUserData)->ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); }