From ac849cea62f00efc694519f14051265bab7cceda Mon Sep 17 00:00:00 2001 From: Ka3dr Date: Tue, 29 Sep 2026 05:00:40 +0300 Subject: [PATCH] Add Legit Network finished map indicators --- CMakeLists.txt | 2 + data/BestClient/languages/russian.txt | 4 + .../languages/simplified_chinese.txt | 4 + .../shared/config_variables_bestclient.h | 1 + .../bestclient/legit_finished_maps.cpp | 159 ++++++++++++++++++ .../bestclient/legit_finished_maps.h | 43 +++++ .../bestclient/menus_bestclient.cpp | 7 +- src/game/client/components/menus_browser.cpp | 2 +- src/game/client/gameclient.cpp | 1 + src/game/client/gameclient.h | 2 + 10 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 src/game/client/components/bestclient/legit_finished_maps.cpp create mode 100644 src/game/client/components/bestclient/legit_finished_maps.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 571ae59a0..89ddfc482 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3027,6 +3027,8 @@ if(CLIENT) components/bestclient/gif_bubbles.h components/bestclient/ego_finished_maps.cpp components/bestclient/ego_finished_maps.h + components/bestclient/legit_finished_maps.cpp + components/bestclient/legit_finished_maps.h components/bestclient/ego_tiles_prediction.cpp components/bestclient/ego_tiles_prediction.h components/bestclient/edgehelper.cpp diff --git a/data/BestClient/languages/russian.txt b/data/BestClient/languages/russian.txt index fbc18a18e..f94822c3f 100644 --- a/data/BestClient/languages/russian.txt +++ b/data/BestClient/languages/russian.txt @@ -1202,6 +1202,10 @@ Short EGO/KoG server name Show finished map on EGO == Показывать пройденную карту на EGO +[BestClient] +Show finished map on Legit Network +== Показывать пройденную карту на Legit Network + [BestClient] Use BestClient MasterServer == Использовать мастерсервер BestClient diff --git a/data/BestClient/languages/simplified_chinese.txt b/data/BestClient/languages/simplified_chinese.txt index 6f55254a0..ed56a5c05 100644 --- a/data/BestClient/languages/simplified_chinese.txt +++ b/data/BestClient/languages/simplified_chinese.txt @@ -1202,6 +1202,10 @@ Short EGO/KoG server name Show finished map on EGO == 在 EGO 上显示已完成地图 +[BestClient] +Show finished map on Legit Network +== 在 Legit Network 上显示已完成地图 + [BestClient] Use BestClient MasterServer == 使用 BestClient 主服务器 diff --git a/src/engine/shared/config_variables_bestclient.h b/src/engine/shared/config_variables_bestclient.h index f5d28c8e0..b9ff899c7 100644 --- a/src/engine/shared/config_variables_bestclient.h +++ b/src/engine/shared/config_variables_bestclient.h @@ -316,6 +316,7 @@ MACRO_CONFIG_INT(BcAutoServerListRefresh, bc_auto_server_list_refresh, 0, 0, 1, MACRO_CONFIG_INT(BcAutoServerListRefreshSeconds, bc_auto_server_list_refresh_seconds, 10, 1, 300, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Auto refresh interval for the server browser list in seconds") MACRO_CONFIG_INT(BcUseShortKogServerName, bc_use_short_kog_server_name, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use short names for E-Gores and KoG servers in browser list") MACRO_CONFIG_INT(BcShowFinishedMapOnEgo, bc_show_finished_map_on_ego, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show completed EGO maps in the server browser") +MACRO_CONFIG_INT(BcShowFinishedMapOnLegit, bc_show_finished_map_on_legit, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show completed Legit Network maps in the server browser") MACRO_CONFIG_INT(BcMastersrv, bc_mastersrv, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Use the BestClient master server mirror for the server browser") MACRO_CONFIG_INT(BcServerMapPreview, bc_server_map_preview, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show a live map preview under the selected server") MACRO_CONFIG_INT(BrFilterBestclient, br_filter_bestclient, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Filter out servers with no BestClient users") diff --git a/src/game/client/components/bestclient/legit_finished_maps.cpp b/src/game/client/components/bestclient/legit_finished_maps.cpp new file mode 100644 index 000000000..d395e4196 --- /dev/null +++ b/src/game/client/components/bestclient/legit_finished_maps.cpp @@ -0,0 +1,159 @@ +#include "legit_finished_maps.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ +enum class ELegitMode +{ + NONE, + GORES, + DDRACE, +}; + +ELegitMode LegitMode(const CServerInfo *pServer) +{ + if(!pServer || (str_comp_nocase(pServer->m_aCommunityId, "legit") != 0 && str_find_nocase(pServer->m_aName, "Legit Network") == nullptr)) + return ELegitMode::NONE; + + if(str_find_nocase(pServer->m_aGameType, "Gores") != nullptr || str_find_nocase(pServer->m_aName, "Gores") != nullptr) + return ELegitMode::GORES; + if(str_find_nocase(pServer->m_aGameType, "DDraceNetwork") != nullptr) + return ELegitMode::DDRACE; + return ELegitMode::NONE; +} + +bool ParseMapArray(const json_value *pRoot, const char *pKey, std::unordered_set *pMaps) +{ + const json_value *pArray = json_object_get(pRoot, pKey); + if(pArray == &json_value_none || pArray->type != json_array) + return false; + + for(unsigned i = 0; i < pArray->u.array.length; ++i) + { + const json_value *pMap = pArray->u.array.values[i]; + if(pMap && pMap->type == json_string && pMap->u.string.ptr[0] != '\0') + pMaps->emplace(pMap->u.string.ptr); + } + return true; +} + +bool ParseFinishedMaps(const json_value *pRoot, std::unordered_set *pGoresMaps, std::unordered_set *pDDraceMaps) +{ + if(!pRoot || pRoot == &json_value_none || pRoot->type != json_object) + return false; + return ParseMapArray(pRoot, "gores", pGoresMaps) && ParseMapArray(pRoot, "ddrace", pDDraceMaps); +} +} + +bool CLegitFinishedMaps::Enabled() const +{ + return g_Config.m_BcShowFinishedMapOnLegit != 0; +} + +bool CLegitFinishedMaps::IsFinishedMap(const CServerInfo *pServer) const +{ + switch(LegitMode(pServer)) + { + case ELegitMode::GORES: + return Enabled() && m_GoresFinishedMaps.contains(pServer->m_aMap); + case ELegitMode::DDRACE: + return Enabled() && m_DDraceFinishedMaps.contains(pServer->m_aMap); + case ELegitMode::NONE: + return false; + } + return false; +} + +void CLegitFinishedMaps::ResetRequest() +{ + if(m_pTask) + m_pTask->Abort(); + m_pTask.reset(); +} + +void CLegitFinishedMaps::OnReset() +{ + ResetRequest(); +} + +void CLegitFinishedMaps::StartRequest() +{ + char aEscapedPlayerName[256]; + EscapeUrl(aEscapedPlayerName, sizeof(aEscapedPlayerName), m_aPlayerName); + + char aUrl[512]; + str_format(aUrl, sizeof(aUrl), "https://legit.tw/api_finished_maps.php?name=%s", aEscapedPlayerName); + + m_pTask = HttpGet(aUrl); + m_pTask->Timeout(CTimeout{8000, 0, 500, 5}); + m_pTask->LogProgress(HTTPLOG::FAILURE); + m_pTask->FailOnErrorStatus(false); + Http()->Run(m_pTask); +} + +void CLegitFinishedMaps::ProcessRequest() +{ + if(!m_pTask || !m_pTask->Done()) + return; + + bool Success = false; + if(m_pTask->State() == EHttpState::DONE && m_pTask->StatusCode() == 200) + { + json_value *pRoot = m_pTask->ResultJson(); + std::unordered_set GoresMaps; + std::unordered_set DDraceMaps; + Success = ParseFinishedMaps(pRoot, &GoresMaps, &DDraceMaps); + if(Success) + { + m_GoresFinishedMaps = std::move(GoresMaps); + m_DDraceFinishedMaps = std::move(DDraceMaps); + str_copy(m_aLoadedPlayerName, m_aPlayerName, sizeof(m_aLoadedPlayerName)); + } + if(pRoot) + json_value_free(pRoot); + } + + if(!Success) + m_NextRetry = time_get() + RETRY_DELAY_SECONDS * time_freq(); + m_pTask.reset(); +} + +void CLegitFinishedMaps::OnUpdate() +{ + if(!Enabled()) + { + ResetRequest(); + m_GoresFinishedMaps.clear(); + m_DDraceFinishedMaps.clear(); + m_aPlayerName[0] = '\0'; + m_aLoadedPlayerName[0] = '\0'; + m_NextRetry = 0; + return; + } + + const char *pPlayerName = Client()->PlayerName(); + if(str_comp(m_aPlayerName, pPlayerName) != 0) + { + ResetRequest(); + m_GoresFinishedMaps.clear(); + m_DDraceFinishedMaps.clear(); + m_aLoadedPlayerName[0] = '\0'; + m_NextRetry = 0; + str_copy(m_aPlayerName, pPlayerName, sizeof(m_aPlayerName)); + } + + ProcessRequest(); + + if(!m_aPlayerName[0] || m_pTask || str_comp(m_aPlayerName, m_aLoadedPlayerName) == 0 || time_get() < m_NextRetry) + return; + + StartRequest(); +} diff --git a/src/game/client/components/bestclient/legit_finished_maps.h b/src/game/client/components/bestclient/legit_finished_maps.h new file mode 100644 index 000000000..7234c8319 --- /dev/null +++ b/src/game/client/components/bestclient/legit_finished_maps.h @@ -0,0 +1,43 @@ +#ifndef GAME_CLIENT_COMPONENTS_BESTCLIENT_LEGIT_FINISHED_MAPS_H +#define GAME_CLIENT_COMPONENTS_BESTCLIENT_LEGIT_FINISHED_MAPS_H + +#include + +#include +#include +#include + +class IHttpRequest; +class CServerInfo; + +class CLegitFinishedMaps : public CComponent +{ +public: + int Sizeof() const override { return sizeof(*this); } + + void OnReset() override; + void OnUpdate() override; + + bool IsFinishedMap(const CServerInfo *pServer) const; + +private: + enum + { + PLAYER_NAME_LENGTH = 64, + RETRY_DELAY_SECONDS = 30, + }; + + std::unordered_set m_GoresFinishedMaps; + std::unordered_set m_DDraceFinishedMaps; + std::shared_ptr m_pTask; + char m_aPlayerName[PLAYER_NAME_LENGTH] = ""; + char m_aLoadedPlayerName[PLAYER_NAME_LENGTH] = ""; + int64_t m_NextRetry = 0; + + bool Enabled() const; + void ResetRequest(); + void StartRequest(); + void ProcessRequest(); +}; + +#endif diff --git a/src/game/client/components/bestclient/menus_bestclient.cpp b/src/game/client/components/bestclient/menus_bestclient.cpp index 38f27fe5e..2e57a8b24 100644 --- a/src/game/client/components/bestclient/menus_bestclient.cpp +++ b/src/game/client/components/bestclient/menus_bestclient.cpp @@ -3962,7 +3962,7 @@ void CMenus::RenderSettingsBestClientOthers(CUIRect MainView) Column.HSplitTop(MarginBetweenViews, nullptr, &Column); - const float BrowserUtilsBlockHeight = 6.0f * LineSize + 5.0f * MarginSmall; + const float BrowserUtilsBlockHeight = 7.0f * LineSize + 6.0f * MarginSmall; CUIRect BrowserUtilsBlock; Column.HSplitTop(BrowserUtilsBlockHeight, &BrowserUtilsBlock, &Column); @@ -3995,6 +3995,11 @@ void CMenus::RenderSettingsBestClientOthers(CUIRect MainView) BcMenuBadges::DrawNew(Graphics(), Ui(), TextRender(), &Content, MarginSmall); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_BcShowFinishedMapOnEgo, BcLocalize("Show finished map on EGO"), &g_Config.m_BcShowFinishedMapOnEgo, &Content, LineSize); + BrowserUtilsBlock.HSplitTop(MarginSmall, nullptr, &BrowserUtilsBlock); + BrowserUtilsBlock.HSplitTop(LineSize, &Content, &BrowserUtilsBlock); + BcMenuBadges::DrawNew(Graphics(), Ui(), TextRender(), &Content, MarginSmall); + DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_BcShowFinishedMapOnLegit, BcLocalize("Show finished map on Legit Network"), &g_Config.m_BcShowFinishedMapOnLegit, &Content, LineSize); + BrowserUtilsBlock.HSplitTop(MarginSmall, nullptr, &BrowserUtilsBlock); BrowserUtilsBlock.HSplitTop(LineSize, &Content, &BrowserUtilsBlock); DoButton_CheckBoxAutoVMarginAndSet(&g_Config.m_BcMastersrv, BcLocalize("Use BestClient MasterServer"), &g_Config.m_BcMastersrv, &Content, LineSize); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 3a3b9d961..86bfbd439 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -505,7 +505,7 @@ void CMenus::RenderServerbrowserServerList(CUIRect View, bool &WasListboxItemAct Button.VMargin(4.0f, &Button); Button.VSplitLeft(Button.h, &Icon, &Button); // bestclient - if((g_Config.m_BrIndicateFinished && pItem->m_HasRank == CServerInfo::RANK_RANKED) || GameClient()->m_EgoFinishedMaps.IsFinishedMap(pItem)) + if((g_Config.m_BrIndicateFinished && pItem->m_HasRank == CServerInfo::RANK_RANKED) || GameClient()->m_EgoFinishedMaps.IsFinishedMap(pItem) || GameClient()->m_LegitFinishedMaps.IsFinishedMap(pItem)) // bestclient { Icon.Margin(2.0f, &Icon); diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 1fe839fe3..8975c6cbe 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -198,6 +198,7 @@ void CGameClient::OnConsoleInit() &m_BrowserUtils, // bestclient &m_TwitchChat, // bestclient &m_EgoFinishedMaps, // bestclient + &m_LegitFinishedMaps, // bestclient &m_ShowPoints, // bestclient &m_ClientIndicator, // bestclient &m_VoiceChat, // bestclient diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index b1558b5fc..a5b0a506b 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -45,6 +45,7 @@ #include "components/bestclient/music_player.h" // bestclient #include "components/bestclient/browser_utils.h" // bestclient #include "components/bestclient/ego_finished_maps.h" // bestclient +#include "components/bestclient/legit_finished_maps.h" // bestclient #include "components/bestclient/ego_tiles_prediction.h" // bestclient #include "components/bestclient/edgehelper.h" // bestclient #include "components/bestclient/fast_practice.h" // bestclient @@ -256,6 +257,7 @@ class CGameClient : public IGameClient CBrowserUtils m_BrowserUtils; // bestclient CTwitchChat m_TwitchChat; // bestclient CEgoFinishedMaps m_EgoFinishedMaps; // bestclient + CLegitFinishedMaps m_LegitFinishedMaps; // bestclient CEdgeHelper m_EdgeHelper; // bestclient CFinishPrediction m_FinishPrediction; // bestclient CKeystrokes m_Keystrokes; // bestclient