3 * ===============================================================
4 * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved.
5 * ===============================================================
7 * This file is not open source and may not be copied without explicit
8 * written permission of AlliedModders LLC. This file may not be redistributed
9 * in whole or significant part.
10 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
15 #include "ConCmdManager.h"
16 #include "sm_srvcmds.h"
17 #include "AdminCache.h"
18 #include "sm_stringutil.h"
19 #include "PlayerManager.h"
20 #include "Translator.h"
22 ConCmdManager g_ConCmds;
24 SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
25 SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int);
33 typedef List<PlCmdInfo> CmdList;
34 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info);
36 ConCmdManager::ConCmdManager() : m_Strings(1024)
38 m_pCmds = sm_trie_create();
39 m_pCmdGrps = sm_trie_create();
42 ConCmdManager::~ConCmdManager()
44 sm_trie_destroy(m_pCmds);
45 sm_trie_destroy(m_pCmdGrps);
48 void ConCmdManager::OnSourceModAllInitialized()
50 g_PluginSys.AddPluginsListener(this);
51 g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
52 SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
55 void ConCmdManager::OnSourceModShutdown()
57 /* All commands should already be removed by the time we're done */
58 SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
59 g_RootMenu.RemoveRootConsoleCommand("cmds", this);
62 void ConCmdManager::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 ConCmdManager::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 ConCmdManager::SetCommandClient(int client)
134 m_CmdClient = client + 1;
137 ResultType ConCmdManager::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->pf->IsRunnable())
158 if (pHook->pAdmin && !CheckAccess(client, cmd, pHook->pAdmin))
160 if (result < Pl_Handled)
166 pHook->pf->PushCell(client);
167 pHook->pf->PushCell(args);
168 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
170 if (tempres > result)
174 if (result == Pl_Stop)
180 type = (ResultType)result;
186 void ConCmdManager::InternalDispatch()
189 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
190 * OR this dispatch. They will NEVER go through both.
192 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
195 const char *cmd = engine->Cmd_Argv(0);
197 if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
202 cell_t result = Pl_Continue;
203 int args = engine->Cmd_Argc() - 1;
205 List<CmdHook *>::iterator iter;
208 /* Execute server-only commands if viable */
209 if (m_CmdClient == 0 && pInfo->srvhooks.size())
211 cell_t tempres = result;
212 for (iter=pInfo->srvhooks.begin();
213 iter!=pInfo->srvhooks.end();
217 if (!pHook->pf->IsRunnable())
221 pHook->pf->PushCell(args);
222 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
224 if (tempres > result)
228 if (result == Pl_Stop)
235 /* Check if there's an early stop */
236 if (result >= Pl_Stop)
238 if (!pInfo->sourceMod)
240 RETURN_META(MRES_SUPERCEDE);
246 /* Execute console commands */
247 if (pInfo->conhooks.size())
249 cell_t tempres = result;
250 for (iter=pInfo->conhooks.begin();
251 iter!=pInfo->conhooks.end();
255 if (!pHook->pf->IsRunnable())
261 && !CheckAccess(m_CmdClient, cmd, pHook->pAdmin))
263 if (result < Pl_Handled)
269 pHook->pf->PushCell(m_CmdClient);
270 pHook->pf->PushCell(args);
271 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
273 if (tempres > result)
277 if (result == Pl_Stop)
285 if (result >= Pl_Handled)
287 if (!pInfo->sourceMod)
289 RETURN_META(MRES_SUPERCEDE);
295 bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin)
297 FlagBits cmdflags = pAdmin->eflags;
303 CPlayer *player = g_Players.GetPlayerByIndex(client);
309 AdminId adm = player->GetAdminId();
310 if (adm != INVALID_ADMIN_ID)
312 FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective);
314 /* root knows all, WHOA */
315 if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT)
320 /* See if our other flags match */
321 if ((bits & cmdflags) == cmdflags)
326 /* Check for overrides */
327 unsigned int groups = g_Admins.GetAdminGroupCount(adm);
330 bool override = false;
331 for (unsigned int i=0; i<groups; i++)
333 gid = g_Admins.GetAdminGroup(adm, i, NULL);
334 /* First get group-level override */
335 override = g_Admins.GetGroupCommandOverride(gid, cmd, Override_CommandGroup, &rule);
336 /* Now get the specific command override */
337 if (g_Admins.GetGroupCommandOverride(gid, cmd, Override_Command, &rule))
343 if (rule == Command_Allow)
346 } else if (rule == Command_Deny) {
353 if (player->IsFakeClient()
354 || player->GetEdict() == NULL)
359 /* If we got here, the command failed... */
361 if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
364 snprintf(buffer, sizeof(buffer), "You do not have access to this command");
367 char fullbuffer[192];
368 snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
370 engine->ClientPrintf(player->GetEdict(), fullbuffer);
375 bool ConCmdManager::AddConsoleCommand(IPluginFunction *pFunction,
377 const char *description,
380 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
387 CmdHook *pHook = new CmdHook();
389 pHook->pf = pFunction;
390 if (description && description[0])
392 pHook->helptext.assign(description);
394 pInfo->conhooks.push_back(pHook);
396 /* Add to the plugin */
398 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
399 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
401 pList = new CmdList();
402 pPlugin->SetProperty("CommandList", pList);
406 info.type = Cmd_Console;
408 AddToPlCmdList(pList, info);
413 bool ConCmdManager::AddAdminCommand(IPluginFunction *pFunction,
417 const char *description,
420 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
427 CmdHook *pHook = new CmdHook();
428 AdminCmdInfo *pAdmin = new AdminCmdInfo();
430 pHook->pf = pFunction;
431 if (description && description[0])
433 pHook->helptext.assign(description);
435 pHook->pAdmin = pAdmin;
439 if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
441 grpid = m_Strings.AddString(group);
442 sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
447 pAdmin->cmdGrpId = grpid;
448 pAdmin->flags = adminflags;
450 /* First get the command group override, if any */
451 bool override = g_Admins.GetCommandOverride(group,
452 Override_CommandGroup,
455 /* Next get the command override, if any */
456 if (g_Admins.GetCommandOverride(name,
463 /* Assign normal flags if there were no overrides */
466 pAdmin->eflags = pAdmin->flags;
469 /* Finally, add the hook */
470 pInfo->conhooks.push_back(pHook);
472 /* Now add to the plugin */
474 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
475 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
477 pList = new CmdList();
478 pPlugin->SetProperty("CommandList", pList);
482 info.type = Cmd_Admin;
484 AddToPlCmdList(pList, info);
489 bool ConCmdManager::AddServerCommand(IPluginFunction *pFunction,
491 const char *description,
495 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
502 CmdHook *pHook = new CmdHook();
504 pHook->pf = pFunction;
505 if (description && description[0])
507 pHook->helptext.assign(description);
510 pInfo->srvhooks.push_back(pHook);
512 /* Add to the plugin */
514 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
515 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
517 pList = new CmdList();
518 pPlugin->SetProperty("CommandList", pList);
522 info.type = Cmd_Server;
524 AddToPlCmdList(pList, info);
529 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
531 CmdList::iterator iter = pList->begin();
532 bool inserted = false;
533 const char *orig = NULL;
535 orig = info.pInfo->pCmd->GetName();
537 /* Insert this into the help list, SORTED alphabetically. */
538 while (iter != pList->end())
540 PlCmdInfo &obj = (*iter);
541 const char *cmd = obj.pInfo->pCmd->GetName();
542 if (strcmp(orig, cmd) < 0)
544 pList->insert(iter, info);
553 pList->push_back(info);
557 void ConCmdManager::AddToCmdList(ConCmdInfo *info)
559 List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
561 bool inserted = false;
562 const char *orig = NULL;
564 orig = info->pCmd->GetName();
566 /* Insert this into the help list, SORTED alphabetically. */
567 while (iter != m_CmdList.end())
569 const char *cmd = NULL;
571 cmd = pInfo->pCmd->GetName();
572 if (strcmp(orig, cmd) < 0)
574 m_CmdList.insert(iter, info);
583 m_CmdList.push_back(info);
587 void ConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits)
591 if (type == Override_Command)
593 if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
598 List<CmdHook *>::iterator iter;
601 for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++)
608 pHook->pAdmin->eflags = bits;
610 pHook->pAdmin->eflags = pHook->pAdmin->flags;
614 } else if (type == Override_CommandGroup) {
616 if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object))
620 int grpid = (int)object;
622 /* This is bad :( loop through all commands */
623 List<ConCmdInfo *>::iterator iter;
625 for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++)
628 for (List<CmdHook *>::iterator citer=pInfo->conhooks.begin();
629 citer!=pInfo->conhooks.end();
633 if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid)
637 pHook->pAdmin->eflags = bits;
639 pHook->pAdmin->eflags = pHook->pAdmin->flags;
647 void ConCmdManager::RemoveConCmd(ConCmdInfo *info)
649 /* Remove console-specific information
650 * This should always be true as of right now
654 /* Remove from the trie */
655 sm_trie_delete(m_pCmds, info->pCmd->GetName());
659 /* Unlink from SourceMM */
660 g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
661 /* Delete the command's memory */
662 char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
663 char *new_name = const_cast<char *>(info->pCmd->GetName());
668 /* Remove the external hook */
669 SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
673 /* Remove from list */
674 m_CmdList.remove(info);
679 ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
682 if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
684 pInfo = new ConCmdInfo();
685 /* Find the commandopan */
686 ConCommandBase *pBase = icvar->GetCommands();
687 ConCommand *pCmd = NULL;
690 if (strcmp(pBase->GetName(), name) == 0)
692 /* Don't want to return convar with same name */
693 if (!pBase->IsCommand())
698 pCmd = (ConCommand *)pBase;
701 pBase = const_cast<ConCommandBase *>(pBase->GetNext());
706 /* Note that we have to duplicate because the source might not be
707 * a static string, and these expect static memory.
713 char *new_name = sm_strdup(name);
714 char *new_help = sm_strdup(description);
715 pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
716 pInfo->sourceMod = true;
718 SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
723 sm_trie_insert(m_pCmds, name, pInfo);
730 void ConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
734 const char *text = engine->Cmd_Argv(2);
736 CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
740 g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
744 const sm_plugininfo_t *plinfo = pPlugin->GetPublicInfo();
745 const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : pPlugin->GetFilename();
748 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
750 g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
755 g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
759 CmdList::iterator iter;
760 const char *type = NULL;
763 g_RootMenu.ConsolePrint("[SM] Listing %d commands for: %s", pList->size(), plname);
764 g_RootMenu.ConsolePrint(" %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
765 for (iter=pList->begin();
769 PlCmdInfo &cmd = (*iter);
770 if (cmd.type == Cmd_Server)
773 } else if (cmd.type == Cmd_Console) {
775 } else if (cmd.type == Cmd_Admin) {
778 name = cmd.pInfo->pCmd->GetName();
779 if (cmd.pHook->helptext.size())
781 help = cmd.pHook->helptext.c_str();
783 help = cmd.pInfo->pCmd->GetHelpText();
785 g_RootMenu.ConsolePrint(" %-17.16s %-12.11s %s", name, type, help);
791 g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
794 static int g_yam_state = 0;
795 void _YamState(int state)
799 g_RootMenu.ConsolePrint("Welcome to the SourceMod Text Adventure.");
800 g_RootMenu.ConsolePrint("Type sm_text to see the last message.");
801 g_RootMenu.ConsolePrint("Type sm_text <word> to follow a capital word.");
802 g_RootMenu.ConsolePrint("-------------------------------");
803 g_RootMenu.ConsolePrint("You are at VALVE HEADQUARTERS.");
804 g_RootMenu.ConsolePrint("To your LEFT is BAILOPAN, rearranging the letters to spell");
805 g_RootMenu.ConsolePrint("\"A HARD VEAL QUEST\". FORWARD is the entrance to the building.");
806 g_RootMenu.ConsolePrint("To your RIGHT is your last chance to flee in terror.");
807 } else if (state == 1) {
808 g_RootMenu.ConsolePrint("BAILOPAN tells you that his name his pronounced");
809 g_RootMenu.ConsolePrint("\"bye low pahn,\" not \"bay low pan.\" Do you ");
810 g_RootMenu.ConsolePrint("MOCK him, or NOD quietly?");
811 } else if (state == 2) {
812 g_RootMenu.ConsolePrint("You enter the Valve building. You hear screams coming from within.");
813 g_RootMenu.ConsolePrint("A grotesque figure lumbers up to greet you; it is Gabe Newell.");
814 g_RootMenu.ConsolePrint("\"Welcome,\" he belches, \"to my lair.\"");
815 g_RootMenu.ConsolePrint("Do you SHAKE Gaben's hand, WALK past him, or OFFER a donut?");
816 } else if (state == 3) {
817 g_RootMenu.ConsolePrint("You walk into the break room. Alfred \"Adolf\" Reynolds and");
818 g_RootMenu.ConsolePrint("Yahn \"Yeti\" Bernier are discussing something (you overhear ");
819 g_RootMenu.ConsolePrint("the phrase \"and next Steam update, here's what we should break\").");
820 g_RootMenu.ConsolePrint("Should you DIE in a fire, REPORT a bug, REQUEST a feature, or ");
821 g_RootMenu.ConsolePrint("SPRAY them with butter?");
826 void _IntExt_CallYams()
828 const char *arg = engine->Cmd_Argv(1);
830 /* should be impossible */
831 if (!arg || arg[0] == '\0')
833 _YamState(g_yam_state);
836 if (g_yam_state == 1)
838 if (strcasecmp(arg, "mock") == 0)
840 g_RootMenu.ConsolePrint("You mock BAILOPAN's pronounciation. In a fit of rage, ");
841 g_RootMenu.ConsolePrint("he sticks an INT 3 call into your chest, rendering you broken.");
842 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
844 } else if (strcasecmp(arg, "nod") == 0) {
845 g_RootMenu.ConsolePrint("You nod quietly, and then slowly back away into the Valve headquarters.");
848 g_RootMenu.ConsolePrint("Commands are MOCK and NOD.");
850 } else if (g_yam_state == 3) {
851 if (strcasecmp(arg, "report") == 0)
853 g_RootMenu.ConsolePrint("You report a bug to Alfred and Yeti. Immediately, both fall asleep.");
854 g_RootMenu.ConsolePrint("You decay in the break room for two years while they sleep.");
855 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
857 } else if (strcasecmp(arg, "request") == 0) {
858 g_RootMenu.ConsolePrint("You request a feature to Alfred and Yeti. They both mutter something");
859 g_RootMenu.ConsolePrint("about it being implemented \"soon.\" Then, by accident, someone sends");
860 g_RootMenu.ConsolePrint("a message over \"Friends.\" The entire building catches fire.");
861 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
863 } else if (strcasecmp(arg, "die") == 0) {
864 g_RootMenu.ConsolePrint("For no reason, you suddenly catch fire. Alfred and Yeti find this");
865 g_RootMenu.ConsolePrint("deeply disturbing, and cover your flaming corpse with Episode 2");
866 g_RootMenu.ConsolePrint("advertisements. Coming soon, with Team Fortress 2, and Portal!");
867 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
869 } else if (strcasecmp(arg, "spray") == 0) {
870 g_RootMenu.ConsolePrint("You spray Alfred and Yeti with butter. Like Jack Thompson to an");
871 g_RootMenu.ConsolePrint("ambulance, Gabe Newell instantly appears and devours them both.");
872 g_RootMenu.ConsolePrint("You run away, just in time, as Gabe Newell explodes, registering ");
873 g_RootMenu.ConsolePrint("a 5.6 earthquake. Outside, world peace is achieved.");
874 g_RootMenu.ConsolePrint("YOU HAVE WON.");
877 } else if (g_yam_state == 2) {
878 if (strcasecmp(arg, "shake") == 0)
880 g_RootMenu.ConsolePrint("You shake Gaben's hand. It is a terrifying and disgusting experience.");
881 g_RootMenu.ConsolePrint("However, you survive, and continue on.");
883 } else if (strcasecmp(arg, "offer") == 0) {
884 g_RootMenu.ConsolePrint("You offer Gabe Newell one (1) donut. With a gleam in his eyes, ");
885 g_RootMenu.ConsolePrint("he picks you up and devours you whole.");
886 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
888 } else if (strcasecmp(arg, "walk") == 0) {
889 g_RootMenu.ConsolePrint("You walk past Gabe Newell. He can't keep up with your pace!");
892 g_RootMenu.ConsolePrint("Commands are SHAKE, OFFER, and WALK.");
894 } else if (g_yam_state == 0) {
895 if (strcasecmp(arg, "left") == 0)
898 } else if (strcasecmp(arg, "right") == 0) {
899 g_RootMenu.ConsolePrint("You run away from the Valve headquarters in sheer terror.");
900 g_RootMenu.ConsolePrint("While running, you smash into an unknown person, who turns out to be your soul mate.");
901 g_RootMenu.ConsolePrint("You marry and raise a family of 3 kids.");
902 g_RootMenu.ConsolePrint("Many years later, you look back, and realize this was your best choice.");
903 g_RootMenu.ConsolePrint("YOU HAVE WON.");
905 } else if (strcasecmp(arg, "forward") == 0) {
907 } else if (arg[0] != '\0') {
908 g_RootMenu.ConsolePrint("Commands are FORWARD, LEFT, and RIGHT.");
913 void _IntExt_EnableYams()
915 static ConCommand *pCmd = NULL;
918 pCmd = new ConCommand("sm_text", _IntExt_CallYams, "Fountain of Yams Adventure Game", FCVAR_GAMEDLL);
919 g_RootMenu.ConsolePrint("Something is now enabled...");
921 g_RootMenu.ConsolePrint("Something is already enabled...");