Merge pull request #7915 from Robyt3/Browser-Community-Tabs

Add tabs for favorite communities, separate country/type filters
This commit is contained in:
Dennis Felsing 2024-02-03 23:08:24 +00:00 committed by GitHub
commit f0da0aa4a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1165 additions and 243 deletions

View file

@ -1117,6 +1117,7 @@ set(EXPECTED_DATA
autoexec_server.cfg autoexec_server.cfg
blob.png blob.png
censorlist.txt censorlist.txt
communityicons/none.png
console.png console.png
console_bar.png console_bar.png
countryflags/AD.png countryflags/AD.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -280,7 +280,6 @@ public:
virtual SWarning *GetCurWarning() = 0; virtual SWarning *GetCurWarning() = 0;
virtual CChecksumData *ChecksumData() = 0; virtual CChecksumData *ChecksumData() = 0;
virtual bool InfoTaskRunning() = 0;
virtual int UdpConnectivity(int NetType) = 0; virtual int UdpConnectivity(int NetType) = 0;
#if defined(CONF_FAMILY_WINDOWS) #if defined(CONF_FAMILY_WINDOWS)

View file

@ -499,7 +499,6 @@ public:
SWarning *GetCurWarning() override; SWarning *GetCurWarning() override;
CChecksumData *ChecksumData() override { return &m_Checksum.m_Data; } CChecksumData *ChecksumData() override { return &m_Checksum.m_Data; }
bool InfoTaskRunning() override { return m_pDDNetInfoTask != nullptr; }
int UdpConnectivity(int NetType) override; int UdpConnectivity(int NetType) override;
#if defined(CONF_FAMILY_WINDOWS) #if defined(CONF_FAMILY_WINDOWS)

View file

@ -584,7 +584,13 @@ bool CGraphics_Threaded::LoadPNG(CImageInfo *pImg, const char *pFilename, int St
if(File) if(File)
{ {
io_seek(File, 0, IOSEEK_END); io_seek(File, 0, IOSEEK_END);
unsigned int FileSize = io_tell(File); long int FileSize = io_tell(File);
if(FileSize <= 0)
{
io_close(File);
log_error("game/png", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
return false;
}
io_seek(File, 0, IOSEEK_START); io_seek(File, 0, IOSEEK_START);
TImageByteBuffer ByteBuffer; TImageByteBuffer ByteBuffer;

View file

@ -50,9 +50,8 @@ bool matchesExactly(const char *a, const char *b)
} }
CServerBrowser::CServerBrowser() : CServerBrowser::CServerBrowser() :
m_CommunitiesFilter(g_Config.m_BrFilterExcludeCommunities, sizeof(g_Config.m_BrFilterExcludeCommunities)), m_CountriesFilter([this]() { return CurrentCommunities(); }),
m_CountriesFilter(g_Config.m_BrFilterExcludeCountries, sizeof(g_Config.m_BrFilterExcludeCountries)), m_TypesFilter([this]() { return CurrentCommunities(); })
m_TypesFilter(g_Config.m_BrFilterExcludeTypes, sizeof(g_Config.m_BrFilterExcludeTypes))
{ {
m_ppServerlist = nullptr; m_ppServerlist = nullptr;
m_pSortedServerlist = nullptr; m_pSortedServerlist = nullptr;
@ -108,12 +107,106 @@ void CServerBrowser::OnInit()
void CServerBrowser::RegisterCommands() void CServerBrowser::RegisterCommands()
{ {
m_pConfigManager->RegisterCallback(CServerBrowser::ConfigSaveCallback, this);
m_pConsole->Register("add_favorite_community", "s[community_id]", CFGFLAG_CLIENT, Con_AddFavoriteCommunity, this, "Add a community as a favorite");
m_pConsole->Register("remove_favorite_community", "s[community_id]", CFGFLAG_CLIENT, Con_RemoveFavoriteCommunity, this, "Remove a community from the favorites");
m_pConsole->Register("add_excluded_community", "s[community_id]", CFGFLAG_CLIENT, Con_AddExcludedCommunity, this, "Add a community to the exclusion filter");
m_pConsole->Register("remove_excluded_community", "s[community_id]", CFGFLAG_CLIENT, Con_RemoveExcludedCommunity, this, "Remove a community from the exclusion filter");
m_pConsole->Register("add_excluded_country", "s[community_id] s[country_code]", CFGFLAG_CLIENT, Con_AddExcludedCountry, this, "Add a country to the exclusion filter for a specific community");
m_pConsole->Register("remove_excluded_country", "s[community_id] s[country_code]", CFGFLAG_CLIENT, Con_RemoveExcludedCountry, this, "Remove a country from the exclusion filter for a specific community");
m_pConsole->Register("add_excluded_type", "s[community_id] s[type]", CFGFLAG_CLIENT, Con_AddExcludedType, this, "Add a type to the exclusion filter for a specific community");
m_pConsole->Register("remove_excluded_type", "s[community_id] s[type]", CFGFLAG_CLIENT, Con_RemoveExcludedType, this, "Remove a type from the exclusion filter for a specific community");
m_pConsole->Register("leak_ip_address_to_all_servers", "", CFGFLAG_CLIENT, Con_LeakIpAddress, this, "Leaks your IP address to all servers by pinging each of them, also acquiring the latency in the process"); m_pConsole->Register("leak_ip_address_to_all_servers", "", CFGFLAG_CLIENT, Con_LeakIpAddress, this, "Leaks your IP address to all servers by pinging each of them, also acquiring the latency in the process");
} }
void CServerBrowser::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
pThis->FavoriteCommunitiesFilter().Save(pConfigManager);
pThis->CommunitiesFilter().Save(pConfigManager);
pThis->CountriesFilter().Save(pConfigManager);
pThis->TypesFilter().Save(pConfigManager);
}
void CServerBrowser::Con_AddFavoriteCommunity(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
if(!pThis->ValidateCommunityId(pCommunityId))
return;
pThis->FavoriteCommunitiesFilter().Add(pCommunityId);
}
void CServerBrowser::Con_RemoveFavoriteCommunity(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
if(!pThis->ValidateCommunityId(pCommunityId))
return;
pThis->FavoriteCommunitiesFilter().Remove(pCommunityId);
}
void CServerBrowser::Con_AddExcludedCommunity(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
if(!pThis->ValidateCommunityId(pCommunityId))
return;
pThis->CommunitiesFilter().Add(pCommunityId);
}
void CServerBrowser::Con_RemoveExcludedCommunity(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
if(!pThis->ValidateCommunityId(pCommunityId))
return;
pThis->CommunitiesFilter().Remove(pCommunityId);
}
void CServerBrowser::Con_AddExcludedCountry(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
const char *pCountryName = pResult->GetString(1);
if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateCountryName(pCountryName))
return;
pThis->CountriesFilter().Add(pCommunityId, pCountryName);
}
void CServerBrowser::Con_RemoveExcludedCountry(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
const char *pCountryName = pResult->GetString(1);
if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateCountryName(pCountryName))
return;
pThis->CountriesFilter().Remove(pCommunityId, pCountryName);
}
void CServerBrowser::Con_AddExcludedType(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
const char *pTypeName = pResult->GetString(1);
if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateTypeName(pTypeName))
return;
pThis->TypesFilter().Add(pCommunityId, pTypeName);
}
void CServerBrowser::Con_RemoveExcludedType(IConsole::IResult *pResult, void *pUserData)
{
CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
const char *pCommunityId = pResult->GetString(0);
const char *pTypeName = pResult->GetString(1);
if(!pThis->ValidateCommunityId(pCommunityId) || !pThis->ValidateTypeName(pTypeName))
return;
pThis->TypesFilter().Remove(pCommunityId, pTypeName);
}
void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData) void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData)
{ {
CServerBrowser *pThis = (CServerBrowser *)pUserData; CServerBrowser *pThis = static_cast<CServerBrowser *>(pUserData);
// We only consider the first address of every server. // We only consider the first address of every server.
@ -170,6 +263,50 @@ void CServerBrowser::Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserDa
} }
} }
static bool ValidIdentifier(const char *pId, size_t MaxLength)
{
if(pId[0] == '\0' || (size_t)str_length(pId) >= MaxLength)
{
return false;
}
for(int i = 0; pId[i] != '\0'; ++i)
{
if(pId[i] == '"' || pId[i] == '/' || pId[i] == '\\')
{
return false;
}
}
return true;
}
static bool ValidateIdentifier(const char *pId, size_t MaxLength, const char *pContext, IConsole *pConsole)
{
if(!ValidIdentifier(pId, MaxLength))
{
char aError[32 + IConsole::CMDLINE_LENGTH];
str_format(aError, sizeof(aError), "%s '%s' is not valid", pContext, pId);
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "serverbrowser", aError);
return false;
}
return true;
}
bool CServerBrowser::ValidateCommunityId(const char *pCommunityId) const
{
return ValidateIdentifier(pCommunityId, CServerInfo::MAX_COMMUNITY_ID_LENGTH, "Community ID", m_pConsole);
}
bool CServerBrowser::ValidateCountryName(const char *pCountryName) const
{
return ValidateIdentifier(pCountryName, CServerInfo::MAX_COMMUNITY_COUNTRY_LENGTH, "Country name", m_pConsole);
}
bool CServerBrowser::ValidateTypeName(const char *pTypeName) const
{
return ValidateIdentifier(pTypeName, CServerInfo::MAX_COMMUNITY_TYPE_LENGTH, "Type name", m_pConsole);
}
int CServerBrowser::Players(const CServerInfo &Item) const int CServerBrowser::Players(const CServerInfo &Item) const
{ {
return g_Config.m_BrFilterSpectators ? Item.m_NumPlayers : Item.m_NumClients; return g_Config.m_BrFilterSpectators ? Item.m_NumPlayers : Item.m_NumClients;
@ -299,11 +436,18 @@ void CServerBrowser::Filter()
Filtered = true; Filtered = true;
else else
{ {
if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES) if(!Communities().empty())
{ {
Filtered = CommunitiesFilter().Filtered(Info.m_aCommunityId); if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
Filtered = Filtered || CountriesFilter().Filtered(Info.m_aCommunityCountry); {
Filtered = Filtered || TypesFilter().Filtered(Info.m_aCommunityType); Filtered = CommunitiesFilter().Filtered(Info.m_aCommunityId);
}
if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES ||
(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_3))
{
Filtered = Filtered || CountriesFilter().Filtered(Info.m_aCommunityCountry);
Filtered = Filtered || TypesFilter().Filtered(Info.m_aCommunityType);
}
} }
if(!Filtered && g_Config.m_BrFilterCountry) if(!Filtered && g_Config.m_BrFilterCountry)
@ -771,9 +915,9 @@ void CServerBrowser::OnServerInfoUpdate(const NETADDR &Addr, int Token, const CS
RequestResort(); RequestResort();
} }
void CServerBrowser::Refresh(int Type) void CServerBrowser::Refresh(int Type, bool Force)
{ {
bool ServerListTypeChanged = m_ServerlistType != Type; bool ServerListTypeChanged = Force || m_ServerlistType != Type;
int OldServerListType = m_ServerlistType; int OldServerListType = m_ServerlistType;
m_ServerlistType = Type; m_ServerlistType = Type;
secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed)); secure_random_fill(m_aTokenSeed, sizeof(m_aTokenSeed));
@ -813,7 +957,7 @@ void CServerBrowser::Refresh(int Type)
if(g_Config.m_Debug) if(g_Config.m_Debug)
m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "serverbrowser", "broadcasting for servers"); m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "serverbrowser", "broadcasting for servers");
} }
else if(Type == IServerBrowser::TYPE_FAVORITES || Type == IServerBrowser::TYPE_INTERNET) else
{ {
m_pHttp->Refresh(); m_pHttp->Refresh();
m_pPingCache->Load(); m_pPingCache->Load();
@ -915,7 +1059,37 @@ void CServerBrowser::UpdateFromHttp()
std::function<bool(const NETADDR *, int)> Want = [](const NETADDR *pAddrs, int NumAddrs) { return true; }; std::function<bool(const NETADDR *, int)> Want = [](const NETADDR *pAddrs, int NumAddrs) { return true; };
if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES) if(m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
{ {
Want = [&](const NETADDR *pAddrs, int NumAddrs) -> bool { return m_pFavorites->IsFavorite(pAddrs, NumAddrs) != TRISTATE::NONE; }; Want = [this](const NETADDR *pAddrs, int NumAddrs) -> bool {
return m_pFavorites->IsFavorite(pAddrs, NumAddrs) != TRISTATE::NONE;
};
}
else if(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_3)
{
const size_t CommunityIndex = m_ServerlistType - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1;
std::vector<const CCommunity *> vpFavoriteCommunities = FavoriteCommunities();
dbg_assert(CommunityIndex < vpFavoriteCommunities.size(), "Invalid community index");
const CCommunity *pWantedCommunity = vpFavoriteCommunities[CommunityIndex];
const bool IsNoneCommunity = str_comp(pWantedCommunity->Id(), COMMUNITY_NONE) == 0;
Want = [this, pWantedCommunity, IsNoneCommunity](const NETADDR *pAddrs, int NumAddrs) -> bool {
for(int AddressIndex = 0; AddressIndex < NumAddrs; AddressIndex++)
{
const auto CommunityServer = m_CommunityServersByAddr.find(pAddrs[AddressIndex]);
if(CommunityServer != m_CommunityServersByAddr.end())
{
if(IsNoneCommunity)
{
// Servers with community "none" are not present in m_CommunityServersByAddr, so we ignore
// any server that is found in this map to determine only the servers without community.
return false;
}
else if(str_comp(CommunityServer->second.CommunityId(), pWantedCommunity->Id()) == 0)
{
return true;
}
}
}
return IsNoneCommunity;
};
} }
for(int i = 0; i < NumServers; i++) for(int i = 0; i < NumServers; i++)
@ -1233,14 +1407,12 @@ void CServerBrowser::LoadDDNetServers()
if(!m_pDDNetInfo) if(!m_pDDNetInfo)
{ {
CleanFilters();
return; return;
} }
const json_value &Communities = (*m_pDDNetInfo)["communities"]; const json_value &Communities = (*m_pDDNetInfo)["communities"];
if(Communities.type != json_array) if(Communities.type != json_array)
{ {
CleanFilters();
return; return;
} }
@ -1382,11 +1554,6 @@ void CServerBrowser::UpdateServerRank(CServerInfo *pInfo) const
const char *CServerBrowser::GetTutorialServer() const char *CServerBrowser::GetTutorialServer()
{ {
// Use internet tab as default after joining tutorial, also makes sure Find() actually works.
// Note that when no server info has been loaded yet, this will not return a result immediately.
m_pConfigManager->Reset("ui_page");
Refresh(IServerBrowser::TYPE_INTERNET);
const CCommunity *pCommunity = Community(COMMUNITY_DDNET); const CCommunity *pCommunity = Community(COMMUNITY_DDNET);
if(pCommunity == nullptr) if(pCommunity == nullptr)
return nullptr; return nullptr;
@ -1397,10 +1564,10 @@ const char *CServerBrowser::GetTutorialServer()
{ {
for(const auto &Server : Country.Servers()) for(const auto &Server : Country.Servers())
{ {
CServerEntry *pEntry = Find(Server.Address()); if(str_comp(Server.TypeName(), "Tutorial") != 0)
if(!pEntry)
continue; continue;
if(str_find(pEntry->m_Info.m_aName, "(Tutorial)") == 0) const CServerEntry *pEntry = Find(Server.Address());
if(!pEntry)
continue; continue;
if(pEntry->m_Info.m_NumPlayers > pEntry->m_Info.m_MaxPlayers - 10) if(pEntry->m_Info.m_NumPlayers > pEntry->m_Info.m_MaxPlayers - 10)
continue; continue;
@ -1433,6 +1600,20 @@ int CServerBrowser::LoadingProgression() const
return 100.0f * Loaded / Servers; return 100.0f * Loaded / Servers;
} }
bool CCommunity::HasCountry(const char *pCountryName) const
{
return std::find_if(Countries().begin(), Countries().end(), [pCountryName](const auto &Elem) {
return str_comp(Elem.Name(), pCountryName) == 0;
}) != Countries().end();
}
bool CCommunity::HasType(const char *pTypeName) const
{
return std::find_if(Types().begin(), Types().end(), [pTypeName](const auto &Elem) {
return str_comp(Elem.Name(), pTypeName) == 0;
}) != Types().end();
}
CServerInfo::ERankState CCommunity::HasRank(const char *pMap) const CServerInfo::ERankState CCommunity::HasRank(const char *pMap) const
{ {
if(!HasRanks()) if(!HasRanks())
@ -1467,121 +1648,474 @@ std::vector<const CCommunity *> CServerBrowser::SelectedCommunities() const
return vpSelected; return vpSelected;
} }
void CFilterList::Add(const char *pElement) std::vector<const CCommunity *> CServerBrowser::FavoriteCommunities() const
{ {
if(Filtered(pElement)) // This is done differently than SelectedCommunities because the favorite
return; // communities should be returned in the order specified by the user.
std::vector<const CCommunity *> vpFavorites;
if(m_pFilter[0] != '\0') for(const auto &CommunityId : FavoriteCommunitiesFilter().Entries())
str_append(m_pFilter, ",", m_FilterSize); {
str_append(m_pFilter, pElement, m_FilterSize); const CCommunity *pCommunity = Community(CommunityId.Id());
if(pCommunity)
{
vpFavorites.push_back(pCommunity);
}
}
return vpFavorites;
} }
void CFilterList::Remove(const char *pElement) std::vector<const CCommunity *> CServerBrowser::CurrentCommunities() const
{ {
if(!Filtered(pElement)) if(m_ServerlistType == IServerBrowser::TYPE_INTERNET || m_ServerlistType == IServerBrowser::TYPE_FAVORITES)
return;
// rewrite exclude/filter list
char aBuf[512];
str_copy(aBuf, m_pFilter);
m_pFilter[0] = '\0';
char aToken[512];
for(const char *pTok = aBuf; (pTok = str_next_token(pTok, ",", aToken, sizeof(aToken)));)
{ {
if(str_comp_nocase(pElement, aToken) != 0) return SelectedCommunities();
}
else if(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_3)
{
const size_t CommunityIndex = m_ServerlistType - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1;
std::vector<const CCommunity *> vpFavoriteCommunities = FavoriteCommunities();
dbg_assert(CommunityIndex < vpFavoriteCommunities.size(), "Invalid favorite community serverbrowser type");
return {vpFavoriteCommunities[CommunityIndex]};
}
else
{
return {};
}
}
unsigned CServerBrowser::CurrentCommunitiesHash() const
{
std::vector<const CCommunity *> vpCommunities = CurrentCommunities();
unsigned Hash = 5381;
for(const CCommunity *pCommunity : CurrentCommunities())
{
Hash = (Hash << 5) + Hash + str_quickhash(pCommunity->Id());
}
return Hash;
}
void CFavoriteCommunityFilterList::Add(const char *pCommunityId)
{
// Remove community if it's already a favorite, so it will be added again at
// the end of the list, to allow setting the entire list easier with binds.
Remove(pCommunityId);
// Ensure maximum number of favorite communities, by removing least-recently
// added communities from the beginning. One more than the maximum is removed
// to make room for the new community.
constexpr size_t MaxFavoriteCommunities = 3;
if(m_vEntries.size() >= MaxFavoriteCommunities)
{
m_vEntries.erase(m_vEntries.begin(), m_vEntries.begin() + (MaxFavoriteCommunities - 1));
}
m_vEntries.emplace_back(pCommunityId);
}
void CFavoriteCommunityFilterList::Remove(const char *pCommunityId)
{
auto FoundCommunity = std::find(m_vEntries.begin(), m_vEntries.end(), CCommunityId(pCommunityId));
if(FoundCommunity != m_vEntries.end())
{
m_vEntries.erase(FoundCommunity);
}
}
void CFavoriteCommunityFilterList::Clear()
{
m_vEntries.clear();
}
bool CFavoriteCommunityFilterList::Filtered(const char *pCommunityId) const
{
return std::find(m_vEntries.begin(), m_vEntries.end(), CCommunityId(pCommunityId)) != m_vEntries.end();
}
bool CFavoriteCommunityFilterList::Empty() const
{
return m_vEntries.empty();
}
void CFavoriteCommunityFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
{
auto It = std::remove_if(m_vEntries.begin(), m_vEntries.end(), [&](const auto &Community) {
return std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) {
return str_comp(Community.Id(), AllowedCommunity.Id()) == 0;
}) == vAllowedCommunities.end();
});
m_vEntries.erase(It, m_vEntries.end());
}
void CFavoriteCommunityFilterList::Save(IConfigManager *pConfigManager) const
{
char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH];
for(const auto &FavoriteCommunity : m_vEntries)
{
str_copy(aBuf, "add_favorite_community \"");
str_append(aBuf, FavoriteCommunity.Id());
str_append(aBuf, "\"");
pConfigManager->WriteLine(aBuf);
}
}
const std::vector<CCommunityId> &CFavoriteCommunityFilterList::Entries() const
{
return m_vEntries;
}
void CExcludedCommunityFilterList::Add(const char *pCommunityId)
{
m_Entries.emplace(pCommunityId);
}
void CExcludedCommunityFilterList::Remove(const char *pCommunityId)
{
m_Entries.erase(CCommunityId(pCommunityId));
}
void CExcludedCommunityFilterList::Clear()
{
m_Entries.clear();
}
bool CExcludedCommunityFilterList::Filtered(const char *pCommunityId) const
{
return std::find(m_Entries.begin(), m_Entries.end(), CCommunityId(pCommunityId)) != m_Entries.end();
}
bool CExcludedCommunityFilterList::Empty() const
{
return m_Entries.empty();
}
void CExcludedCommunityFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
{
for(auto It = m_Entries.begin(); It != m_Entries.end();)
{
const bool Found = std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) {
return str_comp(It->Id(), AllowedCommunity.Id()) == 0;
}) != vAllowedCommunities.end();
if(Found)
{ {
if(m_pFilter[0] != '\0') ++It;
str_append(m_pFilter, ",", m_FilterSize); }
str_append(m_pFilter, aToken, m_FilterSize); else
{
It = m_Entries.erase(It);
}
}
// Prevent filter that would exclude all allowed communities
if(m_Entries.size() == vAllowedCommunities.size())
{
m_Entries.clear();
}
}
void CExcludedCommunityFilterList::Save(IConfigManager *pConfigManager) const
{
char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH];
for(const auto &ExcludedCommunity : m_Entries)
{
str_copy(aBuf, "add_excluded_community \"");
str_append(aBuf, ExcludedCommunity.Id());
str_append(aBuf, "\"");
pConfigManager->WriteLine(aBuf);
}
}
void CExcludedCommunityCountryFilterList::Add(const char *pCountryName)
{
for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
{
if(pCommunity->HasCountry(pCountryName))
{
Add(pCommunity->Id(), pCountryName);
} }
} }
} }
void CFilterList::Clear() void CExcludedCommunityCountryFilterList::Add(const char *pCommunityId, const char *pCountryName)
{ {
m_pFilter[0] = '\0'; CCommunityId CommunityId(pCommunityId);
if(m_Entries.find(CommunityId) == m_Entries.end())
{
m_Entries[CommunityId] = {};
}
m_Entries[CommunityId].emplace(pCountryName);
} }
bool CFilterList::Filtered(const char *pElement) const void CExcludedCommunityCountryFilterList::Remove(const char *pCountryName)
{
for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
{
Remove(pCommunity->Id(), pCountryName);
}
}
void CExcludedCommunityCountryFilterList::Remove(const char *pCommunityId, const char *pCountryName)
{
auto CommunityEntry = m_Entries.find(CCommunityId(pCommunityId));
if(CommunityEntry != m_Entries.end())
{
CommunityEntry->second.erase(pCountryName);
}
}
void CExcludedCommunityCountryFilterList::Clear()
{
for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
{
auto CommunityEntry = m_Entries.find(pCommunity->Id());
if(CommunityEntry != m_Entries.end())
{
CommunityEntry->second.clear();
}
}
}
bool CExcludedCommunityCountryFilterList::Filtered(const char *pCountryName) const
{ {
// If the needle is not defined, we exclude it if there is any other // If the needle is not defined, we exclude it if there is any other
// exclusion, i.e. we only show those elements when the filter is empty. // exclusion, i.e. we only show those elements when the filter is empty.
if(pElement[0] == '\0') if(pCountryName[0] == '\0')
return !Empty(); return !Empty();
// Special case: "*element" means anything except that element is excluded. const auto Communities = m_CurrentCommunitiesGetter();
// Necessary because the default filter cannot exclude unknown elements, return std::none_of(Communities.begin(), Communities.end(), [&](const CCommunity *pCommunity) {
// but we want to select only the DDNet community by default. if(!pCommunity->HasCountry(pCountryName))
if(m_pFilter[0] == '*') return false;
return str_comp(m_pFilter + 1, pElement) != 0;
// Comma separated list of excluded elements. auto CommunityEntry = m_Entries.find(CCommunityId(pCommunity->Id()));
return str_in_list(m_pFilter, ",", pElement); if(CommunityEntry == m_Entries.end())
return true;
const auto &CountryEntries = CommunityEntry->second;
if(CountryEntries.find(CCommunityCountryName(pCountryName)) == CountryEntries.end())
return true;
return false;
});
} }
bool CFilterList::Empty() const bool CExcludedCommunityCountryFilterList::Empty() const
{ {
return m_pFilter[0] == '\0'; for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
}
void CFilterList::Clean(const std::vector<const char *> &vpAllowedElements)
{
size_t NumFiltered = 0;
char aNewList[512];
aNewList[0] = '\0';
for(const char *pElement : vpAllowedElements)
{ {
if(Filtered(pElement)) auto CommunityEntry = m_Entries.find(CCommunityId(pCommunity->Id()));
return CommunityEntry == m_Entries.end() || CommunityEntry->second.empty();
}
return false;
}
void CExcludedCommunityCountryFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
{
for(auto It = m_Entries.begin(); It != m_Entries.end();)
{
const bool Found = std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) {
return str_comp(It->first.Id(), AllowedCommunity.Id()) == 0;
}) != vAllowedCommunities.end();
if(Found)
{ {
if(aNewList[0] != '\0') ++It;
str_append(aNewList, ","); }
str_append(aNewList, pElement); else
++NumFiltered; {
It = m_Entries.erase(It);
} }
} }
// Prevent filter that would exclude all allowed elements for(const CCommunity &AllowedCommunity : vAllowedCommunities)
if(NumFiltered == vpAllowedElements.size()) {
m_pFilter[0] = '\0'; auto CommunityEntry = m_Entries.find(CCommunityId(AllowedCommunity.Id()));
else if(CommunityEntry != m_Entries.end())
str_copy(m_pFilter, aNewList, m_FilterSize); {
auto &CountryEntries = CommunityEntry->second;
for(auto It = CountryEntries.begin(); It != CountryEntries.end();)
{
if(AllowedCommunity.HasCountry(It->Name()))
{
++It;
}
else
{
It = CountryEntries.erase(It);
}
}
// Prevent filter that would exclude all allowed countries
if(CountryEntries.size() == AllowedCommunity.Countries().size())
{
CountryEntries.clear();
}
}
}
}
void CExcludedCommunityCountryFilterList::Save(IConfigManager *pConfigManager) const
{
char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH + CServerInfo::MAX_COMMUNITY_COUNTRY_LENGTH];
for(const auto &[Community, Countries] : m_Entries)
{
for(const auto &Country : Countries)
{
str_copy(aBuf, "add_excluded_country \"");
str_append(aBuf, Community.Id());
str_append(aBuf, "\" \"");
str_append(aBuf, Country.Name());
str_append(aBuf, "\"");
pConfigManager->WriteLine(aBuf);
}
}
}
void CExcludedCommunityTypeFilterList::Add(const char *pTypeName)
{
for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
{
if(pCommunity->HasType(pTypeName))
{
Add(pCommunity->Id(), pTypeName);
}
}
}
void CExcludedCommunityTypeFilterList::Add(const char *pCommunityId, const char *pTypeName)
{
CCommunityId CommunityId(pCommunityId);
if(m_Entries.find(CommunityId) == m_Entries.end())
{
m_Entries[CommunityId] = {};
}
m_Entries[CommunityId].emplace(pTypeName);
}
void CExcludedCommunityTypeFilterList::Remove(const char *pTypeName)
{
for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
{
Remove(pCommunity->Id(), pTypeName);
}
}
void CExcludedCommunityTypeFilterList::Remove(const char *pCommunityId, const char *pTypeName)
{
auto CommunityEntry = m_Entries.find(CCommunityId(pCommunityId));
if(CommunityEntry != m_Entries.end())
{
CommunityEntry->second.erase(pTypeName);
}
}
void CExcludedCommunityTypeFilterList::Clear()
{
for(const CCommunity *pCommunity : m_CurrentCommunitiesGetter())
{
auto CommunityEntry = m_Entries.find(pCommunity->Id());
if(CommunityEntry != m_Entries.end())
{
CommunityEntry->second.clear();
}
}
}
bool CExcludedCommunityTypeFilterList::Filtered(const char *pTypeName) const
{
// If the needle is not defined, we exclude it if there is any other
// exclusion, i.e. we only show those elements when the filter is empty.
if(pTypeName[0] == '\0')
return !Empty();
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(pCommunity->Id()));
if(CommunityEntry == m_Entries.end())
return true;
const auto &TypeEntries = CommunityEntry->second;
return 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;
}
void CExcludedCommunityTypeFilterList::Clean(const std::vector<CCommunity> &vAllowedCommunities)
{
for(auto It = m_Entries.begin(); It != m_Entries.end();)
{
const bool Found = std::find_if(vAllowedCommunities.begin(), vAllowedCommunities.end(), [&](const CCommunity &AllowedCommunity) {
return str_comp(It->first.Id(), AllowedCommunity.Id()) == 0;
}) != vAllowedCommunities.end();
if(Found)
{
++It;
}
else
{
It = m_Entries.erase(It);
}
}
for(const CCommunity &AllowedCommunity : vAllowedCommunities)
{
auto CommunityEntry = m_Entries.find(CCommunityId(AllowedCommunity.Id()));
if(CommunityEntry != m_Entries.end())
{
auto &TypeEntries = CommunityEntry->second;
for(auto It = TypeEntries.begin(); It != TypeEntries.end();)
{
if(AllowedCommunity.HasType(It->Name()))
{
++It;
}
else
{
It = TypeEntries.erase(It);
}
}
// Prevent filter that would exclude all allowed countries
if(TypeEntries.size() == AllowedCommunity.Types().size())
{
TypeEntries.clear();
}
}
}
}
void CExcludedCommunityTypeFilterList::Save(IConfigManager *pConfigManager) const
{
char aBuf[32 + CServerInfo::MAX_COMMUNITY_ID_LENGTH + CServerInfo::MAX_COMMUNITY_TYPE_LENGTH];
for(const auto &[Community, Types] : m_Entries)
{
for(const auto &Type : Types)
{
str_copy(aBuf, "add_excluded_type \"");
str_append(aBuf, Community.Id());
str_append(aBuf, "\" \"");
str_append(aBuf, Type.Name());
str_append(aBuf, "\"");
pConfigManager->WriteLine(aBuf);
}
}
} }
void CServerBrowser::CleanFilters() void CServerBrowser::CleanFilters()
{ {
CommunitiesFilterClean(); // Keep filters if we failed to load any communities
CountriesFilterClean(); if(Communities().empty())
TypesFilterClean(); return;
} FavoriteCommunitiesFilter().Clean(Communities());
CommunitiesFilter().Clean(Communities());
void CServerBrowser::CommunitiesFilterClean() CountriesFilter().Clean(Communities());
{ TypesFilter().Clean(Communities());
std::vector<const char *> vpCommunityNames;
for(const auto &Community : Communities())
vpCommunityNames.push_back(Community.Id());
m_CommunitiesFilter.Clean(vpCommunityNames);
}
void CServerBrowser::CountriesFilterClean()
{
std::vector<const char *> vpCountryNames;
for(const CCommunity *pCommunity : SelectedCommunities())
for(const auto &Country : pCommunity->Countries())
vpCountryNames.push_back(Country.Name());
m_CountriesFilter.Clean(vpCountryNames);
}
void CServerBrowser::TypesFilterClean()
{
std::vector<const char *> vpTypeNames;
for(const CCommunity *pCommunity : SelectedCommunities())
for(const auto &Type : pCommunity->Types())
vpTypeNames.push_back(Type.Name());
m_TypesFilter.Clean(vpTypeNames);
} }
bool CServerBrowser::IsRegistered(const NETADDR &Addr) bool CServerBrowser::IsRegistered(const NETADDR &Addr)

