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