diff --git a/Client/core/CClientVariables.cpp b/Client/core/CClientVariables.cpp index 85dee800fa..34bfd58840 100644 --- a/Client/core/CClientVariables.cpp +++ b/Client/core/CClientVariables.cpp @@ -277,7 +277,6 @@ void CClientVariables::LoadDefaults() DEFAULT("debugfile", _S("")); // debug filename DEFAULT("console_pos", CVector2D(0, 0)); // console position DEFAULT("console_size", CVector2D(200, 200)); // console size - DEFAULT("serverbrowser_size", CVector2D(720.0f, 495.0f)); // serverbrowser size DEFAULT("fps_limit", 100); // frame limiter DEFAULT("chat_font", 2); // chatbox font type DEFAULT("chat_lines", 10); // chatbox lines diff --git a/Client/core/CQueryReceiver.cpp b/Client/core/CQueryReceiver.cpp index 4d1e516de9..b9ea2dde33 100644 --- a/Client/core/CQueryReceiver.cpp +++ b/Client/core/CQueryReceiver.cpp @@ -192,6 +192,35 @@ SQueryInfo CQueryReceiver::GetServerResponse() if (!strHttpPort.empty()) info.httpPort = atoi(strHttpPort); + // Check if this reply includes rules + if (strncmp(szBuffer + i, "RULES", 5) == 0) + { + g_pCore->GetConsole()->Printf("Parsing rules for server: %s", info.serverName.c_str()); + + i += 5; + while (i < len) + { + // Check if it's the end of rules + if ((unsigned char)szBuffer[i] == 1) + { + i++; + break; + } + + SString key, value; + if (!ReadString(key, szBuffer, i, len)) + return info; + if (!ReadString(value, szBuffer, i, len)) + return info; + + info.rules[key] = value; + + g_pCore->GetConsole()->Printf(" Rule: %s = %s", key.c_str(), value.c_str()); + } + + g_pCore->GetConsole()->Printf("Finished parsing rules"); + } + // Get player nicks while (i < len) { diff --git a/Client/core/CQueryReceiver.h b/Client/core/CQueryReceiver.h index 4bc56b90d7..58e77bf601 100644 --- a/Client/core/CQueryReceiver.h +++ b/Client/core/CQueryReceiver.h @@ -47,6 +47,7 @@ struct SQueryInfo ushort pingTime; std::vector playersPool; + std::unordered_map rules; }; class CQueryReceiver diff --git a/Client/core/ServerBrowser/CServerList.cpp b/Client/core/ServerBrowser/CServerList.cpp index 044f88e496..eec93f8f45 100644 --- a/Client/core/ServerBrowser/CServerList.cpp +++ b/Client/core/ServerBrowser/CServerList.cpp @@ -511,6 +511,8 @@ bool CServerListItem::ParseQuery() m_iBuildNumber = info.buildNum; m_usHttpPort = info.httpPort; + rules = info.rules; + if ((uiMasterServerSaysRestrictions & RESTRICTION_PLAYER_LIST) == false) vecPlayers = info.playersPool; diff --git a/Client/core/ServerBrowser/CServerList.h b/Client/core/ServerBrowser/CServerList.h index 5d0e450873..b7aecb3b0d 100644 --- a/Client/core/ServerBrowser/CServerList.h +++ b/Client/core/ServerBrowser/CServerList.h @@ -222,6 +222,7 @@ class CServerListItem CQueryReceiver queryReceiver; std::vector vecPlayers; + std::unordered_map rules; void Query(); diff --git a/Server/mods/deathmatch/logic/ASE.cpp b/Server/mods/deathmatch/logic/ASE.cpp index de9da405b9..5449c5e3d8 100644 --- a/Server/mods/deathmatch/logic/ASE.cpp +++ b/Server/mods/deathmatch/logic/ASE.cpp @@ -187,8 +187,8 @@ void ASE::DoPulse() break; } case 'r': - { // Our own lighter query for ingame browser - Release version only - strReply = QueryLightCached(); + { // New query for ingame server browser + strReply = QueryNewBrowserCached(); break; } case 'x': @@ -395,6 +395,133 @@ const std::string* ASE::QueryLightCached() return &m_strLightCached; } +// Protect against a flood of server queries. +// Send cached version unless player count has changed, or last re-cache is older than m_lNewMinInterval +const std::string* ASE::QueryNewBrowserCached() +{ + if (m_uiCurrentPlayerCount != m_uiNewLastPlayerCount || m_llCurrentTime - m_llNewLastTime > m_lNewMinInterval || m_strNewCached == "") + { + m_strNewCached = QueryNewBrowser(); + m_llNewLastTime = m_llCurrentTime; + m_uiNewLastPlayerCount = m_uiCurrentPlayerCount; + } + return &m_strNewCached; +} + +std::string ASE::QueryNewBrowser() +{ + std::stringstream reply; + + int iJoinedPlayers = m_pPlayerManager->CountJoined(); + int iMaxPlayers = m_pMainConfig->GetMaxPlayers(); + SString strPlayerCount = SString("%d/%d", iJoinedPlayers, iMaxPlayers); + SString strBuildType = SString("%d", MTASA_VERSION_TYPE); + SString strBuildNumber = SString("%d", MTASA_VERSION_BUILD); + SFixedString<32> strPingStatusFixed; + SFixedString<32> strNetRouteFixed; + g_pNetServer->GetPingStatus(&strPingStatusFixed); + g_pNetServer->GetNetRoute(&strNetRouteFixed); + SString strPingStatus = (const char*)strPingStatusFixed; + SString strNetRoute = (const char*)strNetRouteFixed; + SString strUpTime("%d", (uint)(time(NULL) - m_tStartTime)); + SString strHttpPort("%d", m_pMainConfig->GetHTTPPort()); + + uint uiExtraDataLength = (strPlayerCount.length() + 1 + strBuildType.length() + 1 + strBuildNumber.length() + 1 + strPingStatus.length() + 1 + + strNetRoute.length() + 1 + strUpTime.length() + 1 + strHttpPort.length() + 1); + uint uiMaxMapNameLength = 250 - uiExtraDataLength; + m_strMapName = m_strMapName.Left(uiMaxMapNameLength); + + reply << "EYE2"; + // game + reply << (unsigned char)4; + reply << "mta"; + // port + reply << (unsigned char)(m_strPort.length() + 1); + reply << m_strPort; + // server name + reply << (unsigned char)(m_pMainConfig->GetServerName().length() + 1); + reply << m_pMainConfig->GetServerName(); + // game type + reply << (unsigned char)(m_strGameType.length() + 1); + reply << m_strGameType; + // map name with backwardly compatible large player count, build type and build number + reply << (unsigned char)(m_strMapName.length() + 1 + uiExtraDataLength); + reply << m_strMapName; + reply << (unsigned char)0; + reply << strPlayerCount; + reply << (unsigned char)0; + reply << strBuildType; + reply << (unsigned char)0; + reply << strBuildNumber; + reply << (unsigned char)0; + reply << strPingStatus; + reply << (unsigned char)0; + reply << strNetRoute; + reply << (unsigned char)0; + reply << strUpTime; + reply << (unsigned char)0; + reply << strHttpPort; + // version + std::string temp = MTA_DM_ASE_VERSION; + reply << (unsigned char)(temp.length() + 1); + reply << temp; + // passworded + reply << (unsigned char)((m_pMainConfig->HasPassword()) ? 1 : 0); + // serial verification? + reply << (unsigned char)((m_pMainConfig->GetSerialVerificationEnabled()) ? 1 : 0); + // players count + reply << (unsigned char)std::min(iJoinedPlayers, 255); + // players max + reply << (unsigned char)std::min(iMaxPlayers, 255); + + // rules - this informs this response contains them + // previous version of this query did not have rules + reply << "RULES"; + + // send a max of 20 rules + list::iterator rIter = IterBegin(); + int rulesCount = 0; + for (; rIter != IterEnd() && rulesCount < 20; rIter++) + { + reply << (unsigned char)(strlen((*rIter)->GetKey()) + 1); + reply << (*rIter)->GetKey(); + reply << (unsigned char)(strlen((*rIter)->GetValue()) + 1); + reply << (*rIter)->GetValue(); + rulesCount++; + } + reply << (unsigned char)1; + + // players + CPlayer* pPlayer = NULL; + + // Keep the packet under 1350 bytes to try to avoid fragmentation + int iBytesLeft = 1340 - (int)reply.tellp(); + int iPlayersLeft = iJoinedPlayers; + + list::const_iterator pIter = m_pPlayerManager->IterBegin(); + for (; pIter != m_pPlayerManager->IterEnd(); pIter++) + { + pPlayer = *pIter; + if (pPlayer->IsJoined()) + { + // nick + std::string strPlayerName = RemoveColorCodes(pPlayer->GetNick()); + if (strPlayerName.length() == 0) + strPlayerName = pPlayer->GetNick(); + + // Check if we can fit more names + iBytesLeft -= strPlayerName.length() + 1; + if (iBytesLeft < iPlayersLeft--) + strPlayerName = ""; + + reply << (unsigned char)(strPlayerName.length() + 1); + reply << strPlayerName.c_str(); + } + } + + return reply.str(); +} + std::string ASE::QueryLight() { std::stringstream reply; diff --git a/Server/mods/deathmatch/logic/ASE.h b/Server/mods/deathmatch/logic/ASE.h index c517ddfb88..54d39012c9 100644 --- a/Server/mods/deathmatch/logic/ASE.h +++ b/Server/mods/deathmatch/logic/ASE.h @@ -84,6 +84,8 @@ class ASE const std::string* QueryFullCached(); std::string QueryFull(); const std::string* QueryLightCached(); + std::string QueryNewBrowser(); + const std::string* QueryNewBrowserCached(); const std::string* QueryXfireLightCached(); std::string QueryXfireLight(); @@ -120,6 +122,12 @@ class ASE long m_lLightMinInterval; std::string m_strLightCached; + // New query cache + unsigned int m_uiNewLastPlayerCount; + long long m_llNewLastTime; + long m_lNewMinInterval; + std::string m_strNewCached; + // XFire Light query cache unsigned int m_uiXfireLightLastPlayerCount; long long m_llXfireLightLastTime;