View file

@ -9,7 +9,9 @@
#include <engine/serverbrowser.h> #include <engine/serverbrowser.h>
#include <engine/shared/memheap.h> #include <engine/shared/memheap.h>
#include <functional>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
typedef struct _json_value json_value; typedef struct _json_value json_value;
class CNetClient; class CNetClient;
@ -23,6 +25,87 @@ class IServerBrowserPingCache;
class IStorage; class IStorage;
class IHttp; class IHttp;
class CCommunityId
{
char m_aId[CServerInfo::MAX_COMMUNITY_ID_LENGTH];
public:
CCommunityId(const char *pCommunityId)
{
str_copy(m_aId, pCommunityId);
}
const char *Id() const { return m_aId; }
bool operator==(const CCommunityId &Other) const
{
return str_comp(Id(), Other.Id()) == 0;
}
};
template<>
struct std::hash<CCommunityId>
{
size_t operator()(const CCommunityId &Elem) const noexcept
{
return str_quickhash(Elem.Id());
}
};
class CCommunityCountryName
{
char m_aName[CServerInfo::MAX_COMMUNITY_COUNTRY_LENGTH];
public:
CCommunityCountryName(const char *pCountryName)
{
str_copy(m_aName, pCountryName);
}
const char *Name() const { return m_aName; }
bool operator==(const CCommunityCountryName &Other) const
{
return str_comp(Name(), Other.Name()) == 0;
}
};
template<>
struct std::hash<CCommunityCountryName>
{
size_t operator()(const CCommunityCountryName &Elem) const noexcept
{
return str_quickhash(Elem.Name());
}
};
class CCommunityTypeName
{
char m_aName[CServerInfo::MAX_COMMUNITY_TYPE_LENGTH];
public:
CCommunityTypeName(const char *pTypeName)
{
str_copy(m_aName, pTypeName);
}
const char *Name() const { return m_aName; }
bool operator==(const CCommunityTypeName &Other) const
{
return str_comp(Name(), Other.Name()) == 0;
}
};
template<>
struct std::hash<CCommunityTypeName>
{
size_t operator()(const CCommunityTypeName &Elem) const noexcept
{
return str_quickhash(Elem.Name());
}
};
class CCommunityServer class CCommunityServer
{ {
char m_aCommunityId[CServerInfo::MAX_COMMUNITY_ID_LENGTH]; char m_aCommunityId[CServerInfo::MAX_COMMUNITY_ID_LENGTH];
@ -42,23 +125,81 @@ public:
const char *TypeName() const { return m_aTypeName; } const char *TypeName() const { return m_aTypeName; }
}; };
class CFilterList : public IFilterList class CFavoriteCommunityFilterList : public IFilterList
{ {
char *m_pFilter;
size_t m_FilterSize;
public: public:
CFilterList(char *pFilter, size_t FilterSize) : void Add(const char *pCommunityId) override;
m_pFilter(pFilter), m_FilterSize(FilterSize) void Remove(const char *pCommunityId) override;
void Clear() override;
bool Filtered(const char *pCommunityId) const override;
bool Empty() const override;
void Clean(const std::vector<CCommunity> &vAllowedCommunities);
void Save(IConfigManager *pConfigManager) const;
const std::vector<CCommunityId> &Entries() const;
private:
std::vector<CCommunityId> m_vEntries;
};
class CExcludedCommunityFilterList : public IFilterList
{
public:
void Add(const char *pCommunityId) override;
void Remove(const char *pCommunityId) override;
void Clear() override;
bool Filtered(const char *pCommunityId) const override;
bool Empty() const override;
void Clean(const std::vector<CCommunity> &vAllowedCommunities);
void Save(IConfigManager *pConfigManager) const;
private:
std::unordered_set<CCommunityId> m_Entries;
};
class CExcludedCommunityCountryFilterList : public IFilterList
{
public:
CExcludedCommunityCountryFilterList(std::function<std::vector<const CCommunity *>()> CurrentCommunitiesGetter) :
m_CurrentCommunitiesGetter(CurrentCommunitiesGetter)
{ {
} }
void Add(const char *pElement) override; void Add(const char *pCountryName) override;
void Remove(const char *pElement) override; void Add(const char *pCommunityId, const char *pCountryName);
void Remove(const char *pCountryName) override;
void Remove(const char *pCommunityId, const char *pCountryName);
void Clear() override; void Clear() override;
bool Filtered(const char *pElement) const override; bool Filtered(const char *pCountryName) const override;
bool Empty() const override; bool Empty() const override;
void Clean(const std::vector<const char *> &vpAllowedElements); void Clean(const std::vector<CCommunity> &vAllowedCommunities);
void Save(IConfigManager *pConfigManager) const;
private:
std::function<std::vector<const CCommunity *>()> m_CurrentCommunitiesGetter;
std::unordered_map<CCommunityId, std::unordered_set<CCommunityCountryName>> m_Entries;
};
class CExcludedCommunityTypeFilterList : public IFilterList
{
public:
CExcludedCommunityTypeFilterList(std::function<std::vector<const CCommunity *>()> CurrentCommunitiesGetter) :
m_CurrentCommunitiesGetter(CurrentCommunitiesGetter)
{
}
void Add(const char *pTypeName) override;
void Add(const char *pCommunityId, const char *pTypeName);
void Remove(const char *pTypeName) override;
void Remove(const char *pCommunityId, const char *pTypeName);
void Clear() override;
bool Filtered(const char *pTypeName) const override;
bool Empty() const override;
void Clean(const std::vector<CCommunity> &vAllowedCommunities);
void Save(IConfigManager *pConfigManager) const;
private:
std::function<std::vector<const CCommunity *>()> m_CurrentCommunitiesGetter;
std::unordered_map<CCommunityId, std::unordered_set<CCommunityTypeName>> m_Entries;
}; };
class CServerBrowser : public IServerBrowser class CServerBrowser : public IServerBrowser
@ -80,7 +221,7 @@ public:
virtual ~CServerBrowser(); virtual ~CServerBrowser();
// interface functions // interface functions
void Refresh(int Type) override; void Refresh(int Type, bool Force = false) override;
bool IsRefreshing() const override; bool IsRefreshing() const override;
bool IsGettingServerlist() const override; bool IsGettingServerlist() const override;
int LoadingProgression() const override; int LoadingProgression() const override;
@ -106,20 +247,23 @@ public:
const std::vector<CCommunity> &Communities() const override; const std::vector<CCommunity> &Communities() const override;
const CCommunity *Community(const char *pCommunityId) const override; const CCommunity *Community(const char *pCommunityId) const override;
std::vector<const CCommunity *> SelectedCommunities() const override; std::vector<const CCommunity *> SelectedCommunities() const override;
std::vector<const CCommunity *> FavoriteCommunities() const override;
std::vector<const CCommunity *> CurrentCommunities() const override;
unsigned CurrentCommunitiesHash() const override;
bool DDNetInfoAvailable() const override { return m_pDDNetInfo != nullptr; }
int64_t DDNetInfoUpdateTime() const override { return m_DDNetInfoUpdateTime; } int64_t DDNetInfoUpdateTime() const override { return m_DDNetInfoUpdateTime; }
CFilterList &CommunitiesFilter() override { return m_CommunitiesFilter; } CFavoriteCommunityFilterList &FavoriteCommunitiesFilter() override { return m_FavoriteCommunitiesFilter; }
CFilterList &CountriesFilter() override { return m_CountriesFilter; } CExcludedCommunityFilterList &CommunitiesFilter() override { return m_CommunitiesFilter; }
CFilterList &TypesFilter() override { return m_TypesFilter; } CExcludedCommunityCountryFilterList &CountriesFilter() override { return m_CountriesFilter; }
const CFilterList &CommunitiesFilter() const override { return m_CommunitiesFilter; } CExcludedCommunityTypeFilterList &TypesFilter() override { return m_TypesFilter; }
const CFilterList &CountriesFilter() const override { return m_CountriesFilter; } const CFavoriteCommunityFilterList &FavoriteCommunitiesFilter() const override { return m_FavoriteCommunitiesFilter; }
const CFilterList &TypesFilter() const override { return m_TypesFilter; } const CExcludedCommunityFilterList &CommunitiesFilter() const override { return m_CommunitiesFilter; }
const CExcludedCommunityCountryFilterList &CountriesFilter() const override { return m_CountriesFilter; }
const CExcludedCommunityTypeFilterList &TypesFilter() const override { return m_TypesFilter; }
void CleanFilters() override; void CleanFilters() override;
void CommunitiesFilterClean();
void CountriesFilterClean();
void TypesFilterClean();
// //
void Update(); void Update();
void OnServerInfoUpdate(const NETADDR &Addr, int Token, const CServerInfo *pInfo); void OnServerInfoUpdate(const NETADDR &Addr, int Token, const CServerInfo *pInfo);
@ -162,9 +306,10 @@ private:
int m_OwnLocation = CServerInfo::LOC_UNKNOWN; int m_OwnLocation = CServerInfo::LOC_UNKNOWN;
CFilterList m_CommunitiesFilter; CFavoriteCommunityFilterList m_FavoriteCommunitiesFilter;
CFilterList m_CountriesFilter; CExcludedCommunityFilterList m_CommunitiesFilter;
CFilterList m_TypesFilter; CExcludedCommunityCountryFilterList m_CountriesFilter;
CExcludedCommunityTypeFilterList m_TypesFilter;
json_value *m_pDDNetInfo; json_value *m_pDDNetInfo;
int64_t m_DDNetInfoUpdateTime; int64_t m_DDNetInfoUpdateTime;
@ -217,8 +362,21 @@ private:
void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int *pBasicToken, int *pToken, bool RandomToken) const; void RequestImpl(const NETADDR &Addr, CServerEntry *pEntry, int *pBasicToken, int *pToken, bool RandomToken) const;
void RegisterCommands(); void RegisterCommands();
static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData);
static void Con_AddFavoriteCommunity(IConsole::IResult *pResult, void *pUserData);
static void Con_RemoveFavoriteCommunity(IConsole::IResult *pResult, void *pUserData);
static void Con_AddExcludedCommunity(IConsole::IResult *pResult, void *pUserData);
static void Con_RemoveExcludedCommunity(IConsole::IResult *pResult, void *pUserData);
static void Con_AddExcludedCountry(IConsole::IResult *pResult, void *pUserData);
static void Con_RemoveExcludedCountry(IConsole::IResult *pResult, void *pUserData);
static void Con_AddExcludedType(IConsole::IResult *pResult, void *pUserData);
static void Con_RemoveExcludedType(IConsole::IResult *pResult, void *pUserData);
static void Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData); static void Con_LeakIpAddress(IConsole::IResult *pResult, void *pUserData);
bool ValidateCommunityId(const char *pCommunityId) const;
bool ValidateCountryName(const char *pCountryName) const;
bool ValidateTypeName(const char *pTypeName) const;
void SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const; void SetInfo(CServerEntry *pEntry, const CServerInfo &Info) const;
void SetLatency(NETADDR Addr, int Latency); void SetLatency(NETADDR Addr, int Latency);

