From fbc4c9cd6ebb3e3623e0ad6deea6772c37503ba2 Mon Sep 17 00:00:00 2001 From: Jupeyy Date: Tue, 4 Oct 2022 18:40:24 +0200 Subject: [PATCH] Add favorite skins --- src/game/client/components/menus.cpp | 10 + src/game/client/components/menus.h | 10 + src/game/client/components/menus_settings.cpp | 172 ++++++++++++++++-- src/game/client/components/skins.cpp | 4 + src/game/client/components/skins.h | 3 + 5 files changed, 182 insertions(+), 17 deletions(-) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 2a3805aae..061c1959c 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -1048,6 +1049,15 @@ void CMenus::OnInit() Storage()->ListDirectory(IStorage::TYPE_ALL, "menuimages", MenuImageScan, this); } +void CMenus::OnConsoleInit() +{ + auto *pConfigManager = Kernel()->RequestInterface(); + if(pConfigManager != nullptr) + pConfigManager->RegisterCallback(CMenus::ConfigSaveCallback, this); + Console()->Register("add_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_AddFavoriteSkin, this, "Add a skin as a favorite"); + Console()->Register("remove_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_RemFavoriteSkin, this, "Remove a skin from the favorites"); +} + void CMenus::ConchainUpdateMusicState(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index 254a3ca9c..fd5293b8b 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -520,6 +521,14 @@ protected: static void ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); static void ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); + // skin favorite list + bool m_SkinFavoritesChanged = false; + std::unordered_set m_SkinFavorites; + static void Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData); + static void Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData); + static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData); + void OnConfigSave(IConfigManager *pConfigManager); + // found in menus_settings.cpp void RenderLanguageSelection(CUIRect MainView); void RenderThemeSelection(CUIRect MainView, bool Header = true); @@ -560,6 +569,7 @@ public: void KillServer(); virtual void OnInit() override; + void OnConsoleInit() override; virtual void OnStateChange(int NewState, int OldState) override; virtual void OnReset() override; diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 2224e7f3d..044b817ac 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -426,6 +426,49 @@ void CMenus::RefreshSkins() } } +void CMenus::Con_AddFavoriteSkin(IConsole::IResult *pResult, void *pUserData) +{ + auto *pSelf = (CMenus *)pUserData; + if(pResult->NumArguments() >= 1) + { + pSelf->m_SkinFavorites.emplace(pResult->GetString(0)); + pSelf->m_SkinFavoritesChanged = true; + } +} + +void CMenus::Con_RemFavoriteSkin(IConsole::IResult *pResult, void *pUserData) +{ + auto *pSelf = (CMenus *)pUserData; + if(pResult->NumArguments() >= 1) + { + const auto it = pSelf->m_SkinFavorites.find(pResult->GetString(0)); + if(it != pSelf->m_SkinFavorites.end()) + { + pSelf->m_SkinFavorites.erase(it); + pSelf->m_SkinFavoritesChanged = true; + } + } +} + +void CMenus::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData) +{ + auto *pSelf = (CMenus *)pUserData; + pSelf->OnConfigSave(pConfigManager); +} + +void CMenus::OnConfigSave(IConfigManager *pConfigManager) +{ + for(const auto &Entry : m_SkinFavorites) + { + char aBuffer[256]; + char aNameEscaped[256]; + char *pDst = aNameEscaped; + str_escape(&pDst, Entry.c_str(), aNameEscaped + std::size(aNameEscaped)); + str_format(aBuffer, std::size(aBuffer), "add_favorite_skin \"%s\"", Entry.c_str()); + pConfigManager->WriteLine(aBuffer); + } +} + void CMenus::RenderSettingsTee(CUIRect MainView) { CUIRect Button, Label, Dummy, DummyLabel, SkinList, QuickSearch, QuickSearchClearButton, SkinDB, SkinPrefix, SkinPrefixLabel, DirectoryButton, RefreshButton, Eyes, EyesLabel, EyesTee, EyesRight; @@ -658,33 +701,92 @@ void CMenus::RenderSettingsTee(CUIRect MainView) MainView.HSplitTop(20.0f, 0, &MainView); MainView.HSplitTop(230.0f - RenderEyesBelow * 25.0f, &SkinList, &MainView); static std::vector s_vSkinList; + static std::vector s_vSkinListHelper; + static std::vector s_vFavoriteSkinListHelper; static int s_SkinCount = 0; static float s_ScrollValue = 0.0f; - if(s_InitSkinlist || m_pClient->m_Skins.Num() != s_SkinCount) + // be nice to the CPU + static auto s_SkinLastRebuildTime = time_get_nanoseconds(); + const auto CurTime = time_get_nanoseconds(); + if(s_InitSkinlist || m_pClient->m_Skins.Num() != s_SkinCount || m_SkinFavoritesChanged || (m_pClient->m_Skins.IsDownloadingSkins() && (CurTime - s_SkinLastRebuildTime > 500ms))) { + s_SkinLastRebuildTime = CurTime; s_vSkinList.clear(); + s_vSkinListHelper.clear(); + s_vFavoriteSkinListHelper.clear(); + // set skin count early, since Find of the skin class might load + // a downloading skin + s_SkinCount = m_pClient->m_Skins.Num(); + m_SkinFavoritesChanged = false; + bool RequiresRebuild = false; + + auto &&SkinNotFiltered = [&](const CSkin *pSkinToBeSelected) { + // filter quick search + if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkinToBeSelected->m_aName, g_Config.m_ClSkinFilterString)) + return false; + + // no special skins + if((pSkinToBeSelected->m_aName[0] == 'x' && pSkinToBeSelected->m_aName[1] == '_')) + return false; + + if(pSkinToBeSelected == 0) + return false; + + return true; + }; + + for(const auto &it : m_SkinFavorites) + { + const auto FirstSkinIndex = m_pClient->m_Skins.Find(it.c_str()); + // second call is intended, our implemention doesnt return the index in the call where the download finished + const auto SkinIndex = m_pClient->m_Skins.Find(it.c_str()); + if(SkinIndex == -1) + continue; + if(FirstSkinIndex == -1 && SkinIndex != -1) + { + // skin list changed, rebuild next frame + RequiresRebuild = true; + } + const CSkin *pSkinToBeSelected = m_pClient->m_Skins.Get(SkinIndex); + + if(!SkinNotFiltered(pSkinToBeSelected)) + continue; + + s_vFavoriteSkinListHelper.emplace_back(pSkinToBeSelected); + } for(int i = 0; i < m_pClient->m_Skins.Num(); ++i) { const CSkin *pSkinToBeSelected = m_pClient->m_Skins.Get(i); - // filter quick search - if(g_Config.m_ClSkinFilterString[0] != '\0' && !str_utf8_find_nocase(pSkinToBeSelected->m_aName, g_Config.m_ClSkinFilterString)) + if(!SkinNotFiltered(pSkinToBeSelected)) continue; - // no special skins - if((pSkinToBeSelected->m_aName[0] == 'x' && pSkinToBeSelected->m_aName[1] == '_')) - continue; - - if(pSkinToBeSelected == 0) - continue; - - s_vSkinList.emplace_back(pSkinToBeSelected); + if(std::find(m_SkinFavorites.begin(), m_SkinFavorites.end(), pSkinToBeSelected->m_aName) == m_SkinFavorites.end()) + s_vSkinListHelper.emplace_back(pSkinToBeSelected); } - std::sort(s_vSkinList.begin(), s_vSkinList.end()); - s_InitSkinlist = false; - s_SkinCount = m_pClient->m_Skins.Num(); + std::sort(s_vSkinListHelper.begin(), s_vSkinListHelper.end()); + std::sort(s_vFavoriteSkinListHelper.begin(), s_vFavoriteSkinListHelper.end()); + s_vSkinList = s_vFavoriteSkinListHelper; + s_vSkinList.insert(s_vSkinList.end(), s_vSkinListHelper.begin(), s_vSkinListHelper.end()); + s_InitSkinlist = RequiresRebuild; } + auto &&RenderFavIcon = [&](const CUIRect &FavIcon, bool AsFav) { + TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_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); + if(AsFav) + TextRender()->TextColor({1, 1, 0, 1}); + else + TextRender()->TextColor({0.5f, 0.5f, 0.5f, 1}); + TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor()); + SLabelProperties Props; + Props.m_MaxWidth = FavIcon.w; + UI()->DoLabel(&FavIcon, "\xef\x80\x85", 12.0f, TEXTALIGN_RIGHT, Props); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + TextRender()->SetRenderFlags(0); + TextRender()->SetCurFont(nullptr); + }; + int OldSelected = -1; UiDoListboxStart(&s_InitSkinlist, &SkinList, 50.0f, Localize("Skins"), "", s_vSkinList.size(), 4, OldSelected, s_ScrollValue); for(size_t i = 0; i < s_vSkinList.size(); ++i) @@ -697,6 +799,8 @@ void CMenus::RenderSettingsTee(CUIRect MainView) CListboxItem Item = UiDoListboxNextItem(pSkinToBeDraw, OldSelected >= 0 && (size_t)OldSelected == i); if(Item.m_Visible) { + auto OriginalRect = Item.m_Rect; + CTeeRenderInfo Info = OwnSkinInfo; Info.m_CustomColoredSkin = *pUseCustomColor; @@ -709,9 +813,11 @@ void CMenus::RenderSettingsTee(CUIRect MainView) RenderTools()->RenderTee(pIdleState, &Info, Emote, vec2(1.0f, 0.0f), TeeRenderPos); Item.m_Rect.VSplitLeft(60.0f, 0, &Item.m_Rect); - SLabelProperties Props; - Props.m_MaxWidth = Item.m_Rect.w; - UI()->DoLabel(&Item.m_Rect, pSkinToBeDraw->m_aName, 12.0f, TEXTALIGN_LEFT, Props); + { + SLabelProperties Props; + Props.m_MaxWidth = Item.m_Rect.w; + UI()->DoLabel(&Item.m_Rect, pSkinToBeDraw->m_aName, 12.0f, TEXTALIGN_LEFT, Props); + } if(g_Config.m_Debug) { ColorRGBA BloodColor = *pUseCustomColor ? color_cast(ColorHSLA(*pColorBody)) : pSkinToBeDraw->m_BloodColor; @@ -722,6 +828,38 @@ void CMenus::RenderSettingsTee(CUIRect MainView) Graphics()->QuadsDrawTL(&QuadItem, 1); Graphics()->QuadsEnd(); } + + // render skin favorite icon + { + const auto SkinItFav = m_SkinFavorites.find(pSkinToBeDraw->m_aName); + const auto IsFav = SkinItFav != m_SkinFavorites.end(); + CUIRect FavIcon; + OriginalRect.HSplitTop(20.0f, &FavIcon, nullptr); + FavIcon.VSplitRight(20.0f, nullptr, &FavIcon); + if(IsFav) + { + RenderFavIcon(FavIcon, IsFav); + } + else + { + if(UI()->MouseInside(&FavIcon)) + { + RenderFavIcon(FavIcon, IsFav); + } + } + if(UI()->DoButtonLogic(pSkinToBeDraw->m_aName, 0, &FavIcon)) + { + if(IsFav) + { + m_SkinFavorites.erase(SkinItFav); + } + else + { + m_SkinFavorites.emplace(pSkinToBeDraw->m_aName); + } + s_InitSkinlist = true; + } + } } } diff --git a/src/game/client/components/skins.cpp b/src/game/client/components/skins.cpp index c9af05e10..d759410a8 100644 --- a/src/game/client/components/skins.cpp +++ b/src/game/client/components/skins.cpp @@ -349,6 +349,7 @@ void CSkins::Refresh(TSkinLoadedCBFunc &&SkinLoadedFunc) m_vSkins.clear(); m_vDownloadSkins.clear(); + m_DownloadingSkins = 0; SSkinScanUser SkinScanUser; SkinScanUser.m_pThis = this; SkinScanUser.m_SkinLoadedFunc = SkinLoadedFunc; @@ -432,10 +433,12 @@ int CSkins::FindImpl(const char *pName) Storage()->RenameFile(RangeBegin->m_aPath, aPath, IStorage::TYPE_SAVE); LoadSkin(RangeBegin->m_aName, RangeBegin->m_pTask->m_Info); RangeBegin->m_pTask = nullptr; + --m_DownloadingSkins; } if(RangeBegin->m_pTask && (RangeBegin->m_pTask->State() == HTTP_ERROR || RangeBegin->m_pTask->State() == HTTP_ABORTED)) { RangeBegin->m_pTask = nullptr; + --m_DownloadingSkins; } return -1; } @@ -452,5 +455,6 @@ int CSkins::FindImpl(const char *pName) Skin.m_pTask = std::make_shared(this, aUrl, Storage(), Skin.m_aPath); m_pClient->Engine()->AddJob(Skin.m_pTask); m_vDownloadSkins.insert(std::lower_bound(m_vDownloadSkins.begin(), m_vDownloadSkins.end(), Skin), std::move(Skin)); + ++m_DownloadingSkins; return -1; } diff --git a/src/game/client/components/skins.h b/src/game/client/components/skins.h index b824f100b..d089ecab6 100644 --- a/src/game/client/components/skins.h +++ b/src/game/client/components/skins.h @@ -54,9 +54,12 @@ public: const CSkin *Get(int Index); int Find(const char *pName); + bool IsDownloadingSkins() { return m_DownloadingSkins; } + private: std::vector m_vSkins; std::vector m_vDownloadSkins; + size_t m_DownloadingSkins = 0; char m_aEventSkinPrefix[24]; bool LoadSkinPNG(CImageInfo &Info, const char *pName, const char *pPath, int DirType);