core/ConCmdManager.cpp
author Phong Dang <teame06@gmail.com>
Sun Jul 29 19:20:42 2007 +0000 (2007-07-29)
changeset 1211 990912ee1e1e
parent 1119 9e3486386c04
child 1231 17badaf66ce4
permissions -rw-r--r--
Added amb<536> requested for using sm_kick without putting quotes around reason.
     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::CheckCommandAccess(int client, const char *cmd, FlagBits cmdflags)
   310 {
   311 	if (cmdflags == 0)
   312 	{
   313 		return true;
   314 	}
   315 
   316 	/* If running listen server, then client 1 is the server host and should have 'root' access */
   317 	if (client == 1 && !engine->IsDedicatedServer())
   318 	{
   319 		return true;
   320 	}
   321 
   322 	CPlayer *player = g_Players.GetPlayerByIndex(client);
   323 	if (!player 
   324 		|| player->GetEdict() == NULL
   325 		|| player->IsFakeClient())
   326 	{
   327 		return false;
   328 	}
   329 
   330 	AdminId adm = player->GetAdminId();
   331 	if (adm != INVALID_ADMIN_ID)
   332 	{
   333 		FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective);
   334 
   335 		/* root knows all, WHOA */
   336 		if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT)
   337 		{
   338 			return true;
   339 		}
   340 
   341 		/* See if our other flags match */
   342 		if ((bits & cmdflags) == cmdflags)
   343 		{
   344 			return true;
   345 		}
   346 
   347 		/* Check for overrides */
   348 		unsigned int groups = g_Admins.GetAdminGroupCount(adm);
   349 		GroupId gid;
   350 		OverrideRule rule;
   351 		bool override = false;
   352 		for (unsigned int i=0; i<groups; i++)
   353 		{
   354 			gid = g_Admins.GetAdminGroup(adm, i, NULL);
   355 			/* First get group-level override */
   356 			override = g_Admins.GetGroupCommandOverride(gid, cmd, Override_CommandGroup, &rule);
   357 			/* Now get the specific command override */
   358 			if (g_Admins.GetGroupCommandOverride(gid, cmd, Override_Command, &rule))
   359 			{
   360 				override = true;
   361 			}
   362 			if (override)
   363 			{
   364 				if (rule == Command_Allow)
   365 				{
   366 					return true;
   367 				} else if (rule == Command_Deny) {
   368 					break;
   369 				}
   370 			}
   371 		}
   372 	}
   373 
   374 	return false;
   375 }
   376 
   377 bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin)
   378 {
   379 	if (CheckCommandAccess(client, cmd, pAdmin->eflags))
   380 	{
   381 		return true;
   382 	}
   383 
   384 	edict_t *pEdict = engine->PEntityOfEntIndex(client);
   385 	
   386 	/* If we got here, the command failed... */
   387 	char buffer[128];
   388 	if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
   389 		!= Trans_Okay)
   390 	{
   391 		snprintf(buffer, sizeof(buffer), "You do not have access to this command");
   392 	}
   393 
   394 	unsigned int replyto = g_ChatTriggers.GetReplyTo();
   395 	if (replyto == SM_REPLY_CONSOLE)
   396 	{
   397 		char fullbuffer[192];
   398 		snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
   399 		engine->ClientPrintf(pEdict, fullbuffer);
   400 	} else if (replyto == SM_REPLY_CHAT) {
   401 		char fullbuffer[192];
   402 		snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
   403 		g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
   404 	}
   405 	
   406 	return false;
   407 }
   408 
   409 bool ConCmdManager::AddConsoleCommand(IPluginFunction *pFunction, 
   410 									   const char *name, 
   411 									   const char *description, 
   412 									   int flags)
   413 {
   414 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   415 
   416 	if (!pInfo)
   417 	{
   418 		return false;
   419 	}
   420 
   421 	CmdHook *pHook = new CmdHook();
   422 
   423 	pHook->pf = pFunction;
   424 	if (description && description[0])
   425 	{
   426 		pHook->helptext.assign(description);
   427 	}
   428 	pInfo->conhooks.push_back(pHook);
   429 
   430 	/* Add to the plugin */
   431 	CmdList *pList;
   432 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   433 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   434 	{
   435 		pList = new CmdList();
   436 		pPlugin->SetProperty("CommandList", pList);
   437 	}
   438 	PlCmdInfo info;
   439 	info.pInfo = pInfo;
   440 	info.type = Cmd_Console;
   441 	info.pHook = pHook;
   442 	AddToPlCmdList(pList, info);
   443 
   444 	return true;
   445 }
   446 
   447 bool ConCmdManager::AddAdminCommand(IPluginFunction *pFunction, 
   448 									 const char *name, 
   449 									 const char *group,
   450 									 int adminflags,
   451 									 const char *description, 
   452 									 int flags)
   453 {
   454 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   455 
   456 	if (!pInfo)
   457 	{
   458 		return false;
   459 	}
   460 
   461 	CmdHook *pHook = new CmdHook();
   462 	AdminCmdInfo *pAdmin = new AdminCmdInfo();
   463 
   464 	pHook->pf = pFunction;
   465 	if (description && description[0])
   466 	{
   467 		pHook->helptext.assign(description);
   468 	}
   469 	pHook->pAdmin = pAdmin;
   470 
   471 	void *object;
   472 	int grpid;
   473 	if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
   474 	{
   475 		grpid = m_Strings.AddString(group);
   476 		sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
   477 	} else {
   478 		grpid = (int)object;
   479 	}
   480 
   481 	pAdmin->cmdGrpId = grpid;
   482 	pAdmin->flags = adminflags;
   483 
   484 	/* First get the command group override, if any */
   485 	bool override = g_Admins.GetCommandOverride(group, 
   486 		Override_CommandGroup,
   487 		&(pAdmin->eflags));
   488 
   489 	/* Next get the command override, if any */
   490 	if (g_Admins.GetCommandOverride(name,
   491 		Override_Command,
   492 		&(pAdmin->eflags)))
   493 	{
   494 		override = true;
   495 	}
   496 
   497 	/* Assign normal flags if there were no overrides */
   498 	if (!override)
   499 	{
   500 		pAdmin->eflags = pAdmin->flags;
   501 	}
   502 
   503 	/* Finally, add the hook */
   504 	pInfo->conhooks.push_back(pHook);
   505 	pInfo->admin = *(pHook->pAdmin);
   506 
   507 	/* Now add to the plugin */
   508 	CmdList *pList;
   509 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   510 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   511 	{
   512 		pList = new CmdList();
   513 		pPlugin->SetProperty("CommandList", pList);
   514 	}
   515 	PlCmdInfo info;
   516 	info.pInfo = pInfo;
   517 	info.type = Cmd_Admin;
   518 	info.pHook = pHook;
   519 	AddToPlCmdList(pList, info);
   520 
   521 	return true;
   522 }
   523 
   524 bool ConCmdManager::AddServerCommand(IPluginFunction *pFunction, 
   525 									  const char *name, 
   526 									  const char *description, 
   527 									  int flags)
   528 
   529 {
   530 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   531 
   532 	if (!pInfo)
   533 	{
   534 		return false;
   535 	}
   536 
   537 	CmdHook *pHook = new CmdHook();
   538 
   539 	pHook->pf = pFunction;
   540 	if (description && description[0])
   541 	{
   542 		pHook->helptext.assign(description);
   543 	}
   544 
   545 	pInfo->srvhooks.push_back(pHook);
   546 
   547 	/* Add to the plugin */
   548 	CmdList *pList;
   549 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   550 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   551 	{
   552 		pList = new CmdList();
   553 		pPlugin->SetProperty("CommandList", pList);
   554 	}
   555 	PlCmdInfo info;
   556 	info.pInfo = pInfo;
   557 	info.type = Cmd_Server;
   558 	info.pHook = pHook;
   559 	AddToPlCmdList(pList, info);
   560 
   561 	return true;
   562 }
   563 
   564 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
   565 {
   566 	CmdList::iterator iter = pList->begin();
   567 	bool inserted = false;
   568 	const char *orig = NULL;
   569 
   570 	orig = info.pInfo->pCmd->GetName();
   571 
   572 	/* Insert this into the help list, SORTED alphabetically. */
   573 	while (iter != pList->end())
   574 	{
   575 		PlCmdInfo &obj = (*iter);
   576 		const char *cmd = obj.pInfo->pCmd->GetName();
   577 		if (strcmp(orig, cmd) < 0)
   578 		{
   579 			pList->insert(iter, info);
   580 			inserted = true;
   581 			break;
   582 		}
   583 		iter++;
   584 	}
   585 
   586 	if (!inserted)
   587 	{
   588 		pList->push_back(info);
   589 	}
   590 }
   591 
   592 void ConCmdManager::AddToCmdList(ConCmdInfo *info)
   593 {
   594 	List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
   595 	ConCmdInfo *pInfo;
   596 	bool inserted = false;
   597 	const char *orig = NULL;
   598 
   599 	orig = info->pCmd->GetName();
   600 
   601 	/* Insert this into the help list, SORTED alphabetically. */
   602 	while (iter != m_CmdList.end())
   603 	{
   604 		const char *cmd = NULL;
   605 		pInfo = (*iter);
   606 		cmd = pInfo->pCmd->GetName();
   607 		if (strcmp(orig, cmd) < 0)
   608 		{
   609 			m_CmdList.insert(iter, info);
   610 			inserted = true;
   611 			break;
   612 		}
   613 		iter++;
   614 	}
   615 
   616 	if (!inserted)
   617 	{
   618 		m_CmdList.push_back(info);
   619 	}
   620 }
   621 
   622 void ConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits)
   623 {
   624 	ConCmdInfo *pInfo;
   625 
   626 	if (type == Override_Command)
   627 	{
   628 		if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   629 		{
   630 			return;
   631 		}
   632 
   633 		List<CmdHook *>::iterator iter;
   634 		CmdHook *pHook;
   635 
   636 		for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++)
   637 		{
   638 			pHook = (*iter);
   639 			if (pHook->pAdmin)
   640 			{
   641 				if (bits)
   642 				{
   643 					pHook->pAdmin->eflags = bits;
   644 				} else {
   645 					pHook->pAdmin->eflags = pHook->pAdmin->flags;
   646 				}
   647 				pInfo->admin = *(pHook->pAdmin);
   648 			}
   649 		}
   650 	} else if (type == Override_CommandGroup) {
   651 		void *object;
   652 		if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object))
   653 		{
   654 			return;
   655 		}
   656 		int grpid = (int)object;
   657 
   658 		/* This is bad :( loop through all commands */
   659 		List<ConCmdInfo *>::iterator iter;
   660 		CmdHook *pHook;
   661 		for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++)
   662 		{
   663 			pInfo = (*iter);
   664 			for (List<CmdHook *>::iterator citer=pInfo->conhooks.begin();
   665 				 citer!=pInfo->conhooks.end();
   666 				 citer++)
   667 			{
   668 				pHook = (*citer);
   669 				if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid)
   670 				{
   671 					if (bits)
   672 					{
   673 						pHook->pAdmin->eflags = bits;
   674 					} else {
   675 						pHook->pAdmin->eflags = pHook->pAdmin->flags;
   676 					}
   677 					pInfo->admin = *(pHook->pAdmin);
   678 				}
   679 			}
   680 		}
   681 	}
   682 }
   683 
   684 void ConCmdManager::RemoveConCmd(ConCmdInfo *info)
   685 {
   686 	/* Remove console-specific information
   687 	 * This should always be true as of right now
   688 	 */
   689 	if (info->pCmd)
   690 	{
   691 		/* Remove from the trie */
   692 		sm_trie_delete(m_pCmds, info->pCmd->GetName());
   693 
   694 		if (info->sourceMod)
   695 		{
   696 			/* Unlink from SourceMM */
   697 			g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
   698 			/* Delete the command's memory */
   699 			char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
   700 			char *new_name = const_cast<char *>(info->pCmd->GetName());
   701 			delete [] new_help;
   702 			delete [] new_name;
   703 			delete info->pCmd;
   704 		} else {
   705 			/* Remove the external hook */
   706 			SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
   707 		}
   708 	}
   709 	
   710 	/* Remove from list */
   711 	m_CmdList.remove(info);
   712 
   713 	delete info;
   714 }
   715 
   716 bool ConCmdManager::LookForSourceModCommand(const char *cmd)
   717 {
   718 	ConCmdInfo *pInfo;
   719 
   720 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   721 	{
   722 		return false;
   723 	}
   724 
   725 	return pInfo->sourceMod && (pInfo->conhooks.size() > 0);
   726 }
   727 
   728 ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
   729 {
   730 	ConCmdInfo *pInfo;
   731 	if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
   732 	{
   733 		pInfo = new ConCmdInfo();
   734 		/* Find the commandopan */
   735 		ConCommandBase *pBase = icvar->GetCommands();
   736 		ConCommand *pCmd = NULL;
   737 		while (pBase)
   738 		{
   739 			if (strcmp(pBase->GetName(), name) == 0)
   740 			{
   741 				/* Don't want to return convar with same name */
   742 				if (!pBase->IsCommand())
   743 				{
   744 					return NULL;
   745 				}
   746 
   747 				pCmd = (ConCommand *)pBase;
   748 				break;
   749 			}
   750 			pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   751 		}
   752 
   753 		if (!pCmd)
   754 		{
   755 			/* Note that we have to duplicate because the source might not be 
   756 			 * a static string, and these expect static memory.
   757 			 */
   758 			if (!description)
   759 			{
   760 				description = "";
   761 			}
   762 			char *new_name = sm_strdup(name);
   763 			char *new_help = sm_strdup(description);
   764 			pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
   765 			pInfo->sourceMod = true;
   766 		} else {
   767 			SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
   768 		}
   769 
   770 		pInfo->pCmd = pCmd;
   771 
   772 		sm_trie_insert(m_pCmds, name, pInfo);
   773 		AddToCmdList(pInfo);
   774 	}
   775 
   776 	return pInfo;
   777 }
   778 
   779 void ConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
   780 {
   781 	if (argcount >= 3)
   782 	{
   783 		const char *text = engine->Cmd_Argv(2);
   784 
   785 		int id = atoi(text);
   786 		CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
   787 
   788 		if (!pPlugin)
   789 		{
   790 			g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
   791 			return;
   792 		}
   793 
   794 		const sm_plugininfo_t *plinfo = pPlugin->GetPublicInfo();
   795 		const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : pPlugin->GetFilename();
   796 
   797 		CmdList *pList;
   798 		if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   799 		{
   800 			g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
   801 			return;
   802 		}
   803 		if (!pList->size())
   804 		{
   805 			g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
   806 			return;
   807 		}
   808 
   809 		CmdList::iterator iter;
   810 		const char *type = NULL;
   811 		const char *name;
   812 		const char *help;
   813 		g_RootMenu.ConsolePrint("[SM] Listing %d commands for: %s", pList->size(), plname);
   814 		g_RootMenu.ConsolePrint("  %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
   815 		for (iter=pList->begin();
   816 			 iter!=pList->end();
   817 			 iter++, id++)
   818 		{
   819 			PlCmdInfo &cmd = (*iter);
   820 			if (cmd.type == Cmd_Server)
   821 			{
   822 				type = "server";
   823 			} else if (cmd.type == Cmd_Console) {
   824 				type = "console";
   825 			} else if (cmd.type == Cmd_Admin) {
   826 				type = "admin";
   827 			}
   828 			name = cmd.pInfo->pCmd->GetName();
   829 			if (cmd.pHook->helptext.size())
   830 			{
   831 				help = cmd.pHook->helptext.c_str();
   832 			} else {
   833 				help = cmd.pInfo->pCmd->GetHelpText();
   834 			}
   835 			g_RootMenu.ConsolePrint("  %-17.16s %-12.11s %s", name, type, help);		
   836 		}
   837 
   838 		return;
   839 	}
   840 
   841 	g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
   842 }
   843 
   844 static int g_yam_state = 0;
   845 void _YamState(int state)
   846 {
   847 	if (state == 0)
   848 	{
   849 		g_RootMenu.ConsolePrint("Welcome to the SourceMod Text Adventure.");
   850 		g_RootMenu.ConsolePrint("Type sm_text to see the last message.");
   851 		g_RootMenu.ConsolePrint("Type sm_text <word> to follow a capital word.");
   852 		g_RootMenu.ConsolePrint("-------------------------------");
   853 		g_RootMenu.ConsolePrint("You are at VALVE HEADQUARTERS.");
   854 		g_RootMenu.ConsolePrint("To your LEFT is BAILOPAN, rearranging the letters to spell");
   855 		g_RootMenu.ConsolePrint("\"A HARD VEAL QUEST\".  FORWARD is the entrance to the building.");
   856 		g_RootMenu.ConsolePrint("To your RIGHT is your last chance to flee in terror.");
   857 	} else if (state == 1) {
   858 		g_RootMenu.ConsolePrint("BAILOPAN tells you that his name his pronounced");
   859 		g_RootMenu.ConsolePrint("\"bye low pahn,\" not \"bay low pan.\"  Do you ");
   860 		g_RootMenu.ConsolePrint("MOCK him, or NOD quietly?");
   861 	} else if (state == 2) {
   862 		g_RootMenu.ConsolePrint("You enter the Valve building.  You hear screams coming from within.");
   863 		g_RootMenu.ConsolePrint("A grotesque figure lumbers up to greet you; it is Gabe Newell.");
   864 		g_RootMenu.ConsolePrint("\"Welcome,\" he belches, \"to my lair.\"");
   865 		g_RootMenu.ConsolePrint("Do you SHAKE Gaben's hand, WALK past him, or OFFER a donut?");
   866 	} else if (state == 3) {
   867 		g_RootMenu.ConsolePrint("You walk into the break room.  Alfred \"Adolf\" Reynolds and");
   868 		g_RootMenu.ConsolePrint("Yahn \"Yeti\" Bernier are discussing something (you overhear ");
   869 		g_RootMenu.ConsolePrint("the phrase \"and next Steam update, here's what we should break\").");
   870 		g_RootMenu.ConsolePrint("Should you DIE in a fire, REPORT a bug, REQUEST a feature, or ");
   871 		g_RootMenu.ConsolePrint("SPRAY them with butter?");
   872 	}
   873 	g_yam_state = state;
   874 }
   875 
   876 void _IntExt_CallYams()
   877 {
   878 	const char *arg = engine->Cmd_Argv(1);
   879 
   880 	/* should be impossible */
   881 	if (!arg || arg[0] == '\0')
   882 	{
   883 		_YamState(g_yam_state);
   884 	}
   885 
   886 	if (g_yam_state == 1)
   887 	{
   888 		if (strcasecmp(arg, "mock") == 0)
   889 		{
   890 			g_RootMenu.ConsolePrint("You mock BAILOPAN's pronunciation.  In a fit of rage, ");
   891 			g_RootMenu.ConsolePrint("he sticks an INT 3 call into your chest, rendering you broken.");
   892 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   893 			g_yam_state = 0;
   894 		} else if (strcasecmp(arg, "nod") == 0) {
   895 			g_RootMenu.ConsolePrint("You nod quietly, and then slowly back away into the Valve headquarters.");
   896 			_YamState(2);
   897 		} else {
   898 			g_RootMenu.ConsolePrint("Commands are MOCK and NOD.");
   899 		}
   900 	} else if (g_yam_state == 3) {
   901 		if (strcasecmp(arg, "report") == 0)
   902 		{
   903 			g_RootMenu.ConsolePrint("You report a bug to Alfred and Yeti.  Immediately, both fall asleep.");
   904 			g_RootMenu.ConsolePrint("You decay in the break room for two years while they sleep.");
   905 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   906 			g_yam_state = 0;
   907 		} else if (strcasecmp(arg, "request") == 0) {
   908 			g_RootMenu.ConsolePrint("You request a feature to Alfred and Yeti.  They both mutter something");
   909 			g_RootMenu.ConsolePrint("about it being implemented \"soon.\"  Then, by accident, someone sends");
   910 			g_RootMenu.ConsolePrint("a message over \"Friends.\"  The entire building catches fire.");
   911 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   912 			g_yam_state = 0;
   913 		} else if (strcasecmp(arg, "die") == 0) {
   914 			g_RootMenu.ConsolePrint("For no reason, you suddenly catch fire.  Alfred and Yeti find this");
   915 			g_RootMenu.ConsolePrint("deeply disturbing, and cover your flaming corpse with Episode 2");
   916 			g_RootMenu.ConsolePrint("advertisements.  Coming soon, with Team Fortress 2, and Portal!");
   917 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   918 			g_yam_state = 0;
   919 		} else if (strcasecmp(arg, "spray") == 0) {
   920 			g_RootMenu.ConsolePrint("You spray Alfred and Yeti with butter.  Like Jack Thompson to an");
   921 			g_RootMenu.ConsolePrint("ambulance, Gabe Newell instantly appears and devours them both.");
   922 			g_RootMenu.ConsolePrint("You run away, just in time, as Gabe Newell explodes, registering ");
   923 			g_RootMenu.ConsolePrint("a 5.6 earthquake.  Outside, world peace is achieved.");
   924 			g_RootMenu.ConsolePrint("YOU HAVE WON.");
   925 			g_yam_state = 0;
   926 		}
   927 	} else if (g_yam_state == 2) {
   928 		if (strcasecmp(arg, "shake") == 0)
   929 		{
   930 			g_RootMenu.ConsolePrint("You shake Gaben's hand.  It is a terrifying and disgusting experience.");
   931 			g_RootMenu.ConsolePrint("However, you survive, and continue on.");
   932 			_YamState(3);
   933 		} else if (strcasecmp(arg, "offer") == 0) {
   934 			g_RootMenu.ConsolePrint("You offer Gabe Newell one (1) donut.  With a gleam in his eyes, ");
   935 			g_RootMenu.ConsolePrint("he picks you up and devours you whole.");
   936 			g_RootMenu.ConsolePrint("YOU HAVE DIED.  GAME OVER.");
   937 			g_yam_state = 0;
   938 		} else if (strcasecmp(arg, "walk") == 0) {
   939 			g_RootMenu.ConsolePrint("You walk past Gabe Newell.  He can't keep up with your pace!");
   940 			_YamState(3);
   941 		} else {
   942 			g_RootMenu.ConsolePrint("Commands are SHAKE, OFFER, and WALK.");
   943 		}
   944 	} else if (g_yam_state == 0) {
   945 		if (strcasecmp(arg, "left") == 0)
   946 		{
   947 			_YamState(1);
   948 		} else if (strcasecmp(arg, "right") == 0) {
   949 			g_RootMenu.ConsolePrint("You run away from the Valve headquarters in sheer terror.");
   950 			g_RootMenu.ConsolePrint("While running, you smash into an unknown person, who turns out to be your soul mate.");
   951 			g_RootMenu.ConsolePrint("You marry and raise a family of 3 kids.");
   952 			g_RootMenu.ConsolePrint("Many years later, you look back, and realize this was your best choice.");
   953 			g_RootMenu.ConsolePrint("YOU HAVE WON.");
   954 			g_yam_state = 0;
   955 		} else if (strcasecmp(arg, "forward") == 0) {
   956 			_YamState(2);
   957 		} else if (arg[0] != '\0') {
   958 			g_RootMenu.ConsolePrint("Commands are FORWARD, LEFT, and RIGHT.");
   959 		}
   960 	}
   961 }
   962 
   963 void _IntExt_EnableYams()
   964 {
   965 	static ConCommand *pCmd = NULL;
   966 	if (!pCmd)
   967 	{
   968 		pCmd = new ConCommand("sm_text", _IntExt_CallYams, "Fountain of Yams Adventure Game", FCVAR_GAMEDLL);
   969 		g_RootMenu.ConsolePrint("Something is now enabled...");
   970 	} else {
   971 		g_RootMenu.ConsolePrint("Something is already enabled...");
   972 	}
   973 }