diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index 865d91b5a..fa7afb26f 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -53,8 +53,9 @@ bool matchesExactly(const char *a, const char *b) } CServerBrowser::CServerBrowser() : - m_CountriesFilter([this]() { return CurrentCommunities(); }), - m_TypesFilter([this]() { return CurrentCommunities(); }) + m_CommunityCache(this), + m_CountriesFilter(&m_CommunityCache), + m_TypesFilter(&m_CommunityCache) { m_ppServerlist = nullptr; m_pSortedServerlist = nullptr; @@ -1728,6 +1729,78 @@ unsigned CServerBrowser::CurrentCommunitiesHash() const return Hash; } +void CCommunityCache::Update(bool Force) +{ + const unsigned CommunitiesHash = m_pServerBrowser->CurrentCommunitiesHash(); + const bool TypeChanged = m_LastType != m_pServerBrowser->GetCurrentType(); + const bool CurrentCommunitiesChanged = m_LastType == m_pServerBrowser->GetCurrentType() && m_SelectedCommunitiesHash != CommunitiesHash; + if(CurrentCommunitiesChanged && m_pServerBrowser->GetCurrentType() >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_pServerBrowser->GetCurrentType() <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5) + { + // Favorite community was changed while its type is active, + // refresh to get correct serverlist for updated community. + m_pServerBrowser->Refresh(m_pServerBrowser->GetCurrentType(), true); + } + + if(!Force && m_InfoSha256 == m_pServerBrowser->DDNetInfoSha256() && + !CurrentCommunitiesChanged && !TypeChanged) + { + return; + } + + m_InfoSha256 = m_pServerBrowser->DDNetInfoSha256(); + m_LastType = m_pServerBrowser->GetCurrentType(); + m_SelectedCommunitiesHash = CommunitiesHash; + m_vpSelectedCommunities = m_pServerBrowser->CurrentCommunities(); + + m_vpSelectableCountries.clear(); + m_vpSelectableTypes.clear(); + for(const CCommunity *pCommunity : m_vpSelectedCommunities) + { + for(const auto &Country : pCommunity->Countries()) + { + const auto ExistingCountry = std::find_if(m_vpSelectableCountries.begin(), m_vpSelectableCountries.end(), [&](const CCommunityCountry *pOther) { + return str_comp(Country.Name(), pOther->Name()) == 0 && Country.FlagId() == pOther->FlagId(); + }); + if(ExistingCountry == m_vpSelectableCountries.end()) + { + m_vpSelectableCountries.push_back(&Country); + } + } + for(const auto &Type : pCommunity->Types()) + { + const auto ExistingType = std::find_if(m_vpSelectableTypes.begin(), m_vpSelectableTypes.end(), [&](const CCommunityType *pOther) { + return str_comp(Type.Name(), pOther->Name()) == 0; + }); + if(ExistingType == m_vpSelectableTypes.end()) + { + m_vpSelectableTypes.push_back(&Type); + } + } + } + + m_AnyRanksAvailable = std::any_of(m_vpSelectedCommunities.begin(), m_vpSelectedCommunities.end(), [](const CCommunity *pCommunity) { + return pCommunity->HasRanks(); + }); + + // Country/type filters not shown if there are no countries and types, or if only the none-community is selected + m_CountryTypesFilterAvailable = (!m_vpSelectableCountries.empty() || !m_vpSelectableTypes.empty()) && + (m_vpSelectedCommunities.size() != 1 || str_comp(m_vpSelectedCommunities[0]->Id(), IServerBrowser::COMMUNITY_NONE) != 0); + + if(m_pServerBrowser->GetCurrentType() >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_pServerBrowser->GetCurrentType() <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5) + { + const size_t CommunityIndex = m_pServerBrowser->GetCurrentType() - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1; + std::vector vpFavoriteCommunities = m_pServerBrowser->FavoriteCommunities(); + dbg_assert(CommunityIndex < vpFavoriteCommunities.size(), "Invalid favorite community serverbrowser type"); + m_pCountryTypeFilterKey = vpFavoriteCommunities[CommunityIndex]->Id(); + } + else + { + m_pCountryTypeFilterKey = IServerBrowser::COMMUNITY_ALL; + } + + m_pServerBrowser->CleanFilters(); +} + void CFavoriteCommunityFilterList::Add(const char *pCommunityId) { // Remove community if it's already a favorite, so it will be added again at @@ -1797,6 +1870,14 @@ const std::vector &CFavoriteCommunityFilterList::Entries() const return m_vEntries; } +template +static bool IsSubsetEquals(const std::vector &vpLeft, const std::unordered_set &Right) +{ + return vpLeft.size() <= Right.size() && std::all_of(vpLeft.begin(), vpLeft.end(), [&](const TNamedElement *pElem) { + return Right.count(TElementName(pElem->Name())) > 0; + }); +} + void CExcludedCommunityFilterList::Add(const char *pCommunityId) { m_Entries.emplace(pCommunityId); @@ -1859,13 +1940,18 @@ void CExcludedCommunityFilterList::Save(IConfigManager *pConfigManager) const void CExcludedCommunityCountryFilterList::Add(const char *pCountryName) { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) + // Handle special case that all selectable entries are currently filtered, + // where adding more entries to the exclusion list would have no effect. + auto CommunityEntry = m_Entries.find(m_pCommunityCache->CountryTypeFilterKey()); + if(CommunityEntry != m_Entries.end() && IsSubsetEquals(m_pCommunityCache->SelectableCountries(), CommunityEntry->second)) { - if(pCommunity->HasCountry(pCountryName)) + for(const CCommunityCountry *pSelectableCountry : m_pCommunityCache->SelectableCountries()) { - Add(pCommunity->Id(), pCountryName); + CommunityEntry->second.erase(pSelectableCountry->Name()); } } + + Add(m_pCommunityCache->CountryTypeFilterKey(), pCountryName); } void CExcludedCommunityCountryFilterList::Add(const char *pCommunityId, const char *pCountryName) @@ -1880,10 +1966,7 @@ void CExcludedCommunityCountryFilterList::Add(const char *pCommunityId, const ch void CExcludedCommunityCountryFilterList::Remove(const char *pCountryName) { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) - { - Remove(pCommunity->Id(), pCountryName); - } + Remove(m_pCommunityCache->CountryTypeFilterKey(), pCountryName); } void CExcludedCommunityCountryFilterList::Remove(const char *pCommunityId, const char *pCountryName) @@ -1897,50 +1980,38 @@ void CExcludedCommunityCountryFilterList::Remove(const char *pCommunityId, const void CExcludedCommunityCountryFilterList::Clear() { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) + auto CommunityEntry = m_Entries.find(m_pCommunityCache->CountryTypeFilterKey()); + if(CommunityEntry != m_Entries.end()) { - auto CommunityEntry = m_Entries.find(pCommunity->Id()); - if(CommunityEntry != m_Entries.end()) - { - CommunityEntry->second.clear(); - } + CommunityEntry->second.clear(); } } bool CExcludedCommunityCountryFilterList::Filtered(const char *pCountryName) const { - const auto Communities = m_CurrentCommunitiesGetter(); - return std::none_of(Communities.begin(), Communities.end(), [&](const CCommunity *pCommunity) { - if(!pCommunity->HasCountry(pCountryName)) - return false; - - auto CommunityEntry = m_Entries.find(CCommunityId(pCommunity->Id())); - if(CommunityEntry == m_Entries.end()) - return true; - - const auto &CountryEntries = CommunityEntry->second; - if(CountryEntries.find(CCommunityCountryName(pCountryName)) == CountryEntries.end()) - return true; - + auto CommunityEntry = m_Entries.find(CCommunityId(m_pCommunityCache->CountryTypeFilterKey())); + if(CommunityEntry == m_Entries.end()) return false; - }); + + const auto &CountryEntries = CommunityEntry->second; + return !IsSubsetEquals(m_pCommunityCache->SelectableCountries(), CountryEntries) && + CountryEntries.find(CCommunityCountryName(pCountryName)) != CountryEntries.end(); } bool CExcludedCommunityCountryFilterList::Empty() const { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) - { - auto CommunityEntry = m_Entries.find(CCommunityId(pCommunity->Id())); - return CommunityEntry == m_Entries.end() || CommunityEntry->second.empty(); - } - return false; + auto CommunityEntry = m_Entries.find(CCommunityId(m_pCommunityCache->CountryTypeFilterKey())); + return CommunityEntry == m_Entries.end() || + CommunityEntry->second.empty() || + IsSubsetEquals(m_pCommunityCache->SelectableCountries(), CommunityEntry->second); } void CExcludedCommunityCountryFilterList::Clean(const std::vector &vAllowedCommunities) { for(auto It = m_Entries.begin(); It != m_Entries.end();) { - const bool Found = std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) { + const bool AllEntry = str_comp(It->first.Id(), IServerBrowser::COMMUNITY_ALL) == 0; + const bool Found = AllEntry || std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) { return str_comp(It->first.Id(), AllowedCommunity.Id()) == 0; }) != vAllowedCommunities.end(); if(Found) @@ -1977,6 +2048,36 @@ void CExcludedCommunityCountryFilterList::Clean(const std::vector &v } } } + + auto AllCommunityEntry = m_Entries.find(CCommunityId(IServerBrowser::COMMUNITY_ALL)); + if(AllCommunityEntry != m_Entries.end()) + { + auto &CountryEntries = AllCommunityEntry->second; + for(auto It = CountryEntries.begin(); It != CountryEntries.end();) + { + if(std::any_of(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const auto &Community) { return Community.HasCountry(It->Name()); })) + { + ++It; + } + else + { + It = CountryEntries.erase(It); + } + } + // Prevent filter that would exclude all allowed countries + std::unordered_set UniqueCountries; + for(const CCommunity &AllowedCommunity : vAllowedCommunities) + { + for(const CCommunityCountry &Country : AllowedCommunity.Countries()) + { + UniqueCountries.emplace(Country.Name()); + } + } + if(CountryEntries.size() == UniqueCountries.size()) + { + CountryEntries.clear(); + } + } } void CExcludedCommunityCountryFilterList::Save(IConfigManager *pConfigManager) const @@ -1998,13 +2099,18 @@ void CExcludedCommunityCountryFilterList::Save(IConfigManager *pConfigManager) c void CExcludedCommunityTypeFilterList::Add(const char *pTypeName) { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) + // Handle special case that all selectable entries are currently filtered, + // where adding more entries to the exclusion list would have no effect. + auto CommunityEntry = m_Entries.find(m_pCommunityCache->CountryTypeFilterKey()); + if(CommunityEntry != m_Entries.end() && IsSubsetEquals(m_pCommunityCache->SelectableTypes(), CommunityEntry->second)) { - if(pCommunity->HasType(pTypeName)) + for(const CCommunityType *pSelectableType : m_pCommunityCache->SelectableTypes()) { - Add(pCommunity->Id(), pTypeName); + CommunityEntry->second.erase(pSelectableType->Name()); } } + + Add(m_pCommunityCache->CountryTypeFilterKey(), pTypeName); } void CExcludedCommunityTypeFilterList::Add(const char *pCommunityId, const char *pTypeName) @@ -2019,10 +2125,7 @@ void CExcludedCommunityTypeFilterList::Add(const char *pCommunityId, const char void CExcludedCommunityTypeFilterList::Remove(const char *pTypeName) { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) - { - Remove(pCommunity->Id(), pTypeName); - } + Remove(m_pCommunityCache->CountryTypeFilterKey(), pTypeName); } void CExcludedCommunityTypeFilterList::Remove(const char *pCommunityId, const char *pTypeName) @@ -2036,47 +2139,38 @@ void CExcludedCommunityTypeFilterList::Remove(const char *pCommunityId, const ch void CExcludedCommunityTypeFilterList::Clear() { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) + auto CommunityEntry = m_Entries.find(m_pCommunityCache->CountryTypeFilterKey()); + if(CommunityEntry != m_Entries.end()) { - auto CommunityEntry = m_Entries.find(pCommunity->Id()); - if(CommunityEntry != m_Entries.end()) - { - CommunityEntry->second.clear(); - } + CommunityEntry->second.clear(); } } bool CExcludedCommunityTypeFilterList::Filtered(const char *pTypeName) const { - const auto Communities = m_CurrentCommunitiesGetter(); - return std::none_of(Communities.begin(), Communities.end(), [&](const CCommunity *pCommunity) { - if(!pCommunity->HasType(pTypeName)) - return false; + auto CommunityEntry = m_Entries.find(CCommunityId(m_pCommunityCache->CountryTypeFilterKey())); + if(CommunityEntry == m_Entries.end()) + return false; - auto CommunityEntry = m_Entries.find(CCommunityId(pCommunity->Id())); - if(CommunityEntry == m_Entries.end()) - return true; - - const auto &TypeEntries = CommunityEntry->second; - return TypeEntries.find(CCommunityTypeName(pTypeName)) == TypeEntries.end(); - }); + const auto &TypeEntries = CommunityEntry->second; + return !IsSubsetEquals(m_pCommunityCache->SelectableTypes(), TypeEntries) && + TypeEntries.find(CCommunityTypeName(pTypeName)) != TypeEntries.end(); } bool CExcludedCommunityTypeFilterList::Empty() const { - for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter()) - { - auto CommunityEntry = m_Entries.find(CCommunityId(pCommunity->Id())); - return CommunityEntry == m_Entries.end() || CommunityEntry->second.empty(); - } - return false; + auto CommunityEntry = m_Entries.find(CCommunityId(m_pCommunityCache->CountryTypeFilterKey())); + return CommunityEntry == m_Entries.end() || + CommunityEntry->second.empty() || + IsSubsetEquals(m_pCommunityCache->SelectableTypes(), CommunityEntry->second); } void CExcludedCommunityTypeFilterList::Clean(const std::vector &vAllowedCommunities) { for(auto It = m_Entries.begin(); It != m_Entries.end();) { - const bool Found = std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) { + const bool AllEntry = str_comp(It->first.Id(), IServerBrowser::COMMUNITY_ALL) == 0; + const bool Found = AllEntry || std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) { return str_comp(It->first.Id(), AllowedCommunity.Id()) == 0; }) != vAllowedCommunities.end(); if(Found) @@ -2106,13 +2200,43 @@ void CExcludedCommunityTypeFilterList::Clean(const std::vector &vAll It = TypeEntries.erase(It); } } - // Prevent filter that would exclude all allowed countries + // Prevent filter that would exclude all allowed types if(TypeEntries.size() == AllowedCommunity.Types().size()) { TypeEntries.clear(); } } } + + auto AllCommunityEntry = m_Entries.find(CCommunityId(IServerBrowser::COMMUNITY_ALL)); + if(AllCommunityEntry != m_Entries.end()) + { + auto &TypeEntries = AllCommunityEntry->second; + for(auto It = TypeEntries.begin(); It != TypeEntries.end();) + { + if(std::any_of(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const auto &Community) { return Community.HasType(It->Name()); })) + { + ++It; + } + else + { + It = TypeEntries.erase(It); + } + } + // Prevent filter that would exclude all allowed types + std::unordered_set UniqueTypes; + for(const CCommunity &AllowedCommunity : vAllowedCommunities) + { + for(const CCommunityType &Type : AllowedCommunity.Types()) + { + UniqueTypes.emplace(Type.Name()); + } + } + if(TypeEntries.size() == UniqueTypes.size()) + { + TypeEntries.clear(); + } + } } void CExcludedCommunityTypeFilterList::Save(IConfigManager *pConfigManager) const diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h index 0e3c8683e..132620892 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h @@ -160,8 +160,8 @@ private: class CExcludedCommunityCountryFilterList : public IFilterList { public: - CExcludedCommunityCountryFilterList(std::function()> CurrentCommunitiesGetter) : - m_CurrentCommunitiesGetter(CurrentCommunitiesGetter) + CExcludedCommunityCountryFilterList(const ICommunityCache *pCommunityCache) : + m_pCommunityCache(pCommunityCache) { } @@ -176,15 +176,15 @@ public: void Save(IConfigManager *pConfigManager) const; private: - std::function()> m_CurrentCommunitiesGetter; + const ICommunityCache *m_pCommunityCache; std::unordered_map> m_Entries; }; class CExcludedCommunityTypeFilterList : public IFilterList { public: - CExcludedCommunityTypeFilterList(std::function()> CurrentCommunitiesGetter) : - m_CurrentCommunitiesGetter(CurrentCommunitiesGetter) + CExcludedCommunityTypeFilterList(const ICommunityCache *pCommunityCache) : + m_pCommunityCache(pCommunityCache) { } @@ -199,10 +199,38 @@ public: void Save(IConfigManager *pConfigManager) const; private: - std::function()> m_CurrentCommunitiesGetter; + const ICommunityCache *m_pCommunityCache; std::unordered_map> m_Entries; }; +class CCommunityCache : public ICommunityCache +{ + IServerBrowser *m_pServerBrowser; + SHA256_DIGEST m_InfoSha256 = SHA256_ZEROED; + int m_LastType = IServerBrowser::NUM_TYPES; // initial value does not appear normally, marking uninitialized cache + unsigned m_SelectedCommunitiesHash = 0; + std::vector m_vpSelectedCommunities; + std::vector m_vpSelectableCountries; + std::vector m_vpSelectableTypes; + bool m_AnyRanksAvailable = false; + bool m_CountryTypesFilterAvailable = false; + const char *m_pCountryTypeFilterKey = IServerBrowser::COMMUNITY_ALL; + +public: + CCommunityCache(IServerBrowser *pServerBrowser) : + m_pServerBrowser(pServerBrowser) + { + } + + void Update(bool Force) override; + const std::vector &SelectedCommunities() const override { return m_vpSelectedCommunities; } + const std::vector &SelectableCountries() const override { return m_vpSelectableCountries; } + const std::vector &SelectableTypes() const override { return m_vpSelectableTypes; } + bool AnyRanksAvailable() const override { return m_AnyRanksAvailable; } + bool CountriesTypesFilterAvailable() const override { return m_CountryTypesFilterAvailable; } + const char *CountryTypeFilterKey() const override { return m_pCountryTypeFilterKey; } +}; + class CServerBrowser : public IServerBrowser { public: @@ -255,6 +283,8 @@ public: bool DDNetInfoAvailable() const override { return m_pDDNetInfo != nullptr; } SHA256_DIGEST DDNetInfoSha256() const override { return m_DDNetInfoSha256; } + ICommunityCache &CommunityCache() override { return m_CommunityCache; } + const ICommunityCache &CommunityCache() const override { return m_CommunityCache; } CFavoriteCommunityFilterList &FavoriteCommunitiesFilter() override { return m_FavoriteCommunitiesFilter; } CExcludedCommunityFilterList &CommunitiesFilter() override { return m_CommunitiesFilter; } CExcludedCommunityCountryFilterList &CountriesFilter() override { return m_CountriesFilter; } @@ -307,6 +337,7 @@ private: int m_OwnLocation = CServerInfo::LOC_UNKNOWN; + CCommunityCache m_CommunityCache; CFavoriteCommunityFilterList m_FavoriteCommunitiesFilter; CExcludedCommunityFilterList m_CommunitiesFilter; CExcludedCommunityCountryFilterList m_CountriesFilter; diff --git a/src/engine/serverbrowser.h b/src/engine/serverbrowser.h index 91b4b883b..5d6d19479 100644 --- a/src/engine/serverbrowser.h +++ b/src/engine/serverbrowser.h @@ -248,6 +248,18 @@ public: virtual bool Filtered(const char *pElement) const = 0; }; +class ICommunityCache +{ +public: + virtual void Update(bool Force) = 0; + virtual const std::vector &SelectedCommunities() const = 0; + virtual const std::vector &SelectableCountries() const = 0; + virtual const std::vector &SelectableTypes() const = 0; + virtual bool AnyRanksAvailable() const = 0; + virtual bool CountriesTypesFilterAvailable() const = 0; + virtual const char *CountryTypeFilterKey() const = 0; +}; + class IServerBrowser : public IInterface { MACRO_INTERFACE("serverbrowser") @@ -286,6 +298,11 @@ public: static constexpr const char *COMMUNITY_DDNET = "ddnet"; static constexpr const char *COMMUNITY_NONE = "none"; + /** + * Special community value for country/type filters that + * affect all communities. + */ + static constexpr const char *COMMUNITY_ALL = "all"; static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";"; @@ -313,6 +330,8 @@ public: virtual bool DDNetInfoAvailable() const = 0; virtual SHA256_DIGEST DDNetInfoSha256() const = 0; + virtual ICommunityCache &CommunityCache() = 0; + virtual const ICommunityCache &CommunityCache() const = 0; virtual IFilterList &FavoriteCommunitiesFilter() = 0; virtual IFilterList &CommunitiesFilter() = 0; virtual IFilterList &CountriesFilter() = 0; diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 2bd9afeb2..162607686 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -663,12 +663,6 @@ void CMenus::RenderMenubar(CUIRect Box, IClient::EClientState ClientState) static CButtonContainer s_InternetButton; if(DoButton_MenuTab(&s_InternetButton, FONT_ICON_EARTH_AMERICAS, ActivePage == PAGE_INTERNET, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_INTERNET])) { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) - { - if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - } NewPage = PAGE_INTERNET; } GameClient()->m_Tooltips.DoToolTip(&s_InternetButton, &Button, Localize("Internet")); @@ -677,8 +671,6 @@ void CMenus::RenderMenubar(CUIRect Box, IClient::EClientState ClientState) static CButtonContainer s_LanButton; if(DoButton_MenuTab(&s_LanButton, FONT_ICON_NETWORK_WIRED, ActivePage == PAGE_LAN, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_LAN])) { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN) - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); NewPage = PAGE_LAN; } GameClient()->m_Tooltips.DoToolTip(&s_LanButton, &Button, Localize("LAN")); @@ -687,12 +679,6 @@ void CMenus::RenderMenubar(CUIRect Box, IClient::EClientState ClientState) static CButtonContainer s_FavoritesButton; if(DoButton_MenuTab(&s_FavoritesButton, FONT_ICON_STAR, ActivePage == PAGE_FAVORITES, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_FAVORITES])) { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) - { - if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); - } NewPage = PAGE_FAVORITES; } GameClient()->m_Tooltips.DoToolTip(&s_FavoritesButton, &Button, Localize("Favorites")); @@ -710,13 +696,6 @@ void CMenus::RenderMenubar(CUIRect Box, IClient::EClientState ClientState) const int Page = PAGE_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex; if(DoButton_MenuTab(&s_aFavoriteCommunityButtons[FavoriteCommunityIndex], FONT_ICON_ELLIPSIS, ActivePage == Page, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIT_TAB_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex], nullptr, nullptr, nullptr, 10.0f, FindCommunityIcon(pCommunity->Id()))) { - const int BrowserType = IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex; - if(ServerBrowser()->GetCurrentType() != BrowserType) - { - if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(BrowserType); - } NewPage = Page; } GameClient()->m_Tooltips.DoToolTip(&s_aFavoriteCommunityButtons[FavoriteCommunityIndex], &Button, pCommunity->Name()); @@ -1036,7 +1015,7 @@ void CMenus::Render() static int s_Frame = 0; if(s_Frame == 0) { - RefreshBrowserTab(g_Config.m_UiPage); + RefreshBrowserTab(true); s_Frame++; } else if(s_Frame == 1) @@ -1851,7 +1830,7 @@ void CMenus::RenderPopupConnecting(CUIRect Screen) { Client()->Disconnect(); Ui()->SetActiveItem(nullptr); - RefreshBrowserTab(g_Config.m_UiPage); + RefreshBrowserTab(true); } } @@ -1970,7 +1949,7 @@ void CMenus::RenderPopupLoading(CUIRect Screen) { Client()->Disconnect(); Ui()->SetActiveItem(nullptr); - RefreshBrowserTab(g_Config.m_UiPage); + RefreshBrowserTab(true); } } @@ -2383,30 +2362,63 @@ const CMenus::CMenuImage *CMenus::FindMenuImage(const char *pName) void CMenus::SetMenuPage(int NewPage) { + const int OldPage = m_MenuPage; m_MenuPage = NewPage; if(NewPage >= PAGE_INTERNET && NewPage <= PAGE_FAVORITE_COMMUNITY_5) + { g_Config.m_UiPage = NewPage; + if(!m_ShowStart && OldPage != NewPage) + { + RefreshBrowserTab(false); + } + } } -void CMenus::RefreshBrowserTab(int UiPage) +void CMenus::RefreshBrowserTab(bool Force) { - if(UiPage == PAGE_INTERNET) + if(g_Config.m_UiPage == PAGE_INTERNET) { - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); + if(Force || ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) + { + if(Force || ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) + { + Client()->RequestDDNetInfo(); + } + ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); + UpdateCommunityCache(true); + } } - else if(UiPage == PAGE_LAN) + else if(g_Config.m_UiPage == PAGE_LAN) { - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); + if(Force || ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN) + { + ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); + UpdateCommunityCache(true); + } } - else if(UiPage == PAGE_FAVORITES) + else if(g_Config.m_UiPage == PAGE_FAVORITES) { - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); + if(Force || ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) + { + if(Force || ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) + { + Client()->RequestDDNetInfo(); + } + ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); + UpdateCommunityCache(true); + } } - else if(UiPage >= PAGE_FAVORITE_COMMUNITY_1 && UiPage <= PAGE_FAVORITE_COMMUNITY_5) + else if(g_Config.m_UiPage >= PAGE_FAVORITE_COMMUNITY_1 && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_5) { - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(UiPage - PAGE_FAVORITE_COMMUNITY_1 + IServerBrowser::TYPE_FAVORITE_COMMUNITY_1); + const int BrowserType = g_Config.m_UiPage - PAGE_FAVORITE_COMMUNITY_1 + IServerBrowser::TYPE_FAVORITE_COMMUNITY_1; + if(Force || ServerBrowser()->GetCurrentType() != BrowserType) + { + if(Force || ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) + { + Client()->RequestDDNetInfo(); + } + ServerBrowser()->Refresh(BrowserType); + UpdateCommunityCache(true); + } } } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 59f182b83..db43779a2 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -508,17 +508,6 @@ protected: static void ConchainFavoritesUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainCommunitiesUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainUiPageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); - struct SCommunityCache - { - SHA256_DIGEST m_InfoSha256 = SHA256_ZEROED; - int m_LastPage = 0; - unsigned m_SelectedCommunitiesHash; - std::vector m_vpSelectedCommunities; - std::vector m_vpSelectableCountries; - std::vector m_vpSelectableTypes; - bool m_AnyRanksAvailable; - }; - SCommunityCache m_CommunityCache; void UpdateCommunityCache(bool Force); // community icons @@ -773,7 +762,7 @@ public: private: static int GhostlistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser); void SetMenuPage(int NewPage); - void RefreshBrowserTab(int UiPage); + void RefreshBrowserTab(bool Force); // found in menus_ingame.cpp void RenderInGameNetwork(CUIRect MainView); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 52c424d21..0b0647362 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -591,7 +591,7 @@ void CMenus::RenderServerbrowserStatusBox(CUIRect StatusBox, bool WasListboxItem static CButtonContainer s_RefreshButton; if(Ui()->DoButton_Menu(m_RefreshButton, &s_RefreshButton, RefreshLabelFunc, &ButtonRefresh, Props) || (!Ui()->IsPopupOpen() && (Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed())))) { - RefreshBrowserTab(g_Config.m_UiPage); + RefreshBrowserTab(true); } } @@ -715,7 +715,7 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) g_Config.m_BrFilterConnectingPlayers ^= 1; // map finish filters - if(m_CommunityCache.m_AnyRanksAvailable) + if(ServerBrowser()->CommunityCache().AnyRanksAvailable()) { View.HSplitTop(RowHeight, &Button, &View); if(DoButton_CheckBox(&g_Config.m_BrIndicateFinished, Localize("Indicate map finish"), g_Config.m_BrIndicateFinished, &Button)) @@ -737,9 +737,8 @@ void CMenus::RenderServerbrowserFilters(CUIRect View) } } - // countries and types filters (not shown if there are no countries and types, or if only the none community is selected) - if((!m_CommunityCache.m_vpSelectableCountries.empty() || !m_CommunityCache.m_vpSelectableTypes.empty()) && - (m_CommunityCache.m_vpSelectedCommunities.size() != 1 || str_comp(m_CommunityCache.m_vpSelectedCommunities[0]->Id(), IServerBrowser::COMMUNITY_NONE) != 0)) + // countries and types filters + if(ServerBrowser()->CommunityCache().CountriesTypesFilterAvailable()) { const ColorRGBA ColorActive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f); const ColorRGBA ColorInactive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f); @@ -806,7 +805,7 @@ void CMenus::ResetServerbrowserFilters() if(g_Config.m_UiPage != PAGE_LAN) { - if(m_CommunityCache.m_AnyRanksAvailable) + if(ServerBrowser()->CommunityCache().AnyRanksAvailable()) { g_Config.m_BrFilterUnfinishedMap = 0; } @@ -891,10 +890,14 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View, break; } } - // when last one is removed, reset (re-enable all) + // When last one is removed, re-enable all currently selectable items. + // Don't use Clear, to avoid enabling also currently unselectable items. if(AllFilteredExceptUs) { - Filter.Clear(); + for(int j = 0; j < MaxItems; ++j) + { + Filter.Remove(GetItemName(j)); + } } else if(Active) { @@ -912,8 +915,11 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View, } else if(Click == 3) { - // middle click to reset (re-enable all) - Filter.Clear(); + // middle click to reset (re-enable all currently selectable items) + for(int j = 0; j < MaxItems; ++j) + { + Filter.Remove(GetItemName(j)); + } Client()->ServerBrowserUpdate(); if(UpdateCommunityCacheOnChange) UpdateCommunityCache(true); @@ -991,7 +997,7 @@ void CMenus::RenderServerbrowserCommunitiesFilter(CUIRect View) void CMenus::RenderServerbrowserCountriesFilter(CUIRect View) { - const int MaxEntries = m_CommunityCache.m_vpSelectableCountries.size(); + const int MaxEntries = ServerBrowser()->CommunityCache().SelectableCountries().size(); const int EntriesPerRow = MaxEntries > 8 ? 5 : 4; static CScrollRegion s_ScrollRegion; @@ -1001,14 +1007,14 @@ void CMenus::RenderServerbrowserCountriesFilter(CUIRect View) const float Spacing = 2.0f; const auto &&GetItemName = [&](int ItemIndex) { - return m_CommunityCache.m_vpSelectableCountries[ItemIndex]->Name(); + return ServerBrowser()->CommunityCache().SelectableCountries()[ItemIndex]->Name(); }; const auto &&RenderItem = [&](int ItemIndex, CUIRect Item, const void *pItemId, bool Active) { Item.Margin(Spacing, &Item); const float OldWidth = Item.w; Item.w = Item.h * 2.0f; Item.x += (OldWidth - Item.w) / 2.0f; - m_pClient->m_CountryFlags.Render(m_CommunityCache.m_vpSelectableCountries[ItemIndex]->FlagId(), ColorRGBA(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (Ui()->HotItem() == pItemId ? 0.1f : 0.0f)), Item.x, Item.y, Item.w, Item.h); + m_pClient->m_CountryFlags.Render(ServerBrowser()->CommunityCache().SelectableCountries()[ItemIndex]->FlagId(), ColorRGBA(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (Ui()->HotItem() == pItemId ? 0.1f : 0.0f)), Item.x, Item.y, Item.w, Item.h); }; RenderServerbrowserDDNetFilter(View, ServerBrowser()->CountriesFilter(), ItemHeight + 2.0f * Spacing, MaxEntries, EntriesPerRow, s_ScrollRegion, s_vItemIds, false, GetItemName, RenderItem); @@ -1016,7 +1022,7 @@ void CMenus::RenderServerbrowserCountriesFilter(CUIRect View) void CMenus::RenderServerbrowserTypesFilter(CUIRect View) { - const int MaxEntries = m_CommunityCache.m_vpSelectableTypes.size(); + const int MaxEntries = ServerBrowser()->CommunityCache().SelectableTypes().size(); const int EntriesPerRow = 3; static CScrollRegion s_ScrollRegion; @@ -1026,7 +1032,7 @@ void CMenus::RenderServerbrowserTypesFilter(CUIRect View) const float Spacing = 2.0f; const auto &&GetItemName = [&](int ItemIndex) { - return m_CommunityCache.m_vpSelectableTypes[ItemIndex]->Name(); + return ServerBrowser()->CommunityCache().SelectableTypes()[ItemIndex]->Name(); }; const auto &&RenderItem = [&](int ItemIndex, CUIRect Item, const void *pItemId, bool Active) { Item.Margin(Spacing, &Item); @@ -1848,7 +1854,6 @@ void CMenus::ConchainCommunitiesUpdate(IConsole::IResult *pResult, void *pUserDa void CMenus::ConchainUiPageUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { - const int OldPage = g_Config.m_UiPage; pfnCallback(pResult, pCallbackUserData); CMenus *pThis = static_cast(pUserData); if(pResult->NumArguments() >= 1) @@ -1861,11 +1866,6 @@ void CMenus::ConchainUiPageUpdate(IConsole::IResult *pResult, void *pUserData, I } pThis->SetMenuPage(g_Config.m_UiPage); - - if(!pThis->m_ShowStart && g_Config.m_UiPage != OldPage) - { - pThis->RefreshBrowserTab(g_Config.m_UiPage); - } } } @@ -1876,63 +1876,13 @@ void CMenus::UpdateCommunityCache(bool Force) { // Reset page to internet when there is no favorite community for this page, // i.e. when favorite community is removed via console while the page is open. + // This also updates the community cache because the page is changed. SetMenuPage(PAGE_INTERNET); - RefreshBrowserTab(g_Config.m_UiPage); } - - const unsigned CommunitiesHash = ServerBrowser()->CurrentCommunitiesHash(); - const bool PageChanged = m_CommunityCache.m_LastPage != 0 && m_CommunityCache.m_LastPage != g_Config.m_UiPage; - const bool CurrentCommunitiesChanged = m_CommunityCache.m_LastPage != 0 && m_CommunityCache.m_LastPage == g_Config.m_UiPage && m_CommunityCache.m_SelectedCommunitiesHash != CommunitiesHash; - if(CurrentCommunitiesChanged && g_Config.m_UiPage >= PAGE_FAVORITE_COMMUNITY_1 && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_5) + else { - // Favorite community was changed while its page is active, - // refresh to get correct serverlist for updated community. - ServerBrowser()->Refresh(g_Config.m_UiPage - PAGE_FAVORITE_COMMUNITY_1 + IServerBrowser::TYPE_FAVORITE_COMMUNITY_1, true); + ServerBrowser()->CommunityCache().Update(Force); } - - if(!Force && m_CommunityCache.m_InfoSha256 != SHA256_ZEROED && - m_CommunityCache.m_InfoSha256 == ServerBrowser()->DDNetInfoSha256() && - !CurrentCommunitiesChanged && !PageChanged) - { - return; - } - - ServerBrowser()->CleanFilters(); - - m_CommunityCache.m_InfoSha256 = ServerBrowser()->DDNetInfoSha256(); - m_CommunityCache.m_LastPage = g_Config.m_UiPage; - m_CommunityCache.m_SelectedCommunitiesHash = CommunitiesHash; - m_CommunityCache.m_vpSelectedCommunities = ServerBrowser()->CurrentCommunities(); - - m_CommunityCache.m_vpSelectableCountries.clear(); - m_CommunityCache.m_vpSelectableTypes.clear(); - for(const CCommunity *pCommunity : m_CommunityCache.m_vpSelectedCommunities) - { - for(const auto &Country : pCommunity->Countries()) - { - const auto ExistingCountry = std::find_if(m_CommunityCache.m_vpSelectableCountries.begin(), m_CommunityCache.m_vpSelectableCountries.end(), [&](const CCommunityCountry *pOther) { - return str_comp(Country.Name(), pOther->Name()) == 0 && Country.FlagId() == pOther->FlagId(); - }); - if(ExistingCountry == m_CommunityCache.m_vpSelectableCountries.end()) - { - m_CommunityCache.m_vpSelectableCountries.push_back(&Country); - } - } - for(const auto &Type : pCommunity->Types()) - { - const auto ExistingType = std::find_if(m_CommunityCache.m_vpSelectableTypes.begin(), m_CommunityCache.m_vpSelectableTypes.end(), [&](const CCommunityType *pOther) { - return str_comp(Type.Name(), pOther->Name()) == 0; - }); - if(ExistingType == m_CommunityCache.m_vpSelectableTypes.end()) - { - m_CommunityCache.m_vpSelectableTypes.push_back(&Type); - } - } - } - - m_CommunityCache.m_AnyRanksAvailable = std::any_of(m_CommunityCache.m_vpSelectedCommunities.begin(), m_CommunityCache.m_vpSelectedCommunities.end(), [](const CCommunity *pCommunity) { - return pCommunity->HasRanks(); - }); } CMenus::CAbstractCommunityIconJob::CAbstractCommunityIconJob(CMenus *pMenus, const char *pCommunityId, int StorageType) : diff --git a/src/game/client/components/menus_ingame.cpp b/src/game/client/components/menus_ingame.cpp index 668af84bf..1f808e4d1 100644 --- a/src/game/client/components/menus_ingame.cpp +++ b/src/game/client/components/menus_ingame.cpp @@ -64,7 +64,7 @@ void CMenus::RenderGame(CUIRect MainView) else { Client()->Disconnect(); - RefreshBrowserTab(g_Config.m_UiPage); + RefreshBrowserTab(true); } } @@ -847,12 +847,6 @@ void CMenus::RenderInGameNetwork(CUIRect MainView) static CButtonContainer s_InternetButton; if(DoButton_MenuTab(&s_InternetButton, FONT_ICON_EARTH_AMERICAS, g_Config.m_UiPage == PAGE_INTERNET, &Button, IGraphics::CORNER_NONE)) { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) - { - if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); - } NewPage = PAGE_INTERNET; } GameClient()->m_Tooltips.DoToolTip(&s_InternetButton, &Button, Localize("Internet")); @@ -861,8 +855,6 @@ void CMenus::RenderInGameNetwork(CUIRect MainView) static CButtonContainer s_LanButton; if(DoButton_MenuTab(&s_LanButton, FONT_ICON_NETWORK_WIRED, g_Config.m_UiPage == PAGE_LAN, &Button, IGraphics::CORNER_NONE)) { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN) - ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); NewPage = PAGE_LAN; } GameClient()->m_Tooltips.DoToolTip(&s_LanButton, &Button, Localize("LAN")); @@ -871,12 +863,6 @@ void CMenus::RenderInGameNetwork(CUIRect MainView) static CButtonContainer s_FavoritesButton; if(DoButton_MenuTab(&s_FavoritesButton, FONT_ICON_STAR, g_Config.m_UiPage == PAGE_FAVORITES, &Button, IGraphics::CORNER_NONE)) { - if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) - { - if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); - } NewPage = PAGE_FAVORITES; } GameClient()->m_Tooltips.DoToolTip(&s_FavoritesButton, &Button, Localize("Favorites")); @@ -890,13 +876,6 @@ void CMenus::RenderInGameNetwork(CUIRect MainView) const int Page = PAGE_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex; if(DoButton_MenuTab(&s_aFavoriteCommunityButtons[FavoriteCommunityIndex], FONT_ICON_ELLIPSIS, g_Config.m_UiPage == Page, &Button, IGraphics::CORNER_NONE, nullptr, nullptr, nullptr, nullptr, 10.0f, FindCommunityIcon(pCommunity->Id()))) { - const int BrowserType = IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex; - if(ServerBrowser()->GetCurrentType() != BrowserType) - { - if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN) - Client()->RequestDDNetInfo(); - ServerBrowser()->Refresh(BrowserType); - } NewPage = Page; } GameClient()->m_Tooltips.DoToolTip(&s_aFavoriteCommunityButtons[FavoriteCommunityIndex], &Button, pCommunity->Name()); diff --git a/src/game/client/components/menus_start.cpp b/src/game/client/components/menus_start.cpp index 40b13fa15..d2cd41aee 100644 --- a/src/game/client/components/menus_start.cpp +++ b/src/game/client/components/menus_start.cpp @@ -72,7 +72,7 @@ void CMenus::RenderStartMenu(CUIRect MainView) // Activate internet tab before joining tutorial to make sure the server info // for the tutorial servers is available. SetMenuPage(PAGE_INTERNET); - RefreshBrowserTab(IServerBrowser::TYPE_INTERNET); + RefreshBrowserTab(true); const char *pAddr = ServerBrowser()->GetTutorialServer(); if(pAddr) {