View file

@ -231,6 +231,8 @@ public:
const SHA256_DIGEST &IconSha256() const { return m_IconSha256; } const SHA256_DIGEST &IconSha256() const { return m_IconSha256; }
const std::vector<CCommunityCountry> &Countries() const { return m_vCountries; } const std::vector<CCommunityCountry> &Countries() const { return m_vCountries; }
const std::vector<CCommunityType> &Types() const { return m_vTypes; } const std::vector<CCommunityType> &Types() const { return m_vTypes; }
bool HasCountry(const char *pCountryName) const;
bool HasType(const char *pTypeName) const;
bool HasRanks() const { return m_HasFinishes; } bool HasRanks() const { return m_HasFinishes; }
CServerInfo::ERankState HasRank(const char *pMap) const; CServerInfo::ERankState HasRank(const char *pMap) const;
}; };
@ -271,6 +273,9 @@ public:
TYPE_INTERNET = 0, TYPE_INTERNET = 0,
TYPE_LAN, TYPE_LAN,
TYPE_FAVORITES, TYPE_FAVORITES,
TYPE_FAVORITE_COMMUNITY_1,
TYPE_FAVORITE_COMMUNITY_2,
TYPE_FAVORITE_COMMUNITY_3,
NUM_TYPES, NUM_TYPES,
}; };
@ -279,7 +284,7 @@ public:
static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";"; static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";";
virtual void Refresh(int Type) = 0; virtual void Refresh(int Type, bool Force = false) = 0;
virtual bool IsGettingServerlist() const = 0; virtual bool IsGettingServerlist() const = 0;
virtual bool IsRefreshing() const = 0; virtual bool IsRefreshing() const = 0;
virtual int LoadingProgression() const = 0; virtual int LoadingProgression() const = 0;
@ -296,11 +301,18 @@ public:
virtual const std::vector<CCommunity> &Communities() const = 0; virtual const std::vector<CCommunity> &Communities() const = 0;
virtual const CCommunity *Community(const char *pCommunityId) const = 0; virtual const CCommunity *Community(const char *pCommunityId) const = 0;
virtual std::vector<const CCommunity *> SelectedCommunities() const = 0; virtual std::vector<const CCommunity *> SelectedCommunities() const = 0;
virtual std::vector<const CCommunity *> FavoriteCommunities() const = 0;
virtual std::vector<const CCommunity *> CurrentCommunities() const = 0;
virtual unsigned CurrentCommunitiesHash() const = 0;
virtual bool DDNetInfoAvailable() const = 0;
virtual int64_t DDNetInfoUpdateTime() const = 0; virtual int64_t DDNetInfoUpdateTime() const = 0;
virtual IFilterList &FavoriteCommunitiesFilter() = 0;
virtual IFilterList &CommunitiesFilter() = 0; virtual IFilterList &CommunitiesFilter() = 0;
virtual IFilterList &CountriesFilter() = 0; virtual IFilterList &CountriesFilter() = 0;
virtual IFilterList &TypesFilter() = 0; virtual IFilterList &TypesFilter() = 0;
virtual const IFilterList &FavoriteCommunitiesFilter() const = 0;
virtual const IFilterList &CommunitiesFilter() const = 0; virtual const IFilterList &CommunitiesFilter() const = 0;
virtual const IFilterList &CountriesFilter() const = 0; virtual const IFilterList &CountriesFilter() const = 0;
virtual const IFilterList &TypesFilter() const = 0; virtual const IFilterList &TypesFilter() const = 0;

