2 * ===============================================================
3 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
4 * ===============================================================
6 * This file is not open source and may not be copied without explicit
7 * written permission of AlliedModders LLC. This file may not be redistributed
8 * in whole or significant part.
9 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
14 #include "CConCmdManager.h"
15 #include "sm_srvcmds.h"
16 #include "AdminCache.h"
17 #include "sm_stringutil.h"
18 #include "CPlayerManager.h"
19 #include "CTranslator.h"
21 CConCmdManager g_ConCmds;
23 SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
24 SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int);
32 typedef List<PlCmdInfo> CmdList;
33 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info);
35 CConCmdManager::CConCmdManager() : m_Strings(1024)
37 m_pCmds = sm_trie_create();
38 m_pCmdGrps = sm_trie_create();
41 CConCmdManager::~CConCmdManager()
43 sm_trie_destroy(m_pCmds);
44 sm_trie_destroy(m_pCmdGrps);
47 void CConCmdManager::OnSourceModAllInitialized()
49 g_PluginSys.AddPluginsListener(this);
50 g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
51 SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false);
54 void CConCmdManager::OnSourceModShutdown()
56 /* All commands should already be removed by the time we're done */
57 SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false);
58 g_RootMenu.RemoveRootConsoleCommand("cmds", this);
59 g_PluginSys.RemovePluginsListener(this);
62 void CConCmdManager::RemoveConCmds(List<CmdHook *> &cmdlist, IPluginContext *pContext)
64 List<CmdHook *>::iterator iter = cmdlist.begin();
67 while (iter != cmdlist.end())
70 if (pHook->pf->GetParentContext() == pContext)
74 iter = cmdlist.erase(iter);
81 void CConCmdManager::OnPluginDestroyed(IPlugin *plugin)
84 List<ConCmdInfo *> removed;
85 if (plugin->GetProperty("CommandList", (void **)&pList, true))
87 IPluginContext *pContext = plugin->GetBaseContext();
88 CmdList::iterator iter;
90 * Only bother if there's an actual command list on this plugin...
92 for (iter=pList->begin();
96 PlCmdInfo &cmd = (*iter);
97 ConCmdInfo *pInfo = cmd.pInfo;
99 /* Has this chain already been fully cleaned/removed? */
100 if (removed.find(pInfo) != removed.end())
105 /* Remove any hooks from us on this command */
106 RemoveConCmds(pInfo->conhooks, pContext);
107 RemoveConCmds(pInfo->srvhooks, pContext);
109 /* See if there are still hooks */
110 if (pInfo->srvhooks.size())
114 if (pInfo->conhooks.size())
119 /* Remove the command, it should be safe now */
121 removed.push_back(pInfo);
127 void CommandCallback()
129 g_ConCmds.InternalDispatch();
132 void CConCmdManager::SetCommandClient(int client)
134 m_CmdClient = client + 1;
137 ResultType CConCmdManager::DispatchClientCommand(int client, ResultType type)
139 const char *cmd = engine->Cmd_Argv(0);
140 int args = engine->Cmd_Argc() - 1;
143 if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
145 cell_t result = type;
146 cell_t tempres = result;
147 List<CmdHook *>::iterator iter;
149 for (iter=pInfo->conhooks.begin();
150 iter!=pInfo->conhooks.end();
154 if (pHook->pAdmin && !CheckAccess(client, cmd, pHook->pAdmin))
156 if (result < Pl_Handled)
162 pHook->pf->PushCell(client);
163 pHook->pf->PushCell(args);
164 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
166 if (tempres > result)
170 if (result == Pl_Stop)
176 type = (ResultType)result;
182 void CConCmdManager::InternalDispatch()
185 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
186 * OR this dispatch. They will NEVER go through both.
188 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
191 const char *cmd = engine->Cmd_Argv(0);
193 if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
198 cell_t result = Pl_Continue;
199 int args = engine->Cmd_Argc() - 1;
201 List<CmdHook *>::iterator iter;
204 /* Execute server-only commands if viable */
205 if (m_CmdClient == 0 && pInfo->srvhooks.size())
207 cell_t tempres = result;
208 for (iter=pInfo->srvhooks.begin();
209 iter!=pInfo->srvhooks.end();
213 pHook->pf->PushCell(args);
214 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
216 if (tempres > result)
220 if (result == Pl_Stop)
227 /* Check if there's an early stop */
228 if (result >= Pl_Stop)
230 if (!pInfo->sourceMod)
232 RETURN_META(MRES_SUPERCEDE);
238 /* Execute console commands */
239 if (pInfo->conhooks.size())
241 cell_t tempres = result;
242 for (iter=pInfo->conhooks.begin();
243 iter!=pInfo->conhooks.end();
249 && !CheckAccess(m_CmdClient, cmd, pHook->pAdmin))
251 if (result < Pl_Handled)
257 pHook->pf->PushCell(m_CmdClient);
258 pHook->pf->PushCell(args);
259 if (pHook->pf->Execute(&tempres) != SP_ERROR_NONE)
261 if (tempres > result)
265 if (result == Pl_Stop)
273 if (result >= Pl_Handled)
275 if (!pInfo->sourceMod)
277 RETURN_META(MRES_SUPERCEDE);
283 bool CConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin)
285 FlagBits cmdflags = pAdmin->eflags;
291 CPlayer *player = g_Players.GetPlayerByIndex(client);
297 AdminId adm = player->GetAdminId();
298 if (adm != INVALID_ADMIN_ID)
300 FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective);
302 /* root knows all, WHOA */
303 if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT)
308 /* See if our other flags match */
309 if ((bits & cmdflags) == cmdflags)
314 /* Check for overrides */
315 unsigned int groups = g_Admins.GetAdminGroupCount(adm);
318 bool override = false;
319 for (unsigned int i=0; i<groups; i++)
321 gid = g_Admins.GetAdminGroup(adm, i, NULL);
322 /* First get group-level override */
323 override = g_Admins.GetGroupCommandOverride(gid, cmd, Override_CommandGroup, &rule);
324 /* Now get the specific command override */
325 if (g_Admins.GetGroupCommandOverride(gid, cmd, Override_Command, &rule))
331 if (rule == Command_Allow)
334 } else if (rule == Command_Deny) {
341 if (player->IsFakeClient()
342 || player->GetEdict() == NULL)
347 /* If we got here, the command failed... */
349 if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
352 snprintf(buffer, sizeof(buffer), "You do not have access to this command");
355 char fullbuffer[192];
356 snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
358 engine->ClientPrintf(player->GetEdict(), fullbuffer);
363 void CConCmdManager::AddConsoleCommand(IPluginFunction *pFunction,
365 const char *description,
368 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
369 CmdHook *pHook = new CmdHook();
371 pHook->pf = pFunction;
372 if (description && description[0])
374 pHook->helptext.assign(description);
376 pInfo->conhooks.push_back(pHook);
378 /* Add to the plugin */
380 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
381 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
383 pList = new CmdList();
384 pPlugin->SetProperty("CommandList", pList);
388 info.type = Cmd_Console;
390 AddToPlCmdList(pList, info);
393 bool CConCmdManager::AddAdminCommand(IPluginFunction *pFunction,
397 const char *description,
400 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
402 CmdHook *pHook = new CmdHook();
403 AdminCmdInfo *pAdmin = new AdminCmdInfo();
405 pHook->pf = pFunction;
406 if (description && description[0])
408 pHook->helptext.assign(description);
410 pHook->pAdmin = pAdmin;
414 if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
416 grpid = m_Strings.AddString(group);
417 sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
422 pAdmin->cmdGrpId = grpid;
423 pAdmin->flags = adminflags;
425 /* First get the command group override, if any */
426 bool override = g_Admins.GetCommandOverride(group,
427 Override_CommandGroup,
430 /* Next get the command override, if any */
431 if (g_Admins.GetCommandOverride(name,
438 /* Assign normal flags if there were no overrides */
441 pAdmin->eflags = pAdmin->flags;
444 /* Finally, add the hook */
445 pInfo->conhooks.push_back(pHook);
447 /* Now add to the plugin */
449 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
450 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
452 pList = new CmdList();
453 pPlugin->SetProperty("CommandList", pList);
457 info.type = Cmd_Admin;
459 AddToPlCmdList(pList, info);
464 void CConCmdManager::AddServerCommand(IPluginFunction *pFunction,
466 const char *description,
470 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
471 CmdHook *pHook = new CmdHook();
473 pHook->pf = pFunction;
474 if (description && description[0])
476 pHook->helptext.assign(description);
479 pInfo->srvhooks.push_back(pHook);
481 /* Add to the plugin */
483 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
484 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
486 pList = new CmdList();
487 pPlugin->SetProperty("CommandList", pList);
491 info.type = Cmd_Server;
493 AddToPlCmdList(pList, info);
496 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
498 CmdList::iterator iter = pList->begin();
499 bool inserted = false;
500 const char *orig = NULL;
502 orig = info.pInfo->pCmd->GetName();
504 /* Insert this into the help list, SORTED alphabetically. */
505 while (iter != pList->end())
507 PlCmdInfo &obj = (*iter);
508 const char *cmd = obj.pInfo->pCmd->GetName();
509 if (strcmp(orig, cmd) < 0)
511 pList->insert(iter, info);
520 pList->push_back(info);
524 void CConCmdManager::AddToCmdList(ConCmdInfo *info)
526 List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
528 bool inserted = false;
529 const char *orig = NULL;
531 orig = info->pCmd->GetName();
533 /* Insert this into the help list, SORTED alphabetically. */
534 while (iter != m_CmdList.end())
536 const char *cmd = NULL;
538 cmd = pInfo->pCmd->GetName();
539 if (strcmp(orig, cmd) < 0)
541 m_CmdList.insert(iter, info);
550 m_CmdList.push_back(info);
554 void CConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits)
558 if (type == Override_Command)
560 if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
565 List<CmdHook *>::iterator iter;
568 for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++)
575 pHook->pAdmin->eflags = bits;
577 pHook->pAdmin->eflags = pHook->pAdmin->flags;
581 } else if (type == Override_CommandGroup) {
583 if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object))
587 int grpid = (int)object;
589 /* This is bad :( loop through all commands */
590 List<ConCmdInfo *>::iterator iter;
592 for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++)
595 for (List<CmdHook *>::iterator citer=pInfo->conhooks.begin();
596 citer!=pInfo->conhooks.end();
600 if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid)
604 pHook->pAdmin->eflags = bits;
606 pHook->pAdmin->eflags = pHook->pAdmin->flags;
614 void CConCmdManager::RemoveConCmd(ConCmdInfo *info)
616 /* Remove console-specific information
617 * This should always be true as of right now
621 /* Remove from the trie */
622 sm_trie_delete(m_pCmds, info->pCmd->GetName());
626 /* Unlink from SourceMM */
627 g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
628 /* Delete the command's memory */
629 char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
630 char *new_name = const_cast<char *>(info->pCmd->GetName());
635 /* Remove the external hook */
636 SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
640 /* Remove from list */
641 m_CmdList.remove(info);
646 ConCmdInfo *CConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
649 if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
651 pInfo = new ConCmdInfo();
652 /* Find the commandopan */
653 ConCommandBase *pBase = icvar->GetCommands();
654 ConCommand *pCmd = NULL;
657 if (pBase->IsCommand()
658 && (strcmp(pBase->GetName(), name) == 0))
660 pCmd = (ConCommand *)pBase;
663 pBase = const_cast<ConCommandBase *>(pBase->GetNext());
668 /* Note that we have to duplicate because the source might not be
669 * a static string, and these expect static memory.
675 char *new_name = sm_strdup(name);
676 char *new_help = sm_strdup(description);
677 pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
678 pInfo->sourceMod = true;
680 SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
685 sm_trie_insert(m_pCmds, name, pInfo);
692 void CConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
696 const char *text = engine->Cmd_Argv(2);
698 CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
702 g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
707 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
709 g_RootMenu.ConsolePrint("[SM] No commands found for %s", pPlugin->GetFilename());
714 g_RootMenu.ConsolePrint("[SM] No commands found for %s", pPlugin->GetFilename());
718 CmdList::iterator iter;
719 const char *type = NULL;
722 g_RootMenu.ConsolePrint(" %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
723 for (iter=pList->begin();
727 PlCmdInfo &cmd = (*iter);
728 if (cmd.type == Cmd_Server)
731 } else if (cmd.type == Cmd_Console) {
733 } else if (cmd.type == Cmd_Admin) {
736 name = cmd.pInfo->pCmd->GetName();
737 if (cmd.pHook->helptext.size())
739 help = cmd.pHook->helptext.c_str();
741 help = cmd.pInfo->pCmd->GetHelpText();
743 g_RootMenu.ConsolePrint(" %-17.16s %-12.11s %s", name, type, help);
749 g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");