Skip to content
50 changes: 46 additions & 4 deletions Client/mods/deathmatch/ClientCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*****************************************************************************/

#include "StdInc.h"
#include "logic/CRegisteredCommands.h"
#include <game/CWeapon.h>
#include <game/CTaskManager.h>
#include <game/Task.h>
Expand Down Expand Up @@ -57,10 +58,42 @@ 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);

// Call the onClientConsole event
CClientPlayer* localPlayer = g_pClientGame->GetLocalPlayer();

// First try to process with registered Lua commands
CommandExecutionResult commandResult = g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommandBufferPointer, szArguments, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now, when you type /foo, do you see the message "Unknown command or cvar"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I fixed this .. they get silently sent to the server. My implementation preserves this original behavior:

  1. If command is cancelled by onClientCommand → return true (don't send to server, don't show unknown command)
  2. If command is not cancelled → continue normal flow (send to server, return true)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops .. wait i need to check your suggestion again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done @FileEX

2025-09-08.12-29-13.mp4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also if handled by script & onClientCommand, it will not return unknown
image

Copy link
Contributor Author

@MohabCodeX MohabCodeX Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

found new bug while using editor 😅, it also made heavy load than original behavior & network trouble when starting/restarting editor resource
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed ✅


// If command was handled by Lua, don't send to server
if (commandResult.wasExecuted)
{
return true; // Command was handled locally, don't send to server
}

// 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)
{
Expand Down Expand Up @@ -108,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);
{
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;
}
Expand Down
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions Client/mods/deathmatch/logic/CRegisteredCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,14 @@ 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;

// Call the handler for every virtual machine that matches the given key
int iCompareResult;
bool bHandled = false;
m_bIteratingList = true;
list<SCommand*>::const_iterator iter = m_Commands.begin();
for (; iter != m_Commands.end(); iter++)
Expand All @@ -171,14 +172,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)
Expand Down
8 changes: 7 additions & 1 deletion Client/mods/deathmatch/logic/CRegisteredCommands.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ enum class MultiCommandHandlerPolicy : std::uint8_t
ALLOW = 2
};

struct CommandExecutionResult
{
bool wasCancelled = false;
bool wasExecuted = false;
};

class CRegisteredCommands
{
struct SCommand
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*****************************************************************************/

#include "StdInc.h"
#include "CRegisteredCommands.h"

int CLuaFunctionDefs::AddCommandHandler(lua_State* luaVM)
{
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 19 additions & 2 deletions Server/mods/deathmatch/logic/CConsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,15 @@ bool CConsole::HandleInput(const char* szCommand, CClient* pClient, CClient* pEc

// Let the script handle it
int iClientType = pClient->GetClientType();
bool wasHandled = false;

switch (iClientType)
{
case CClient::CLIENT_PLAYER:
{
// See if any registered command can process it
CPlayer* pPlayer = static_cast<CPlayer*>(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))
Expand All @@ -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<CConsoleClient*>(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
Expand Down