View file

@ -134,7 +134,7 @@ MACRO_CONFIG_INT(ClPlayerDefaultEyes, player_default_eyes, 0, 0, 5, CFGFLAG_CLIE
MACRO_CONFIG_STR(ClSkinPrefix, cl_skin_prefix, 12, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Replace the skins by skins with this prefix (e.g. kitty, santa)") MACRO_CONFIG_STR(ClSkinPrefix, cl_skin_prefix, 12, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Replace the skins by skins with this prefix (e.g. kitty, santa)")
MACRO_CONFIG_INT(ClFatSkins, cl_fat_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable fat skins") MACRO_CONFIG_INT(ClFatSkins, cl_fat_skins, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable fat skins")
MACRO_CONFIG_INT(UiPage, ui_page, 6, 6, 10, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface page") MACRO_CONFIG_INT(UiPage, ui_page, 6, 6, 11, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface page")
MACRO_CONFIG_INT(UiSettingsPage, ui_settings_page, 0, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface settings page") MACRO_CONFIG_INT(UiSettingsPage, ui_settings_page, 0, 0, 9, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Interface settings page")
MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Toolbox page") MACRO_CONFIG_INT(UiToolboxPage, ui_toolbox_page, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Toolbox page")
MACRO_CONFIG_STR(UiServerAddress, ui_server_address, 1024, "localhost:8303", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Interface server address") MACRO_CONFIG_STR(UiServerAddress, ui_server_address, 1024, "localhost:8303", CFGFLAG_CLIENT | CFGFLAG_SAVE | CFGFLAG_INSENSITIVE, "Interface server address")
@ -286,9 +286,6 @@ MACRO_CONFIG_INT(BrFilterConnectingPlayers, br_filter_connecting_players, 1, 0,
MACRO_CONFIG_STR(BrFilterServerAddress, br_filter_serveraddress, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server address to filter") MACRO_CONFIG_STR(BrFilterServerAddress, br_filter_serveraddress, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Server address to filter")
MACRO_CONFIG_INT(BrFilterUnfinishedMap, br_filter_unfinished_map, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show only servers with unfinished maps") MACRO_CONFIG_INT(BrFilterUnfinishedMap, br_filter_unfinished_map, 0, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show only servers with unfinished maps")
MACRO_CONFIG_STR(BrFilterExcludeCommunities, br_filter_exclude_communities, 512, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out servers by community")
MACRO_CONFIG_STR(BrFilterExcludeCountries, br_filter_exclude_countries, 512, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out communities' servers by country")
MACRO_CONFIG_STR(BrFilterExcludeTypes, br_filter_exclude_types, 512, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Filter out communities' servers by gametype")
MACRO_CONFIG_INT(BrIndicateFinished, br_indicate_finished, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show whether you have finished a DDNet map (transmits your player name to info.ddnet.org/info)") MACRO_CONFIG_INT(BrIndicateFinished, br_indicate_finished, 1, 0, 1, CFGFLAG_SAVE | CFGFLAG_CLIENT, "Show whether you have finished a DDNet map (transmits your player name to info.ddnet.org/info)")
MACRO_CONFIG_STR(BrLocation, br_location, 16, "auto", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Override location for ping estimation, available: auto, af, as, as:cn, eu, na, oc, sa (Automatic, Africa, Asia, China, Europe, North America, Oceania/Australia, South America") MACRO_CONFIG_STR(BrLocation, br_location, 16, "auto", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Override location for ping estimation, available: auto, af, as, as:cn, eu, na, oc, sa (Automatic, Africa, Asia, China, Europe, North America, Oceania/Australia, South America")
MACRO_CONFIG_STR(BrCachedBestServerinfoUrl, br_cached_best_serverinfo_url, 256, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Do not set this variable, instead create a ddnet-serverlist-urls.cfg next to settings_ddnet.cfg to specify all possible serverlist URLs") MACRO_CONFIG_STR(BrCachedBestServerinfoUrl, br_cached_best_serverinfo_url, 256, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Do not set this variable, instead create a ddnet-serverlist-urls.cfg next to settings_ddnet.cfg to specify all possible serverlist URLs")

View file

@ -59,7 +59,6 @@ CMenus::CMenus()
m_ActivePage = PAGE_INTERNET; m_ActivePage = PAGE_INTERNET;
m_MenuPage = 0; m_MenuPage = 0;
m_GamePage = PAGE_GAME; m_GamePage = PAGE_GAME;
m_JoinTutorial = false;
m_NeedRestartGraphics = false; m_NeedRestartGraphics = false;
m_NeedRestartSound = false; m_NeedRestartSound = false;
@ -157,7 +156,7 @@ int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText,
return UI()->DoButtonLogic(pButtonContainer, Checked, pRect); return UI()->DoButtonLogic(pButtonContainer, Checked, pRect);
} }
int CMenus::DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator, const ColorRGBA *pDefaultColor, const ColorRGBA *pActiveColor, const ColorRGBA *pHoverColor, float EdgeRounding) int CMenus::DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator, const ColorRGBA *pDefaultColor, const ColorRGBA *pActiveColor, const ColorRGBA *pHoverColor, float EdgeRounding, const SCommunityIcon *pCommunityIcon)
{ {
const bool MouseInside = UI()->HotItem() == pButtonContainer; const bool MouseInside = UI()->HotItem() == pButtonContainer;
CUIRect Rect = *pRect; CUIRect Rect = *pRect;
@ -230,9 +229,18 @@ int CMenus::DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pTe
} }
} }
CUIRect Temp; if(pCommunityIcon)
Rect.HMargin(2.0f, &Temp); {
UI()->DoLabel(&Temp, pText, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC); CUIRect CommunityIcon;
Rect.Margin(2.0f, &CommunityIcon);
RenderCommunityIcon(pCommunityIcon, CommunityIcon, true);
}
else
{
CUIRect Label;
Rect.HMargin(2.0f, &Label);
UI()->DoLabel(&Label, pText, Label.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
}
return UI()->DoButtonLogic(pButtonContainer, Checked, pRect); return UI()->DoButtonLogic(pButtonContainer, Checked, pRect);
} }
@ -250,6 +258,24 @@ int CMenus::DoButton_GridHeader(const void *pID, const char *pText, int Checked,
return UI()->DoButtonLogic(pID, Checked, pRect); return UI()->DoButtonLogic(pID, Checked, pRect);
} }
int CMenus::DoButton_Favorite(const void *pButtonId, const void *pParentId, bool Checked, const CUIRect *pRect)
{
if(Checked || (pParentId != nullptr && UI()->HotItem() == pParentId) || UI()->HotItem() == pButtonId)
{
TextRender()->SetFontPreset(EFontPreset::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);
const float Alpha = UI()->HotItem() == pButtonId ? 0.2f : 0.0f;
TextRender()->TextColor(Checked ? ColorRGBA(1.0f, 0.85f, 0.3f, 0.8f + Alpha) : ColorRGBA(0.5f, 0.5f, 0.5f, 0.8f + Alpha));
SLabelProperties Props;
Props.m_MaxWidth = pRect->w;
UI()->DoLabel(pRect, FONT_ICON_STAR, 12.0f, TEXTALIGN_MC, Props);
TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
}
return UI()->DoButtonLogic(pButtonId, 0, pRect);
}
int CMenus::DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect) int CMenus::DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect)
{ {
CUIRect Box, Label; CUIRect Box, Label;
@ -573,7 +599,7 @@ void CMenus::RenderMenubar(CUIRect Box)
{ {
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET)
{ {
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN)
Client()->RequestDDNetInfo(); Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
} }
@ -597,7 +623,7 @@ void CMenus::RenderMenubar(CUIRect Box)
{ {
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES)
{ {
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET) if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN)
Client()->RequestDDNetInfo(); Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
} }
@ -605,6 +631,33 @@ void CMenus::RenderMenubar(CUIRect Box)
} }
GameClient()->m_Tooltips.DoToolTip(&s_FavoritesButton, &Button, Localize("Favorites")); GameClient()->m_Tooltips.DoToolTip(&s_FavoritesButton, &Button, Localize("Favorites"));
size_t FavoriteCommunityIndex = 0;
static CButtonContainer s_aFavoriteCommunityButtons[3];
static_assert(std::size(s_aFavoriteCommunityButtons) == (size_t)PAGE_FAVORITE_COMMUNITY_3 - PAGE_FAVORITE_COMMUNITY_1 + 1);
static_assert(std::size(s_aFavoriteCommunityButtons) == (size_t)BIT_TAB_FAVORITE_COMMUNITY_3 - BIT_TAB_FAVORITE_COMMUNITY_1 + 1);
static_assert(std::size(s_aFavoriteCommunityButtons) == (size_t)IServerBrowser::TYPE_FAVORITE_COMMUNITY_3 - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 + 1);
for(const CCommunity *pCommunity : ServerBrowser()->FavoriteCommunities())
{
Box.VSplitLeft(75.0f, &Button, &Box);
const int Page = PAGE_FAVORITE_COMMUNITY_1 + FavoriteCommunityIndex;
if(DoButton_MenuTab(&s_aFavoriteCommunityButtons[FavoriteCommunityIndex], FONT_ICON_ELLIPSIS, m_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());
++FavoriteCommunityIndex;
if(FavoriteCommunityIndex >= std::size(s_aFavoriteCommunityButtons))
break;
}
TextRender()->SetRenderFlags(0); TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
} }
@ -790,10 +843,22 @@ void CMenus::OnInit()
if(g_Config.m_ClShowWelcome) if(g_Config.m_ClShowWelcome)
{ {
m_Popup = POPUP_LANGUAGE; m_Popup = POPUP_LANGUAGE;
str_copy(g_Config.m_BrFilterExcludeCommunities, "*ddnet"); m_CreateDefaultFavoriteCommunities = true;
} }
if(g_Config.m_UiPage >= PAGE_FAVORITE_COMMUNITY_1 && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_3 &&
(size_t)(g_Config.m_UiPage - PAGE_FAVORITE_COMMUNITY_1) >= ServerBrowser()->FavoriteCommunities().size())
{
// Reset page to internet when there is no favorite community for this page.
g_Config.m_UiPage = PAGE_INTERNET;
}
if(g_Config.m_ClSkipStartMenu) if(g_Config.m_ClSkipStartMenu)
{
m_ShowStart = false; m_ShowStart = false;
}
SetMenuPage(g_Config.m_UiPage);
m_RefreshButton.Init(UI(), -1); m_RefreshButton.Init(UI(), -1);
m_ConnectButton.Init(UI(), -1); m_ConnectButton.Init(UI(), -1);
@ -802,7 +867,15 @@ void CMenus::OnInit()
Console()->Chain("remove_favorite", ConchainFavoritesUpdate, this); Console()->Chain("remove_favorite", ConchainFavoritesUpdate, this);
Console()->Chain("add_friend", ConchainFriendlistUpdate, this); Console()->Chain("add_friend", ConchainFriendlistUpdate, this);
Console()->Chain("remove_friend", ConchainFriendlistUpdate, this); Console()->Chain("remove_friend", ConchainFriendlistUpdate, this);
Console()->Chain("br_filter_exclude_communities", ConchainCommunitiesUpdate, this);
Console()->Chain("add_excluded_community", ConchainCommunitiesUpdate, this);
Console()->Chain("remove_excluded_community", ConchainCommunitiesUpdate, this);
Console()->Chain("add_excluded_country", ConchainCommunitiesUpdate, this);
Console()->Chain("remove_excluded_country", ConchainCommunitiesUpdate, this);
Console()->Chain("add_excluded_type", ConchainCommunitiesUpdate, this);
Console()->Chain("remove_excluded_type", ConchainCommunitiesUpdate, this);
Console()->Chain("ui_page", ConchainUiPageUpdate, this);
Console()->Chain("snd_enable", ConchainUpdateMusicState, this); Console()->Chain("snd_enable", ConchainUpdateMusicState, this);
Console()->Chain("snd_enable_music", ConchainUpdateMusicState, this); Console()->Chain("snd_enable_music", ConchainUpdateMusicState, this);
@ -928,15 +1001,32 @@ void CMenus::Render()
static int s_Frame = 0; static int s_Frame = 0;
if(s_Frame == 0) if(s_Frame == 0)
{ {
SetMenuPage(g_Config.m_UiPage); RefreshBrowserTab(g_Config.m_UiPage);
s_Frame++; s_Frame++;
} }
else if(s_Frame == 1) else if(s_Frame == 1)
{ {
UpdateMusicState(); UpdateMusicState();
RefreshBrowserTab(g_Config.m_UiPage);
s_Frame++; s_Frame++;
} }
else
{
UpdateCommunityIcons();
}
// Initially add DDNet as favorite community and select its tab.
// This must be delayed until the DDNet info is available.
if(m_CreateDefaultFavoriteCommunities && ServerBrowser()->DDNetInfoAvailable())
{
m_CreateDefaultFavoriteCommunities = false;
if(ServerBrowser()->Community(IServerBrowser::COMMUNITY_DDNET) != nullptr)
{
ServerBrowser()->FavoriteCommunitiesFilter().Clear();
ServerBrowser()->FavoriteCommunitiesFilter().Add(IServerBrowser::COMMUNITY_DDNET);
SetMenuPage(PAGE_FAVORITE_COMMUNITY_1);
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITE_COMMUNITY_1);
}
}
if(Client()->State() == IClient::STATE_ONLINE || Client()->State() == IClient::STATE_DEMOPLAYBACK) if(Client()->State() == IClient::STATE_ONLINE || Client()->State() == IClient::STATE_DEMOPLAYBACK)
{ {
@ -965,13 +1055,18 @@ void CMenus::Render()
if(m_Popup == POPUP_NONE) if(m_Popup == POPUP_NONE)
{ {
if(m_JoinTutorial && !Client()->InfoTaskRunning() && !ServerBrowser()->IsGettingServerlist()) if(m_JoinTutorial && ServerBrowser()->DDNetInfoAvailable() && !ServerBrowser()->IsGettingServerlist())
{ {
m_JoinTutorial = false; m_JoinTutorial = false;
// This is only reached on first launch, when the DDNet community tab has been created and
// activated by default, so the server info for the tutorial server should be available.
const char *pAddr = ServerBrowser()->GetTutorialServer(); const char *pAddr = ServerBrowser()->GetTutorialServer();
if(pAddr) if(pAddr)
{
Client()->Connect(pAddr); Client()->Connect(pAddr);
}
} }
if(m_ShowStart && Client()->State() == IClient::STATE_OFFLINE) if(m_ShowStart && Client()->State() == IClient::STATE_OFFLINE)
{ {
m_pBackground->ChangePosition(CMenuBackground::POS_START); m_pBackground->ChangePosition(CMenuBackground::POS_START);
@ -1047,10 +1142,16 @@ void CMenus::Render()
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_FAVORITES); m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_FAVORITES);
RenderServerbrowser(MainView); RenderServerbrowser(MainView);
} }
else if(m_MenuPage >= PAGE_FAVORITE_COMMUNITY_1 && m_MenuPage <= PAGE_FAVORITE_COMMUNITY_3)
{
m_pBackground->ChangePosition(m_MenuPage - PAGE_FAVORITE_COMMUNITY_1 + CMenuBackground::POS_BROWSER_CUSTOM0);
RenderServerbrowser(MainView);
}
else if(m_MenuPage == PAGE_SETTINGS) else if(m_MenuPage == PAGE_SETTINGS)
{
RenderSettings(MainView); RenderSettings(MainView);
}
// do tab bar
RenderMenubar(TabBar); RenderMenubar(TabBar);
} }
} }
@ -2179,11 +2280,8 @@ const CMenus::CMenuImage *CMenus::FindMenuImage(const char *pName)
void CMenus::SetMenuPage(int NewPage) void CMenus::SetMenuPage(int NewPage)
{ {
if(NewPage == PAGE_DDNET_LEGACY || NewPage == PAGE_KOG_LEGACY)
NewPage = PAGE_INTERNET;
m_MenuPage = NewPage; m_MenuPage = NewPage;
if(NewPage >= PAGE_INTERNET && NewPage <= PAGE_FAVORITES) if(NewPage >= PAGE_INTERNET && NewPage <= PAGE_FAVORITE_COMMUNITY_3)
g_Config.m_UiPage = NewPage; g_Config.m_UiPage = NewPage;
} }
@ -2203,4 +2301,9 @@ void CMenus::RefreshBrowserTab(int UiPage)
Client()->RequestDDNetInfo(); Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
} }
else if(UiPage >= PAGE_FAVORITE_COMMUNITY_1 && UiPage <= PAGE_FAVORITE_COMMUNITY_3)
{
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(UiPage - PAGE_FAVORITE_COMMUNITY_1 + IServerBrowser::TYPE_FAVORITE_COMMUNITY_1);
}
} }

