Fixes #3160 - New onClientCommand event - #4426
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new client-side onClientCommand event intended to let scripts observe and potentially cancel command execution, and adjusts command handling on both client and server to better report/propagate “unknown command” cases.
Changes:
- Adds the
onClientCommandbuilt-in client event and invokes it during client command execution flow. - Refactors client registered-command execution to return a
CommandExecutionResultinstead of a boolean. - Updates server console command handling to track whether a script handler processed a command and to report unknown commands to players.
Please ensure the final commit message clearly captures the motivation/goal of the change (incl. intended semantics for client vs server commands), and how you tested it (manual steps and/or automated coverage).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| Server/mods/deathmatch/logic/CConsole.cpp | Tracks whether script commands were handled; adds unknown-command feedback and returns handled state. |
| Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.Commands.cpp | Updates executeCommandHandler to use the new CommandExecutionResult return type. |
| Client/mods/deathmatch/logic/CRegisteredCommands.h | Introduces CommandExecutionResult and updates ProcessCommand signature accordingly. |
| Client/mods/deathmatch/logic/CRegisteredCommands.cpp | Implements the new CommandExecutionResult return type for client command processing. |
| Client/mods/deathmatch/logic/CClientGame.cpp | Registers the new onClientCommand built-in event. |
| Client/mods/deathmatch/ClientCommands.cpp | Invokes onClientCommand and changes client command routing behavior based on local Lua handler presence. |
Suppressed comments (1)
Client/mods/deathmatch/ClientCommands.cpp:77
onClientCommandcurrently receivesexecutedByFunctionas the 2nd parameter (pushed before the args). The PR description’s usage example expectsexecutedByFunctionto be the last vararg (after all args), so scripts will misinterpret the values as soon as any arguments are present.
CLuaArguments arguments;
arguments.PushString(szCommandBufferPointer);
arguments.PushBoolean(false); // executedByFunction
if (szArguments && *szArguments)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // First try to process with registered Lua commands | ||
| CommandExecutionResult commandResult = g_pClientGame->GetRegisteredCommands()->ProcessCommand(szCommandBufferPointer, szArguments, false); | ||
|
|
||
| // 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 | ||
| } |
| CommandExecutionResult result = m_pRegisteredCommands->ProcessCommand(strKey, strArgs, true); | ||
| if (result.wasExecuted && !result.wasCancelled) |
| CommandExecutionResult CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArguments, bool executedByFunction) | ||
| { | ||
| assert(szKey); | ||
|
|
||
| CommandExecutionResult result; |
| // Console events | ||
| m_Events.AddEvent("onClientConsole", "text", NULL, false); | ||
| m_Events.AddEvent("onClientCoreCommand", "command", NULL, false); | ||
| m_Events.AddEvent("onClientCommand", "command, executedByFunction, ...", NULL, false); |
| // If command wasn't handled, it's unknown | ||
| if (!wasHandled) | ||
| { | ||
| return false; | ||
| } |
Add the onClientCommand event to allow client-side resource scripts to intercept, handle, or cancel client commands before execution via cancelEvent(). Goals & Motivation: - Provide parity with server-side command cancellation capabilities. - Allow resources to intercept client commands early and prevent execution or server transmission when cancelled. Key Changes: - Dispatch onClientCommand event on localPlayer prior to Lua command execution in COMMAND_Executed. - If WasEventCancelled() is true, immediately abort command execution and server dispatch. - Refactor ProcessCommand to return a CommandExecutionResult struct tracking execution and cancellation status. - Revert premature 'Unknown command' server echo in CConsole.cpp to maintain onConsole handler compatibility. - Formatted changed C++ files using utils/clang-format.ps1.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
Client/mods/deathmatch/ClientCommands.cpp:86
- The early
return truewhen a registered Lua command executes meansonClientConsoleno longer fires for commands handled client-side. The linked issue explicitly calls outonClientConsoleas showing all executed commands, so this looks like a backwards-incompatible behavioral change. If the goal is only to stop sending the command to the server, keep firingonClientConsoleand only skip the network send when handled locally.
// If command was handled by Lua, don't send to server
if (commandResult.wasExecuted)
{
return true;
Client/mods/deathmatch/logic/CClientGame.cpp:2752
- The event signature string for
onClientCommand("command, executedByFunction, ...") does not match the usage shown in the PR description (function(command, ...)withexecutedByFunctionbeing the last vararg). Update the declared argument list to reflect the actual order/shape that scripts should expect.
m_Events.AddEvent("onClientCommand", "command, executedByFunction, ...", NULL, false);
Client/mods/deathmatch/ClientCommands.cpp:77
onClientCommandis documented in the PR description asfunction(command, ...)withexecutedByFunctionas the last vararg and command arguments passed as individual varargs. The current implementation passes(command, executedByFunction, "<args string>"), so scripts using the documented pattern will misinterpret the arguments. Consider pushing each argument token as a separate Lua argument and appendingexecutedByFunctionlast; also use the boolean return ofCallEventrather than readingWasEventCancelled()out-of-band.
This issue also appears on line 82 of the same file.
CLuaArguments arguments;
arguments.PushString(szCommandBufferPointer);
arguments.PushBoolean(false); // executedByFunction
arguments.PushString(szArguments ? szArguments : "");
localPlayer->CallEvent("onClientCommand", arguments, false);
// If command was intercepted and cancelled by event, don't execute or send to server
if (g_pClientGame->GetEvents()->WasEventCancelled())
{
Client/mods/deathmatch/logic/CRegisteredCommands.cpp:159
ProcessCommandnow acceptsexecutedByFunctionand returnsCommandExecutionResult.wasCancelled, butexecutedByFunctionis unused andwasCancelledis never set. This makesexecuteCommandHandler's!result.wasCancelledcheck ineffective and prevents scripts from cancelling function-driven client commands. Consider triggeringonClientCommandhere whenexecutedByFunction == true, settingwasCancelledand returning early when the event is cancelled.
CommandExecutionResult CRegisteredCommands::ProcessCommand(const char* szKey, const char* szArguments, bool executedByFunction)
{
assert(szKey);
CommandExecutionResult result;
| // Let the script handle it | ||
| int iClientType = pClient->GetClientType(); | ||
|
|
||
| switch (iClientType) |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (4)
Client/mods/deathmatch/ClientCommands.cpp:144
- This hunk changes core-command handling by silently treating unknown core commands as handled when
bAllowScriptedBindis true. That behavior change isn’t mentioned in the PR description (which focuses on addingonClientCommand) and could hide legitimate “unknown command” feedback outside of keybind scenarios.
// Call our comand-handlers for core-executed commands too, if allowed
if (bAllowScriptedBind)
{
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;
}
}
Client/mods/deathmatch/ClientCommands.cpp:78
onClientCommandis currently triggered (and cancellation is honored) for all commands typed by the user, which also prevents server-side commands from being sent to the server when the event is cancelled. Per #3160, cancellation should only stop client-side (Lua-registered) commands, while server-side commands should continue to work as before.
This issue also appears on line 133 of the same file.
// Trigger onClientCommand event first to allow cancellation of any client command
if (localPlayer != nullptr)
{
CLuaArguments arguments;
arguments.PushString(szCommandBufferPointer);
arguments.PushBoolean(false); // executedByFunction
arguments.PushString(szArguments ? szArguments : "");
localPlayer->CallEvent("onClientCommand", arguments, false);
// If command was intercepted and cancelled by event, don't execute or send to server
if (g_pClientGame->GetEvents()->WasEventCancelled())
{
return true;
}
}
Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.Commands.cpp:96
executeCommandHandlerchecks!result.wasCancelled, butCommandExecutionResult::wasCancelledis never set inCRegisteredCommands::ProcessCommand(andexecutedByFunctionis currently unused). As-is, cancellation via the newonClientCommandevent can’t affectexecuteCommandHandler, and the API suggests behavior that isn’t implemented.
CommandExecutionResult result = m_pRegisteredCommands->ProcessCommand(strKey, strArgs, true);
if (result.wasExecuted && !result.wasCancelled)
{
Client/mods/deathmatch/logic/CClientGame.cpp:2752
onClientCommandis registered with argument signature"command, executedByFunction, ...", but the implementation currently passes a single arguments string (not varargs). This mismatch makes the event contract/documentation ambiguous.
m_Events.AddEvent("onClientCommand", "command, executedByFunction, ...", NULL, false);
Summary of Changes
Adds the
onClientCommandevent to MTA client-side logic, enabling Lua resources to intercept, inspect, and cancel client commands prior to local handler execution or server transmission. (Fixes #3160)Motivation & Semantics
onConsoleandaddCommandHandlerlogic. Previously, client-side commands executed locally or transmitted over the network without giving client resources an early interception hook.onClientCommandprovides client-side parity, allowing resource event handlers to invokecancelEvent()to block execution locally and prevent sending cancelled commands across the network to the server.Testing & Verification
Client Lua:
Testing Script