From bc69821d17830c1c936638dc6791242b7f0762ba Mon Sep 17 00:00:00 2001 From: Mohab <133429578+MohabCodeX@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:46:30 +0300 Subject: [PATCH 1/4] command cancellation support with new event --- Client/mods/deathmatch/ClientCommands.cpp | 10 ++++-- Client/mods/deathmatch/logic/CClientGame.cpp | 1 + .../deathmatch/logic/CRegisteredCommands.cpp | 32 ++++++++++++++++--- .../deathmatch/logic/CRegisteredCommands.h | 8 ++++- .../logic/lua/CLuaFunctionDefs.Commands.cpp | 4 ++- .../deathmatch/logic/lua/CLuaFunctionDefs.h | 1 + 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Client/mods/deathmatch/ClientCommands.cpp b/Client/mods/deathmatch/ClientCommands.cpp index 84529b4c9d5..b724abdfe69 100644 --- a/Client/mods/deathmatch/ClientCommands.cpp +++ b/Client/mods/deathmatch/ClientCommands.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include "logic/CRegisteredCommands.h" #include #include #include @@ -57,7 +58,12 @@ bool COMMAND_Executed(const char* szCommand, const char* szArguments, bool bHand strClumpedCommandUTF = strClumpedCommandUTF.substr(0, MAX_COMMAND_LENGTH); strClumpedCommand = UTF16ToMbUTF8(strClumpedCommandUTF); - g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommandBufferPointer, szArguments); + CommandExecutionResult commandResult = g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommandBufferPointer, szArguments, false); + + if (commandResult.wasCancelled) + { + return true; + } // Call the onClientConsole event CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); @@ -108,7 +114,7 @@ bool COMMAND_Executed(const char* szCommand, const char* szArguments, bool bHand // Call our comand-handlers for core-executed commands too, if allowed if (bAllowScriptedBind) - g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommand, szArguments); + g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommand, szArguments, false); } return false; } diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 899a68cf0d8..14c842abb82 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -2730,6 +2730,7 @@ void CClientGame::AddBuiltInEvents() // Console events m_Events.AddEvent("onClientConsole", "text", NULL, false); m_Events.AddEvent("onClientCoreCommand", "command", NULL, false); + m_Events.AddEvent("onClientCommand", "command, ..., executedByFunction", NULL, false); // Chat events m_Events.AddEvent("onClientChatMessage", "text, r, g, b, messageType", NULL, false); diff --git a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp index 370176b8797..a5dd62d73c0 100644 --- a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp +++ b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp @@ -150,13 +150,36 @@ bool CRegisteredCommands::CommandExists(const char* szKey, CLuaMain* pLuaMain) return GetCommand(szKey, pLuaMain) != nullptr; } -bool CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArguments) +CommandExecutionResult CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArguments, bool executedByFunction) { assert(szKey); + CommandExecutionResult result; + + CLuaArguments arguments; + arguments.PushString(szKey); + + if (szArguments && *szArguments) + { + const std::string_view argsView{szArguments}; + std::istringstream stream{std::string{argsView}}; + + for (std::string arg; stream >> arg;) + { + arguments.PushString(arg.c_str()); + } + } + + arguments.PushBoolean(executedByFunction); + + g_pClientGame->GetRootEntity()->CallEvent("onClientCommand", arguments, true); + result.wasCancelled = g_pClientGame->GetEvents()->WasEventCancelled(); + + if (result.wasCancelled) + return result; + // Call the handler for every virtual machine that matches the given key int iCompareResult; - bool bHandled = false; m_bIteratingList = true; list::const_iterator iter = m_Commands.begin(); for (; iter != m_Commands.end(); iter++) @@ -171,14 +194,13 @@ bool CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArgume { // Call it CallCommandHandler((*iter)->pLuaMain, (*iter)->iLuaFunction, (*iter)->strKey, szArguments); - bHandled = true; + result.wasExecuted = true; } } m_bIteratingList = false; TakeOutTheTrash(); - // Return whether some handler was called or not - return bHandled; + return result; } CRegisteredCommands::SCommand* CRegisteredCommands::GetCommand(const char* szKey, class CLuaMain* pLuaMain) diff --git a/Client/mods/deathmatch/logic/CRegisteredCommands.h b/Client/mods/deathmatch/logic/CRegisteredCommands.h index 1fe63e25cfb..481a600f3dd 100644 --- a/Client/mods/deathmatch/logic/CRegisteredCommands.h +++ b/Client/mods/deathmatch/logic/CRegisteredCommands.h @@ -24,6 +24,12 @@ enum class MultiCommandHandlerPolicy : std::uint8_t ALLOW = 2 }; +struct CommandExecutionResult +{ + bool wasCancelled = false; + bool wasExecuted = false; +}; + class CRegisteredCommands { struct SCommand @@ -48,7 +54,7 @@ class CRegisteredCommands void GetCommands(lua_State* luaVM); void GetCommands(lua_State* luaVM, CLuaMain* pTargetLuaMain); - bool ProcessCommand(const char* szKey, const char* szArguments); + CommandExecutionResult ProcessCommand(const char* szKey, const char* szArguments, bool executedByFunction = false); private: SCommand* GetCommand(const char* szKey, class CLuaMain* pLuaMain = NULL); diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.Commands.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.Commands.cpp index e72ca469461..0077a532de4 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.Commands.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.Commands.cpp @@ -9,6 +9,7 @@ *****************************************************************************/ #include "StdInc.h" +#include "CRegisteredCommands.h" int CLuaFunctionDefs::AddCommandHandler(lua_State* luaVM) { @@ -90,7 +91,8 @@ int CLuaFunctionDefs::ExecuteCommandHandler(lua_State* luaVM) if (pLuaMain) { // Call it - if (m_pRegisteredCommands->ProcessCommand(strKey, strArgs)) + CommandExecutionResult result = m_pRegisteredCommands->ProcessCommand(strKey, strArgs, true); + if (result.wasExecuted && !result.wasCancelled) { lua_pushboolean(luaVM, true); return 1; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h index a6592f9df04..8ce3ce8122b 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h @@ -17,6 +17,7 @@ class CLuaFunctionDefinitions; #include "CLuaTimerManager.h" class CRegisteredCommands; +struct CommandExecutionResult; #define LUA_DECLARE(x) static int x ( lua_State * luaVM ); From 1526f24df69c002a9f1a62ce2fc53f8ffa303da9 Mon Sep 17 00:00:00 2001 From: Mohab <133429578+MohabCodeX@users.noreply.github.com> Date: Mon, 8 Sep 2025 11:56:31 +0300 Subject: [PATCH 2/4] Refactor --- Client/mods/deathmatch/logic/CClientGame.cpp | 2 +- Client/mods/deathmatch/logic/CRegisteredCommands.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index 14c842abb82..7066d67e969 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -2730,7 +2730,7 @@ void CClientGame::AddBuiltInEvents() // Console events m_Events.AddEvent("onClientConsole", "text", NULL, false); m_Events.AddEvent("onClientCoreCommand", "command", NULL, false); - m_Events.AddEvent("onClientCommand", "command, ..., executedByFunction", NULL, false); + m_Events.AddEvent("onClientCommand", "command, executedByFunction, ...", NULL, false); // Chat events m_Events.AddEvent("onClientChatMessage", "text, r, g, b, messageType", NULL, false); diff --git a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp index a5dd62d73c0..46f2e952163 100644 --- a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp +++ b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp @@ -158,11 +158,11 @@ CommandExecutionResult CRegisteredCommands::ProcessCommand(const char* szKey, co CLuaArguments arguments; arguments.PushString(szKey); + arguments.PushBoolean(executedByFunction); if (szArguments && *szArguments) { - const std::string_view argsView{szArguments}; - std::istringstream stream{std::string{argsView}}; + std::istringstream stream{szArguments}; for (std::string arg; stream >> arg;) { @@ -170,9 +170,11 @@ CommandExecutionResult CRegisteredCommands::ProcessCommand(const char* szKey, co } } - arguments.PushBoolean(executedByFunction); - - g_pClientGame->GetRootEntity()->CallEvent("onClientCommand", arguments, true); + CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); + if (localPlayer) + { + localPlayer->CallEvent("onClientCommand", arguments, false); + } result.wasCancelled = g_pClientGame->GetEvents()->WasEventCancelled(); if (result.wasCancelled) From 026c3d15d44fb208be7c06c274fb1797d6d9e178 Mon Sep 17 00:00:00 2001 From: Mohab <133429578+MohabCodeX@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:31:59 +0300 Subject: [PATCH 3/4] Enhance command handling --- Client/mods/deathmatch/ClientCommands.cpp | 4 ++-- .../deathmatch/logic/lua/CLuaFunctionDefs.h | 1 - Server/mods/deathmatch/logic/CConsole.cpp | 21 +++++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Client/mods/deathmatch/ClientCommands.cpp b/Client/mods/deathmatch/ClientCommands.cpp index b724abdfe69..68857106a72 100644 --- a/Client/mods/deathmatch/ClientCommands.cpp +++ b/Client/mods/deathmatch/ClientCommands.cpp @@ -60,11 +60,11 @@ bool COMMAND_Executed(const char* szCommand, const char* szArguments, bool bHand CommandExecutionResult commandResult = g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommandBufferPointer, szArguments, false); + // If command was cancelled by onClientCommand event, block it completely if (commandResult.wasCancelled) { - return true; + return true; // Command was handled (cancelled), don't show unknown message } - // Call the onClientConsole event CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h index 8ce3ce8122b..a6592f9df04 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h @@ -17,7 +17,6 @@ class CLuaFunctionDefinitions; #include "CLuaTimerManager.h" class CRegisteredCommands; -struct CommandExecutionResult; #define LUA_DECLARE(x) static int x ( lua_State * luaVM ); diff --git a/Server/mods/deathmatch/logic/CConsole.cpp b/Server/mods/deathmatch/logic/CConsole.cpp index 0106fb1f5f1..577abf79a98 100644 --- a/Server/mods/deathmatch/logic/CConsole.cpp +++ b/Server/mods/deathmatch/logic/CConsole.cpp @@ -85,6 +85,7 @@ bool CConsole::HandleInput(const char* szCommand, CClient* pClient, CClient* pEc // Let the script handle it int iClientType = pClient->GetClientType(); + bool wasHandled = false; switch (iClientType) { @@ -92,7 +93,7 @@ bool CConsole::HandleInput(const char* szCommand, CClient* pClient, CClient* pEc { // See if any registered command can process it CPlayer* pPlayer = static_cast(pClient); - m_pRegisteredCommands->ProcessCommand(szKey, szArguments, pClient); + wasHandled = m_pRegisteredCommands->ProcessCommand(szKey, szArguments, pClient); // HACK: if the client gets destroyed before here, dont continue if (m_pPlayerManager->Exists(pPlayer)) @@ -102,23 +103,39 @@ bool CConsole::HandleInput(const char* szCommand, CClient* pClient, CClient* pEc Arguments.PushString(szCommand); pPlayer->CallEvent("onConsole", Arguments); } + + // If command wasn't handled, send "unknown command" message to client + if (!wasHandled && m_pPlayerManager->Exists(pPlayer)) + { + SString strError("Unknown command or cvar: %s", szKey); + pPlayer->SendEcho(strError); + return false; + } break; } case CClient::CLIENT_CONSOLE: { // See if any registered command can process it CConsoleClient* pConsole = static_cast(pClient); - m_pRegisteredCommands->ProcessCommand(szKey, szArguments, pClient); + wasHandled = m_pRegisteredCommands->ProcessCommand(szKey, szArguments, pClient); // Call the console event CLuaArguments Arguments; Arguments.PushString(szCommand); pConsole->CallEvent("onConsole", Arguments); + + // If command wasn't handled, it's unknown + if (!wasHandled) + { + return false; + } break; } default: break; } + + return wasHandled; } // Doesn't exist From 75310940242aab301c65a2ffcc15f8cdf1f03f01 Mon Sep 17 00:00:00 2001 From: Mohab <133429578+MohabCodeX@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:56:45 +0300 Subject: [PATCH 4/4] Refactor command execution flow --- Client/mods/deathmatch/ClientCommands.cpp | 48 ++++++++++++++++--- .../deathmatch/logic/CRegisteredCommands.cpp | 24 ---------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/Client/mods/deathmatch/ClientCommands.cpp b/Client/mods/deathmatch/ClientCommands.cpp index 68857106a72..5b6af538c2b 100644 --- a/Client/mods/deathmatch/ClientCommands.cpp +++ b/Client/mods/deathmatch/ClientCommands.cpp @@ -58,15 +58,42 @@ bool COMMAND_Executed(const char* szCommand, const char* szArguments, bool bHand strClumpedCommandUTF = strClumpedCommandUTF.substr(0, MAX_COMMAND_LENGTH); strClumpedCommand = UTF16ToMbUTF8(strClumpedCommandUTF); + CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); + + // First try to process with registered Lua commands CommandExecutionResult commandResult = g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommandBufferPointer, szArguments, false); - // If command was cancelled by onClientCommand event, block it completely - if (commandResult.wasCancelled) + // If command was handled by Lua, don't send to server + if (commandResult.wasExecuted) { - return true; // Command was handled (cancelled), don't show unknown message + return true; // Command was handled locally, don't send to server } - // Call the onClientConsole event - CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); + + // If no Lua handler was found, trigger onClientCommand event to allow interception + CLuaArguments arguments; + arguments.PushString(szCommandBufferPointer); + arguments.PushBoolean(false); // executedByFunction + + if (szArguments && *szArguments) + { + std::istringstream stream{szArguments}; + for (std::string arg; stream >> arg;) + { + arguments.PushString(arg.c_str()); + } + } + + if (localPlayer) + { + localPlayer->CallEvent("onClientCommand", arguments, false); + + // If command was handled by onClientCommand event, don't send to server + if (g_pClientGame->GetEvents()->WasEventCancelled()) + { + return true; // Command was intercepted and handled + } + } + if (localPlayer != nullptr) { @@ -114,7 +141,16 @@ bool COMMAND_Executed(const char* szCommand, const char* szArguments, bool bHand // Call our comand-handlers for core-executed commands too, if allowed if (bAllowScriptedBind) - g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommand, szArguments, false); + { + CommandExecutionResult coreCommandResult = g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommand, szArguments, false); + + // If core command failed, don't show unknown message (these are usually keybinds) + if (!coreCommandResult.wasExecuted) + { + // Silently ignore failed keybind commands to prevent spam + return true; + } + } } return false; } diff --git a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp index 46f2e952163..cbe0987a813 100644 --- a/Client/mods/deathmatch/logic/CRegisteredCommands.cpp +++ b/Client/mods/deathmatch/logic/CRegisteredCommands.cpp @@ -156,30 +156,6 @@ CommandExecutionResult CRegisteredCommands::ProcessCommand(const char* szKey, co CommandExecutionResult result; - CLuaArguments arguments; - arguments.PushString(szKey); - arguments.PushBoolean(executedByFunction); - - if (szArguments && *szArguments) - { - std::istringstream stream{szArguments}; - - for (std::string arg; stream >> arg;) - { - arguments.PushString(arg.c_str()); - } - } - - CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer(); - if (localPlayer) - { - localPlayer->CallEvent("onClientCommand", arguments, false); - } - result.wasCancelled = g_pClientGame->GetEvents()->WasEventCancelled(); - - if (result.wasCancelled) - return result; - // Call the handler for every virtual machine that matches the given key int iCompareResult; m_bIteratingList = true;