View file

@ -45,6 +45,14 @@ public:
virtual bool OnInput(const IInput::CEvent &Event) override; virtual bool OnInput(const IInput::CEvent &Event) override;
}; };
struct SCommunityIcon
{
char m_aCommunityId[CServerInfo::MAX_COMMUNITY_ID_LENGTH];
SHA256_DIGEST m_Sha256;
IGraphics::CTextureHandle m_OrgTexture;
IGraphics::CTextureHandle m_GreyTexture;
};
class CMenus : public CComponent class CMenus : public CComponent
{ {
static ColorRGBA ms_GuiColor; static ColorRGBA ms_GuiColor;
@ -61,7 +69,7 @@ class CMenus : public CComponent
int DoButton_FontIcon(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners = IGraphics::CORNER_ALL, bool Enabled = true); int DoButton_FontIcon(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners = IGraphics::CORNER_ALL, bool Enabled = true);
int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active); int DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active);
int DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName = nullptr, int Corners = IGraphics::CORNER_ALL, float Rounding = 5.0f, float FontFactor = 0.0f, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f)); int DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName = nullptr, int Corners = IGraphics::CORNER_ALL, float Rounding = 5.0f, float FontFactor = 0.0f, ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f));
int DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator = nullptr, const ColorRGBA *pDefaultColor = nullptr, const ColorRGBA *pActiveColor = nullptr, const ColorRGBA *pHoverColor = nullptr, float EdgeRounding = 10.0f); int DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator = nullptr, const ColorRGBA *pDefaultColor = nullptr, const ColorRGBA *pActiveColor = nullptr, const ColorRGBA *pHoverColor = nullptr, float EdgeRounding = 10.0f, const SCommunityIcon *pCommunityIcon = nullptr);
int DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect); int DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect);
int DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect); int DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect);
@ -72,6 +80,7 @@ class CMenus : public CComponent
ColorHSLA DoButton_ColorPicker(const CUIRect *pRect, unsigned int *pHslaColor, bool Alpha); ColorHSLA DoButton_ColorPicker(const CUIRect *pRect, unsigned int *pHslaColor, bool Alpha);
void DoLaserPreview(const CUIRect *pRect, ColorHSLA OutlineColor, ColorHSLA InnerColor, const int LaserType); void DoLaserPreview(const CUIRect *pRect, ColorHSLA OutlineColor, ColorHSLA InnerColor, const int LaserType);
int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect); int DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect);
int DoButton_Favorite(const void *pButtonId, const void *pParentId, bool Checked, const CUIRect *pRect);
int DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination); int DoKeyReader(const void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination);
@ -158,7 +167,9 @@ protected:
int m_ActivePage; int m_ActivePage;
bool m_ShowStart; bool m_ShowStart;
bool m_MenuActive; bool m_MenuActive;
bool m_JoinTutorial;
bool m_JoinTutorial = false;
bool m_CreateDefaultFavoriteCommunities = false;
char m_aNextServer[256]; char m_aNextServer[256];
@ -492,10 +503,12 @@ protected:
static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainFavoritesUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); 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 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 struct SCommunityCache
{ {
int64_t m_UpdateTime = 0; int64_t m_UpdateTime = 0;
bool m_PageWithCommunities; int m_LastPage = 0;
unsigned m_SelectedCommunitiesHash;
std::vector<const CCommunity *> m_vpSelectedCommunities; std::vector<const CCommunity *> m_vpSelectedCommunities;
std::vector<const CCommunityCountry *> m_vpSelectableCountries; std::vector<const CCommunityCountry *> m_vpSelectableCountries;
std::vector<const CCommunityType *> m_vpSelectableTypes; std::vector<const CCommunityType *> m_vpSelectableTypes;
@ -521,7 +534,7 @@ protected:
public: public:
const char *CommunityId() const { return m_aCommunityId; } const char *CommunityId() const { return m_aCommunityId; }
bool Success() const { return m_Success; } bool Success() const { return m_Success; }
SHA256_DIGEST &&Sha256() { return std::move(m_Sha256); } const SHA256_DIGEST &Sha256() const { return m_Sha256; }
}; };
class CCommunityIconLoadJob : public IJob, public CAbstractCommunityIconJob class CCommunityIconLoadJob : public IJob, public CAbstractCommunityIconJob
@ -535,7 +548,7 @@ protected:
CCommunityIconLoadJob(CMenus *pMenus, const char *pCommunityId, int StorageType); CCommunityIconLoadJob(CMenus *pMenus, const char *pCommunityId, int StorageType);
~CCommunityIconLoadJob(); ~CCommunityIconLoadJob();
CImageInfo &&ImageInfo() { return std::move(m_ImageInfo); } CImageInfo &ImageInfo() { return m_ImageInfo; }
}; };
class CCommunityIconDownloadJob : public CHttpRequest, public CAbstractCommunityIconJob class CCommunityIconDownloadJob : public CHttpRequest, public CAbstractCommunityIconJob
@ -544,13 +557,6 @@ protected:
CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl, const SHA256_DIGEST &Sha256); CCommunityIconDownloadJob(CMenus *pMenus, const char *pCommunityId, const char *pUrl, const SHA256_DIGEST &Sha256);
}; };
struct SCommunityIcon
{
char m_aCommunityId[CServerInfo::MAX_COMMUNITY_ID_LENGTH];
SHA256_DIGEST m_Sha256;
IGraphics::CTextureHandle m_OrgTexture;
IGraphics::CTextureHandle m_GreyTexture;
};
std::vector<SCommunityIcon> m_vCommunityIcons; std::vector<SCommunityIcon> m_vCommunityIcons;
std::deque<std::shared_ptr<CCommunityIconLoadJob>> m_CommunityIconLoadJobs; std::deque<std::shared_ptr<CCommunityIconLoadJob>> m_CommunityIconLoadJobs;
std::deque<std::shared_ptr<CCommunityIconDownloadJob>> m_CommunityIconDownloadJobs; std::deque<std::shared_ptr<CCommunityIconDownloadJob>> m_CommunityIconDownloadJobs;
@ -558,7 +564,7 @@ protected:
static int CommunityIconScan(const char *pName, int IsDir, int DirType, void *pUser); static int CommunityIconScan(const char *pName, int IsDir, int DirType, void *pUser);
const SCommunityIcon *FindCommunityIcon(const char *pCommunityId); const SCommunityIcon *FindCommunityIcon(const char *pCommunityId);
bool LoadCommunityIconFile(const char *pPath, int DirType, CImageInfo &Info, SHA256_DIGEST &Sha256); bool LoadCommunityIconFile(const char *pPath, int DirType, CImageInfo &Info, SHA256_DIGEST &Sha256);
void LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &&Info, SHA256_DIGEST &&Sha256); void LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &Info, const SHA256_DIGEST &Sha256);
void RenderCommunityIcon(const SCommunityIcon *pIcon, CUIRect Rect, bool Active); void RenderCommunityIcon(const SCommunityIcon *pIcon, CUIRect Rect, bool Active);
void UpdateCommunityIcons(); void UpdateCommunityIcons();
@ -633,8 +639,9 @@ public:
PAGE_INTERNET, PAGE_INTERNET,
PAGE_LAN, PAGE_LAN,
PAGE_FAVORITES, PAGE_FAVORITES,
PAGE_DDNET_LEGACY, // removed, redirects to PAGE_INTERNET PAGE_FAVORITE_COMMUNITY_1,
PAGE_KOG_LEGACY, // removed, redirects to PAGE_INTERNET PAGE_FAVORITE_COMMUNITY_2,
PAGE_FAVORITE_COMMUNITY_3,
PAGE_DEMOS, PAGE_DEMOS,
PAGE_SETTINGS, PAGE_SETTINGS,
PAGE_NETWORK, PAGE_NETWORK,
@ -659,6 +666,9 @@ public:
BIG_TAB_INTERNET, BIG_TAB_INTERNET,
BIG_TAB_LAN, BIG_TAB_LAN,
BIG_TAB_FAVORITES, BIG_TAB_FAVORITES,
BIT_TAB_FAVORITE_COMMUNITY_1,
BIT_TAB_FAVORITE_COMMUNITY_2,
BIT_TAB_FAVORITE_COMMUNITY_3,
BIG_TAB_DEMOS, BIG_TAB_DEMOS,
BIG_TAB_LENGTH, BIG_TAB_LENGTH,

