diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index be12fb87..3265756d 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -33,6 +33,7 @@ #include "sourcemod.h" #include "sourcemm_api.h" #include "UserMessages.h" +#include "PlayerManager.h" CHalfLife2 g_HL2; bool g_IsOriginalEngine = false; @@ -333,3 +334,38 @@ bool CHalfLife2::ShowVGUIMenu(int client, const char *name, KeyValues *data, boo return true; } + +void CHalfLife2::AddToFakeCliCmdQueue(int client, int userid, const char *cmd) +{ + DelayedFakeCliCmd *pFake; + + if (m_FreeCmds.empty()) + { + pFake = new DelayedFakeCliCmd; + } else { + pFake = m_FreeCmds.front(); + m_FreeCmds.pop(); + } + + pFake->client = client; + pFake->userid = userid; + pFake->cmd.assign(cmd); + + m_CmdQueue.push(pFake); +} + +void CHalfLife2::ProcessFakeCliCmdQueue() +{ + while (!m_CmdQueue.empty()) + { + DelayedFakeCliCmd *pFake = m_CmdQueue.first(); + + if (g_Players.GetClientOfUserId(pFake->userid) == pFake->client) + { + CPlayer *pPlayer = g_Players.GetPlayerByIndex(pFake->client); + serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), pFake->cmd.c_str()); + } + + m_CmdQueue.pop(); + } +} diff --git a/core/HalfLife2.h b/core/HalfLife2.h index 2a376da6..a617fcf4 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -33,9 +33,11 @@ #define _INCLUDE_SOURCEMOD_CHALFLIFE2_H_ #include +#include #include #include "sm_trie.h" #include "sm_globals.h" +#include "sm_queue.h" #include #include @@ -57,6 +59,13 @@ struct DataMapTrie Trie *trie; }; +struct DelayedFakeCliCmd +{ + String cmd; + int client; + int userid; +}; + class CHalfLife2 : public SMGlobalClass, public IGameHelpers @@ -77,6 +86,9 @@ public: //IGameHelpers bool TextMsg(int client, int dest, const char *msg); bool HintTextMsg(int client, const char *msg); bool ShowVGUIMenu(int client, const char *name, KeyValues *data, bool show); +public: + void AddToFakeCliCmdQueue(int client, int userid, const char *cmd); + void ProcessFakeCliCmdQueue(); private: DataTableInfo *_FindServerClass(const char *classname); private: @@ -86,6 +98,8 @@ private: int m_MsgTextMsg; int m_HinTextMsg; int m_VGUIMenu; + Queue m_CmdQueue; + CStack m_FreeCmds; }; extern CHalfLife2 g_HL2; diff --git a/core/smn_console.cpp b/core/smn_console.cpp index afaf0d50..4a2a4c2c 100644 --- a/core/smn_console.cpp +++ b/core/smn_console.cpp @@ -838,7 +838,7 @@ static cell_t FakeClientCommandEx(IPluginContext *pContext, const cell_t *params return 0; } - serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer); + g_HL2.AddToFakeCliCmdQueue(params[1], engine->GetPlayerUserId(pPlayer->GetEdict()), buffer); return 1; } @@ -1020,5 +1020,6 @@ REGISTER_NATIVES(consoleNatives) {"GetCommandIterator", GetCommandIterator}, {"ReadCommandIterator", ReadCommandIterator}, {"CheckCommandAccess", CheckCommandAccess}, + {"FakeClientCommandEx", FakeClientCommandEx}, {NULL, NULL} }; diff --git a/core/sourcemod.cpp b/core/sourcemod.cpp index 7ce025c1..2c65dd39 100644 --- a/core/sourcemod.cpp +++ b/core/sourcemod.cpp @@ -49,6 +49,7 @@ #include "MenuStyle_Valve.h" #include "MenuStyle_Radio.h" #include "Database.h" +#include "HalfLife2.h" SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool); SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false); @@ -396,6 +397,7 @@ void SimulateTick() void SourceModBase::GameFrame(bool simulating) { g_DBMan.RunFrame(); + g_HL2.ProcessFakeCliCmdQueue(); /** * Note: This is all hardcoded rather than delegated to save diff --git a/plugins/include/console.inc b/plugins/include/console.inc index fb3ced77..d2f65b8c 100644 --- a/plugins/include/console.inc +++ b/plugins/include/console.inc @@ -150,6 +150,12 @@ native ClientCommand(client, const String:fmt[], any:...); /** * Executes a client command on the server without being networked. * + * FakeClientCommand() overwrites the command tokenization buffer. This can + * cause undesired effects because future calls to GetCmdArg* will return + * data from the FakeClientCommand(), not the parent command. If you are in + * a hook where this matters (for example, a "say" hook), you should use + * FakeClientCommandEx() instead. + * * @param client Index of the client. * @param fmt Format of the client command. * @param ... Format parameters @@ -158,6 +164,19 @@ native ClientCommand(client, const String:fmt[], any:...); */ native FakeClientCommand(client, const String:fmt[], any:...); +/** + * Executes a client command on the server without being networked. The + * execution of the client command is delayed by one frame to prevent any + * re-entrancy issues that might surface with FakeClientCommand(). + * + * @param client Index of the client. + * @param fmt Format of the client command. + * @param ... Format parameters + * @noreturn + * @error Invalid client index, or client not connected. + */ +native FakeClientCommandEx(client, const String:fmt[], any:...); + /** * Sends a message to the server console. *