From 2e84c96a4342a97827d90cbf45d77e509fc0948c Mon Sep 17 00:00:00 2001 From: Diqere1 Date: Tue, 28 Jul 2026 22:43:39 +0300 Subject: [PATCH 1/2] visibility configs --- CMakeLists.txt | 2 + src/engine/shared/config_variables_tclient.h | 4 + .../components/bestclient/hud_editor.cpp | 8 +- src/game/client/components/hud_layout.cpp | 2 + src/game/client/components/hud_layout.h | 1 + .../client/components/tclient/hud_watch.cpp | 338 ++++++++++++++++++ .../client/components/tclient/hud_watch.h | 45 +++ .../components/tclient/menus_tclient.cpp | 229 +++++++++++- src/game/client/gameclient.cpp | 1 + src/game/client/gameclient.h | 2 + 10 files changed, 630 insertions(+), 2 deletions(-) create mode 100644 src/game/client/components/tclient/hud_watch.cpp create mode 100644 src/game/client/components/tclient/hud_watch.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 597d57eafe..3b1f8006ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2823,6 +2823,8 @@ if(CLIENT) components/tclient/tclient.h components/tclient/trails.cpp components/tclient/trails.h + components/tclient/hud_watch.cpp + components/tclient/hud_watch.h components/bestclient/3d_particles.cpp components/bestclient/3d_particles.h components/bestclient/admin_panel.cpp diff --git a/src/engine/shared/config_variables_tclient.h b/src/engine/shared/config_variables_tclient.h index dfc8c4c0c3..0c46e3a47e 100644 --- a/src/engine/shared/config_variables_tclient.h +++ b/src/engine/shared/config_variables_tclient.h @@ -290,4 +290,8 @@ MACRO_CONFIG_INT(TcUiCompactList, tc_ui_compact_list, 0, 0, 1, CFGFLAG_CLIENT | // Dummy Info MACRO_CONFIG_INT(TcShowhudDummyPosition, tc_showhud_dummy_position, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Dummy Position)") MACRO_CONFIG_INT(TcShowhudDummySpeed, tc_showhud_dummy_speed, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Dummy Speed)") + +// Hud Watch - config variable watcher displayed on screen +MACRO_CONFIG_INT(TcHudWatchEnable, tc_hud_watch_enable, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable HUD watch overlay") +MACRO_CONFIG_STR(TcHudWatchData, tc_hud_watch_data, 2048, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Serialized HUD watch data (var|label|hexcolor;...)") MACRO_CONFIG_INT(TcShowhudDummyAngle, tc_showhud_dummy_angle, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show ingame HUD (Dummy Aim Angle)") diff --git a/src/game/client/components/bestclient/hud_editor.cpp b/src/game/client/components/bestclient/hud_editor.cpp index 42f7efbdb2..b4b4487180 100644 --- a/src/game/client/components/bestclient/hud_editor.cpp +++ b/src/game/client/components/bestclient/hud_editor.cpp @@ -224,7 +224,8 @@ namespace Module == HudLayout::MODULE_FINISH_PREDICTION || Module == HudLayout::MODULE_VOICE_TALKERS || Module == HudLayout::MODULE_VOICE_STATUS || - Module == HudLayout::MODULE_MUSIC_PLAYER; + Module == HudLayout::MODULE_MUSIC_PLAYER || + Module == HudLayout::MODULE_HUD_WATCH; } bool PointInRect(vec2 Point, const CUIRect &Rect) @@ -572,6 +573,10 @@ CHudEditor::SModuleVisual CHudEditor::GetModuleVisual(HudLayout::EModule Module) Visual.m_Rect = GameClient()->m_VoiceChat.GetHudMuteStatusIndicatorRect(Width, Height, true); Visual.m_Rounding = 2.3f; break; + case HudLayout::MODULE_HUD_WATCH: + Visual.m_Rect = GameClient()->m_HudWatch.GetHudEditorRect(); + Visual.m_Rounding = 4.0f; + break; default: Visual.m_Rect = GetFallbackModuleRect(Module); Visual.m_Rounding = 4.0f; @@ -619,6 +624,7 @@ void CHudEditor::CollectModuleVisuals(SModuleVisual *pOut, int &Count) const AddModule(HudLayout::MODULE_MUSIC_PLAYER); AddModule(HudLayout::MODULE_VOICE_TALKERS); AddModule(HudLayout::MODULE_VOICE_STATUS); + AddModule(HudLayout::MODULE_HUD_WATCH); } HudLayout::EModule CHudEditor::HitTestModule(vec2 MousePos) const diff --git a/src/game/client/components/hud_layout.cpp b/src/game/client/components/hud_layout.cpp index 12657fd8a1..e67384a70b 100644 --- a/src/game/client/components/hud_layout.cpp +++ b/src/game/client/components/hud_layout.cpp @@ -48,6 +48,7 @@ namespace HudLayout // when the keyboard preset changes. {84.5f, 152.0f, 100, 0, true, false, 0x66000000U}, // KEYSTROKES_MOUSE {484.0f, 172.0f, 100, 0, true, true, 0x66000000U}, // DUMMY_ACTIONS + {5.0f, 60.0f, 100, 0, true, false, 0x66000000U}, // HUD_WATCH }; static const char *gs_apModuleNames[MODULE_COUNT] = { @@ -73,6 +74,7 @@ namespace HudLayout "Keyboard", "Mouse", "Dummy Actions", + "Config Watch", }; static SModuleLayout gs_aRuntimeModuleLayouts[MODULE_COUNT]; diff --git a/src/game/client/components/hud_layout.h b/src/game/client/components/hud_layout.h index 115420e9c0..bfb41c0e7f 100644 --- a/src/game/client/components/hud_layout.h +++ b/src/game/client/components/hud_layout.h @@ -39,6 +39,7 @@ namespace HudLayout MODULE_KEYSTROKES_KEYBOARD, MODULE_KEYSTROKES_MOUSE, MODULE_DUMMY_ACTIONS, + MODULE_HUD_WATCH, MODULE_COUNT, }; diff --git a/src/game/client/components/tclient/hud_watch.cpp b/src/game/client/components/tclient/hud_watch.cpp new file mode 100644 index 0000000000..3206f4341b --- /dev/null +++ b/src/game/client/components/tclient/hud_watch.cpp @@ -0,0 +1,338 @@ +#include "hud_watch.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +CHudWatch::CHudWatch() : + m_Dirty(false) +{ +} + +void CHudWatch::OnInit() +{ + LoadFromConfig(); +} + +void CHudWatch::LoadFromConfig() +{ + m_vWatches.clear(); + const char *pData = g_Config.m_TcHudWatchData; + if(!pData || pData[0] == '\0') + return; + + char aData[2048]; + str_copy(aData, pData); + char *pEntry = aData; + while(pEntry && pEntry[0]) + { + const char *pSemi_const = str_find(pEntry, ";"); + char *pSemi = const_cast(pSemi_const); + if(pSemi) + *pSemi = '\0'; + + char *pParts[3] = {}; + int NumParts = 0; + char *pTok = pEntry; + for(int i = 0; i < 3; i++) + { + const char *pPipe_const = str_find(pTok, "|"); + char *pPipe = const_cast(pPipe_const); + if(pPipe) + { + *pPipe = '\0'; + pParts[i] = pTok; + pTok = pPipe + 1; + NumParts++; + } + else + { + pParts[i] = pTok; + NumParts++; + break; + } + } + if(NumParts >= 1 && pParts[0] && pParts[0][0]) + { + const char *pVarName = pParts[0]; + const char *pLabel = (NumParts >= 2 && pParts[1]) ? pParts[1] : pParts[0]; + ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f); + std::unordered_map ValueColors; + if(NumParts >= 3 && pParts[2] && pParts[2][0]) + { + const char *pColorPart = pParts[2]; + const char *pComma = str_find(pColorPart, ","); + if(pComma) + { + char aDefColor[16]; + str_copy(aDefColor, pColorPart, (int)(pComma - pColorPart) + 1); + unsigned char aDefHex[4] = {}; + if(str_hex_decode(aDefHex, sizeof(aDefHex), aDefColor) == 0) + { + Color.r = aDefHex[0] / 255.0f; + Color.g = aDefHex[1] / 255.0f; + Color.b = aDefHex[2] / 255.0f; + Color.a = aDefHex[3] / 255.0f; + } + const char *pCond = pComma + 1; + while(pCond && pCond[0]) + { + const char *pComma2 = str_find(pCond, ","); + char aCond[64]; + if(pComma2) + { + str_copy(aCond, pCond, (int)(pComma2 - pCond) + 1); + pCond = pComma2 + 1; + } + else + { + str_copy(aCond, pCond); + pCond = nullptr; + } + const char *pColon = str_find(aCond, ":"); + if(pColon) + { + int Val = str_toint(aCond); + unsigned char aHex[4] = {}; + if(str_hex_decode(aHex, sizeof(aHex), pColon + 1) == 0) + { + ValueColors[Val] = ColorRGBA( + aHex[0] / 255.0f, + aHex[1] / 255.0f, + aHex[2] / 255.0f, + aHex[3] / 255.0f); + } + } + } + } + else + { + unsigned char aHex[4] = {}; + if(str_hex_decode(aHex, sizeof(aHex), pColorPart) == 0) + { + Color.r = aHex[0] / 255.0f; + Color.g = aHex[1] / 255.0f; + Color.b = aHex[2] / 255.0f; + Color.a = aHex[3] / 255.0f; + } + } + } + + struct SFindData + { + const char *m_pName; + const SConfigVariable *m_pResult; + }; + SFindData FindData = {pVarName, nullptr}; + auto Finder = [](const SConfigVariable *pVar, void *pUserData) { + auto *pData = static_cast(pUserData); + if(str_comp(pVar->m_pScriptName, pData->m_pName) == 0) + pData->m_pResult = pVar; + }; + GameClient()->ConfigManager()->PossibleConfigVariables("", CFGFLAG_CLIENT, Finder, &FindData); + if(FindData.m_pResult) + { + SHudWatchItem Item; + Item.m_pVar = FindData.m_pResult; + str_copy(Item.m_aLabel, pLabel); + Item.m_Color = Color; + Item.m_vValueColors = std::move(ValueColors); + m_vWatches.push_back(Item); + } + } + + if(pSemi) + pEntry = pSemi + 1; + else + break; + } +} + +void CHudWatch::SaveToConfig() +{ + char aData[2048] = ""; + int Offset = 0; + for(size_t i = 0; i < m_vWatches.size(); i++) + { + if(Offset > 0 && Offset < (int)sizeof(aData) - 2) + { + aData[Offset] = ';'; + Offset++; + } + const SHudWatchItem &Item = m_vWatches[i]; + char aColorBuf[512]; + int ColorOff = str_format(aColorBuf, sizeof(aColorBuf), "%02X%02X%02X%02X", + (int)(Item.m_Color.r * 255), + (int)(Item.m_Color.g * 255), + (int)(Item.m_Color.b * 255), + (int)(Item.m_Color.a * 255)); + for(const auto &[Val, Col] : Item.m_vValueColors) + { + ColorOff += str_format(aColorBuf + ColorOff, (int)sizeof(aColorBuf) - ColorOff, ",%d:%02X%02X%02X%02X", + Val, + (int)(Col.r * 255), + (int)(Col.g * 255), + (int)(Col.b * 255), + (int)(Col.a * 255)); + } + int Written = str_format(aData + Offset, (int)sizeof(aData) - Offset, "%s|%s|%s", + Item.m_pVar->m_pScriptName, + Item.m_aLabel, + aColorBuf); + if(Written > 0) + Offset += Written; + } + str_copy(g_Config.m_TcHudWatchData, aData); + GameClient()->ConfigManager()->Save(); +} + +void CHudWatch::OnRender() +{ + if(!g_Config.m_TcHudWatchEnable || m_vWatches.empty()) + return; + + float Width = 300.0f * Graphics()->ScreenAspect(); + float Height = 300.0f; + Graphics()->MapScreen(0.0f, 0.0f, Width, Height); + + const auto Layout = HudLayout::Get(HudLayout::MODULE_HUD_WATCH, Width, Height); + const float Scale = std::clamp(Layout.m_Scale / 100.0f, 0.25f, 3.0f); + const float FontSize = 6.0f * Scale; + + float X = Layout.m_X; + float Y = Layout.m_Y; + float LineHeight = FontSize + 2.0f * Scale; + + for(const SHudWatchItem &Item : m_vWatches) + { + ITextRender *pTextRender = TextRender(); + + int CurrentValue = 0; + char aValue[256] = ""; + if(Item.m_pVar->m_Type == SConfigVariable::VAR_INT) + { + const SIntConfigVariable *pInt = static_cast(Item.m_pVar); + CurrentValue = *pInt->m_pVariable; + str_format(aValue, sizeof(aValue), "%d", CurrentValue); + } + else if(Item.m_pVar->m_Type == SConfigVariable::VAR_STRING) + { + const SStringConfigVariable *pStr = static_cast(Item.m_pVar); + str_copy(aValue, pStr->m_pStr); + } + else if(Item.m_pVar->m_Type == SConfigVariable::VAR_COLOR) + { + const SColorConfigVariable *pCol = static_cast(Item.m_pVar); + unsigned ColVal = *pCol->m_pVariable; + if(pCol->m_Alpha) + str_format(aValue, sizeof(aValue), "#%08X", ColVal); + else + str_format(aValue, sizeof(aValue), "#%06X", ColVal & 0xFFFFFF); + } + + auto it = Item.m_vValueColors.find(CurrentValue); + if(!Item.m_vValueColors.empty() && it == Item.m_vValueColors.end()) + { + Y += LineHeight; + continue; + } + ColorRGBA UseColor = (it != Item.m_vValueColors.end()) ? it->second : Item.m_Color; + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "%s: %s", Item.m_aLabel, aValue); + + pTextRender->TextColor(UseColor); + pTextRender->Text(X, Y, FontSize, aBuf, -1.0f); + + Y += LineHeight; + } + + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); +} + +CUIRect CHudWatch::GetHudEditorRect(bool ForcePreview) const +{ + if(m_vWatches.empty() && !ForcePreview) + return {0.0f, 0.0f, 0.0f, 0.0f}; + + float Width = 300.0f * Graphics()->ScreenAspect(); + float Height = 300.0f; + + const auto Layout = HudLayout::Get(HudLayout::MODULE_HUD_WATCH, Width, Height); + const float Scale = std::clamp(Layout.m_Scale / 100.0f, 0.25f, 3.0f); + const float FontSize = 6.0f * Scale; + const float LineHeight = FontSize + 2.0f * Scale; + + size_t NumItems = m_vWatches.empty() ? 1 : m_vWatches.size(); + float MaxWidth = 80.0f * Scale; + for(size_t i = 0; i < m_vWatches.size(); i++) + { + char aBuf[128]; + str_format(aBuf, sizeof(aBuf), "%s: 000", m_vWatches[i].m_aLabel); + float TextWidth = TextRender()->TextWidth(FontSize, aBuf, -1, -1.0f); + if(TextWidth > MaxWidth) + MaxWidth = TextWidth; + } + + CUIRect Rect; + Rect.x = Layout.m_X; + Rect.y = Layout.m_Y; + Rect.w = MaxWidth + 4.0f; + Rect.h = NumItems * LineHeight + 2.0f; + + Rect.x = std::clamp(Rect.x, 0.0f, std::max(0.0f, Width - Rect.w)); + Rect.y = std::clamp(Rect.y, 0.0f, std::max(0.0f, Height - Rect.h)); + return Rect; +} + +bool CHudWatch::IsWatched(const SConfigVariable *pVar) const +{ + for(const auto &Item : m_vWatches) + if(Item.m_pVar == pVar) + return true; + return false; +} + +void CHudWatch::SetWatch(const SConfigVariable *pVar, const char *pLabel, ColorRGBA Color, std::unordered_map ValueColors) +{ + for(auto &Item : m_vWatches) + { + if(Item.m_pVar == pVar) + { + str_copy(Item.m_aLabel, pLabel); + Item.m_Color = Color; + Item.m_vValueColors = std::move(ValueColors); + SaveToConfig(); + return; + } + } + SHudWatchItem Item; + Item.m_pVar = pVar; + str_copy(Item.m_aLabel, pLabel); + Item.m_Color = Color; + Item.m_vValueColors = std::move(ValueColors); + m_vWatches.push_back(Item); + SaveToConfig(); +} + +void CHudWatch::RemoveWatch(const SConfigVariable *pVar) +{ + for(auto it = m_vWatches.begin(); it != m_vWatches.end(); ++it) + { + if(it->m_pVar == pVar) + { + m_vWatches.erase(it); + SaveToConfig(); + return; + } + } +} diff --git a/src/game/client/components/tclient/hud_watch.h b/src/game/client/components/tclient/hud_watch.h new file mode 100644 index 0000000000..9f7ef22d04 --- /dev/null +++ b/src/game/client/components/tclient/hud_watch.h @@ -0,0 +1,45 @@ +#ifndef GAME_CLIENT_COMPONENTS_TCLIENT_HUD_WATCH_H +#define GAME_CLIENT_COMPONENTS_TCLIENT_HUD_WATCH_H + +#include +#include + +#include +#include + +#include +#include + +struct SHudWatchItem +{ + const SConfigVariable *m_pVar; + char m_aLabel[64]; + ColorRGBA m_Color; + std::unordered_map m_vValueColors; +}; + +class CHudWatch : public CComponent +{ + std::vector m_vWatches; + bool m_Dirty; + + void LoadFromConfig(); + void SaveToConfig(); + +public: + CHudWatch(); + int Sizeof() const override { return sizeof(*this); } + void OnInit() override; + void OnRender() override; + void OnMessage(int MsgType, void *pRawMsg) override {} + + bool IsWatched(const SConfigVariable *pVar) const; + void SetWatch(const SConfigVariable *pVar, const char *pLabel, ColorRGBA Color, std::unordered_map ValueColors = {}); + void RemoveWatch(const SConfigVariable *pVar); + + const std::vector &GetWatches() const { return m_vWatches; } + + CUIRect GetHudEditorRect(bool ForcePreview = false) const; +}; + +#endif diff --git a/src/game/client/components/tclient/menus_tclient.cpp b/src/game/client/components/tclient/menus_tclient.cpp index 422d1f59a6..9e38f66d0e 100644 --- a/src/game/client/components/tclient/menus_tclient.cpp +++ b/src/game/client/components/tclient/menus_tclient.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include +#include #include #include #include @@ -2607,6 +2609,157 @@ void CMenus::RenderSettingsTClientProfiles(CUIRect MainView) s_SelectedProfile = s_ListBox.DoEnd(); } +struct SWatchEditPopupContext : public SPopupMenuId +{ + CGameClient *m_pGameClient; + CUi *m_pUI; + const SConfigVariable *m_pVar; + CLineInputBuffered<64> m_LabelInput; + unsigned m_ColorValue; + ColorHSLA m_DisplayHsla; + CUi::SColorPickerPopupContext m_ColorPickerContext; + bool m_NeedsReopen{false}; + + struct SCondition + { + CLineInputNumber m_ValueInput; + unsigned m_ColorValue{0xFFFFFFFF}; + ColorHSLA m_DisplayHsla{0.0f, 0.0f, 1.0f, 1.0f}; + CUi::SColorPickerPopupContext m_ColorPickerContext; + CButtonContainer m_RemoveBtnId; + }; + std::list m_vConditions; +}; + +static CUi::EPopupMenuFunctionResult WatchEditPopupFunc(void *pContext, CUIRect View, bool Active) +{ + SWatchEditPopupContext *pCtx = static_cast(pContext); + CGameClient *pGameClient = pCtx->m_pGameClient; + CUi *pUI = pCtx->m_pUI; + + const float RowHeight = 16.0f; + const float Margin = 3.0f; + const float Padding = 5.0f; + + // Label row + CUIRect LabelRow; + View.HSplitTop(Padding, nullptr, &View); + View.HSplitTop(RowHeight, &LabelRow, &View); + LabelRow.VSplitLeft(35.0f, nullptr, &LabelRow); + pUI->DoLabel(&LabelRow, "Имя:", RowHeight * 0.8f, TEXTALIGN_ML); + + CUIRect LabelInput; + LabelRow.VSplitRight(0.0f, &LabelRow, &LabelInput); + LabelRow.VSplitLeft(45.0f, nullptr, &LabelInput); + pUI->DoEditBox(&pCtx->m_LabelInput, &LabelInput, 12.0f); + + // Value→Color conditions header + CUIRect CondHeader; + View.HSplitTop(Margin + 1.0f, nullptr, &View); + View.HSplitTop(RowHeight, &CondHeader, &View); + pUI->DoLabel(&CondHeader, "Цвета значений:", RowHeight * 0.8f, TEXTALIGN_ML); + + // Condition rows + for(auto it = pCtx->m_vConditions.begin(); it != pCtx->m_vConditions.end(); ) + { + auto &Cond = *it; + + CUIRect CondRow, ValInput, ColSquare, RemoveBtn; + View.HSplitTop(Margin, nullptr, &View); + View.HSplitTop(RowHeight, &CondRow, &View); + + CondRow.VSplitLeft(40.0f, &ValInput, &CondRow); + pUI->DoEditBox(&Cond.m_ValueInput, &ValInput, 10.0f); + + CondRow.VSplitLeft(Margin, nullptr, &CondRow); + CondRow.VSplitLeft(22.0f, &ColSquare, &CondRow); + ColSquare.h = RowHeight - 2.0f; + ColSquare.y += 1.0f; + { + ColorRGBA Outline(1.0f, 1.0f, 1.0f, 0.25f); + ColSquare.Draw(Outline, IGraphics::CORNER_ALL, 3.0f); + CUIRect Inner; + ColSquare.Margin(1.5f, &Inner); + ColorRGBA ColPrev = color_cast(Cond.m_DisplayHsla); + Inner.Draw(ColPrev, IGraphics::CORNER_ALL, 2.0f); + } + + if(Active && pUI->DoButtonLogic(&Cond.m_ColorValue, 0, &ColSquare, BUTTONFLAG_LEFT)) + { + Cond.m_ColorPickerContext.m_pHslaColor = &Cond.m_ColorValue; + Cond.m_ColorPickerContext.m_HslaColor = Cond.m_DisplayHsla; + Cond.m_ColorPickerContext.m_HsvaColor = color_cast(Cond.m_DisplayHsla); + Cond.m_ColorPickerContext.m_RgbaColor = color_cast(Cond.m_DisplayHsla); + Cond.m_ColorPickerContext.m_Alpha = true; + pUI->ShowPopupColorPicker(pUI->MouseX(), pUI->MouseY(), &Cond.m_ColorPickerContext); + } + + if(pUI->IsPopupOpen(&Cond.m_ColorPickerContext) && Cond.m_ColorPickerContext.m_pHslaColor == &Cond.m_ColorValue) + { + Cond.m_DisplayHsla = color_cast(Cond.m_ColorPickerContext.m_HsvaColor); + } + + CondRow.VSplitRight(18.0f, &CondRow, &RemoveBtn); + if(pUI->DoButton_FontIcon(&Cond.m_RemoveBtnId, FontIcon::XMARK, 0, &RemoveBtn, BUTTONFLAG_LEFT, IGraphics::CORNER_ALL, true, std::nullopt)) + { + if(Active) + { + it = pCtx->m_vConditions.erase(it); + pCtx->m_NeedsReopen = true; + return CUi::POPUP_CLOSE_CURRENT; + } + } + ++it; + } + + // Add condition button + CUIRect AddCondRow; + View.HSplitTop(Margin, nullptr, &View); + View.HSplitTop(RowHeight, &AddCondRow, &View); + static CButtonContainer s_AddCondBtn; + if(pGameClient->m_Menus.DoButton_Menu(&s_AddCondBtn, "+ Добавить условие", 0, &AddCondRow)) + { + pCtx->m_vConditions.emplace_back(); + pCtx->m_NeedsReopen = true; + return CUi::POPUP_CLOSE_CURRENT; + } + + // Button row + CUIRect ButtonRow; + View.HSplitTop(Margin + 5.0f, nullptr, &View); + float ButtonRowHeight = 18.0f; + View.HSplitTop(ButtonRowHeight, &ButtonRow, &View); + + CUIRect SaveBtn, RemoveBtnB; + CUIRect TmpRow = ButtonRow; + TmpRow.VSplitLeft((TmpRow.w - Margin) / 2.0f, &SaveBtn, &TmpRow); + TmpRow.VSplitLeft(Margin, nullptr, &TmpRow); + RemoveBtnB = TmpRow; + + static CButtonContainer s_SaveBtn, s_RemoveBtn; + if(pGameClient->m_Menus.DoButton_Menu(&s_SaveBtn, "Сохранить", 0, &SaveBtn)) + { + ColorRGBA FinalColor(1.0f, 1.0f, 1.0f, 1.0f); + std::unordered_map ValueColors; + for(const auto &Cond : pCtx->m_vConditions) + { + int Val = Cond.m_ValueInput.GetInteger(); + ValueColors[Val] = color_cast(Cond.m_DisplayHsla); + } + pGameClient->m_HudWatch.SetWatch(pCtx->m_pVar, pCtx->m_LabelInput.GetString(), FinalColor, ValueColors); + return CUi::POPUP_CLOSE_CURRENT; + } + if(pGameClient->m_Menus.DoButton_Menu(&s_RemoveBtn, "Удалить", 0, &RemoveBtnB)) + { + pGameClient->m_HudWatch.RemoveWatch(pCtx->m_pVar); + return CUi::POPUP_CLOSE_CURRENT; + } + + View.HSplitTop(4.0f, nullptr, &View); + + return CUi::POPUP_KEEP_OPEN; +} + void CMenus::RenderSettingsTClientConfigs(CUIRect MainView) { // hi hello, this is a relatively self contained mess, sorry if you're forking or need to modify this -Tater @@ -2908,11 +3061,85 @@ void CMenus::RenderSettingsTClientConfigs(CUIRect MainView) RowContent.HSplitTop(LineSize, &TopLine, &Below); } CUIRect NameLine, Right; - TopLine.VSplitRight(320.0f, &NameLine, &Right); + TopLine.VSplitRight(344.0f, &NameLine, &Right); NameLine.VSplitLeft(10.0f, nullptr, &NameLine); + CUIRect EyeBtn; + NameLine.VSplitRight(24.0f, &NameLine, &EyeBtn); + EyeBtn.h = LineSize; + EyeBtn.y = TopLine.y + (TopLine.h - LineSize) / 2.0f; + Ui()->DoLabel(&NameLine, pVar->m_pScriptName, FontSize, TEXTALIGN_ML); + { + const bool Watched = GameClient()->m_HudWatch.IsWatched(pVar); + static std::unordered_map s_EyeBtns; + static std::unordered_map s_EditPopups; + CButtonContainer &EyeBtnId = s_EyeBtns[pVar]; + bool EyeClicked = DoButton_Menu(&EyeBtnId, "", 0, &EyeBtn); + 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); + TextRender()->TextColor(Watched ? ColorRGBA(1.0f, 1.0f, 0.0f, 1.0f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.35f)); + Ui()->DoLabel(&EyeBtn, Watched ? FontIcon::EYE : FontIcon::EYE_SLASH, EyeBtn.h * 0.5f, TEXTALIGN_MC); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + if(EyeClicked) + { + SWatchEditPopupContext &PopupCtx = s_EditPopups[pVar]; + PopupCtx.m_pGameClient = GameClient(); + PopupCtx.m_pUI = Ui(); + PopupCtx.m_pVar = pVar; + PopupCtx.m_LabelInput.Clear(); + PopupCtx.m_vConditions.clear(); + PopupCtx.m_NeedsReopen = false; + bool Found = false; + for(const auto &W : GameClient()->m_HudWatch.GetWatches()) + { + if(W.m_pVar == pVar) + { + PopupCtx.m_LabelInput.Set(W.m_aLabel); + PopupCtx.m_DisplayHsla = color_cast(W.m_Color); + ColorRGBA Col = W.m_Color; + PopupCtx.m_ColorValue = ((unsigned)(Col.a * 255) << 24) | ((unsigned)(Col.r * 255) << 16) | ((unsigned)(Col.g * 255) << 8) | (unsigned)(Col.b * 255); + for(const auto &[Val, CCol] : W.m_vValueColors) + { + PopupCtx.m_vConditions.emplace_back(); + auto &Cond = PopupCtx.m_vConditions.back(); + Cond.m_ValueInput.SetInteger(Val); + Cond.m_DisplayHsla = color_cast(CCol); + ColorRGBA TmpCol = CCol; + Cond.m_ColorValue = ((unsigned)(TmpCol.a * 255) << 24) | ((unsigned)(TmpCol.r * 255) << 16) | ((unsigned)(TmpCol.g * 255) << 8) | (unsigned)(TmpCol.b * 255); + } + Found = true; + break; + } + } + if(!Found) + { + PopupCtx.m_LabelInput.Set(pVar->m_pScriptName); + PopupCtx.m_DisplayHsla = ColorHSLA(0.0f, 0.0f, 1.0f, 1.0f); + PopupCtx.m_ColorValue = 0xFFFFFFFF; + } + PopupCtx.m_ColorPickerContext.m_ColorMode = CUi::SColorPickerPopupContext::MODE_UNSET; + PopupCtx.m_ColorPickerContext.m_Alpha = false; + PopupCtx.m_ColorPickerContext.m_pHslaColor = nullptr; + PopupCtx.m_ColorPickerContext.m_State = EEditState::NONE; + float PopupHeight = 90.0f + PopupCtx.m_vConditions.size() * 19.0f; + Ui()->DoPopupMenu(&PopupCtx, EyeBtn.x, EyeBtn.y, 240.0f, PopupHeight, &PopupCtx, WatchEditPopupFunc); + } + else + { + auto it = s_EditPopups.find(pVar); + if(it != s_EditPopups.end() && it->second.m_NeedsReopen) + { + it->second.m_NeedsReopen = false; + float PopupHeight = 90.0f + it->second.m_vConditions.size() * 19.0f; + Ui()->DoPopupMenu(&it->second, EyeBtn.x, EyeBtn.y, 240.0f, PopupHeight, &it->second, WatchEditPopupFunc); + } + } + } + CUIRect Controls, ResetRect; Right.VSplitRight(120.0f, &Controls, &ResetRect); Controls.h = LineSize; diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 5f2868d849..8d0f584ce7 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -332,6 +332,7 @@ void CGameClient::OnConsoleInit() &m_CustomCommunities, // TClient &m_MusicPlayer, // BestClient &m_Hud, + &m_HudWatch, &m_Spectator, &m_Emoticon, &m_BindChat, // TClient diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index a6932345a1..6e54500aab 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -81,6 +81,7 @@ #include "components/tclient/skinprofiles.h" #include "components/tclient/statusbar.h" #include "components/tclient/tclient.h" +#include "components/tclient/hud_watch.h" #include "components/tclient/trails.h" #include "components/bestclient/3d_particles.h" #include "components/bestclient/admin_panel.h" @@ -202,6 +203,7 @@ class CGameClient : public IGameClient CCountryFlags m_CountryFlags; CFlow m_Flow; CHud m_Hud; + CHudWatch m_HudWatch; CImportantAlert m_ImportantAlert; CDebugHud m_DebugHud; CControls m_Controls; From afaf3ca21274ffefcd2eb944a8d565bc6e68a3bf Mon Sep 17 00:00:00 2001 From: Diqere1 Date: Thu, 30 Jul 2026 19:01:17 +0300 Subject: [PATCH 2/2] FixCheckBox eye --- src/game/client/components/tclient/menus_tclient.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/game/client/components/tclient/menus_tclient.cpp b/src/game/client/components/tclient/menus_tclient.cpp index 9e38f66d0e..9b55422c36 100644 --- a/src/game/client/components/tclient/menus_tclient.cpp +++ b/src/game/client/components/tclient/menus_tclient.cpp @@ -3066,6 +3066,7 @@ void CMenus::RenderSettingsTClientConfigs(CUIRect MainView) CUIRect EyeBtn; NameLine.VSplitRight(24.0f, &NameLine, &EyeBtn); + Right.VSplitLeft(5.0f, nullptr, &Right); EyeBtn.h = LineSize; EyeBtn.y = TopLine.y + (TopLine.h - LineSize) / 2.0f; @@ -3125,7 +3126,7 @@ void CMenus::RenderSettingsTClientConfigs(CUIRect MainView) PopupCtx.m_ColorPickerContext.m_Alpha = false; PopupCtx.m_ColorPickerContext.m_pHslaColor = nullptr; PopupCtx.m_ColorPickerContext.m_State = EEditState::NONE; - float PopupHeight = 90.0f + PopupCtx.m_vConditions.size() * 19.0f; + float PopupHeight = 100.0f + PopupCtx.m_vConditions.size() * 19.0f; Ui()->DoPopupMenu(&PopupCtx, EyeBtn.x, EyeBtn.y, 240.0f, PopupHeight, &PopupCtx, WatchEditPopupFunc); } else @@ -3134,7 +3135,7 @@ void CMenus::RenderSettingsTClientConfigs(CUIRect MainView) if(it != s_EditPopups.end() && it->second.m_NeedsReopen) { it->second.m_NeedsReopen = false; - float PopupHeight = 90.0f + it->second.m_vConditions.size() * 19.0f; + float PopupHeight = 100.0f + it->second.m_vConditions.size() * 19.0f; Ui()->DoPopupMenu(&it->second, EyeBtn.x, EyeBtn.y, 240.0f, PopupHeight, &it->second, WatchEditPopupFunc); } }