View file

@ -701,21 +701,6 @@ void CMenus::RenderServerbrowserFilters(CUIRect View)
if(DoButton_CheckBox(&g_Config.m_BrFilterConnectingPlayers, Localize("Filter connecting players"), g_Config.m_BrFilterConnectingPlayers, &Button)) if(DoButton_CheckBox(&g_Config.m_BrFilterConnectingPlayers, Localize("Filter connecting players"), g_Config.m_BrFilterConnectingPlayers, &Button))
g_Config.m_BrFilterConnectingPlayers ^= 1; g_Config.m_BrFilterConnectingPlayers ^= 1;
// community filter
if((g_Config.m_UiPage == PAGE_INTERNET || g_Config.m_UiPage == PAGE_FAVORITES) && !ServerBrowser()->Communities().empty())
{
CUIRect Row;
View.HSplitTop(6.0f, nullptr, &View);
View.HSplitTop(19.0f, &Row, &View);
Row.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f), IGraphics::CORNER_T, 4.0f);
UI()->DoLabel(&Row, Localize("Communities"), 12.0f, TEXTALIGN_MC);
View.HSplitTop(4.0f * 17.0f + CScrollRegion::HEIGHT_MAGIC_FIX, &Row, &View);
View.HSplitTop(3.0f, nullptr, &View);
Row.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_B, 4.0f);
RenderServerbrowserCommunitiesFilter(Row);
}
// map finish filters // map finish filters
if(m_CommunityCache.m_AnyRanksAvailable) if(m_CommunityCache.m_AnyRanksAvailable)
{ {
@ -801,13 +786,24 @@ void CMenus::ResetServerbrowserFilters()
g_Config.m_BrFilterGametype[0] = '\0'; g_Config.m_BrFilterGametype[0] = '\0';
g_Config.m_BrFilterGametypeStrict = 0; g_Config.m_BrFilterGametypeStrict = 0;
g_Config.m_BrFilterConnectingPlayers = 1; g_Config.m_BrFilterConnectingPlayers = 1;
g_Config.m_BrFilterUnfinishedMap = 0;
g_Config.m_BrFilterServerAddress[0] = '\0'; g_Config.m_BrFilterServerAddress[0] = '\0';
ConfigManager()->Reset("br_filter_exclude_communities");
ConfigManager()->Reset("br_filter_exclude_countries"); if(g_Config.m_UiPage != PAGE_LAN)
ConfigManager()->Reset("br_filter_exclude_types"); {
if(m_CommunityCache.m_AnyRanksAvailable)
{
g_Config.m_BrFilterUnfinishedMap = 0;
}
if(g_Config.m_UiPage == PAGE_INTERNET || g_Config.m_UiPage == PAGE_FAVORITES)
{
ServerBrowser()->CommunitiesFilter().Clear();
}
ServerBrowser()->CountriesFilter().Clear();
ServerBrowser()->TypesFilter().Clear();
UpdateCommunityCache(true);
}
Client()->ServerBrowserUpdate(); Client()->ServerBrowserUpdate();
UpdateCommunityCache(true);
} }
void CMenus::RenderServerbrowserDDNetFilter(CUIRect View, void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
@ -914,11 +910,18 @@ void CMenus::RenderServerbrowserDDNetFilter(CUIRect View,
void CMenus::RenderServerbrowserCommunitiesFilter(CUIRect View) void CMenus::RenderServerbrowserCommunitiesFilter(CUIRect View)
{ {
CUIRect Tab;
View.HSplitTop(19.0f, &Tab, &View);
Tab.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f), IGraphics::CORNER_T, 4.0f);
UI()->DoLabel(&Tab, Localize("Communities"), 12.0f, TEXTALIGN_MC);
View.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_B, 4.0f);
const int MaxEntries = ServerBrowser()->Communities().size(); const int MaxEntries = ServerBrowser()->Communities().size();
const int EntriesPerRow = 1; const int EntriesPerRow = 1;
static CScrollRegion s_ScrollRegion; static CScrollRegion s_ScrollRegion;
static std::vector<unsigned char> s_vItemIds; static std::vector<unsigned char> s_vItemIds;
static std::vector<unsigned char> s_vFavoriteButtonIds;
const float ItemHeight = 13.0f; const float ItemHeight = 13.0f;
const float Spacing = 2.0f; const float Spacing = 2.0f;
@ -932,12 +935,14 @@ void CMenus::RenderServerbrowserCommunitiesFilter(CUIRect View)
const auto &&RenderItem = [&](int ItemIndex, CUIRect Item, const void *pItemId, bool Active) { const auto &&RenderItem = [&](int ItemIndex, CUIRect Item, const void *pItemId, bool Active) {
const float Alpha = (Active ? 0.9f : 0.2f) + (UI()->HotItem() == pItemId ? 0.1f : 0.0f); const float Alpha = (Active ? 0.9f : 0.2f) + (UI()->HotItem() == pItemId ? 0.1f : 0.0f);
CUIRect Icon, Label; CUIRect Icon, Label, FavoriteButton;
Item.VSplitRight(Item.h, &Item, &FavoriteButton);
Item.Margin(Spacing, &Item); Item.Margin(Spacing, &Item);
Item.VSplitLeft(Item.h * 2.0f, &Icon, &Label); Item.VSplitLeft(Item.h * 2.0f, &Icon, &Label);
Label.VSplitLeft(Spacing, nullptr, &Label); Label.VSplitLeft(Spacing, nullptr, &Label);
const SCommunityIcon *pIcon = FindCommunityIcon(GetItemName(ItemIndex)); const char *pItemName = GetItemName(ItemIndex);
const SCommunityIcon *pIcon = FindCommunityIcon(pItemName);
if(pIcon != nullptr) if(pIcon != nullptr)
{ {
RenderCommunityIcon(pIcon, Icon, Active); RenderCommunityIcon(pIcon, Icon, Active);
@ -946,8 +951,22 @@ void CMenus::RenderServerbrowserCommunitiesFilter(CUIRect View)
TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha); TextRender()->TextColor(1.0f, 1.0f, 1.0f, Alpha);
UI()->DoLabel(&Label, GetItemDisplayName(ItemIndex), Label.h * CUI::ms_FontmodHeight, TEXTALIGN_ML); UI()->DoLabel(&Label, GetItemDisplayName(ItemIndex), Label.h * CUI::ms_FontmodHeight, TEXTALIGN_ML);
TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->TextColor(TextRender()->DefaultTextColor());
const bool Favorite = ServerBrowser()->FavoriteCommunitiesFilter().Filtered(pItemName);
if(DoButton_Favorite(&s_vFavoriteButtonIds[ItemIndex], pItemId, Favorite, &FavoriteButton))
{
if(Favorite)
{
ServerBrowser()->FavoriteCommunitiesFilter().Remove(pItemName);
}
else
{
ServerBrowser()->FavoriteCommunitiesFilter().Add(pItemName);
}
}
}; };
s_vFavoriteButtonIds.resize(MaxEntries);
RenderServerbrowserDDNetFilter(View, ServerBrowser()->CommunitiesFilter(), ItemHeight + 2.0f * Spacing, MaxEntries, EntriesPerRow, s_ScrollRegion, s_vItemIds, true, GetItemName, RenderItem); RenderServerbrowserDDNetFilter(View, ServerBrowser()->CommunitiesFilter(), ItemHeight + 2.0f * Spacing, MaxEntries, EntriesPerRow, s_ScrollRegion, s_vItemIds, true, GetItemName, RenderItem);
} }
@ -1656,17 +1675,16 @@ void CMenus::RenderServerbrowserToolBox(CUIRect ToolBox)
void CMenus::RenderServerbrowser(CUIRect MainView) void CMenus::RenderServerbrowser(CUIRect MainView)
{ {
UpdateCommunityCache(false); UpdateCommunityCache(false);
UpdateCommunityIcons();
/* /*
+-----------------+ +--tabs--+ +---------------------------+ +---communities---+
| | | | | | | |
| | | | | | +------tabs-------+
| server list | | tool | | server list | | |
| | | box | | | | tool |
| | | | | | | box |
+-----------------+ | | +---------------------------+ | |
status box +--------+ status box +-----------------+
*/ */
CUIRect ServerList, StatusBox, ToolBox, TabBar; CUIRect ServerList, StatusBox, ToolBox, TabBar;
@ -1674,6 +1692,15 @@ void CMenus::RenderServerbrowser(CUIRect MainView)
MainView.Margin(10.0f, &MainView); MainView.Margin(10.0f, &MainView);
MainView.VSplitRight(205.0f, &ServerList, &ToolBox); MainView.VSplitRight(205.0f, &ServerList, &ToolBox);
ServerList.VSplitRight(5.0f, &ServerList, nullptr); ServerList.VSplitRight(5.0f, &ServerList, nullptr);
if((g_Config.m_UiPage == PAGE_INTERNET || g_Config.m_UiPage == PAGE_FAVORITES) && !ServerBrowser()->Communities().empty())
{
CUIRect CommunityFilter;
ToolBox.HSplitTop(19.0f + 4.0f * 17.0f + CScrollRegion::HEIGHT_MAGIC_FIX, &CommunityFilter, &ToolBox);
ToolBox.HSplitTop(8.0f, nullptr, &ToolBox);
RenderServerbrowserCommunitiesFilter(CommunityFilter);
}
ToolBox.HSplitTop(24.0f, &TabBar, &ToolBox); ToolBox.HSplitTop(24.0f, &TabBar, &ToolBox);
ServerList.HSplitBottom(65.0f, &ServerList, &StatusBox); ServerList.HSplitBottom(65.0f, &ServerList, &StatusBox);
@ -1762,28 +1789,70 @@ void CMenus::ConchainCommunitiesUpdate(IConsole::IResult *pResult, void *pUserDa
{ {
pfnCallback(pResult, pCallbackUserData); pfnCallback(pResult, pCallbackUserData);
CMenus *pThis = static_cast<CMenus *>(pUserData); CMenus *pThis = static_cast<CMenus *>(pUserData);
if(pResult->NumArguments() >= 1 && (g_Config.m_UiPage == PAGE_INTERNET || g_Config.m_UiPage == PAGE_FAVORITES)) if(pResult->NumArguments() >= 1 && (g_Config.m_UiPage == PAGE_INTERNET || g_Config.m_UiPage == PAGE_FAVORITES || (g_Config.m_UiPage >= PAGE_FAVORITE_COMMUNITY_1 && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_3)))
{ {
pThis->UpdateCommunityCache(true); pThis->UpdateCommunityCache(true);
pThis->Client()->ServerBrowserUpdate(); pThis->Client()->ServerBrowserUpdate();
} }
} }
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<CMenus *>(pUserData);
if(pResult->NumArguments() >= 1)
{
if(g_Config.m_UiPage >= PAGE_FAVORITE_COMMUNITY_1 && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_3 &&
(size_t)(g_Config.m_UiPage - PAGE_FAVORITE_COMMUNITY_1) >= pThis->ServerBrowser()->FavoriteCommunities().size())
{
// Reset page to internet when there is no favorite community for this page.
g_Config.m_UiPage = PAGE_INTERNET;
}
pThis->SetMenuPage(g_Config.m_UiPage);
if(!pThis->m_ShowStart && g_Config.m_UiPage != OldPage)
{
pThis->RefreshBrowserTab(g_Config.m_UiPage);
}
}
}
void CMenus::UpdateCommunityCache(bool Force) void CMenus::UpdateCommunityCache(bool Force)
{ {
const bool PageWithCommunities = g_Config.m_UiPage == PAGE_INTERNET || g_Config.m_UiPage == PAGE_FAVORITES; if(g_Config.m_UiPage >= PAGE_FAVORITE_COMMUNITY_1 && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_3 &&
if(!Force && m_CommunityCache.m_UpdateTime != 0 && m_CommunityCache.m_UpdateTime == ServerBrowser()->DDNetInfoUpdateTime() && m_CommunityCache.m_PageWithCommunities == PageWithCommunities) (size_t)(g_Config.m_UiPage - PAGE_FAVORITE_COMMUNITY_1) >= ServerBrowser()->FavoriteCommunities().size())
{
// 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.
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_3)
{
// 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);
}
if(!Force && m_CommunityCache.m_UpdateTime != 0 &&
m_CommunityCache.m_UpdateTime == ServerBrowser()->DDNetInfoUpdateTime() &&
!CurrentCommunitiesChanged && !PageChanged)
{
return; return;
}
ServerBrowser()->CleanFilters(); ServerBrowser()->CleanFilters();
m_CommunityCache.m_UpdateTime = ServerBrowser()->DDNetInfoUpdateTime(); m_CommunityCache.m_UpdateTime = ServerBrowser()->DDNetInfoUpdateTime();
m_CommunityCache.m_PageWithCommunities = PageWithCommunities; m_CommunityCache.m_LastPage = g_Config.m_UiPage;
m_CommunityCache.m_SelectedCommunitiesHash = CommunitiesHash;
if(m_CommunityCache.m_PageWithCommunities) m_CommunityCache.m_vpSelectedCommunities = ServerBrowser()->CurrentCommunities();
m_CommunityCache.m_vpSelectedCommunities = ServerBrowser()->SelectedCommunities();
else
m_CommunityCache.m_vpSelectedCommunities.clear();
m_CommunityCache.m_vpSelectableCountries.clear(); m_CommunityCache.m_vpSelectableCountries.clear();
m_CommunityCache.m_vpSelectableTypes.clear(); m_CommunityCache.m_vpSelectableTypes.clear();
@ -1866,7 +1935,7 @@ int CMenus::CommunityIconScan(const char *pName, int IsDir, int DirType, void *p
return 0; return 0;
} }
const CMenus::SCommunityIcon *CMenus::FindCommunityIcon(const char *pCommunityId) const SCommunityIcon *CMenus::FindCommunityIcon(const char *pCommunityId)
{ {
auto Icon = std::find_if(m_vCommunityIcons.begin(), m_vCommunityIcons.end(), [pCommunityId](const SCommunityIcon &Element) { auto Icon = std::find_if(m_vCommunityIcons.begin(), m_vCommunityIcons.end(), [pCommunityId](const SCommunityIcon &Element) {
return str_comp(Element.m_aCommunityId, pCommunityId) == 0; return str_comp(Element.m_aCommunityId, pCommunityId) == 0;
@ -1900,7 +1969,7 @@ bool CMenus::LoadCommunityIconFile(const char *pPath, int DirType, CImageInfo &I
return true; return true;
} }
void CMenus::LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &&Info, SHA256_DIGEST &&Sha256) void CMenus::LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &Info, const SHA256_DIGEST &Sha256)
{ {
SCommunityIcon CommunityIcon; SCommunityIcon CommunityIcon;
str_copy(CommunityIcon.m_aCommunityId, pCommunityId); str_copy(CommunityIcon.m_aCommunityId, pCommunityId);
@ -1979,14 +2048,14 @@ void CMenus::UpdateCommunityIcons()
{ {
std::shared_ptr<CCommunityIconLoadJob> pLoadJob = std::make_shared<CCommunityIconLoadJob>(this, pJob->CommunityId(), IStorage::TYPE_SAVE); std::shared_ptr<CCommunityIconLoadJob> pLoadJob = std::make_shared<CCommunityIconLoadJob>(this, pJob->CommunityId(), IStorage::TYPE_SAVE);
Engine()->AddJob(pLoadJob); Engine()->AddJob(pLoadJob);
m_CommunityIconLoadJobs.emplace_back(std::move(pLoadJob)); m_CommunityIconLoadJobs.push_back(pLoadJob);
} }
m_CommunityIconDownloadJobs.pop_front(); m_CommunityIconDownloadJobs.pop_front();
} }
} }
// Rescan for changed communities only when necessary // Rescan for changed communities only when necessary
if(m_CommunityIconsUpdateTime != 0 && m_CommunityIconsUpdateTime == ServerBrowser()->DDNetInfoUpdateTime()) if(!ServerBrowser()->DDNetInfoAvailable() || (m_CommunityIconsUpdateTime != 0 && m_CommunityIconsUpdateTime == ServerBrowser()->DDNetInfoUpdateTime()))
return; return;
m_CommunityIconsUpdateTime = ServerBrowser()->DDNetInfoUpdateTime(); m_CommunityIconsUpdateTime = ServerBrowser()->DDNetInfoUpdateTime();

View file

@ -837,9 +837,9 @@ void CMenus::RenderInGameNetwork(CUIRect MainView)
static CButtonContainer s_InternetButton; static CButtonContainer s_InternetButton;
if(DoButton_MenuTab(&s_InternetButton, FONT_ICON_EARTH_AMERICAS, g_Config.m_UiPage == PAGE_INTERNET, &Button, IGraphics::CORNER_NONE)) if(DoButton_MenuTab(&s_InternetButton, FONT_ICON_EARTH_AMERICAS, g_Config.m_UiPage == PAGE_INTERNET, &Button, IGraphics::CORNER_NONE))
{ {
if(g_Config.m_UiPage != PAGE_INTERNET) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET)
{ {
if(g_Config.m_UiPage != PAGE_FAVORITES) if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN)
Client()->RequestDDNetInfo(); Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET); ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
} }
@ -851,7 +851,7 @@ void CMenus::RenderInGameNetwork(CUIRect MainView)
static CButtonContainer s_LanButton; static CButtonContainer s_LanButton;
if(DoButton_MenuTab(&s_LanButton, FONT_ICON_NETWORK_WIRED, g_Config.m_UiPage == PAGE_LAN, &Button, IGraphics::CORNER_NONE)) if(DoButton_MenuTab(&s_LanButton, FONT_ICON_NETWORK_WIRED, g_Config.m_UiPage == PAGE_LAN, &Button, IGraphics::CORNER_NONE))
{ {
if(g_Config.m_UiPage != PAGE_LAN) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN)
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN); ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
NewPage = PAGE_LAN; NewPage = PAGE_LAN;
} }
@ -861,9 +861,9 @@ void CMenus::RenderInGameNetwork(CUIRect MainView)
static CButtonContainer s_FavoritesButton; static CButtonContainer s_FavoritesButton;
if(DoButton_MenuTab(&s_FavoritesButton, FONT_ICON_STAR, g_Config.m_UiPage == PAGE_FAVORITES, &Button, IGraphics::CORNER_NONE)) if(DoButton_MenuTab(&s_FavoritesButton, FONT_ICON_STAR, g_Config.m_UiPage == PAGE_FAVORITES, &Button, IGraphics::CORNER_NONE))
{ {
if(g_Config.m_UiPage != PAGE_FAVORITES) if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES)
{ {
if(g_Config.m_UiPage != PAGE_INTERNET) if(ServerBrowser()->GetCurrentType() == IServerBrowser::TYPE_LAN)
Client()->RequestDDNetInfo(); Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES); ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
} }
@ -871,6 +871,31 @@ void CMenus::RenderInGameNetwork(CUIRect MainView)
} }
GameClient()->m_Tooltips.DoToolTip(&s_FavoritesButton, &Button, Localize("Favorites")); GameClient()->m_Tooltips.DoToolTip(&s_FavoritesButton, &Button, Localize("Favorites"));
size_t FavoriteCommunityIndex = 0;
static CButtonContainer s_aFavoriteCommunityButtons[3];
static_assert(std::size(s_aFavoriteCommunityButtons) == (size_t)PAGE_FAVORITE_COMMUNITY_3 - PAGE_FAVORITE_COMMUNITY_1 + 1);
for(const CCommunity *pCommunity : ServerBrowser()->FavoriteCommunities())
{
TabBar.VSplitLeft(75.0f, &Button, &TabBar);
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());
++FavoriteCommunityIndex;
if(FavoriteCommunityIndex >= std::size(s_aFavoriteCommunityButtons))
break;
}
TextRender()->SetRenderFlags(0); TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);

