From 462fba080d52fc250d6737594239ec0e15445f66 Mon Sep 17 00:00:00 2001 From: Solly <76408142+SollyBunny@users.noreply.github.com> Date: Sun, 22 Dec 2024 22:58:33 +0000 Subject: [PATCH] Add bindchat --- CMakeLists.txt | 2 + src/game/client/components/chat.cpp | 11 +- src/game/client/components/chat.h | 2 + src/game/client/components/menus.h | 1 + .../client/components/tclient/bindchat.cpp | 256 ++++++++++++++++++ src/game/client/components/tclient/bindchat.h | 58 ++++ .../client/components/tclient/bindwheel.cpp | 97 +++---- .../client/components/tclient/bindwheel.h | 21 +- .../components/tclient/menus_tclient.cpp | 130 ++++++--- .../client/components/tclient/warlist.cpp | 9 +- src/game/client/gameclient.cpp | 1 + src/game/client/gameclient.h | 5 +- 12 files changed, 495 insertions(+), 98 deletions(-) create mode 100644 src/game/client/components/tclient/bindchat.cpp create mode 100644 src/game/client/components/tclient/bindchat.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e760f989f1a..ce82c95081c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2405,6 +2405,8 @@ if(CLIENT) components/spectator.h components/statboard.cpp components/statboard.h + components/tclient/bindchat.cpp + components/tclient/bindchat.h components/tclient/bindwheel.cpp components/tclient/bindwheel.h components/tclient/menus_tclient.cpp diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 8b17745efbe..e82eed83d0c 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -262,7 +262,10 @@ bool CChat::OnInput(const IInput::CEvent &Event) m_CommandsNeedSorting = false; } - SendChatQueued(m_Input.GetString()); + if (m_pClient->m_Bindchat.ChatDoBinds(m_Input.GetString())) + ; // Do nothing as bindchat was executed + else + SendChatQueued(m_Input.GetString()); m_pHistoryEntry = nullptr; DisableMode(); m_pClient->OnRelease(); @@ -312,7 +315,11 @@ bool CChat::OnInput(const IInput::CEvent &Event) }); } - if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty()) + if (m_pClient->m_Bindchat.ChatDoAutocomplete(ShiftPressed)) + { + + } + else if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty()) { CCommand *pCompletionCommand = 0; diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h index abf80809e8a..c75f0813bd7 100644 --- a/src/game/client/components/chat.h +++ b/src/game/client/components/chat.h @@ -145,6 +145,8 @@ class CChat : public CComponent bool LineShouldHighlight(const char *pLine, const char *pName); void StoreSave(const char *pText); + friend class CBindchat; + public: CChat(); int Sizeof() const override { return sizeof(*this); } diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index c9dc0f49943..fff31a753fc 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -85,6 +85,7 @@ class CMenus : public CComponent int DoButton_CheckBox_Number(const void *pId, const char *pText, int Checked, const CUIRect *pRect); bool DoSliderWithScaledValue(const void *pId, int *pOption, const CUIRect *pRect, const char *pStr, int Min, int Max, int Scale, const IScrollbarScale *pScale, unsigned Flags = 0u, const char *pSuffix = ""); + bool DoEditBoxWithLabel(CLineInput *LineInput, const CUIRect *pRect, const char *pLabel, const char *pDefault, char *pBuf, size_t BufSize); ColorHSLA DoLine_ColorPicker(CButtonContainer *pResetId, float LineSize, float LabelSize, float BottomMargin, CUIRect *pMainRect, const char *pText, unsigned int *pColorValue, ColorRGBA DefaultColor, bool CheckBoxSpacing = true, int *pCheckBoxValue = nullptr, bool Alpha = false); ColorHSLA DoButton_ColorPicker(const CUIRect *pRect, unsigned int *pHslaColor, bool Alpha); diff --git a/src/game/client/components/tclient/bindchat.cpp b/src/game/client/components/tclient/bindchat.cpp new file mode 100644 index 00000000000..a699162df91 --- /dev/null +++ b/src/game/client/components/tclient/bindchat.cpp @@ -0,0 +1,256 @@ +#include +#include + +#include "../chat.h" +#include "../emoticon.h" + +#include "bindchat.h" + +CBindchat::CBindchat() +{ + OnReset(); +} + +void CBindchat::ConAddBindchat(IConsole::IResult *pResult, void *pUserData) +{ + const char *aName = pResult->GetString(0); + const char *aCommand = pResult->GetString(1); + + CBindchat *pThis = static_cast(pUserData); + pThis->AddBind(aName, aCommand); +} + +void CBindchat::ConBindchats(IConsole::IResult *pResult, void *pUserData) +{ + CBindchat *pThis = static_cast(pUserData); + char aBuf[BINDCHAT_MAX_NAME + BINDCHAT_MAX_CMD + 32]; + if(pResult->NumArguments() == 1) + { + const char *pName = pResult->GetString(0); + for(const CBind &Bind : pThis->m_vBinds) + { + if(str_comp_nocase(Bind.m_aName, pName) == 0) + { + str_format(aBuf, sizeof(aBuf), "%s = %s", Bind.m_aName, Bind.m_aCommand); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "bindchat", aBuf); + return; + } + } + str_format(aBuf, sizeof(aBuf), "%s is not bound", pName); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "bindchat", aBuf); + } + else + { + for(const CBind &Bind : pThis->m_vBinds) + { + str_format(aBuf, sizeof(aBuf), "%s = %s", Bind.m_aName, Bind.m_aCommand); + pThis->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "bindchat", aBuf); + } + } +} + +void CBindchat::ConRemoveBindchat(IConsole::IResult *pResult, void *pUserData) +{ + const char *aName = pResult->GetString(0); + CBindchat *pThis = static_cast(pUserData); + pThis->RemoveBind(aName); +} + + +void CBindchat::ConRemoveBindchatAll(IConsole::IResult *pResult, void *pUserData) +{ + CBindchat *pThis = static_cast(pUserData); + pThis->RemoveAllBinds(); +} + +void CBindchat::ConBindchatDefaults(IConsole::IResult *pResult, void *pUserData) +{ + CBindchat *pThis = static_cast(pUserData); + pThis->AddBind("!shrug", "say ¯\\_(ツ)_/¯"); + pThis->AddBind("!flip", "say (╯°□°)╯︵ ┻━┻"); + pThis->AddBind("!unflip", "say ┬─┬ノ( º _ ºノ)"); +} + +void CBindchat::AddBind(const char *pName, const char *pCommand) +{ + if((pName[0] == '\0' && pCommand[0] == '\0') || m_vBinds.size() >= BINDCHAT_MAX_BINDS) + return; + + RemoveBind(pName); // Prevent duplicates + + CBind Bind; + str_copy(Bind.m_aName, pName); + str_copy(Bind.m_aCommand, pCommand); + m_vBinds.push_back(Bind); +} + +void CBindchat::RemoveBind(const char *pName) +{ + if(pName[0] == '\0') + return; + for (auto It = m_vBinds.begin(); It != m_vBinds.end(); ++It) + { + if(str_comp(It->m_aName, pName) == 0) + { + m_vBinds.erase(It); + return; + } + } +} + +void CBindchat::RemoveBind(int Index) +{ + if(Index >= static_cast(m_vBinds.size()) || Index < 0) + return; + auto It = m_vBinds.begin() + Index; + m_vBinds.erase(It); +} + +void CBindchat::RemoveAllBinds() +{ + m_vBinds.clear(); +} + +int CBindchat::GetBind(const char *pCommand) +{ + if(pCommand[0] == '\0') + return -1; + for(auto It = m_vBinds.begin(); It != m_vBinds.end(); ++It) + { + if(str_comp_nocase(It->m_aCommand, pCommand) == 0) + return &*It - m_vBinds.data(); + } + return -1; +} + +CBindchat::CBind *CBindchat::Get(int Index) +{ + if(Index < 0 || Index >= (int)m_vBinds.size()) + return nullptr; + return &m_vBinds[Index]; +} + +void CBindchat::OnConsoleInit() +{ + IConfigManager *pConfigManager = Kernel()->RequestInterface(); + if(pConfigManager) + pConfigManager->RegisterTCallback(ConfigSaveCallback, this); + + Console()->Register("bindchat", "s[name] r[command]", CFGFLAG_CLIENT, ConAddBindchat, this, "Add a chat bind"); + Console()->Register("bindchats", "?s[name]", CFGFLAG_CLIENT, ConBindchats, this, "Print command executed by this name or all chat binds"); + Console()->Register("unbindchat", "s[name] r[command]", CFGFLAG_CLIENT, ConRemoveBindchat, this, "Remove a chat bind"); + Console()->Register("unbindchatall", "", CFGFLAG_CLIENT, ConRemoveBindchatAll, this, "Removes all chat binds"); + Console()->Register("bindchatdefaults", "", CFGFLAG_CLIENT, ConBindchatDefaults, this, "Adds default chat binds"); +} + +void CBindchat::ExecuteBind(int Bind, const char *pArgs) +{ + char aBuf[BINDCHAT_MAX_CMD] = ""; + str_append(aBuf, m_vBinds[Bind].m_aCommand); + if(pArgs) + { + str_append(aBuf, " "); + str_append(aBuf, pArgs); + } + Console()->ExecuteLine(aBuf); +} + +bool CBindchat::ChatDoBinds(const char *pText) +{ + CChat &Chat = GameClient()->m_Chat; + const char *pSpace = str_find(pText, " "); + size_t SpaceIndex = pSpace ? pSpace - pText : strlen(pText); + for(const CBind &Bind : m_vBinds) + { + if(str_comp_nocase_num(pText, Bind.m_aName, SpaceIndex) == 0) + { + ExecuteBind(&Bind - m_vBinds.data(), pSpace ? pSpace + 1 : nullptr); + // Add to history (see CChat::SendChatQueued) + const int Length = str_length(pText); + CChat::CHistoryEntry *pEntry = Chat.m_History.Allocate(sizeof(CChat::CHistoryEntry) + Length); + pEntry->m_Team = 0; // All + str_copy(pEntry->m_aText, pText, Length + 1); + return true; + } + } + return false; +} + +bool CBindchat::ChatDoAutocomplete(bool ShiftPressed) { + CChat &Chat = GameClient()->m_Chat; + + if(m_vBinds.size() == 0) + return false; + if(*Chat.m_aCompletionBuffer == '\0') + return false; + + const CBind *pCompletionBind = nullptr; + + if(ShiftPressed && Chat.m_CompletionUsed) + Chat.m_CompletionChosen--; + else if(!ShiftPressed) + Chat.m_CompletionChosen++; + Chat.m_CompletionChosen = (Chat.m_CompletionChosen + m_vBinds.size()) % m_vBinds.size(); // size != 0 + + Chat.m_CompletionUsed = true; + for(const CBind &Bind : m_vBinds) + { + if(str_startswith_nocase(Bind.m_aName, Chat.m_aCompletionBuffer)) + { + pCompletionBind = &Bind; + Chat.m_CompletionChosen = &Bind - m_vBinds.data(); + break; + } + } + + // insert the command + if(pCompletionBind) + { + char aBuf[CChat::MAX_LINE_LENGTH]; + // add part before the name + str_truncate(aBuf, sizeof(aBuf), Chat.m_Input.GetString(), Chat.m_PlaceholderOffset); + + // add the command + str_append(aBuf, pCompletionBind->m_aName); + + // add separator + // TODO: figure out if the command would accept an extra param + // char commandBuf[128]; + // str_next_token(pCompletionBind->m_aCommand, " ", commandBuf, sizeof(commandBuf)); + // CCommandInfo *pInfo = m_pClient->Console()->GetCommandInfo(commandBuf, CFGFLAG_CLIENT, false); + // if(pInfo && pInfo->m_pParams != '\0') + const char pSeperator[] = " "; + str_append(aBuf, pSeperator); + + // add part after the name + str_append(aBuf, Chat.m_Input.GetString() + Chat.m_PlaceholderOffset + Chat.m_PlaceholderLength); + + Chat.m_PlaceholderLength = sizeof(pSeperator) + str_length(pCompletionBind->m_aName) + 1; + Chat.m_Input.Set(aBuf); + Chat.m_Input.SetCursorOffset(Chat.m_PlaceholderOffset + Chat.m_PlaceholderLength); + } + + return pCompletionBind != nullptr; +} + +void CBindchat::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData) +{ + CBindchat *pThis = (CBindchat *)pUserData; + + for(CBind &Bind : pThis->m_vBinds) + { + char aBuf[BINDCHAT_MAX_CMD * 2] = ""; + char *pEnd = aBuf + sizeof(aBuf); + char *pDst; + str_append(aBuf, "bindchat \""); + // Escape name + pDst = aBuf + str_length(aBuf); + str_escape(&pDst, Bind.m_aName, pEnd); + str_append(aBuf, "\" \""); + // Escape command + pDst = aBuf + str_length(aBuf); + str_escape(&pDst, Bind.m_aCommand, pEnd); + str_append(aBuf, "\""); + pConfigManager->WriteLine(aBuf); + } +} diff --git a/src/game/client/components/tclient/bindchat.h b/src/game/client/components/tclient/bindchat.h new file mode 100644 index 00000000000..3e57801f6a0 --- /dev/null +++ b/src/game/client/components/tclient/bindchat.h @@ -0,0 +1,58 @@ + +#ifndef GAME_CLIENT_COMPONENTS_BINDCHAT_H +#define GAME_CLIENT_COMPONENTS_BINDCHAT_H +#include +class IConfigManager; + +enum +{ + BINDCHAT_MAX_NAME = 64, + BINDCHAT_MAX_CMD = 1024, + BINDCHAT_MAX_BINDS = 256, +}; + +class CBindchat : public CComponent +{ + static void ConAddBindchat(IConsole::IResult *pResult, void *pUserData); + static void ConBindchats(IConsole::IResult *pResult, void *pUserData); + static void ConRemoveBindchat(IConsole::IResult *pResult, void *pUserData); + static void ConRemoveBindchatAll(IConsole::IResult *pResult, void *pUserData); + static void ConBindchatDefaults(IConsole::IResult *pResult, void *pUserData); + + static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData); + + void ExecuteBind(int Bind, const char *pArgs); + +public: + class CBind + { + public: + char m_aName[BINDCHAT_MAX_NAME]; + char m_aCommand[BINDCHAT_MAX_CMD]; + + bool operator==(const CBind &Other) const + { + return str_comp(m_aName, Other.m_aName) == 0 && str_comp(m_aCommand, Other.m_aCommand) == 0; + } + }; + + std::vector m_vBinds; + + CBindchat(); + virtual int Sizeof() const override { return sizeof(*this); } + + virtual void OnConsoleInit() override; + + void AddBind(const char *pName, const char *pCommand); + void RemoveBind(const char *pName); + void RemoveBind(int Index); + void RemoveAllBinds(); + + int GetBind(const char *pCommand); + CBind *Get(int Index); + + bool ChatDoBinds(const char *pText); + bool ChatDoAutocomplete(bool ShiftPressed); +}; + +#endif diff --git a/src/game/client/components/tclient/bindwheel.cpp b/src/game/client/components/tclient/bindwheel.cpp index 01206c5f536..008c0e06a8c 100644 --- a/src/game/client/components/tclient/bindwheel.cpp +++ b/src/game/client/components/tclient/bindwheel.cpp @@ -12,34 +12,34 @@ #include "bindwheel.h" #include -CBindWheel::CBindWheel() +CBindwheel::CBindwheel() { OnReset(); } -void CBindWheel::ConExecuteHover(IConsole::IResult *pResult, void *pUserData) +void CBindwheel::ConBindwheelExecuteHover(IConsole::IResult *pResult, void *pUserData) { - CBindWheel *pThis = (CBindWheel *)pUserData; - pThis->ExecuteHover(); + CBindwheel *pThis = (CBindwheel *)pUserData; + pThis->ExecuteHoveredBind(); } -void CBindWheel::ConOpenBindwheel(IConsole::IResult *pResult, void *pUserData) +void CBindwheel::ConOpenBindwheel(IConsole::IResult *pResult, void *pUserData) { - CBindWheel *pThis = (CBindWheel *)pUserData; + CBindwheel *pThis = (CBindwheel *)pUserData; if(pThis->Client()->State() != IClient::STATE_DEMOPLAYBACK) pThis->m_Active = pResult->GetInteger(0) != 0; } -void CBindWheel::ConAddBindwheelLegacy(IConsole::IResult *pResult, void *pUserData) +void CBindwheel::ConAddBindwheelLegacy(IConsole::IResult *pResult, void *pUserData) { int BindPos = pResult->GetInteger(0); - if (BindPos < 0 || BindPos >= MAX_BINDS) + if (BindPos < 0 || BindPos >= BINDWHEEL_MAX_BINDS) return; const char *aName = pResult->GetString(1); const char *aCommand = pResult->GetString(2); - CBindWheel *pThis = static_cast(pUserData); + CBindwheel *pThis = static_cast(pUserData); while(pThis->m_vBinds.size() <= (size_t)BindPos) pThis->AddBind("EMPTY", ""); @@ -47,44 +47,44 @@ void CBindWheel::ConAddBindwheelLegacy(IConsole::IResult *pResult, void *pUserDa str_copy(pThis->m_vBinds[BindPos].m_aCommand, aCommand); } -void CBindWheel::ConAddBindwheel(IConsole::IResult *pResult, void *pUserData) +void CBindwheel::ConAddBindwheel(IConsole::IResult *pResult, void *pUserData) { const char *aName = pResult->GetString(0); const char *aCommand = pResult->GetString(1); - CBindWheel *pThis = static_cast(pUserData); + CBindwheel *pThis = static_cast(pUserData); pThis->AddBind(aName, aCommand); } -void CBindWheel::ConRemoveBindwheel(IConsole::IResult *pResult, void *pUserData) +void CBindwheel::ConRemoveBindwheel(IConsole::IResult *pResult, void *pUserData) { const char *aName = pResult->GetString(0); const char *aCommand = pResult->GetString(1); - CBindWheel *pThis = static_cast(pUserData); + CBindwheel *pThis = static_cast(pUserData); pThis->RemoveBind(aName, aCommand); } -void CBindWheel::ConRemoveAllBinds(IConsole::IResult *pResult, void *pUserData) +void CBindwheel::ConRemoveAllBindwheelBinds(IConsole::IResult *pResult, void *pUserData) { - CBindWheel *pThis = static_cast(pUserData); + CBindwheel *pThis = static_cast(pUserData); pThis->RemoveAllBinds(); } -void CBindWheel::AddBind(const char *pName, const char *pCommand) +void CBindwheel::AddBind(const char *pName, const char *pCommand) { - if((pName[0] == '\0' && pCommand[0] == '\0') || m_vBinds.size() >= MAX_BINDS) + if((pName[0] == '\0' && pCommand[0] == '\0') || m_vBinds.size() >= BINDWHEEL_MAX_BINDS) return; - SBind Bind; + CBind Bind; str_copy(Bind.m_aName, pName); str_copy(Bind.m_aCommand, pCommand); m_vBinds.push_back(Bind); } -void CBindWheel::RemoveBind(const char *pName, const char *pCommand) +void CBindwheel::RemoveBind(const char *pName, const char *pCommand) { - SBind Bind; + CBind Bind; str_copy(Bind.m_aName, pName); str_copy(Bind.m_aCommand, pCommand); auto it = std::find(m_vBinds.begin(), m_vBinds.end(), Bind); @@ -92,7 +92,7 @@ void CBindWheel::RemoveBind(const char *pName, const char *pCommand) m_vBinds.erase(it); } -void CBindWheel::RemoveBind(int Index) +void CBindwheel::RemoveBind(int Index) { if(Index >= static_cast(m_vBinds.size()) || Index < 0) return; @@ -100,39 +100,39 @@ void CBindWheel::RemoveBind(int Index) m_vBinds.erase(Pos); } -void CBindWheel::RemoveAllBinds() +void CBindwheel::RemoveAllBinds() { m_vBinds.clear(); } -void CBindWheel::OnConsoleInit() +void CBindwheel::OnConsoleInit() { IConfigManager *pConfigManager = Kernel()->RequestInterface(); if(pConfigManager) pConfigManager->RegisterTCallback(ConfigSaveCallback, this); Console()->Register("+bindwheel", "", CFGFLAG_CLIENT, ConOpenBindwheel, this, "Open bindwheel selector"); - Console()->Register("+bindwheel_execute_hover", "", CFGFLAG_CLIENT, ConExecuteHover, this, "Execute hovered bindwheel bind"); + Console()->Register("+bindwheel_execute_hover", "", CFGFLAG_CLIENT, ConBindwheelExecuteHover, this, "Execute hovered bindwheel bind"); Console()->Register("bindwheel", "i[index] s[name] s[command]", CFGFLAG_CLIENT, ConAddBindwheelLegacy, this, "DONT USE THIS! USE add_bindwheel INSTEAD!"); Console()->Register("add_bindwheel", "s[name] s[command]", CFGFLAG_CLIENT, ConAddBindwheel, this, "Add a bind to the bindwheel"); Console()->Register("remove_bindwheel", "s[name] s[command]", CFGFLAG_CLIENT, ConRemoveBindwheel, this, "Remove a bind from the bindwheel"); - Console()->Register("delete_all_bindwheel_binds", "", CFGFLAG_CLIENT, ConRemoveAllBinds, this, "Removes all bindwheel binds"); + Console()->Register("delete_all_bindwheel_binds", "", CFGFLAG_CLIENT, ConRemoveAllBindwheelBinds, this, "Removes all bindwheel binds"); } -void CBindWheel::OnReset() +void CBindwheel::OnReset() { m_WasActive = false; m_Active = false; m_SelectedBind = -1; } -void CBindWheel::OnRelease() +void CBindwheel::OnRelease() { m_Active = false; } -bool CBindWheel::OnCursorMove(float x, float y, IInput::ECursorType CursorType) +bool CBindwheel::OnCursorMove(float x, float y, IInput::ECursorType CursorType) { if(!m_Active) return false; @@ -142,19 +142,19 @@ bool CBindWheel::OnCursorMove(float x, float y, IInput::ECursorType CursorType) return true; } -void CBindWheel::DrawCircle(float x, float y, float r, int Segments) +void CBindwheel::DrawCircle(float x, float y, float r, int Segments) { Graphics()->DrawCircle(x, y, r, Segments); } -void CBindWheel::OnRender() +void CBindwheel::OnRender() { if(!m_Active) { if(g_Config.m_ClResetBindWheelMouse) m_SelectorMouse = vec2(0.0f, 0.0f); if(m_WasActive && m_SelectedBind != -1) - ExecuteBindwheel(m_SelectedBind); + ExecuteBind(m_SelectedBind); m_WasActive = false; return; } @@ -190,7 +190,7 @@ void CBindWheel::OnRender() const float Theta = pi * 2.0f / m_vBinds.size(); for(int i = 0; i < static_cast(m_vBinds.size()); i++) { - const SBind &Bind = m_vBinds[i]; + const CBind &Bind = m_vBinds[i]; const float Angle = Theta * i; vec2 Pos = direction(Angle); Pos *= 140.0f; @@ -210,33 +210,34 @@ void CBindWheel::OnRender() RenderTools()->RenderCursor(m_SelectorMouse + vec2(Screen.w, Screen.h) / 2.0f, 24.0f); } -void CBindWheel::ExecuteBindwheel(int Bind) +void CBindwheel::ExecuteBind(int Bind) { Console()->ExecuteLine(m_vBinds[Bind].m_aCommand); } -void CBindWheel::ExecuteHover() +void CBindwheel::ExecuteHoveredBind() { if(m_SelectedBind >= 0) Console()->ExecuteLine(m_vBinds[m_SelectedBind].m_aCommand); } -static void EscapeParam(char *pDst, const char *pSrc, int Size) -{ - str_escape(&pDst, pSrc, pDst + Size); -} -void CBindWheel::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData) +void CBindwheel::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData) { - CBindWheel *pThis = (CBindWheel *)pUserData; - - char aBuf[2048]; - char aEscapeName[BINDWHEEL_MAX_NAME + 256]; - char aEscapeCommand[BINDWHEEL_MAX_CMD + 256]; + CBindwheel *pThis = (CBindwheel *)pUserData; - for(SBind &Bind : pThis->m_vBinds) + for(CBind &Bind : pThis->m_vBinds) { - EscapeParam(aEscapeName, Bind.m_aName, sizeof(aEscapeName)); - EscapeParam(aEscapeCommand, Bind.m_aCommand, sizeof(aEscapeCommand)); - str_format(aBuf, sizeof(aBuf), "add_bindwheel \"%s\" \"%s\"", aEscapeName, aEscapeCommand); + char aBuf[BINDWHEEL_MAX_CMD * 2] = ""; + char *pEnd = aBuf + sizeof(aBuf); + char *pDst; + str_append(aBuf, "add_bindwheel \""); + // Escape name + pDst = aBuf + str_length(aBuf); + str_escape(&pDst, Bind.m_aName, pEnd); + str_append(aBuf, "\" \""); + // Escape command + pDst = aBuf + str_length(aBuf); + str_escape(&pDst, Bind.m_aCommand, pEnd); + str_append(aBuf, "\""); pConfigManager->WriteLine(aBuf); } } diff --git a/src/game/client/components/tclient/bindwheel.h b/src/game/client/components/tclient/bindwheel.h index 01ead0e82f6..db6494f0d6d 100644 --- a/src/game/client/components/tclient/bindwheel.h +++ b/src/game/client/components/tclient/bindwheel.h @@ -7,10 +7,10 @@ enum { BINDWHEEL_MAX_NAME = 64, BINDWHEEL_MAX_CMD = 1024, - MAX_BINDS = 64 + BINDWHEEL_MAX_BINDS = 64 }; -class CBindWheel : public CComponent +class CBindwheel : public CComponent { void DrawCircle(float x, float y, float r, int Segments); @@ -24,26 +24,27 @@ class CBindWheel : public CComponent static void ConAddBindwheelLegacy(IConsole::IResult *pResult, void *pUserData); static void ConAddBindwheel(IConsole::IResult *pResult, void *pUserData); static void ConRemoveBindwheel(IConsole::IResult *pResult, void *pUserData); - static void ConRemoveAllBinds(IConsole::IResult *pResult, void *pUserData); - static void ConExecuteHover(IConsole::IResult *pResult, void *pUserData); + static void ConRemoveAllBindwheelBinds(IConsole::IResult *pResult, void *pUserData); + static void ConBindwheelExecuteHover(IConsole::IResult *pResult, void *pUserData); static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData); public: - struct SBind + class CBind { + public: char m_aName[BINDWHEEL_MAX_NAME]; char m_aCommand[BINDWHEEL_MAX_CMD]; - bool operator==(const SBind &Other) const + bool operator==(const CBind &Other) const { return str_comp(m_aName, Other.m_aName) == 0 && str_comp(m_aCommand, Other.m_aCommand) == 0; } }; - std::vector m_vBinds; + std::vector m_vBinds; - CBindWheel(); + CBindwheel(); virtual int Sizeof() const override { return sizeof(*this); } virtual void OnReset() override; @@ -57,8 +58,8 @@ class CBindWheel : public CComponent void RemoveBind(int Index); void RemoveAllBinds(); - void ExecuteHover(); - void ExecuteBindwheel(int Bind); + void ExecuteHoveredBind(); + void ExecuteBind(int Bind); }; #endif diff --git a/src/game/client/components/tclient/menus_tclient.cpp b/src/game/client/components/tclient/menus_tclient.cpp index 502d2e76564..37b587861b7 100644 --- a/src/game/client/components/tclient/menus_tclient.cpp +++ b/src/game/client/components/tclient/menus_tclient.cpp @@ -48,9 +48,10 @@ enum { TCLIENT_TAB_SETTINGS = 0, TCLIENT_TAB_BINDWHEEL = 1, - TCLIENT_TAB_WARLIST = 2, - TCLIENT_TAB_INFO = 3, - NUMBER_OF_TCLIENT_TABS = 4 + TCLIENT_TAB_BINDCHAT = 2, + TCLIENT_TAB_WARLIST = 3, + TCLIENT_TAB_INFO = 4, + NUMBER_OF_TCLIENT_TABS = 5 }; typedef struct @@ -66,6 +67,8 @@ using namespace FontIcons; static float s_Time = 0.0f; static bool s_StartedTime = false; +const float FontSize = 14.0f; +const float EditBoxFontSize = 12.0f; const float LineSize = 20.0f; const float ColorPickerLineSize = 25.0f; const float HeadlineFontSize = 20.0f; @@ -113,8 +116,8 @@ bool CMenus::DoSliderWithScaledValue(const void *pId, int *pOption, const CUIRec CUIRect Label, ScrollBar; pRect->VSplitMid(&Label, &ScrollBar, minimum(10.0f, pRect->w * 0.05f)); - const float FontSize = Label.h * CUi::ms_FontmodHeight * 0.8f; - Ui()->DoLabel(&Label, aBuf, FontSize, TEXTALIGN_ML); + const float LabelFontSize = Label.h * CUi::ms_FontmodHeight * 0.8f; + Ui()->DoLabel(&Label, aBuf, LabelFontSize, TEXTALIGN_ML); Value = pScale->ToAbsolute(Ui()->DoScrollbarH(pId, &ScrollBar, pScale->ToRelative(Value, Min, Max)), Min, Max); if(NoClampValue && ((Value == Min && *pOption < Min) || (Value == Max && *pOption > Max))) @@ -130,6 +133,17 @@ bool CMenus::DoSliderWithScaledValue(const void *pId, int *pOption, const CUIRec return false; } + +bool CMenus::DoEditBoxWithLabel(CLineInput *LineInput, const CUIRect *pRect, const char *pLabel, const char *pDefault, char *pBuf, size_t BufSize) +{ + CUIRect Button, Label; + pRect->VSplitLeft(100.0f, &Label, &Button); + Ui()->DoLabel(&Label, pLabel, FontSize, TEXTALIGN_ML); + LineInput->SetBuffer(pBuf, BufSize); + LineInput->SetEmptyText(pDefault); + return Ui()->DoEditBox(LineInput, &Button, EditBoxFontSize); +} + int CMenus::DoButtonLineSize_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, float Line_Size, bool Fake, const char *pImageName, int Corners, float Rounding, float FontFactor, ColorRGBA Color) { CUIRect Text = *pRect; @@ -152,6 +166,7 @@ int CMenus::DoButtonLineSize_Menu(CButtonContainer *pButtonContainer, const char return 0; return Ui()->DoButtonLogic(pButtonContainer, Checked, pRect); + } void CMenus::RenderSettingsTClient(CUIRect MainView) @@ -173,22 +188,16 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) const char *apTabNames[] = { Localize("Settings"), Localize("Bindwheel"), + Localize("Chat Binds"), Localize("Warlist"), Localize("Info")}; for(int Tab = 0; Tab < NUMBER_OF_TCLIENT_TABS; ++Tab) { TabBar.VSplitLeft(TabWidth, &Button, &TabBar); - const int Corners = Tab == 0 ? IGraphics::CORNER_L : Tab == NUMBER_OF_TCLIENT_TABS - 1 ? IGraphics::CORNER_R : - IGraphics::CORNER_NONE; + const int Corners = Tab == 0 ? IGraphics::CORNER_L : Tab == NUMBER_OF_TCLIENT_TABS - 1 ? IGraphics::CORNER_R : IGraphics::CORNER_NONE; if(DoButton_MenuTab(&s_aPageTabs[Tab], apTabNames[Tab], s_CurCustomTab == Tab, &Button, Corners, nullptr, nullptr, nullptr, nullptr, 4.0f)) - { - // if(Tab == TCLIENT_TAB_DISCORD) - // PopupConfirm(Localize("Open TClient Discord"), Localize("Click open to open the TClient Discord invite in your browser"), Localize("Open"), Localize("Cancel"), &CMenus::OpenTClientDiscord); - // else s_CurCustomTab = Tab; - break; - } } MainView.HSplitTop(MarginSmall, nullptr, &MainView); @@ -256,7 +265,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) FeetBox.VSplitMid(&FeetBox, nullptr); static CLineInput s_WhiteFeet(g_Config.m_ClWhiteFeetSkin, sizeof(g_Config.m_ClWhiteFeetSkin)); s_WhiteFeet.SetEmptyText("x_ninja"); - Ui()->DoEditBox(&s_WhiteFeet, &FeetBox, 12.0f); + Ui()->DoEditBox(&s_WhiteFeet, &FeetBox, EditBoxFontSize); } Column.HSplitTop(MarginExtraSmall, nullptr, &Column); @@ -420,7 +429,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) static CLineInput s_LastInput(g_Config.m_ClNotifyWhenLastText, sizeof(g_Config.m_ClNotifyWhenLastText)); s_LastInput.SetEmptyText(Localize("Last!")); Button.HSplitTop(MarginSmall, nullptr, &Button); - Ui()->DoEditBox(&s_LastInput, &Button, 12.0f); + Ui()->DoEditBox(&s_LastInput, &Button, EditBoxFontSize); static CButtonContainer s_ClientNotifyWhenLastColor; DoLine_ColorPicker(&s_ClientNotifyWhenLastColor, ColorPickerLineSize, ColorPickerLabelSize, ColorPickerLineSpacing, &NotificationConfig, "", &g_Config.m_ClNotifyWhenLastColor, ColorRGBA(1.0f, 1.0f, 1.0f), false); } @@ -582,6 +591,57 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) s_ScrollRegion.End(); } + if(s_CurCustomTab == TCLIENT_TAB_BINDCHAT) + { + MainView.HSplitTop(MarginBetweenSections, nullptr, &MainView); + Column = MainView; + + Column.HSplitTop(HeadlineHeight, &Label, &Column); + Ui()->DoLabel(&Label, Localize("Kaomoji"), HeadlineFontSize, TEXTALIGN_ML); + Column.HSplitTop(MarginSmall, nullptr, &Column); + + auto DoBindchat = [&](CLineInput &LineInput, const char *pLabel, const char *pName, const char *pCommand) + { + Column.HSplitTop(LineSize, &Button, &Column); + char *BindCommand; + int BindIndex = GameClient()->m_Bindchat.GetBind(pCommand); + bool BindNew = BindIndex == -1; + if(BindNew) + { + static char s_aBindCommandTemp[BINDCHAT_MAX_CMD] = ""; + BindCommand = s_aBindCommandTemp; + BindNew = true; // Make a new bind, as we arent editing one + } + else + { + auto *Bind = GameClient()->m_Bindchat.Get(BindIndex); + BindCommand = Bind->m_aName; + } + if(DoEditBoxWithLabel(&LineInput, &Button, pLabel, pName, BindCommand, BINDCHAT_MAX_CMD)) + { + if(BindNew && BindCommand[0] != '\0' && LineInput.IsActive()) + { + GameClient()->m_Bindchat.AddBind(BindCommand, pCommand); + BindCommand[0] = '\0'; // Reset for new usage + } + if(!BindNew && BindCommand[0] == '\0') + GameClient()->m_Bindchat.RemoveBind(BindIndex); + } + }; + + static CLineInput s_KaomojiShrug; + DoBindchat(s_KaomojiShrug, Localize("Shrug:"), "!shrug", "say ¯\\_(ツ)_/¯"); + + Column.HSplitTop(MarginSmall, nullptr, &Column); + static CLineInput s_KaomojiFlip; + DoBindchat(s_KaomojiFlip, Localize("Flip:"), "!flip", "say (╯°□°)╯︵ ┻━┻"); + + Column.HSplitTop(MarginSmall, nullptr, &Column); + static CLineInput s_KaomojiUnflip; + DoBindchat(s_KaomojiUnflip, Localize("Unflip:"), "!unflip", "say ┬─┬ノ( º _ ºノ)"); + + } + if(s_CurCustomTab == TCLIENT_TAB_BINDWHEEL) { MainView.HSplitTop(MarginBetweenSections, nullptr, &MainView); @@ -621,8 +681,8 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) } else if(Ui()->MouseButtonClicked(1) && s_SelectedBindIndex >= 0 && HoveringIndex >= 0 && HoveringIndex != s_SelectedBindIndex) { - CBindWheel::SBind BindA = GameClient()->m_Bindwheel.m_vBinds[s_SelectedBindIndex]; - CBindWheel::SBind BindB = GameClient()->m_Bindwheel.m_vBinds[HoveringIndex]; + CBindwheel::CBind BindA = GameClient()->m_Bindwheel.m_vBinds[s_SelectedBindIndex]; + CBindwheel::CBind BindB = GameClient()->m_Bindwheel.m_vBinds[HoveringIndex]; str_copy(GameClient()->m_Bindwheel.m_vBinds[s_SelectedBindIndex].m_aName, BindB.m_aName); str_copy(GameClient()->m_Bindwheel.m_vBinds[s_SelectedBindIndex].m_aCommand, BindB.m_aCommand); str_copy(GameClient()->m_Bindwheel.m_vBinds[HoveringIndex].m_aName, BindA.m_aName); @@ -643,43 +703,43 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) const float Theta = pi * 2.0f / GameClient()->m_Bindwheel.m_vBinds.size(); for(int i = 0; i < static_cast(GameClient()->m_Bindwheel.m_vBinds.size()); i++) { - float FontSize = 12.0f; + float SegmentFontSize = FontSize * 1.2f; if(i == s_SelectedBindIndex) { - FontSize = 20.0f; + SegmentFontSize = FontSize * 1.7f; TextRender()->TextColor(ColorRGBA(0.5f, 1.0f, 0.75f, 1.0f)); } else if(i == HoveringIndex) - FontSize = 14.0f; + SegmentFontSize = FontSize; - const CBindWheel::SBind Bind = GameClient()->m_Bindwheel.m_vBinds[i]; + const CBindwheel::CBind Bind = GameClient()->m_Bindwheel.m_vBinds[i]; const float Angle = Theta * i; vec2 TextPos = direction(Angle); TextPos *= Radius * 0.75f; - float Width = TextRender()->TextWidth(FontSize, Bind.m_aName); + float Width = TextRender()->TextWidth(SegmentFontSize, Bind.m_aName); TextPos += Pos; TextPos.x -= Width / 2.0f; - TextRender()->Text(TextPos.x, TextPos.y, FontSize, Bind.m_aName); + TextRender()->Text(TextPos.x, TextPos.y, SegmentFontSize, Bind.m_aName); TextRender()->TextColor(TextRender()->DefaultTextColor()); } LeftView.HSplitTop(LineSize, &Button, &LeftView); Button.VSplitLeft(100.0f, &Label, &Button); - Ui()->DoLabel(&Label, Localize("Name:"), 14.0f, TEXTALIGN_ML); + Ui()->DoLabel(&Label, Localize("Name:"), FontSize, TEXTALIGN_ML); static CLineInput s_NameInput; s_NameInput.SetBuffer(s_aBindName, sizeof(s_aBindName)); s_NameInput.SetEmptyText("Name"); - Ui()->DoEditBox(&s_NameInput, &Button, 12.0f); + Ui()->DoEditBox(&s_NameInput, &Button, EditBoxFontSize); LeftView.HSplitTop(MarginSmall, nullptr, &LeftView); LeftView.HSplitTop(LineSize, &Button, &LeftView); Button.VSplitLeft(100.0f, &Label, &Button); - Ui()->DoLabel(&Label, Localize("Command:"), 14.0f, TEXTALIGN_ML); + Ui()->DoLabel(&Label, Localize("Command:"), FontSize, TEXTALIGN_ML); static CLineInput s_BindInput; s_BindInput.SetBuffer(s_aBindCommand, sizeof(s_aBindCommand)); s_BindInput.SetEmptyText(Localize("Command")); - Ui()->DoEditBox(&s_BindInput, &Button, 12.0f); + Ui()->DoEditBox(&s_BindInput, &Button, EditBoxFontSize); static CButtonContainer s_AddButton, s_RemoveButton, s_OverrideButton; @@ -687,7 +747,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) LeftView.HSplitTop(LineSize, &Button, &LeftView); if(DoButton_Menu(&s_OverrideButton, Localize("Override Selected"), 0, &Button) && s_SelectedBindIndex >= 0) { - CBindWheel::SBind TempBind; + CBindwheel::CBind TempBind; if(str_length(s_aBindName) == 0) str_copy(TempBind.m_aName, "*"); else @@ -702,7 +762,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) Button.VSplitMid(&ButtonRemove, &ButtonAdd, MarginSmall); if(DoButton_Menu(&s_AddButton, Localize("Add Bind"), 0, &ButtonAdd)) { - CBindWheel::SBind TempBind; + CBindwheel::CBind TempBind; if(str_length(s_aBindName) == 0) str_copy(TempBind.m_aName, "*"); else @@ -719,11 +779,11 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) LeftView.HSplitTop(MarginSmall, nullptr, &LeftView); LeftView.HSplitTop(LineSize, &Label, &LeftView); - Ui()->DoLabel(&Label, Localize("Use left mouse to select"), 14.0f, TEXTALIGN_ML); + Ui()->DoLabel(&Label, Localize("Use left mouse to select"), FontSize, TEXTALIGN_ML); LeftView.HSplitTop(LineSize, &Label, &LeftView); - Ui()->DoLabel(&Label, Localize("Use right mouse to swap with selected"), 14.0f, TEXTALIGN_ML); + Ui()->DoLabel(&Label, Localize("Use right mouse to swap with selected"), FontSize, TEXTALIGN_ML); LeftView.HSplitTop(LineSize, &Label, &LeftView); - Ui()->DoLabel(&Label, Localize("Use middle mouse select without copy"), 14.0f, TEXTALIGN_ML); + Ui()->DoLabel(&Label, Localize("Use middle mouse select without copy"), FontSize, TEXTALIGN_ML); // Do Settings Key CKeyInfo Key = CKeyInfo{"Bind Wheel Key", "+bindwheel", 0, 0}; @@ -751,7 +811,7 @@ void CMenus::RenderSettingsTClient(CUIRect MainView) char aBuf[64]; str_format(aBuf, sizeof(aBuf), "%s:", Localize((const char *)Key.m_pName)); - Ui()->DoLabel(&KeyLabel, aBuf, 14.0f, TEXTALIGN_ML); + Ui()->DoLabel(&KeyLabel, aBuf, FontSize, TEXTALIGN_ML); int OldId = Key.m_KeyId, OldModifierCombination = Key.m_ModifierCombination, NewModifierCombination; int NewId = DoKeyReader((void *)&Key.m_pName, &Button, OldId, OldModifierCombination, &NewModifierCombination); if(NewId != OldId || NewModifierCombination != OldModifierCombination) @@ -820,6 +880,7 @@ void CMenus::RenderSettingsWarList(CUIRect MainView) static CWarEntry *pSelectedEntry = nullptr; static CWarType *pSelectedType = GameClient()->m_WarList.m_WarTypes[0]; + // Filter the list static CLineInputBuffered<128> s_EntriesFilterInput; std::vector vpFilteredEntries; @@ -891,6 +952,7 @@ void CMenus::RenderSettingsWarList(CUIRect MainView) EntryRect.VSplitLeft(35.0f, &EntryTypeRect, &EntryRect); if(IsClan) + { RenderFontIcon(EntryTypeRect, FONT_ICON_USERS, 18.0f, TEXTALIGN_MC); } @@ -1308,8 +1370,6 @@ void CMenus::RenderSettingsProfiles(CUIRect MainView) CUIRect Label, LabelMid, Section, LabelRight; static int s_SelectedProfile = -1; - const float FontSize = 14.0f; - char *pSkinName = g_Config.m_ClPlayerSkin; int *pUseCustomColor = &g_Config.m_ClPlayerUseCustomColor; unsigned *pColorBody = &g_Config.m_ClPlayerColorBody; diff --git a/src/game/client/components/tclient/warlist.cpp b/src/game/client/components/tclient/warlist.cpp index 6d813f4be20..1d535b0484d 100644 --- a/src/game/client/components/tclient/warlist.cpp +++ b/src/game/client/components/tclient/warlist.cpp @@ -261,7 +261,10 @@ CWarDataCache CWarList::GetWarData(int ClientId) return m_WarPlayers[ClientId]; } -void CWarList::SortWarEntries() {} +void CWarList::SortWarEntries() +{ + // TODO +} void CWarList::UpdateWarPlayers() { @@ -283,7 +286,9 @@ void CWarList::UpdateWarPlayers() m_WarPlayers[i].IsWarName = true; m_WarPlayers[i].m_NameColor = Entry.m_pWarType->m_Color; } - else if(str_comp(GameClient()->m_aClients[i].m_aClan, Entry.m_aClan) == 0) + + else if(str_comp(GameClient()->m_aClients[i].m_aClan, Entry.m_aClan) == 0) + { // Name war reason has priority over clan war reason if(!m_WarPlayers[i].IsWarName) diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 3dff1688401..5541230d6e6 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -148,6 +148,7 @@ void CGameClient::OnConsoleInit() &m_Hud, &m_Spectator, &m_Emoticon, + &m_Bindchat, &m_Bindwheel, &m_WarList, &m_InfoMessages, diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index 60a5128c586..2d7e5c4230e 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -58,6 +58,7 @@ #include "components/spectator.h" #include "components/statboard.h" #include "components/tooltips.h" +#include "components/tclient/bindchat.h" #include "components/tclient/bindwheel.h" #include "components/tclient/outlines.h" #include "components/tclient/player_indicator.h" @@ -154,6 +155,7 @@ class CGameClient : public IGameClient CStatboard m_Statboard; CSounds m_Sounds; CEmoticon m_Emoticon; + CDamageInd m_DamageInd; CTouchControls m_TouchControls; CVoting m_Voting; @@ -180,7 +182,8 @@ class CGameClient : public IGameClient // TClient Components CSkinProfiles m_SkinProfiles; - CBindWheel m_Bindwheel; + CBindchat m_Bindchat; + CBindwheel m_Bindwheel; CTater m_Tater; CPlayerIndicator m_PlayerIndicator; COutlines m_Outlines;