core/ConCmdManager.cpp
author David Anderson <dvander@alliedmods.net>
Sun Jun 17 18:48:46 2007 +0000 (2007-06-17)
changeset 972 63d848bccec5
parent 895 a1068846fee8
child 978 54782a83a1ef
permissions -rw-r--r--
fixed bug amb401
     1 /**
     2  * vim: set ts=4 :
     3  * ===============================================================
     4  * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     5  * ===============================================================
     6  *
     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
    11  *
    12  * Version: $Id$
    13  */
    14 
    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"
    21 #include "HalfLife2.h"
    22 #include "ChatTriggers.h"
    23 
    24 ConCmdManager g_ConCmds;
    25 
    26 SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
    27 SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int);
    28 
    29 struct PlCmdInfo
    30 {
    31 	ConCmdInfo *pInfo;
    32 	CmdHook *pHook;
    33 	CmdType type;
    34 };
    35 typedef List<PlCmdInfo> CmdList;
    36 
    37 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info);
    38 
    39 ConCmdManager::ConCmdManager() : m_Strings(1024)
    40 {
    41 	m_pCmds = sm_trie_create();
    42 	m_pCmdGrps = sm_trie_create();
    43 	m_bServerCfgDone = true;
    44 	m_pExecCmd = NULL;
    45 	m_pServerCfgFile = NULL;
    46 	m_pServerCfgFwd = NULL;
    47 	m_CmdClient = 0;
    48 }
    49 
    50 ConCmdManager::~ConCmdManager()
    51 {
    52 	sm_trie_destroy(m_pCmds);
    53 	sm_trie_destroy(m_pCmdGrps);
    54 }
    55 
    56 void ConCmdManager::OnSourceModAllInitialized()
    57 {
    58 	g_PluginSys.AddPluginsListener(this);
    59 	g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
    60 	SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
    61 
    62 	ConCommandBase *pCmd = icvar->GetCommands();
    63 	const char *name;
    64 	while (pCmd)
    65 	{
    66 		if (pCmd->IsCommand())
    67 		{
    68 			name = pCmd->GetName();
    69 			if (strcmp(name, "exec") == 0)
    70 			{
    71 				m_pExecCmd = (ConCommand *)pCmd;
    72 				break;
    73 			}
    74 		}
    75 		pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
    76 	}
    77 
    78 	if (m_pExecCmd)
    79 	{
    80 		m_pServerCfgFile = (ConVar *)icvar->FindVar("servercfgfile");
    81 		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pExecCmd, this, &ConCmdManager::OnExecCmd, true);
    82 		m_pServerCfgFwd = g_Forwards.CreateForward("OnServerCfg", ET_Ignore, 0, NULL);
    83 	}
    84 }
    85 
    86 void ConCmdManager::OnSourceModShutdown()
    87 {
    88 	if (m_pExecCmd)
    89 	{
    90 		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pExecCmd, this, &ConCmdManager::OnExecCmd, true);
    91 		g_Forwards.ReleaseForward(m_pServerCfgFwd);
    92 		m_pServerCfgFwd = NULL;
    93 		m_pExecCmd = NULL;
    94 	}
    95 
    96 	/* All commands should already be removed by the time we're done */
    97 	SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
    98 	g_RootMenu.RemoveRootConsoleCommand("cmds", this);
    99 }
   100 
   101 void ConCmdManager::OnSourceModLevelChange(const char *mapName)
   102 {
   103 	m_bServerCfgDone = false;
   104 }
   105 
   106 void ConCmdManager::OnExecCmd()
   107 {
   108 	const char *arg = engine->Cmd_Argv(1);
   109 	const char *cfgfile = "server.cfg";
   110 
   111 	if (m_pServerCfgFile)
   112 	{
   113 		cfgfile = m_pServerCfgFile->GetString();
   114 	}
   115 
   116 	if (strcmp(arg, cfgfile) == 0)
   117 	{
   118 		engine->ServerCommand("sm cmds internal 1\n");
   119 	}
   120 }
   121 
   122 void ConCmdManager::NotifyExecDone(const char *file)
   123 {
   124 	if (file == NULL && !m_bServerCfgDone)
   125 	{
   126 		/* Server-cfg file */
   127 		m_bServerCfgDone = true;
   128 		m_pServerCfgFwd->Execute(NULL);
   129 	}
   130 }
   131 
   132 void ConCmdManager::RemoveConCmds(List<CmdHook *> &cmdlist, IPluginContext *pContext)
   133 {
   134 	List<CmdHook *>::iterator iter = cmdlist.begin();
   135 	CmdHook *pHook;
   136 
   137 	while (iter != cmdlist.end())
   138 	{
   139 		pHook = (*iter);
   140 		if (pHook->pf->GetParentContext() == pContext)
   141 		{
   142 			delete pHook->pAdmin;
   143 			delete pHook;
   144 			iter = cmdlist.erase(iter);
   145 		} else {
   146 			iter++;
   147 		}
   148 	}
   149 }
   150 
   151 void ConCmdManager::OnPluginDestroyed(IPlugin *plugin)
   152 {
   153 	CmdList *pList;
   154 	List<ConCmdInfo *> removed;
   155 	if (plugin->GetProperty("CommandList", (void **)&pList, true))
   156 	{
   157 		IPluginContext *pContext = plugin->GetBaseContext();
   158 		CmdList::iterator iter;
   159 		/* First pass!
   160 	 	 * Only bother if there's an actual command list on this plugin... 
   161 	 	 */
   162 		for (iter=pList->begin();
   163 			iter!=pList->end();
   164 			iter++)
   165 		{
   166 			PlCmdInfo &cmd = (*iter);
   167 			ConCmdInfo *pInfo = cmd.pInfo;
   168 
   169 			/* Has this chain already been fully cleaned/removed? */
   170 			if (removed.find(pInfo) != removed.end())
   171 			{
   172 				continue;
   173 			}
   174 
   175 			/* Remove any hooks from us on this command */
   176 			RemoveConCmds(pInfo->conhooks, pContext);
   177 			RemoveConCmds(pInfo->srvhooks, pContext);
   178 
   179 			/* See if there are still hooks */
   180 			if (pInfo->srvhooks.size())
   181 			{
   182 				continue;
   183 			}
   184 			if (pInfo->conhooks.size())
   185 			{
   186 				continue;
   187 			}
   188 
   189 			/* Remove the command, it should be safe now */
   190 			RemoveConCmd(pInfo);
   191 			removed.push_back(pInfo);
   192 		}
   193 		delete pList;
   194 	}
   195 }
   196 
   197 void CommandCallback()
   198 {
   199 	g_ConCmds.InternalDispatch();
   200 }
   201 
   202 void ConCmdManager::SetCommandClient(int client)
   203 {
   204 	m_CmdClient = client + 1;
   205 }
   206 
   207 ResultType ConCmdManager::DispatchClientCommand(int client, ResultType type)
   208 {
   209 	const char *cmd = engine->Cmd_Argv(0);
   210 	int args = engine->Cmd_Argc() - 1;
   211 
   212 	ConCmdInfo *pInfo;
   213 	if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   214 	{
   215 		cell_t result = type;
   216 		cell_t tempres = result;
   217 		List<CmdHook *>::iterator iter;
   218 		CmdHook *pHook;
   219 		for (iter=pInfo->conhooks.begin();
   220 			 iter!=pInfo->conhooks.end();
   221 			 iter++)
   222 		{
   223 			pHook = (*iter);
   224 			if (!pHook->pf->IsRunnable())
   225 			{
   226 				continue;
   227 			}
   228 			if (pHook->pAdmin && !CheckAccess(client, cmd, pHook->pAdmin))
   229 			{
   230 				if (result < Pl_Handled)
   231 				{
   232 					result = Pl_Handled;
   233 				}
   234 				continue;
   235 			}
   236 			pHook->pf->PushCell(client);
   237 			pHook->pf->PushCell(args);
   238 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   239 			{
   240 				if (tempres > result)
   241 				{
   242 					result = tempres;
   243 				}
   244 				if (result == Pl_Stop)
   245 				{
   246 					break;
   247 				}
   248 			}
   249 		}
   250 		type = (ResultType)result;
   251 	}
   252 
   253 	return type;
   254 }
   255 
   256 void ConCmdManager::InternalDispatch()
   257 {
   258 	/**
   259 	 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
   260 	 * OR this dispatch.  They will NEVER go through both.
   261 	 * -- 
   262 	 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
   263 	 */
   264 
   265 	const char *cmd = engine->Cmd_Argv(0);
   266 	ConCmdInfo *pInfo;
   267 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   268 	{
   269 		return;
   270 	}
   271 
   272 	cell_t result = Pl_Continue;
   273 	int args = engine->Cmd_Argc() - 1;
   274 
   275 	List<CmdHook *>::iterator iter;
   276 	CmdHook *pHook;
   277 
   278 	/* Execute server-only commands if viable */
   279 	if (m_CmdClient == 0 && pInfo->srvhooks.size())
   280 	{
   281 		cell_t tempres = result;
   282 		for (iter=pInfo->srvhooks.begin();
   283 			 iter!=pInfo->srvhooks.end();
   284 			 iter++)
   285 		{
   286 			pHook = (*iter);
   287 			if (!pHook->pf->IsRunnable())
   288 			{
   289 				continue;
   290 			}
   291 			pHook->pf->PushCell(args);
   292 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   293 			{
   294 				if (tempres > result)
   295 				{
   296 					result = tempres;
   297 				}
   298 				if (result == Pl_Stop)
   299 				{
   300 					break;
   301 				}
   302 			}
   303 		}
   304 	
   305 		/* Check if there's an early stop */
   306 		if (result >= Pl_Stop)
   307 		{
   308 			if (!pInfo->sourceMod)
   309 			{
   310 				RETURN_META(MRES_SUPERCEDE);
   311 			}
   312 			return;
   313 		}
   314 	}
   315 
   316 	/* Execute console commands */
   317 	if (pInfo->conhooks.size())
   318 	{
   319 		cell_t tempres = result;
   320 		for (iter=pInfo->conhooks.begin();
   321 			iter!=pInfo->conhooks.end();
   322 			iter++)
   323 		{
   324 			pHook = (*iter);
   325 			if (!pHook->pf->IsRunnable())
   326 			{
   327 				continue;
   328 			}
   329 			if (m_CmdClient 
   330 				&& pHook->pAdmin
   331 				&& !CheckAccess(m_CmdClient, cmd, pHook->pAdmin))
   332 			{
   333 				if (result < Pl_Handled)
   334 				{
   335 					result = Pl_Handled;
   336 				}
   337 				continue;
   338 			}
   339 
   340 			/* On a listen server, sometimes the server host's client index can be set as 0.
   341 			 * So index 1 is passed to the command callback to correct this potential problem.
   342 			 */
   343 			if (m_CmdClient == 0 && !engine->IsDedicatedServer())
   344 			{
   345 				pHook->pf->PushCell(1);
   346 			} else {
   347 				pHook->pf->PushCell(m_CmdClient);
   348 			}
   349 
   350 			pHook->pf->PushCell(args);
   351 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   352 			{
   353 				if (tempres > result)
   354 				{
   355 					result = tempres;
   356 				}
   357 				if (result == Pl_Stop)
   358 				{
   359 					break;
   360 				}
   361 			}
   362 		}
   363 	}
   364 
   365 	if (result >= Pl_Handled)
   366 	{
   367 		if (!pInfo->sourceMod)
   368 		{
   369 			RETURN_META(MRES_SUPERCEDE);
   370 		}
   371 		return;
   372 	}
   373 }
   374 
   375 bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin)
   376 {
   377 	FlagBits cmdflags = pAdmin->eflags;
   378 	if (cmdflags == 0)
   379 	{
   380 		return true;
   381 	}
   382 
   383 	/* If running listen server, then client 1 is the server host and should have 'root' access */
   384 	if (client == 1 && !engine->IsDedicatedServer())
   385 	{
   386 		return true;
   387 	}
   388 
   389 	CPlayer *player = g_Players.GetPlayerByIndex(client);
   390 	if (!player)
   391 	{
   392 		return false;
   393 	}
   394 
   395 	AdminId adm = player->GetAdminId();
   396 	if (adm != INVALID_ADMIN_ID)
   397 	{
   398 		FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective);
   399 
   400 		/* root knows all, WHOA */
   401 		if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT)
   402 		{
   403 			return true;
   404 		}
   405 
   406 		/* See if our other flags match */
   407 		if ((bits & cmdflags) == cmdflags)
   408 		{
   409 			return true;
   410 		}
   411 
   412 		/* Check for overrides */
   413 		unsigned int groups = g_Admins.GetAdminGroupCount(adm);
   414 		GroupId gid;
   415 		OverrideRule rule;
   416 		bool override = false;
   417 		for (unsigned int i=0; i<groups; i++)
   418 		{
   419 			gid = g_Admins.GetAdminGroup(adm, i, NULL);
   420 			/* First get group-level override */
   421 			override = g_Admins.GetGroupCommandOverride(gid, cmd, Override_CommandGroup, &rule);
   422 			/* Now get the specific command override */
   423 			if (g_Admins.GetGroupCommandOverride(gid, cmd, Override_Command, &rule))
   424 			{
   425 				override = true;
   426 			}
   427 			if (override)
   428 			{
   429 				if (rule == Command_Allow)
   430 				{
   431 					return true;
   432 				} else if (rule == Command_Deny) {
   433 					break;
   434 				}
   435 			}
   436 		}
   437 	}
   438 
   439 	if (player->IsFakeClient()
   440 		|| player->GetEdict() == NULL)
   441 	{
   442 		return false;
   443 	}
   444 	
   445 	/* If we got here, the command failed... */
   446 	char buffer[128];
   447 	if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
   448 		!= Trans_Okay)
   449 	{
   450 		snprintf(buffer, sizeof(buffer), "You do not have access to this command");
   451 	}
   452 
   453 	unsigned int replyto = g_ChatTriggers.GetReplyTo();
   454 	if (replyto == SM_REPLY_CONSOLE)
   455 	{
   456 		char fullbuffer[192];
   457 		snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
   458 		engine->ClientPrintf(player->GetEdict(), fullbuffer);
   459 	} else if (replyto == SM_REPLY_CHAT) {
   460 		char fullbuffer[192];
   461 		snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
   462 		g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
   463 	}
   464 	
   465 	return false;
   466 }
   467 
   468 bool ConCmdManager::AddConsoleCommand(IPluginFunction *pFunction, 
   469 									   const char *name, 
   470 									   const char *description, 
   471 									   int flags)
   472 {
   473 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   474 
   475 	if (!pInfo)
   476 	{
   477 		return false;
   478 	}
   479 
   480 	CmdHook *pHook = new CmdHook();
   481 
   482 	pHook->pf = pFunction;
   483 	if (description && description[0])
   484 	{
   485 		pHook->helptext.assign(description);
   486 	}
   487 	pInfo->conhooks.push_back(pHook);
   488 
   489 	/* Add to the plugin */
   490 	CmdList *pList;
   491 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   492 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   493 	{
   494 		pList = new CmdList();
   495 		pPlugin->SetProperty("CommandList", pList);
   496 	}
   497 	PlCmdInfo info;
   498 	info.pInfo = pInfo;
   499 	info.type = Cmd_Console;
   500 	info.pHook = pHook;
   501 	AddToPlCmdList(pList, info);
   502 
   503 	return true;
   504 }
   505 
   506 bool ConCmdManager::AddAdminCommand(IPluginFunction *pFunction, 
   507 									 const char *name, 
   508 									 const char *group,
   509 									 int adminflags,
   510 									 const char *description, 
   511 									 int flags)
   512 {
   513 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   514 
   515 	if (!pInfo)
   516 	{
   517 		return false;
   518 	}
   519 
   520 	CmdHook *pHook = new CmdHook();
   521 	AdminCmdInfo *pAdmin = new AdminCmdInfo();
   522 
   523 	pHook->pf = pFunction;
   524 	if (description && description[0])
   525 	{
   526 		pHook->helptext.assign(description);
   527 	}
   528 	pHook->pAdmin = pAdmin;
   529 
   530 	void *object;
   531 	int grpid;
   532 	if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
   533 	{
   534 		grpid = m_Strings.AddString(group);
   535 		sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
   536 	} else {
   537 		grpid = (int)object;
   538 	}
   539 
   540 	pAdmin->cmdGrpId = grpid;
   541 	pAdmin->flags = adminflags;
   542 
   543 	/* First get the command group override, if any */
   544 	bool override = g_Admins.GetCommandOverride(group, 
   545 		Override_CommandGroup,
   546 		&(pAdmin->eflags));
   547 
   548 	/* Next get the command override, if any */
   549 	if (g_Admins.GetCommandOverride(name,
   550 		Override_Command,
   551 		&(pAdmin->eflags)))
   552 	{
   553 		override = true;
   554 	}
   555 
   556 	/* Assign normal flags if there were no overrides */
   557 	if (!override)
   558 	{
   559 		pAdmin->eflags = pAdmin->flags;
   560 	}
   561 
   562 	/* Finally, add the hook */
   563 	pInfo->conhooks.push_back(pHook);
   564 
   565 	/* Now add to the plugin */
   566 	CmdList *pList;
   567 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   568 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   569 	{
   570 		pList = new CmdList();
   571 		pPlugin->SetProperty("CommandList", pList);
   572 	}
   573 	PlCmdInfo info;
   574 	info.pInfo = pInfo;
   575 	info.type = Cmd_Admin;
   576 	info.pHook = pHook;
   577 	AddToPlCmdList(pList, info);
   578 
   579 	return true;
   580 }
   581 
   582 bool ConCmdManager::AddServerCommand(IPluginFunction *pFunction, 
   583 									  const char *name, 
   584 									  const char *description, 
   585 									  int flags)
   586 
   587 {
   588 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   589 
   590 	if (!pInfo)
   591 	{
   592 		return false;
   593 	}
   594 
   595 	CmdHook *pHook = new CmdHook();
   596 
   597 	pHook->pf = pFunction;
   598 	if (description && description[0])
   599 	{
   600 		pHook->helptext.assign(description);
   601 	}
   602 
   603 	pInfo->srvhooks.push_back(pHook);
   604 
   605 	/* Add to the plugin */
   606 	CmdList *pList;
   607 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   608 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   609 	{
   610 		pList = new CmdList();
   611 		pPlugin->SetProperty("CommandList", pList);
   612 	}
   613 	PlCmdInfo info;
   614 	info.pInfo = pInfo;
   615 	info.type = Cmd_Server;
   616 	info.pHook = pHook;
   617 	AddToPlCmdList(pList, info);
   618 
   619 	return true;
   620 }
   621 
   622 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
   623 {
   624 	CmdList::iterator iter = pList->begin();
   625 	bool inserted = false;
   626 	const char *orig = NULL;
   627 
   628 	orig = info.pInfo->pCmd->GetName();
   629 
   630 	/* Insert this into the help list, SORTED alphabetically. */
   631 	while (iter != pList->end())
   632 	{
   633 		PlCmdInfo &obj = (*iter);
   634 		const char *cmd = obj.pInfo->pCmd->GetName();
   635 		if (strcmp(orig, cmd) < 0)
   636 		{
   637 			pList->insert(iter, info);
   638 			inserted = true;
   639 			break;
   640 		}
   641 		iter++;
   642 	}
   643 
   644 	if (!inserted)
   645 	{
   646 		pList->push_back(info);
   647 	}
   648 }
   649 
   650 void ConCmdManager::AddToCmdList(ConCmdInfo *info)
   651 {
   652 	List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
   653 	ConCmdInfo *pInfo;
   654 	bool inserted = false;
   655 	const char *orig = NULL;
   656 
   657 	orig = info->pCmd->GetName();
   658 
   659 	/* Insert this into the help list, SORTED alphabetically. */
   660 	while (iter != m_CmdList.end())
   661 	{
   662 		const char *cmd = NULL;
   663 		pInfo = (*iter);
   664 		cmd = pInfo->pCmd->GetName();
   665 		if (strcmp(orig, cmd) < 0)
   666 		{
   667 			m_CmdList.insert(iter, info);
   668 			inserted = true;
   669 			break;
   670 		}
   671 		iter++;
   672 	}
   673 
   674 	if (!inserted)
   675 	{
   676 		m_CmdList.push_back(info);
   677 	}
   678 }
   679 
   680 void ConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits)
   681 {
   682 	ConCmdInfo *pInfo;
   683 
   684 	if (type == Override_Command)
   685 	{
   686 		if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   687 		{
   688 			return;
   689 		}
   690 
   691 		List<CmdHook *>::iterator iter;
   692 		CmdHook *pHook;
   693 
   694 		for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++)
   695 		{
   696 			pHook = (*iter);
   697 			if (pHook->pAdmin)
   698 			{
   699 				if (bits)
   700 				{
   701 					pHook->pAdmin->eflags = bits;
   702 				} else {
   703 					pHook->pAdmin->eflags = pHook->pAdmin->flags;
   704 				}
   705 			}
   706 		}
   707 	} else if (type == Override_CommandGroup) {
   708 		void *object;
   709 		if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object))
   710 		{
   711 			return;
   712 		}
   713 		int grpid = (int)object;
   714 
   715 		/* This is bad :( loop through all commands */
   716 		List<ConCmdInfo *>::iterator iter;
   717 		CmdHook *pHook;
   718 		for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++)
   719 		{
   720 			pInfo = (*iter);
   721 			for (List<CmdHook *>::iterator citer=pInfo->conhooks.begin();
   722 				 citer!=pInfo->conhooks.end();
   723 				 citer++)
   724 			{
   725 				pHook = (*citer);
   726 				if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid)
   727 				{
   728 					if (bits)
   729 					{
   730 						pHook->pAdmin->eflags = bits;
   731 					} else {
   732 						pHook->pAdmin->eflags = pHook->pAdmin->flags;
   733 					}
   734 				}
   735 			}
   736 		}
   737 	}
   738 }
   739 
   740 void ConCmdManager::RemoveConCmd(ConCmdInfo *info)
   741 {
   742 	/* Remove console-specific information
   743 	 * This should always be true as of right now
   744 	 */
   745 	if (info->pCmd)
   746 	{
   747 		/* Remove from the trie */
   748 		sm_trie_delete(m_pCmds, info->pCmd->GetName());
   749 
   750 		if (info->sourceMod)
   751 		{
   752 			/* Unlink from SourceMM */
   753 			g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
   754 			/* Delete the command's memory */
   755 			char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
   756 			char *new_name = const_cast<char *>(info->pCmd->GetName());
   757 			delete [] new_help;
   758 			delete [] new_name;
   759 			delete info->pCmd;
   760 		} else {
   761 			/* Remove the external hook */
   762 			SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
   763 		}
   764 	}
   765 	
   766 	/* Remove from list */
   767 	m_CmdList.remove(info);
   768 
   769 	delete info;
   770 }
   771 
   772 bool ConCmdManager::LookForSourceModCommand(const char *cmd)
   773 {
   774 	ConCmdInfo *pInfo;
   775 
   776 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   777 	{
   778 		return false;
   779 	}
   780 
   781 	return pInfo->sourceMod && (pInfo->conhooks.size() > 0);
   782 }
   783 
   784 ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
   785 {
   786 	ConCmdInfo *pInfo;
   787 	if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
   788 	{
   789 		pInfo = new ConCmdInfo();
   790 		/* Find the commandopan */
   791 		ConCommandBase *pBase = icvar->GetCommands();
   792 		ConCommand *pCmd = NULL;
   793 		while (pBase)
   794 		{
   795 			if (strcmp(pBase->GetName(), name) == 0)
   796 			{
   797 				/* Don't want to return convar with same name */
   798 				if (!pBase->IsCommand())
   799 				{
   800 					return NULL;
   801 				}
   802 
   803 				pCmd = (ConCommand *)pBase;
   804 				break;
   805 			}
   806 			pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   807 		}
   808 
   809 		if (!pCmd)
   810 		{
   811 			/* Note that we have to duplicate because the source might not be 
   812 			 * a static string, and these expect static memory.
   813 			 */
   814 			if (!description)
   815 			{
   816 				description = "";
   817 			}
   818 			char *new_name = sm_strdup(name);
   819 			char *new_help = sm_strdup(description);
   820 			pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
   821 			pInfo->sourceMod = true;
   822 		} else {
   823 			SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
   824 		}
   825 
   826 		pInfo->pCmd = pCmd;
   827 
   828 		sm_trie_insert(m_pCmds, name, pInfo);
   829 		AddToCmdList(pInfo);
   830 	}
   831 
   832 	return pInfo;
   833 }
   834 
   835 void ConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
   836 {
   837 	if (argcount >= 3)
   838 	{
   839 		const char *text = engine->Cmd_Argv(2);
   840 
   841 		if (strcmp(text, "internal") == 0)
   842 		{
   843 			const char *num = engine->Cmd_Argv(3);
   844 			if (atoi(num) == 1)
   845 			{
   846 				NotifyExecDone(NULL);
   847 			}
   848 			return;
   849 		}
   850 
   851 		int id = atoi(text);
   852 		CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
   853 
   854 		if (!pPlugin)
   855 		{
   856 			g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
   857 			return;
   858 		}
   859 
   860 		const sm_plugininfo_t *plinfo = pPlugin->GetPublicInfo();
   861 		const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : pPlugin->GetFilename();
   862 
   863 		CmdList *pList;
   864 		if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   865 		{
   866 			g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
   867 			return;
   868 		}
   869 		if (!pList->size())
   870 		{
   871 			g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
   872 			return;
   873 		}
   874 
   875 		CmdList::iterator iter;
   876 		const char *type = NULL;
   877 		const char *name;
   878 		const char *help;
   879 		g_RootMenu.ConsolePrint("[SM] Listing %d commands for: %s", pList->size(), plname);
   880 		g_RootMenu.ConsolePrint("  %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
   881 		for (iter=pList->begin();
   882 			 iter!=pList->end();
   883 			 iter++, id++)
   884 		{
   885 			PlCmdInfo &cmd = (*iter);
   886 			if (cmd.type == Cmd_Server)
   887 			{
   888 				type = "server";
   889 			} else if (cmd.type == Cmd_Console) {
   890 				type = "console";
   891 			} else if (cmd.type == Cmd_Admin) {
   892 				type = "admin";
   893 			}
   894 			name = cmd.pInfo->pCmd->GetName();
   895 			if (cmd.pHook->helptext.size())
   896 			{
   897 				help = cmd.pHook->helptext.c_str();
   898 			} else {
   899 				help = cmd.pInfo->pCmd->GetHelpText();
   900 			}
   901 			g_RootMenu.ConsolePrint("  %-17.16s %-12.11s %s", name, type, help);		
   902 		}
   903 
   904 		return;
   905 	}
   906 
   907 	g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
   908 }
   909 
   910 static int g_yam_state = 0;
   911 void _YamState(int state)
   912 {
   913 	if (state == 0)
   914 	{
   915 		g_RootMenu.ConsolePrint("Welcome to the SourceMod Text Adventure.");
   916 		g_RootMenu.ConsolePrint("Type sm_text to see the last message.");
   917 		g_RootMenu.ConsolePrint("Type sm_text <word> to follow a capital word.");
   918 		g_RootMenu.ConsolePrint("-------------------------------");
   919 		g_RootMenu.ConsolePrint("You are at VALVE HEADQUARTERS.");
   920 		g_RootMenu.ConsolePrint("To your LEFT is BAILOPAN, rearranging the letters to spell");
   921 		g_RootMenu.ConsolePrint("\"A HARD VEAL QUEST\".  FORWARD is the entrance to the building.");
   922 		g_RootMenu.ConsolePrint("To your RIGHT is your last chance to flee in terror.");
   923 	} else if (state == 1) {
   924 		g_RootMenu.ConsolePrint("BAILOPAN tells you that his name his pronounced");
   925 		g_RootMenu.ConsolePrint("\"bye low pahn,\" not \"bay low pan.\"  Do you ");
   926 		g_RootMenu.ConsolePrint("MOCK him, or NOD quietly?");
   927 	} else if (state == 2) {
   928 		g_RootMenu.ConsolePrint("You enter the Valve building.  You hear screams coming from within.");
   929 		g_RootMenu.ConsolePrint("A grotesque figure lumbers up to greet you; it is Gabe Newell.");
   930 		g_RootMenu.ConsolePrint("\"Welcome,\" he belches, \"to my lair.\"");
   931 		g_RootMenu.ConsolePrint("Do you SHAKE Gaben's hand, WALK past him, or OFFER a donut?");
   932 	} else if (state == 3) {
   933 		g_RootMenu.ConsolePrint("You walk into the break room.  Alfred \"Adolf\" Reynolds and");
   934 		g_RootMenu.ConsolePrint("Yahn \"Yeti\" Bernier are discussing something (you overhear ");
   935 		g_RootMenu.ConsolePrint("the phrase \"and next Steam update, here's what we should break\").");
   936 		g_RootMenu.ConsolePrint("Should you DIE in a fire, REPORT a bug, REQUEST a feature, or ");
   937 		g_RootMenu.ConsolePrint("SPRAY them with butter?");
   938 	}
   939 	g_yam_state = state;
   940 }
   941 
   942 void _IntExt_CallYams()
   943 {
   944 	const char *arg = engine->Cmd_Argv(1);
   945 
   946 	/* should be impossible */
   947 	if (!arg || arg[0] == '\0')
   948 	{
   949 		_YamState(g_yam_state);
   950 	}
   951 
   952 	if (g_yam_state == 1)
   953 	{
   954 		if (strcasecmp(arg, "mock") == 0)
   955 		{
   956 			g_RootMenu.ConsolePrint("You mock BAILOPAN's pronunciation.  In a fit of rage, ");
   957 			g_RootMenu.ConsolePrint("he sticks an INT 3 call into your chest, rendering you broken.");
   958 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   959 			g_yam_state = 0;
   960 		} else if (strcasecmp(arg, "nod") == 0) {
   961 			g_RootMenu.ConsolePrint("You nod quietly, and then slowly back away into the Valve headquarters.");
   962 			_YamState(2);
   963 		} else {
   964 			g_RootMenu.ConsolePrint("Commands are MOCK and NOD.");
   965 		}
   966 	} else if (g_yam_state == 3) {
   967 		if (strcasecmp(arg, "report") == 0)
   968 		{
   969 			g_RootMenu.ConsolePrint("You report a bug to Alfred and Yeti.  Immediately, both fall asleep.");
   970 			g_RootMenu.ConsolePrint("You decay in the break room for two years while they sleep.");
   971 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   972 			g_yam_state = 0;
   973 		} else if (strcasecmp(arg, "request") == 0) {
   974 			g_RootMenu.ConsolePrint("You request a feature to Alfred and Yeti.  They both mutter something");
   975 			g_RootMenu.ConsolePrint("about it being implemented \"soon.\"  Then, by accident, someone sends");
   976 			g_RootMenu.ConsolePrint("a message over \"Friends.\"  The entire building catches fire.");
   977 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   978 			g_yam_state = 0;
   979 		} else if (strcasecmp(arg, "die") == 0) {
   980 			g_RootMenu.ConsolePrint("For no reason, you suddenly catch fire.  Alfred and Yeti find this");
   981 			g_RootMenu.ConsolePrint("deeply disturbing, and cover your flaming corpse with Episode 2");
   982 			g_RootMenu.ConsolePrint("advertisements.  Coming soon, with Team Fortress 2, and Portal!");
   983 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   984 			g_yam_state = 0;
   985 		} else if (strcasecmp(arg, "spray") == 0) {
   986 			g_RootMenu.ConsolePrint("You spray Alfred and Yeti with butter.  Like Jack Thompson to an");
   987 			g_RootMenu.ConsolePrint("ambulance, Gabe Newell instantly appears and devours them both.");
   988 			g_RootMenu.ConsolePrint("You run away, just in time, as Gabe Newell explodes, registering ");
   989 			g_RootMenu.ConsolePrint("a 5.6 earthquake.  Outside, world peace is achieved.");
   990 			g_RootMenu.ConsolePrint("YOU HAVE WON.");
   991 			g_yam_state = 0;
   992 		}
   993 	} else if (g_yam_state == 2) {
   994 		if (strcasecmp(arg, "shake") == 0)
   995 		{
   996 			g_RootMenu.ConsolePrint("You shake Gaben's hand.  It is a terrifying and disgusting experience.");
   997 			g_RootMenu.ConsolePrint("However, you survive, and continue on.");
   998 			_YamState(3);
   999 		} else if (strcasecmp(arg, "offer") == 0) {
  1000 			g_RootMenu.ConsolePrint("You offer Gabe Newell one (1) donut.  With a gleam in his eyes, ");
  1001 			g_RootMenu.ConsolePrint("he picks you up and devours you whole.");
  1002 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
  1003 			g_yam_state = 0;
  1004 		} else if (strcasecmp(arg, "walk") == 0) {
  1005 			g_RootMenu.ConsolePrint("You walk past Gabe Newell.  He can't keep up with your pace!");
  1006 			_YamState(3);
  1007 		} else {
  1008 			g_RootMenu.ConsolePrint("Commands are SHAKE, OFFER, and WALK.");
  1009 		}
  1010 	} else if (g_yam_state == 0) {
  1011 		if (strcasecmp(arg, "left") == 0)
  1012 		{
  1013 			_YamState(1);
  1014 		} else if (strcasecmp(arg, "right") == 0) {
  1015 			g_RootMenu.ConsolePrint("You run away from the Valve headquarters in sheer terror.");
  1016 			g_RootMenu.ConsolePrint("While running, you smash into an unknown person, who turns out to be your soul mate.");
  1017 			g_RootMenu.ConsolePrint("You marry and raise a family of 3 kids.");
  1018 			g_RootMenu.ConsolePrint("Many years later, you look back, and realize this was your best choice.");
  1019 			g_RootMenu.ConsolePrint("YOU HAVE WON.");
  1020 			g_yam_state = 0;
  1021 		} else if (strcasecmp(arg, "forward") == 0) {
  1022 			_YamState(2);
  1023 		} else if (arg[0] != '\0') {
  1024 			g_RootMenu.ConsolePrint("Commands are FORWARD, LEFT, and RIGHT.");
  1025 		}
  1026 	}
  1027 }
  1028 
  1029 void _IntExt_EnableYams()
  1030 {
  1031 	static ConCommand *pCmd = NULL;
  1032 	if (!pCmd)
  1033 	{
  1034 		pCmd = new ConCommand("sm_text", _IntExt_CallYams, "Fountain of Yams Adventure Game", FCVAR_GAMEDLL);
  1035 		g_RootMenu.ConsolePrint("Something is now enabled...");
  1036 	} else {
  1037 		g_RootMenu.ConsolePrint("Something is already enabled...");
  1038 	}
  1039 }