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 "ConCmdManager.h"
15 #include "sm_srvcmds.h"
16 #include "AdminCache.h"
17 #include "sm_stringutil.h"
18 #include "PlayerManager.h"
19 #include "Translator.h"
21 ConCmdManager 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 ConCmdManager::ConCmdManager() : m_Strings(1024)
37 m_pCmds = sm_trie_create();
38 m_pCmdGrps = sm_trie_create();
41 ConCmdManager::~ConCmdManager()
43 sm_trie_destroy(m_pCmds);
44 sm_trie_destroy(m_pCmdGrps);
47 void ConCmdManager::OnSourceModAllInitialized()
49 g_PluginSys.AddPluginsListener(this);
50 g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
51 SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
54 void ConCmdManager::OnSourceModShutdown()
56 /* All commands should already be removed by the time we're done */
57 SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
58 g_RootMenu.RemoveRootConsoleCommand("cmds", this);
61 void ConCmdManager::RemoveConCmds(List<CmdHook *> &cmdlist, IPluginContext *pContext)
63 List<CmdHook *>::iterator iter = cmdlist.begin();
66 while (iter != cmdlist.end())
69 if (pHook->pf->GetParentContext() == pContext)
73 iter = cmdlist.erase(iter);
80 void ConCmdManager::OnPluginDestroyed(IPlugin *plugin)
83 List<ConCmdInfo *> removed;
84 if (plugin->GetProperty("CommandList", (void **)&pList, true))
86 IPluginContext *pContext = plugin->GetBaseContext();
87 CmdList::iterator iter;
89 * Only bother if there's an actual command list on this plugin...
91 for (iter=pList->begin();
95 PlCmdInfo &cmd = (*iter);
96 ConCmdInfo *pInfo = cmd.pInfo;
98 /* Has this chain already been fully cleaned/removed? */
99 if (removed.find(pInfo) != removed.end())
104 /* Remove any hooks from us on this command */
105 RemoveConCmds(pInfo->conhooks, pContext);
106 RemoveConCmds(pInfo->srvhooks, pContext);
108 /* See if there are still hooks */
109 if (pInfo->srvhooks.size())
113 if (pInfo->conhooks.size())
118 /* Remove the command, it should be safe now */
120 removed.push_back(pInfo);
126 void CommandCallback()
128 g_ConCmds.InternalDispatch();
131 void ConCmdManager::SetCommandClient(int client)
133 m_CmdClient = client + 1;
136 ResultType ConCmdManager::DispatchClientCommand(int client, ResultType type)
138 const char *cmd = engine->Cmd_Argv(0);
139 int args = engine->Cmd_Argc() - 1;
142 if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
144 cell_t result = type;
145 cell_t tempres = result;
146 List<CmdHook *>::iterator iter;
148 for (iter=pInfo->conhooks.begin();
149 iter!=pInfo->conhooks.end();
153 if (!pHook->pf->IsRunnable())
157 if (pHook->pAdmin && !CheckAccess(client, cmd, pHook->pAdmin))
159 if (result < Pl_Handled)
165 pHook->pf->PushCell(client);
166 pHook->pf->PushCell(args);
167 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
169 if (tempres > result)
173 if (result == Pl_Stop)
179 type = (ResultType)result;
185 void ConCmdManager::InternalDispatch()
188 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
189 * OR this dispatch. They will NEVER go through both.
191 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
194 const char *cmd = engine->Cmd_Argv(0);
196 if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
201 cell_t result = Pl_Continue;
202 int args = engine->Cmd_Argc() - 1;
204 List<CmdHook *>::iterator iter;
207 /* Execute server-only commands if viable */
208 if (m_CmdClient == 0 && pInfo->srvhooks.size())
210 cell_t tempres = result;
211 for (iter=pInfo->srvhooks.begin();
212 iter!=pInfo->srvhooks.end();
216 if (!pHook->pf->IsRunnable())
220 pHook->pf->PushCell(args);
221 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
223 if (tempres > result)
227 if (result == Pl_Stop)
234 /* Check if there's an early stop */
235 if (result >= Pl_Stop)
237 if (!pInfo->sourceMod)
239 RETURN_META(MRES_SUPERCEDE);
245 /* Execute console commands */
246 if (pInfo->conhooks.size())
248 cell_t tempres = result;
249 for (iter=pInfo->conhooks.begin();
250 iter!=pInfo->conhooks.end();
254 if (!pHook->pf->IsRunnable())
260 && !CheckAccess(m_CmdClient, cmd, pHook->pAdmin))
262 if (result < Pl_Handled)
268 pHook->pf->PushCell(m_CmdClient);
269 pHook->pf->PushCell(args);
270 if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
272 if (tempres > result)
276 if (result == Pl_Stop)
284 if (result >= Pl_Handled)
286 if (!pInfo->sourceMod)
288 RETURN_META(MRES_SUPERCEDE);
294 bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin)
296 FlagBits cmdflags = pAdmin->eflags;
302 CPlayer *player = g_Players.GetPlayerByIndex(client);
308 AdminId adm = player->GetAdminId();
309 if (adm != INVALID_ADMIN_ID)
311 FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective);
313 /* root knows all, WHOA */
314 if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT)
319 /* See if our other flags match */
320 if ((bits & cmdflags) == cmdflags)
325 /* Check for overrides */
326 unsigned int groups = g_Admins.GetAdminGroupCount(adm);
329 bool override = false;
330 for (unsigned int i=0; i<groups; i++)
332 gid = g_Admins.GetAdminGroup(adm, i, NULL);
333 /* First get group-level override */
334 override = g_Admins.GetGroupCommandOverride(gid, cmd, Override_CommandGroup, &rule);
335 /* Now get the specific command override */
336 if (g_Admins.GetGroupCommandOverride(gid, cmd, Override_Command, &rule))
342 if (rule == Command_Allow)
345 } else if (rule == Command_Deny) {
352 if (player->IsFakeClient()
353 || player->GetEdict() == NULL)
358 /* If we got here, the command failed... */
360 if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
363 snprintf(buffer, sizeof(buffer), "You do not have access to this command");
366 char fullbuffer[192];
367 snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
369 engine->ClientPrintf(player->GetEdict(), fullbuffer);
374 bool ConCmdManager::AddConsoleCommand(IPluginFunction *pFunction,
376 const char *description,
379 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
386 CmdHook *pHook = new CmdHook();
388 pHook->pf = pFunction;
389 if (description && description[0])
391 pHook->helptext.assign(description);
393 pInfo->conhooks.push_back(pHook);
395 /* Add to the plugin */
397 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
398 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
400 pList = new CmdList();
401 pPlugin->SetProperty("CommandList", pList);
405 info.type = Cmd_Console;
407 AddToPlCmdList(pList, info);
412 bool ConCmdManager::AddAdminCommand(IPluginFunction *pFunction,
416 const char *description,
419 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
426 CmdHook *pHook = new CmdHook();
427 AdminCmdInfo *pAdmin = new AdminCmdInfo();
429 pHook->pf = pFunction;
430 if (description && description[0])
432 pHook->helptext.assign(description);
434 pHook->pAdmin = pAdmin;
438 if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
440 grpid = m_Strings.AddString(group);
441 sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
446 pAdmin->cmdGrpId = grpid;
447 pAdmin->flags = adminflags;
449 /* First get the command group override, if any */
450 bool override = g_Admins.GetCommandOverride(group,
451 Override_CommandGroup,
454 /* Next get the command override, if any */
455 if (g_Admins.GetCommandOverride(name,
462 /* Assign normal flags if there were no overrides */
465 pAdmin->eflags = pAdmin->flags;
468 /* Finally, add the hook */
469 pInfo->conhooks.push_back(pHook);
471 /* Now add to the plugin */
473 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
474 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
476 pList = new CmdList();
477 pPlugin->SetProperty("CommandList", pList);
481 info.type = Cmd_Admin;
483 AddToPlCmdList(pList, info);
488 bool ConCmdManager::AddServerCommand(IPluginFunction *pFunction,
490 const char *description,
494 ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
501 CmdHook *pHook = new CmdHook();
503 pHook->pf = pFunction;
504 if (description && description[0])
506 pHook->helptext.assign(description);
509 pInfo->srvhooks.push_back(pHook);
511 /* Add to the plugin */
513 IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
514 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
516 pList = new CmdList();
517 pPlugin->SetProperty("CommandList", pList);
521 info.type = Cmd_Server;
523 AddToPlCmdList(pList, info);
528 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
530 CmdList::iterator iter = pList->begin();
531 bool inserted = false;
532 const char *orig = NULL;
534 orig = info.pInfo->pCmd->GetName();
536 /* Insert this into the help list, SORTED alphabetically. */
537 while (iter != pList->end())
539 PlCmdInfo &obj = (*iter);
540 const char *cmd = obj.pInfo->pCmd->GetName();
541 if (strcmp(orig, cmd) < 0)
543 pList->insert(iter, info);
552 pList->push_back(info);
556 void ConCmdManager::AddToCmdList(ConCmdInfo *info)
558 List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
560 bool inserted = false;
561 const char *orig = NULL;
563 orig = info->pCmd->GetName();
565 /* Insert this into the help list, SORTED alphabetically. */
566 while (iter != m_CmdList.end())
568 const char *cmd = NULL;
570 cmd = pInfo->pCmd->GetName();
571 if (strcmp(orig, cmd) < 0)
573 m_CmdList.insert(iter, info);
582 m_CmdList.push_back(info);
586 void ConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits)
590 if (type == Override_Command)
592 if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
597 List<CmdHook *>::iterator iter;
600 for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++)
607 pHook->pAdmin->eflags = bits;
609 pHook->pAdmin->eflags = pHook->pAdmin->flags;
613 } else if (type == Override_CommandGroup) {
615 if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object))
619 int grpid = (int)object;
621 /* This is bad :( loop through all commands */
622 List<ConCmdInfo *>::iterator iter;
624 for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++)
627 for (List<CmdHook *>::iterator citer=pInfo->conhooks.begin();
628 citer!=pInfo->conhooks.end();
632 if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid)
636 pHook->pAdmin->eflags = bits;
638 pHook->pAdmin->eflags = pHook->pAdmin->flags;
646 void ConCmdManager::RemoveConCmd(ConCmdInfo *info)
648 /* Remove console-specific information
649 * This should always be true as of right now
653 /* Remove from the trie */
654 sm_trie_delete(m_pCmds, info->pCmd->GetName());
658 /* Unlink from SourceMM */
659 g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
660 /* Delete the command's memory */
661 char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
662 char *new_name = const_cast<char *>(info->pCmd->GetName());
667 /* Remove the external hook */
668 SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
672 /* Remove from list */
673 m_CmdList.remove(info);
678 ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
681 if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
683 pInfo = new ConCmdInfo();
684 /* Find the commandopan */
685 ConCommandBase *pBase = icvar->GetCommands();
686 ConCommand *pCmd = NULL;
689 if (strcmp(pBase->GetName(), name) == 0)
691 /* Don't want to return convar with same name */
692 if (!pBase->IsCommand())
697 pCmd = (ConCommand *)pBase;
700 pBase = const_cast<ConCommandBase *>(pBase->GetNext());
705 /* Note that we have to duplicate because the source might not be
706 * a static string, and these expect static memory.
712 char *new_name = sm_strdup(name);
713 char *new_help = sm_strdup(description);
714 pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
715 pInfo->sourceMod = true;
717 SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
722 sm_trie_insert(m_pCmds, name, pInfo);
729 void ConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
733 const char *text = engine->Cmd_Argv(2);
735 CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
739 g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
743 const sm_plugininfo_t *plinfo = pPlugin->GetPublicInfo();
744 const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : pPlugin->GetFilename();
747 if (!pPlugin->GetProperty("CommandList", (void **)&pList))
749 g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
754 g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
758 CmdList::iterator iter;
759 const char *type = NULL;
762 g_RootMenu.ConsolePrint("[SM] Listing %d commands for: %s", pList->size(), plname);
763 g_RootMenu.ConsolePrint(" %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
764 for (iter=pList->begin();
768 PlCmdInfo &cmd = (*iter);
769 if (cmd.type == Cmd_Server)
772 } else if (cmd.type == Cmd_Console) {
774 } else if (cmd.type == Cmd_Admin) {
777 name = cmd.pInfo->pCmd->GetName();
778 if (cmd.pHook->helptext.size())
780 help = cmd.pHook->helptext.c_str();
782 help = cmd.pInfo->pCmd->GetHelpText();
784 g_RootMenu.ConsolePrint(" %-17.16s %-12.11s %s", name, type, help);
790 g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
793 static int g_yam_state = 0;
794 void _YamState(int state)
798 g_RootMenu.ConsolePrint("Welcome to the SourceMod Text Adventure.");
799 g_RootMenu.ConsolePrint("Type sm_text to see the last message.");
800 g_RootMenu.ConsolePrint("Type sm_text <word> to follow a capital word.");
801 g_RootMenu.ConsolePrint("-------------------------------");
802 g_RootMenu.ConsolePrint("You are at VALVE HEADQUARTERS.");
803 g_RootMenu.ConsolePrint("To your LEFT is BAILOPAN, rearranging the letters to spell");
804 g_RootMenu.ConsolePrint("\"A HARD VEAL QUEST\". FORWARD is the entrance to the building.");
805 g_RootMenu.ConsolePrint("To your RIGHT is your last chance to flee in terror.");
806 } else if (state == 1) {
807 g_RootMenu.ConsolePrint("BAILOPAN tells you that his name his pronounced");
808 g_RootMenu.ConsolePrint("\"bye low pahn,\" not \"bay low pan.\" Do you ");
809 g_RootMenu.ConsolePrint("MOCK him, or NOD quietly?");
810 } else if (state == 2) {
811 g_RootMenu.ConsolePrint("You enter the Valve building. You hear screams coming from within.");
812 g_RootMenu.ConsolePrint("A grotesque figure lumbers up to greet you; it is Gabe Newell.");
813 g_RootMenu.ConsolePrint("\"Welcome,\" he belches, \"to my lair.\"");
814 g_RootMenu.ConsolePrint("Do you SHAKE Gaben's hand, WALK past him, or OFFER a donut?");
815 } else if (state == 3) {
816 g_RootMenu.ConsolePrint("You walk into the break room. Alfred \"Adolf\" Reynolds and");
817 g_RootMenu.ConsolePrint("Yahn \"Yeti\" Bernier are discussing something (you overhear ");
818 g_RootMenu.ConsolePrint("the phrase \"and next Steam update, here's what we should break\").");
819 g_RootMenu.ConsolePrint("Should you DIE in a fire, REPORT a bug, REQUEST a feature, or ");
820 g_RootMenu.ConsolePrint("SPRAY them with butter?");
825 void _IntExt_CallYams()
827 const char *arg = engine->Cmd_Argv(1);
829 /* should be impossible */
830 if (!arg || arg[0] == '\0')
832 _YamState(g_yam_state);
835 if (g_yam_state == 1)
837 if (strcasecmp(arg, "mock") == 0)
839 g_RootMenu.ConsolePrint("You mock BAILOPAN's pronounciation. In a fit of rage, ");
840 g_RootMenu.ConsolePrint("he sticks an INT 3 call into your chest, rendering you broken.");
841 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
843 } else if (strcasecmp(arg, "nod") == 0) {
844 g_RootMenu.ConsolePrint("You nod quietly, and then slowly back away into the Valve headquarters.");
847 g_RootMenu.ConsolePrint("Commands are MOCK and NOD.");
849 } else if (g_yam_state == 3) {
850 if (strcasecmp(arg, "report") == 0)
852 g_RootMenu.ConsolePrint("You report a bug to Alfred and Yeti. Immediately, both fall asleep.");
853 g_RootMenu.ConsolePrint("You decay in the break room for two years while they sleep.");
854 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
856 } else if (strcasecmp(arg, "request") == 0) {
857 g_RootMenu.ConsolePrint("You request a feature to Alfred and Yeti. They both mutter something");
858 g_RootMenu.ConsolePrint("about it being implemented \"soon.\" Then, by accident, someone sends");
859 g_RootMenu.ConsolePrint("a message over \"Friends.\" The entire building catches fire.");
860 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
862 } else if (strcasecmp(arg, "die") == 0) {
863 g_RootMenu.ConsolePrint("For no reason, you suddenly catch fire. Alfred and Yeti find this");
864 g_RootMenu.ConsolePrint("deeply disturbing, and cover your flaming corpse with Episode 2");
865 g_RootMenu.ConsolePrint("advertisements. Coming soon, with Team Fortress 2, and Portal!");
866 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
868 } else if (strcasecmp(arg, "spray") == 0) {
869 g_RootMenu.ConsolePrint("You spray Alfred and Yeti with butter. Like Jack Thompson to an");
870 g_RootMenu.ConsolePrint("ambulance, Gabe Newell instantly appears and devours them both.");
871 g_RootMenu.ConsolePrint("You run away, just in time, as Gabe Newell explodes, registering ");
872 g_RootMenu.ConsolePrint("a 5.6 earthquake. Outside, world peace is achieved.");
873 g_RootMenu.ConsolePrint("YOU HAVE WON.");
876 } else if (g_yam_state == 2) {
877 if (strcasecmp(arg, "shake") == 0)
879 g_RootMenu.ConsolePrint("You shake Gaben's hand. It is a terrifying and disgusting experience.");
880 g_RootMenu.ConsolePrint("However, you survive, and continue on.");
882 } else if (strcasecmp(arg, "offer") == 0) {
883 g_RootMenu.ConsolePrint("You offer Gabe Newell one (1) donut. With a gleam in his eyes, ");
884 g_RootMenu.ConsolePrint("he picks you up and devours you whole.");
885 g_RootMenu.ConsolePrint("YOU HAVE DIED. GAME OVER.");
887 } else if (strcasecmp(arg, "walk") == 0) {
888 g_RootMenu.ConsolePrint("You walk past Gabe Newell. He can't keep up with your pace!");
891 g_RootMenu.ConsolePrint("Commands are SHAKE, OFFER, and WALK.");
893 } else if (g_yam_state == 0) {
894 if (strcasecmp(arg, "left") == 0)
897 } else if (strcasecmp(arg, "right") == 0) {
898 g_RootMenu.ConsolePrint("You run away from the Valve headquarters in sheer terror.");
899 g_RootMenu.ConsolePrint("While running, you smash into an unknown person, who turns out to be your soul mate.");
900 g_RootMenu.ConsolePrint("You marry and raise a family of 3 kids.");
901 g_RootMenu.ConsolePrint("Many years later, you look back, and realize this was your best choice.");
902 g_RootMenu.ConsolePrint("YOU HAVE WON.");
904 } else if (strcasecmp(arg, "forward") == 0) {
906 } else if (arg[0] != '\0') {
907 g_RootMenu.ConsolePrint("Commands are FORWARD, LEFT, and RIGHT.");
912 void _IntExt_EnableYams()
914 static ConCommand *pCmd = NULL;
917 pCmd = new ConCommand("sm_text", _IntExt_CallYams, "Fountain of Yams Adventure Game", FCVAR_GAMEDLL);
918 g_RootMenu.ConsolePrint("Something is now enabled...");
920 g_RootMenu.ConsolePrint("Something is already enabled...");