diff --git a/core/CConCmdManager.cpp b/core/CConCmdManager.cpp new file mode 100644 index 00000000..4c6aeea1 --- /dev/null +++ b/core/CConCmdManager.cpp @@ -0,0 +1,269 @@ +#include "CConCmdManager.h" +#include "sm_srvcmds.h" + +CConCmdManager g_ConCmds; + +SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false); +SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int); + +struct PlCmdInfo +{ + ConCmdInfo *pInfo; + CmdType type; +}; + +typedef List CmdList; + +char *sm_strdup(const char *str) +{ + char *ptr = new char[strlen(str)+1]; + strcpy(ptr, str); + return ptr; +} + +CConCmdManager::CConCmdManager() +{ + m_pCmds = sm_trie_create(); +} + +CConCmdManager::~CConCmdManager() +{ + sm_trie_destroy(m_pCmds); +} + +void CConCmdManager::OnSourceModAllInitialized() +{ + g_PluginSys.AddPluginsListener(this); + g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this); + SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false); +} + +void CConCmdManager::OnSourceModShutdown() +{ + SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false); + g_RootMenu.RemoveRootConsoleCommand("cmds", this); + g_PluginSys.RemovePluginsListener(this); +} + +void CConCmdManager::OnPluginLoaded(IPlugin *plugin) +{ + /* Nothing yet... */ +} + +void CConCmdManager::OnPluginDestroyed(IPlugin *plugin) +{ + CmdList *pList; + if (plugin->GetProperty("CommandList", (void **)&pList, true)) + { + CmdList::iterator iter; + for (iter=pList->begin(); + iter!=pList->end(); + iter++) + { + PlCmdInfo &cmd = (*iter); + if (cmd.type == Cmd_Server + || cmd.type == Cmd_Console) + { + ConCmdInfo *pInfo = cmd.pInfo; + /* See if there are still hooks */ + if (pInfo->srvhooks + && pInfo->srvhooks->GetFunctionCount()) + { + continue; + } + /* Remove the command */ + RemoveConCmd(pInfo); + } + } + delete pList; + } +} + +void CommandCallback() +{ + g_ConCmds.InternalDispatch(); +} + +void CConCmdManager::SetCommandClient(int client) +{ + m_CmdClient = client + 1; +} + +void CConCmdManager::InternalDispatch() +{ + if (m_CmdClient) + { + return; + } + + const char *cmd = engine->Cmd_Argv(0); + ConCmdInfo *pInfo; + if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo)) + { + return; + } + + cell_t result = Pl_Continue; + if (pInfo->srvhooks) + { + pInfo->srvhooks->Execute(&result); + } + + if (result >= Pl_Handled) + { + if (!pInfo->sourceMod) + { + RETURN_META(MRES_SUPERCEDE); + } + return; + } +} + +void CConCmdManager::AddServerCommand(IPluginFunction *pFunction, + const char *name, + const char *description, + int flags) + +{ + ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags); + + if (!pInfo->srvhooks) + { + pInfo->srvhooks = g_Forwards.CreateForwardEx(NULL, ET_Hook, 1, NULL, Param_Cell); + } + + pInfo->srvhooks->AddFunction(pFunction); + + /* Add to the plugin */ + CmdList *pList; + IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext()); + if (!pPlugin->GetProperty("CommandList", (void **)&pList)) + { + pList = new CmdList(); + pPlugin->SetProperty("CommandList", pList); + } + PlCmdInfo info; + info.pInfo = pInfo; + info.type = Cmd_Console; + pList->push_back(info); +} + +void CConCmdManager::AddToCmdList(ConCmdInfo *info) +{ + List::iterator iter = m_CmdList.begin(); + ConCmdInfo *pInfo; + bool inserted = false; + const char *orig; + + if (info->pCmd) + { + orig = info->pCmd->GetName(); + } + + /* Insert this into the help list, SORTED alphabetically. */ + while (iter != m_CmdList.end()) + { + const char *cmd = NULL; + pInfo = (*iter); + if (pInfo->pCmd) + { + cmd = pInfo->pCmd->GetName(); + } + if (strcmp(orig, cmd) < 0) + { + m_CmdList.insert(iter, info); + inserted = true; + break; + } + iter++; + } + + if (!inserted) + { + m_CmdList.push_back(info); + } +} + +void CConCmdManager::RemoveConCmd(ConCmdInfo *info) +{ + /* Remove console-specific information */ + if (info->pCmd) + { + /* Remove from the trie */ + sm_trie_delete(m_pCmds, info->pCmd->GetName()); + + if (info->sourceMod) + { + /* Unlink from SourceMM */ + g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd); + /* Delete the command's memory */ + char *new_help = const_cast(info->pCmd->GetHelpText()); + char *new_name = const_cast(info->pCmd->GetName()); + delete [] new_help; + delete [] new_name; + delete info->pCmd; + } else { + /* Remove the external hook */ + SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false); + } + } + + /* Remove from list */ + m_CmdList.remove(info); + + /* Free forwards */ + if (info->srvhooks) + { + g_Forwards.ReleaseForward(info->srvhooks); + } +} + +ConCmdInfo *CConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags) +{ + ConCmdInfo *pInfo; + if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo)) + { + pInfo = new ConCmdInfo(); + /* Find the commandopan */ + ConCommandBase *pBase = icvar->GetCommands(); + ConCommand *pCmd = NULL; + while (pBase) + { + if (pBase->IsCommand() + && (strcmp(pBase->GetName(), name) == 0)) + { + pCmd = (ConCommand *)pBase; + break; + } + pBase = const_cast(pBase->GetNext()); + } + + if (!pCmd) + { + /* Note that we have to duplicate because the source might not be + * a static string, and these expect static memory. + */ + if (!description) + { + description = ""; + } + char *new_name = sm_strdup(name); + char *new_help = sm_strdup(description); + pCmd = new ConCommand(new_name, CommandCallback, new_help, flags); + pInfo->sourceMod = true; + } else { + SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false); + } + + pInfo->pCmd = pCmd; + + sm_trie_insert(m_pCmds, name, pInfo); + AddToCmdList(pInfo); + } + + return pInfo; +} + +void CConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount) +{ +} diff --git a/core/CConCmdManager.h b/core/CConCmdManager.h new file mode 100644 index 00000000..247b3a64 --- /dev/null +++ b/core/CConCmdManager.h @@ -0,0 +1,81 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * =============================================================== + * + * This file is not open source and may not be copied without explicit + * written permission of AlliedModders LLC. This file may not be redistributed + * in whole or significant part. + * For information, see LICENSE.txt or http://www.sourcemod.net/license.php + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CCONCMDMANAGER_H_ +#define _INCLUDE_SOURCEMOD_CCONCMDMANAGER_H_ + +#include "sm_globals.h" +#include "sourcemm_api.h" +#include "ForwardSys.h" +#include "sm_trie.h" +#include +#include + +using namespace SourceHook; + +enum CmdType +{ + Cmd_Server, + Cmd_Console, + Cmd_Client +}; + +struct ConCmdInfo +{ + ConCmdInfo() + { + sourceMod = false; + pCmd = NULL; + srvhooks = NULL; + } + bool sourceMod; /**< Determines whether or not concmd was created by a SourceMod plugin */ + ConCommand *pCmd; /**< Pointer to the command itself */ + IChangeableForward *srvhooks; /**< Hooks on this name as a server command */ +}; + +class CConCmdManager : + public SMGlobalClass, + public IRootConsoleCommand, + public IPluginsListener +{ + friend void CommandCallback(); +public: + CConCmdManager(); + ~CConCmdManager(); +public: //SMGlobalClass + void OnSourceModAllInitialized(); + void OnSourceModShutdown(); +public: //IPluginsListener + void OnPluginLoaded(IPlugin *plugin); + void OnPluginDestroyed(IPlugin *plugin); +public: //IRootConsoleCommand + void OnRootConsoleCommand(const char *command, unsigned int argcount); +public: + void AddServerCommand(IPluginFunction *pFunction, const char *name, const char *description, int flags); +private: + void InternalDispatch(); + ConCmdInfo *AddOrFindCommand(const char *name, const char *description, int flags); + void SetCommandClient(int client); + void AddToCmdList(ConCmdInfo *info); + void RemoveConCmd(ConCmdInfo *info); +private: + Trie *m_pCmds; + List m_CmdList; + int m_CmdClient; +}; + +extern CConCmdManager g_ConCmds; + +#endif // _INCLUDE_SOURCEMOD_CCONCMDMANAGER_H_ + diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index 7bdd8347..4e251710 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -1,7 +1,7 @@ + + @@ -231,46 +235,6 @@ RelativePath="..\sm_trie.cpp" > - - - - - - - - - - - - - - - - - - - - @@ -289,6 +253,10 @@ RelativePath="..\AdminCache.h" > + + @@ -658,6 +626,50 @@ > + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/smn_convar.cpp b/core/smn_console.cpp similarity index 91% rename from core/smn_convar.cpp rename to core/smn_console.cpp index 9efccae6..4a9275a7 100644 --- a/core/smn_convar.cpp +++ b/core/smn_console.cpp @@ -15,6 +15,7 @@ #include "sourcemm_api.h" #include "HandleSys.h" #include "CConVarManager.h" +#include "CConCmdManager.h" static cell_t sm_CreateConVar(IPluginContext *pContext, const cell_t *params) { @@ -322,6 +323,25 @@ static cell_t sm_ResetConVar(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t sm_RegServerCmd(IPluginContext *pContext, const cell_t *params) +{ + char *name,*help; + IPluginFunction *pFunction; + + pContext->LocalToString(params[1], &name); + pContext->LocalToString(params[3], &help); + pFunction = pContext->GetFunctionById(params[2]); + + if (!pFunction) + { + return pContext->ThrowNativeError("Invalid function id (%X)", params[2]); + } + + g_ConCmds.AddServerCommand(pFunction, name, help, params[4]); + + return 1; +} + REGISTER_NATIVES(convarNatives) { {"CreateConVar", sm_CreateConVar}, @@ -342,5 +362,6 @@ REGISTER_NATIVES(convarNatives) {"GetConVarMin", sm_GetConVarMin}, {"GetConVarMax", sm_GetConVarMax}, {"ResetConVar", sm_ResetConVar}, + {"RegServerCmd", sm_RegServerCmd}, {NULL, NULL} }; diff --git a/plugins/include/console.inc b/plugins/include/console.inc index 1b46c24d..1bacbcf6 100644 --- a/plugins/include/console.inc +++ b/plugins/include/console.inc @@ -73,6 +73,78 @@ native PrintToServer(const String:format[], {Handle,Float,String,_}:...); */ native PrintToConsole(client, const String:format[], {Handle,Float,String,_}:...); +/** + * Called when a server-only command is invoked. + * + * @return A Result value. Not handling the command + * means that Source will report it as "not found." + */ +functag SrvCmd Action:public(); + +/** + * Creates a server-only console command, or hooks an already existing one. + * + * @param cmd Name of the command to hook or create. + * @param callback A function to use as a callback for when the command is invoked. + * @param description Optional description to use for command creation. + * @param flags Optional flags to use for command creation. + * @noreturn + */ +native RegServerCmd(const String:cmd[], SrvCmd:callback, const String:description[]="", flags=0); + +#if 0 +/** + * Called when a generic console command is invoked. + * + * @param client Index of the client, or 0 from the server. + * @return A Result value. Not handling the command + * means that Source will report it as "not found." + */ +functag ConCmd Action:public(client); + +/** + * Creates a console command, or hooks an already existing one. + * + * @param cmd Name of the command to hook or create. + * @param callback A function to use as a callback for when the command is invoked. + * @param description Optional description to use for command creation. + * @param flags Optional flags to use for command creation. + * @noreturn + */ +native RegConsoleCmd(const String:cmd[], ConCmd:callback, const String:description[]="", flags=0); + +/** + * Hooks a specific client-only command. + * + * @param cmd String to match against the beginning of each client command. + * @param callback A function to use as a callback for when the command is invoked. + * @param description Optional description string to use for help. + * @noreturn + */ +native RegClientCmd(const String:cmd[], ConCmd:callback, const String:description[]=""); + +/** + * Creates a console command as an administrative command. If the command does not exist, + * it is created. This command cannot be used to create duplicate admin commands. + * + * @param cmd String containing command to register. + * @param callback A function to use as a callback for when the command is invoked. + * @param adminflags Administrative flags (bitstring) to use for permissions. + * @param group String containing the command group to use. If empty, + * the plugin's filename will be used instead. + * @param description Optional description to use for help. + * @param flags Optional console flags. + * @noreturn + * @error If this command has already been registered as an admin command. + */ +native RegAdminCmd(const String:cmd[], + ConCmd:callback, + adminflags, + const String:group[]="", + const String:description[]="", + flags=0); +#endif + /** * Creates a new console variable. *