View file

@ -801,19 +801,6 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
m_SkinListNeedsUpdate = false; m_SkinListNeedsUpdate = false;
} }
const auto &&RenderFavIcon = [&](const CUIRect &FavIcon, bool AsFav, bool Hot) {
TextRender()->SetFontPreset(EFontPreset::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(AsFav ? ColorRGBA(1.0f, 0.85f, 0.3f, 0.8f + (Hot ? 0.2f : 0.0f)) : ColorRGBA(0.5f, 0.5f, 0.5f, 0.8f + (Hot ? 0.2f : 0.0f)));
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
SLabelProperties Props;
Props.m_MaxWidth = FavIcon.w;
UI()->DoLabel(&FavIcon, FONT_ICON_STAR, 12.0f, TEXTALIGN_MR, Props);
TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->SetRenderFlags(0);
TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT);
};
int OldSelected = -1; int OldSelected = -1;
s_ListBox.DoStart(50.0f, s_vSkinList.size(), 4, 1, OldSelected, &MainView); s_ListBox.DoStart(50.0f, s_vSkinList.size(), 4, 1, OldSelected, &MainView);
for(size_t i = 0; i < s_vSkinList.size(); ++i) for(size_t i = 0; i < s_vSkinList.size(); ++i)
@ -861,11 +848,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
CUIRect FavIcon; CUIRect FavIcon;
Item.m_Rect.HSplitTop(20.0f, &FavIcon, nullptr); Item.m_Rect.HSplitTop(20.0f, &FavIcon, nullptr);
FavIcon.VSplitRight(20.0f, nullptr, &FavIcon); FavIcon.VSplitRight(20.0f, nullptr, &FavIcon);
if(IsFav || UI()->HotItem() == pSkinToBeDraw || UI()->HotItem() == &pSkinToBeDraw->m_Metrics.m_Body) if(DoButton_Favorite(&pSkinToBeDraw->m_Metrics.m_Body, pSkinToBeDraw, IsFav, &FavIcon))
{
RenderFavIcon(FavIcon, IsFav, UI()->HotItem() == &pSkinToBeDraw->m_Metrics.m_Body);
}
if(UI()->DoButtonLogic(&pSkinToBeDraw->m_Metrics.m_Body, 0, &FavIcon))
{ {
if(IsFav) if(IsFav)
{ {

View file

@ -67,6 +67,10 @@ void CMenus::RenderStartMenu(CUIRect MainView)
if(DoButton_Menu(&s_TutorialButton, Localize("Tutorial"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || if(DoButton_Menu(&s_TutorialButton, Localize("Tutorial"), 0, &Button, 0, IGraphics::CORNER_ALL, 5.0f, 0.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) ||
(s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime)) (s_JoinTutorialTime != 0.0f && Client()->LocalTime() >= s_JoinTutorialTime))
{ {
// 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);
const char *pAddr = ServerBrowser()->GetTutorialServer(); const char *pAddr = ServerBrowser()->GetTutorialServer();
if(pAddr) if(pAddr)
{ {
@ -187,12 +191,11 @@ void CMenus::RenderStartMenu(CUIRect MainView)
static CButtonContainer s_PlayButton; static CButtonContainer s_PlayButton;
if(DoButton_Menu(&s_PlayButton, Localize("Play", "Start menu"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "play_game" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || CheckHotKey(KEY_P)) if(DoButton_Menu(&s_PlayButton, Localize("Play", "Start menu"), 0, &Button, g_Config.m_ClShowStartMenuImages ? "play_game" : 0, IGraphics::CORNER_ALL, Rounding, 0.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f)) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || CheckHotKey(KEY_P))
{ {
NewPage = g_Config.m_UiPage >= PAGE_INTERNET && g_Config.m_UiPage <= PAGE_FAVORITES ? g_Config.m_UiPage : PAGE_INTERNET; NewPage = g_Config.m_UiPage >= PAGE_INTERNET && g_Config.m_UiPage <= PAGE_FAVORITE_COMMUNITY_3 ? g_Config.m_UiPage : PAGE_INTERNET;
} }
// render version // render version
CUIRect VersionUpdate, CurVersion; CUIRect VersionUpdate, CurVersion;
MainView.HSplitBottom(30.0f, 0, 0);
MainView.HSplitBottom(20.0f, 0, &VersionUpdate); MainView.HSplitBottom(20.0f, 0, &VersionUpdate);
VersionUpdate.VSplitRight(50.0f, &CurVersion, 0); VersionUpdate.VSplitRight(50.0f, &CurVersion, 0);

View file

@ -12,7 +12,13 @@ int DilateFile(const char *pFilename)
if(File) if(File)
{ {
io_seek(File, 0, IOSEEK_END); io_seek(File, 0, IOSEEK_END);
unsigned int FileSize = io_tell(File); long int FileSize = io_tell(File);
if(FileSize <= 0)
{
io_close(File);
dbg_msg("dilate", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
return false;
}
io_seek(File, 0, IOSEEK_START); io_seek(File, 0, IOSEEK_START);
TImageByteBuffer ByteBuffer; TImageByteBuffer ByteBuffer;
SImageByteBuffer ImageByteBuffer(&ByteBuffer); SImageByteBuffer ImageByteBuffer(&ByteBuffer);

View file

@ -31,7 +31,13 @@ int LoadPNG(CImageInfo *pImg, const char *pFilename)
if(File) if(File)
{ {
io_seek(File, 0, IOSEEK_END); io_seek(File, 0, IOSEEK_END);
unsigned int FileSize = io_tell(File); long int FileSize = io_tell(File);
if(FileSize <= 0)
{
io_close(File);
dbg_msg("map_convert_07", "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
return false;
}
io_seek(File, 0, IOSEEK_START); io_seek(File, 0, IOSEEK_START);
TImageByteBuffer ByteBuffer; TImageByteBuffer ByteBuffer;
SImageByteBuffer ImageByteBuffer(&ByteBuffer); SImageByteBuffer ImageByteBuffer(&ByteBuffer);

View file

@ -319,7 +319,13 @@ bool LoadPNG(CImageInfo *pImg, const char *pFilename)
} }
io_seek(File, 0, IOSEEK_END); io_seek(File, 0, IOSEEK_END);
unsigned int FileSize = io_tell(File); long int FileSize = io_tell(File);
if(FileSize <= 0)
{
io_close(File);
dbg_msg("map_create_pixelart", "ERROR: Failed to get file size (%ld). filename='%s'", FileSize, pFilename);
return false;
}
io_seek(File, 0, IOSEEK_START); io_seek(File, 0, IOSEEK_START);
TImageByteBuffer ByteBuffer; TImageByteBuffer ByteBuffer;
SImageByteBuffer ImageByteBuffer(&ByteBuffer); SImageByteBuffer ImageByteBuffer(&ByteBuffer);

View file

@ -29,7 +29,12 @@ bool LoadPNG(CImageInfo *pImg, const char *pFilename)
if(File) if(File)
{ {
io_seek(File, 0, IOSEEK_END); io_seek(File, 0, IOSEEK_END);
unsigned int FileSize = io_tell(File); long int FileSize = io_tell(File);
if(FileSize <= 0)
{
io_close(File);
return false;
}
io_seek(File, 0, IOSEEK_START); io_seek(File, 0, IOSEEK_START);
TImageByteBuffer ByteBuffer; TImageByteBuffer ByteBuffer;
SImageByteBuffer ImageByteBuffer(&ByteBuffer); SImageByteBuffer ImageByteBuffer(&ByteBuffer);