From 035e7a1068c4cd82174ffdc6e8e0231f6bd131ff Mon Sep 17 00:00:00 2001 From: furo Date: Thu, 14 Dec 2023 00:04:23 +0100 Subject: [PATCH] Add `Sv_CommandInfo` netmsg for autocompletion of chat commands. --- datasrc/network.py | 10 +++++ src/game/client/components/chat.cpp | 57 ++++++++++++++++++++++++----- src/game/client/components/chat.h | 25 +++++++++---- src/game/server/gamecontext.cpp | 22 ++++++++--- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/datasrc/network.py b/datasrc/network.py index d434524f8..7ad2c0244 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -562,4 +562,14 @@ Messages = [ NetBool("m_RecordPersonal"), NetBool("m_RecordServer", default=False), ]), + + NetMessageEx("Sv_CommandInfo", "commandinfo@netmsg.ddnet.org", [ + NetStringStrict("m_pName"), + NetStringStrict("m_pArgsFormat"), + NetStringStrict("m_pHelpText") + ]), + + NetMessageEx("Sv_CommandInfoRemove", "commandinfo-remove@netmsg.ddnet.org", [ + NetStringStrict("m_pName") + ]), ] diff --git a/src/game/client/components/chat.cpp b/src/game/client/components/chat.cpp index 73913aa8b..22fabed7c 100644 --- a/src/game/client/components/chat.cpp +++ b/src/game/client/components/chat.cpp @@ -30,11 +30,11 @@ CChat::CChat() Line.m_QuadContainerIndex = -1; } -#define CHAT_COMMAND(name, params, flags, callback, userdata, help) RegisterCommand(name, params, flags, help); +#define CHAT_COMMAND(name, params, flags, callback, userdata, help) m_vDefaultCommands.emplace_back(name, params, help); #include #undef CHAT_COMMAND - std::sort(m_vCommands.begin(), m_vCommands.end()); + std::sort(m_vDefaultCommands.begin(), m_vDefaultCommands.end()); m_Mode = MODE_NONE; m_Input.SetClipboardLineCallback([this](const char *pStr) { SayChat(pStr); }); @@ -71,9 +71,20 @@ CChat::CChat() }); } -void CChat::RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp) +void CChat::RegisterCommand(const char *pName, const char *pParams, const char *pHelpText) { - m_vCommands.emplace_back(pName, pParams); + // Don't allow duplicate commands. + for(const auto &Command : m_vCommands) + if(str_comp(Command.m_aName, pName) == 0) + return; + + m_vCommands.emplace_back(pName, pParams, pHelpText); + m_CommandsNeedSorting = true; +} + +void CChat::UnregisterCommand(const char *pName) +{ + m_vCommands.erase(std::remove_if(m_vCommands.begin(), m_vCommands.end(), [pName](const CCommand &Command) { return str_comp(Command.m_aName, pName) == 0; }), m_vCommands.end()); } void CChat::RebuildChat() @@ -121,6 +132,8 @@ void CChat::Reset() m_CurrentLine = 0; m_IsInputCensored = false; m_EditingNewLine = true; + m_ServerSupportsCommandInfo = false; + m_CommandsNeedSorting = false; mem_zero(m_aCurrentInputText, sizeof(m_aCurrentInputText)); DisableMode(); @@ -217,6 +230,11 @@ void CChat::OnInit() Console()->Chain("cl_chat_width", ConchainChatWidth, this); } +void CChat::OnMapLoad() +{ + m_vCommands = m_vDefaultCommands; +} + bool CChat::OnInput(const IInput::CEvent &Event) { if(m_Mode == MODE_NONE) @@ -234,6 +252,12 @@ bool CChat::OnInput(const IInput::CEvent &Event) } else if(Event.m_Flags & IInput::FLAG_PRESS && (Event.m_Key == KEY_RETURN || Event.m_Key == KEY_KP_ENTER)) { + if(m_CommandsNeedSorting) + { + std::sort(m_vCommands.begin(), m_vCommands.end()); + m_CommandsNeedSorting = false; + } + if(m_Input.GetString()[0]) { bool AddEntry = false; @@ -305,7 +329,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) }); } - if(m_aCompletionBuffer[0] == '/') + if(m_aCompletionBuffer[0] == '/' && !m_vCommands.empty()) { CCommand *pCompletionCommand = 0; @@ -338,7 +362,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) auto &Command = m_vCommands[Index]; - if(str_startswith(Command.m_pName, pCommandStart)) + if(str_startswith(Command.m_aName, pCommandStart)) { pCompletionCommand = &Command; m_CompletionChosen = Index + SearchType * NumCommands; @@ -355,10 +379,10 @@ bool CChat::OnInput(const IInput::CEvent &Event) // add the command str_append(aBuf, "/"); - str_append(aBuf, pCompletionCommand->m_pName); + str_append(aBuf, pCompletionCommand->m_aName); // add separator - const char *pSeparator = pCompletionCommand->m_pParams[0] == '\0' ? "" : " "; + const char *pSeparator = pCompletionCommand->m_aParams[0] == '\0' ? "" : " "; str_append(aBuf, pSeparator); if(*pSeparator) str_append(aBuf, pSeparator); @@ -366,7 +390,7 @@ bool CChat::OnInput(const IInput::CEvent &Event) // add part after the name str_append(aBuf, m_Input.GetString() + m_PlaceholderOffset + m_PlaceholderLength); - m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_pName) + 1; + m_PlaceholderLength = str_length(pSeparator) + str_length(pCompletionCommand->m_aName) + 1; m_Input.Set(aBuf); m_Input.SetCursorOffset(m_PlaceholderOffset + m_PlaceholderLength); } @@ -538,6 +562,21 @@ void CChat::OnMessage(int MsgType, void *pRawMsg) CNetMsg_Sv_Chat *pMsg = (CNetMsg_Sv_Chat *)pRawMsg; AddLine(pMsg->m_ClientID, pMsg->m_Team, pMsg->m_pMessage); } + else if(MsgType == NETMSGTYPE_SV_COMMANDINFO) + { + CNetMsg_Sv_CommandInfo *pMsg = (CNetMsg_Sv_CommandInfo *)pRawMsg; + if(!m_ServerSupportsCommandInfo) + { + m_vCommands.clear(); + m_ServerSupportsCommandInfo = true; + } + RegisterCommand(pMsg->m_pName, pMsg->m_pArgsFormat, pMsg->m_pHelpText); + } + else if(MsgType == NETMSGTYPE_SV_COMMANDINFOREMOVE) + { + CNetMsg_Sv_CommandInfoRemove *pMsg = (CNetMsg_Sv_CommandInfoRemove *)pRawMsg; + UnregisterCommand(pMsg->m_pName); + } } bool CChat::LineShouldHighlight(const char *pLine, const char *pName) diff --git a/src/game/client/components/chat.h b/src/game/client/components/chat.h index 62ee92439..cf23efc93 100644 --- a/src/game/client/components/chat.h +++ b/src/game/client/components/chat.h @@ -96,21 +96,26 @@ class CChat : public CComponent struct CCommand { - const char *m_pName; - const char *m_pParams; + char m_aName[IConsole::TEMPCMD_NAME_LENGTH]; + char m_aParams[IConsole::TEMPCMD_PARAMS_LENGTH]; + char m_aHelpText[IConsole::TEMPCMD_HELP_LENGTH]; CCommand() = default; - CCommand(const char *pName, const char *pParams) : - m_pName(pName), m_pParams(pParams) + CCommand(const char *pName, const char *pParams, const char *pHelpText) { + str_copy(m_aName, pName); + str_copy(m_aParams, pParams); + str_copy(m_aHelpText, pHelpText); } - bool operator<(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) < 0; } - bool operator<=(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) <= 0; } - bool operator==(const CCommand &Other) const { return str_comp(m_pName, Other.m_pName) == 0; } + bool operator<(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) < 0; } + bool operator<=(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) <= 0; } + bool operator==(const CCommand &Other) const { return str_comp(m_aName, Other.m_aName) == 0; } }; std::vector m_vCommands; + std::vector m_vDefaultCommands; + bool m_CommandsNeedSorting; struct CHistoryEntry { @@ -126,6 +131,8 @@ class CChat : public CComponent char m_aCurrentInputText[MAX_LINE_LENGTH]; bool m_EditingNewLine; + bool m_ServerSupportsCommandInfo; + static void ConSay(IConsole::IResult *pResult, void *pUserData); static void ConSayTeam(IConsole::IResult *pResult, void *pUserData); static void ConChat(IConsole::IResult *pResult, void *pUserData); @@ -151,7 +158,8 @@ public: void DisableMode(); void Say(int Team, const char *pLine); void SayChat(const char *pLine); - void RegisterCommand(const char *pName, const char *pParams, int flags, const char *pHelp); + void RegisterCommand(const char *pName, const char *pParams, const char *pHelpText); + void UnregisterCommand(const char *pName); void Echo(const char *pString); void OnWindowResize() override; @@ -165,6 +173,7 @@ public: void OnMessage(int MsgType, void *pRawMsg) override; bool OnInput(const IInput::CEvent &Event) override; void OnInit() override; + void OnMapLoad() override; void RebuildChat(); diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 56b7f3e44..be838a136 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -1410,15 +1410,19 @@ void CGameContext::OnClientEnter(int ClientID) Msg.m_pName = "team"; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); } + } - for(const IConsole::CCommandInfo *pCmd = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT); - pCmd; pCmd = pCmd->NextCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT)) + for(const IConsole::CCommandInfo *pCmd = Console()->FirstCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT); + pCmd; pCmd = pCmd->NextCommandInfo(IConsole::ACCESS_LEVEL_USER, CFGFLAG_CHAT)) + { + const char *pName = pCmd->m_pName; + + if(Server()->IsSixup(ClientID)) { - if(!str_comp_nocase(pCmd->m_pName, "w") || !str_comp_nocase(pCmd->m_pName, "whisper")) + if(!str_comp_nocase(pName, "w") || !str_comp_nocase(pName, "whisper")) continue; - const char *pName = pCmd->m_pName; - if(!str_comp_nocase(pCmd->m_pName, "r")) + if(!str_comp_nocase(pName, "r")) pName = "rescue"; protocol7::CNetMsg_Sv_CommandInfo Msg; @@ -1427,6 +1431,14 @@ void CGameContext::OnClientEnter(int ClientID) Msg.m_pHelpText = pCmd->m_pHelp; Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); } + else + { + CNetMsg_Sv_CommandInfo Msg; + Msg.m_pName = pName; + Msg.m_pArgsFormat = pCmd->m_pParams; + Msg.m_pHelpText = pCmd->m_pHelp; + Server()->SendPackMsg(&Msg, MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientID); + } } {