core/ConCmdManager.cpp
author Scott Ehlert <ds@alliedmods.net>
Sun Mar 30 07:00:22 2008 +0000 (2008-03-30)
changeset 1952 b7aa2b626807
child 1996 3f0cd604812b
permissions -rw-r--r--
Spring Cleaning, Part Ichi (1)
Various minor things done to project files
Updated sample extension project file and updated makefile to the new unified version (more changes likely on the way)
Updated regex project file and makefile
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2008 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include "ConCmdManager.h"
    33 #include "sm_srvcmds.h"
    34 #include "AdminCache.h"
    35 #include "sm_stringutil.h"
    36 #include "PlayerManager.h"
    37 #include "Translator.h"
    38 #include "HalfLife2.h"
    39 #include "ChatTriggers.h"
    40 
    41 ConCmdManager g_ConCmds;
    42 
    43 #if defined ORANGEBOX_BUILD
    44 	SH_DECL_HOOK1_void(ConCommand, Dispatch, SH_NOATTRIB, false, const CCommand &);
    45 #else
    46 	SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
    47 #endif
    48 
    49 SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int);
    50 
    51 struct PlCmdInfo
    52 {
    53 	ConCmdInfo *pInfo;
    54 	CmdHook *pHook;
    55 	CmdType type;
    56 };
    57 typedef List<PlCmdInfo> CmdList;
    58 
    59 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info);
    60 
    61 ConCmdManager::ConCmdManager() : m_Strings(1024)
    62 {
    63 	m_pCmds = sm_trie_create();
    64 	m_pCmdGrps = sm_trie_create();
    65 	m_CmdClient = 0;
    66 }
    67 
    68 ConCmdManager::~ConCmdManager()
    69 {
    70 	sm_trie_destroy(m_pCmds);
    71 	sm_trie_destroy(m_pCmdGrps);
    72 }
    73 
    74 void ConCmdManager::OnSourceModAllInitialized()
    75 {
    76 	g_PluginSys.AddPluginsListener(this);
    77 	g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
    78 	SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
    79 }
    80 
    81 void ConCmdManager::OnSourceModShutdown()
    82 {
    83 	/* All commands should already be removed by the time we're done */
    84 	SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
    85 	g_RootMenu.RemoveRootConsoleCommand("cmds", this);
    86 }
    87 
    88 void ConCmdManager::OnUnlinkConCommandBase(ConCommandBase *pBase, const char *name, bool is_read_safe)
    89 {
    90 	/* Whoa, first get its information struct */
    91 	ConCmdInfo *pInfo;
    92 
    93 	if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
    94 	{
    95 		return;
    96 	}
    97 
    98 	RemoveConCmds(pInfo->srvhooks);
    99 	RemoveConCmds(pInfo->conhooks);
   100 
   101 	RemoveConCmd(pInfo, name, is_read_safe, false);
   102 }
   103 
   104 void ConCmdManager::RemoveConCmds(List<CmdHook *> &cmdlist)
   105 {
   106 	List<CmdHook *>::iterator iter = cmdlist.begin();
   107 
   108 	while (iter != cmdlist.end())
   109 	{
   110 		CmdHook *pHook = (*iter);
   111 		IPluginContext *pContext = pHook->pf->GetParentContext();
   112 		IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pContext->GetContext());
   113 		CmdList *pList = NULL;
   114 		
   115 		//gaben
   116 		if (!pPlugin->GetProperty("CommandList", (void **)&pList, false) || !pList)
   117 		{
   118 			continue;
   119 		}
   120 
   121 		CmdList::iterator p_iter = pList->begin();
   122 		while (p_iter != pList->end())
   123 		{
   124 			PlCmdInfo &cmd = (*p_iter);
   125 			if (cmd.pHook == pHook)
   126 			{
   127 				p_iter = pList->erase(p_iter);
   128 			}
   129 			else
   130 			{
   131 				p_iter++;
   132 			}
   133 		}
   134 
   135 		delete pHook->pAdmin;
   136 		delete pHook;
   137 		
   138 		iter = cmdlist.erase(iter);
   139 	}
   140 }
   141 
   142 void ConCmdManager::RemoveConCmds(List<CmdHook *> &cmdlist, IPluginContext *pContext)
   143 {
   144 	List<CmdHook *>::iterator iter = cmdlist.begin();
   145 	CmdHook *pHook;
   146 
   147 	while (iter != cmdlist.end())
   148 	{
   149 		pHook = (*iter);
   150 		if (pHook->pf->GetParentContext() == pContext)
   151 		{
   152 			delete pHook->pAdmin;
   153 			delete pHook;
   154 			iter = cmdlist.erase(iter);
   155 		}
   156 		else
   157 		{
   158 			iter++;
   159 		}
   160 	}
   161 }
   162 
   163 void ConCmdManager::OnPluginDestroyed(IPlugin *plugin)
   164 {
   165 	CmdList *pList;
   166 	List<ConCmdInfo *> removed;
   167 	if (plugin->GetProperty("CommandList", (void **)&pList, true))
   168 	{
   169 		IPluginContext *pContext = plugin->GetBaseContext();
   170 		CmdList::iterator iter;
   171 		/* First pass!
   172 	 	 * Only bother if there's an actual command list on this plugin... 
   173 	 	 */
   174 		for (iter=pList->begin();
   175 			iter!=pList->end();
   176 			iter++)
   177 		{
   178 			PlCmdInfo &cmd = (*iter);
   179 			ConCmdInfo *pInfo = cmd.pInfo;
   180 
   181 			/* Has this chain already been fully cleaned/removed? */
   182 			if (removed.find(pInfo) != removed.end())
   183 			{
   184 				continue;
   185 			}
   186 
   187 			/* Remove any hooks from us on this command */
   188 			RemoveConCmds(pInfo->conhooks, pContext);
   189 			RemoveConCmds(pInfo->srvhooks, pContext);
   190 
   191 			/* See if there are still hooks */
   192 			if (pInfo->srvhooks.size())
   193 			{
   194 				continue;
   195 			}
   196 			if (pInfo->conhooks.size())
   197 			{
   198 				continue;
   199 			}
   200 
   201 			/* Remove the command, it should be safe now */
   202 			RemoveConCmd(pInfo, pInfo->pCmd->GetName(), true, true);
   203 			removed.push_back(pInfo);
   204 		}
   205 		delete pList;
   206 	}
   207 }
   208 #if defined ORANGEBOX_BUILD
   209 void CommandCallback(const CCommand &command)
   210 {
   211 #else
   212 void CommandCallback()
   213 {
   214 	CCommand command;
   215 #endif
   216 
   217 	g_HL2.PushCommandStack(&command);
   218 
   219 	g_ConCmds.InternalDispatch(command);
   220 
   221 	g_HL2.PopCommandStack();
   222 }
   223 
   224 void ConCmdManager::SetCommandClient(int client)
   225 {
   226 	m_CmdClient = client + 1;
   227 }
   228 
   229 ResultType ConCmdManager::DispatchClientCommand(int client, const char *cmd, int args, ResultType type)
   230 {
   231 	ConCmdInfo *pInfo;
   232 	if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   233 	{
   234 		cell_t result = type;
   235 		cell_t tempres = result;
   236 		List<CmdHook *>::iterator iter;
   237 		CmdHook *pHook;
   238 		for (iter=pInfo->conhooks.begin();
   239 			 iter!=pInfo->conhooks.end();
   240 			 iter++)
   241 		{
   242 			pHook = (*iter);
   243 			if (!pHook->pf->IsRunnable())
   244 			{
   245 				continue;
   246 			}
   247 			if (pHook->pAdmin && !CheckAccess(client, cmd, pHook->pAdmin))
   248 			{
   249 				if (result < Pl_Handled)
   250 				{
   251 					result = Pl_Handled;
   252 				}
   253 				continue;
   254 			}
   255 			pHook->pf->PushCell(client);
   256 			pHook->pf->PushCell(args);
   257 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   258 			{
   259 				if (tempres > result)
   260 				{
   261 					result = tempres;
   262 				}
   263 				if (result == Pl_Stop)
   264 				{
   265 					break;
   266 				}
   267 			}
   268 		}
   269 		type = (ResultType)result;
   270 	}
   271 
   272 	return type;
   273 }
   274 
   275 void ConCmdManager::InternalDispatch(const CCommand &command)
   276 {
   277 	int client = m_CmdClient;
   278 
   279 	if (client)
   280 	{
   281 		CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   282 		if (!pPlayer || !pPlayer->IsConnected())
   283 		{
   284 			return;
   285 		}
   286 	}
   287 
   288 	/**
   289 	 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
   290 	 * OR this dispatch.  They will NEVER go through both.
   291 	 * -- 
   292 	 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
   293 	 */
   294 	const char *cmd = g_HL2.CurrentCommandName();
   295 
   296 	ConCmdInfo *pInfo;
   297 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   298 	{
   299 		return;
   300 	}
   301 
   302 	/* This is a hack to prevent say triggers from firing on messages that were 
   303 	 * blocked because of flooding.  We won't remove this, but the hack will get 
   304 	 * "nicer" when we expose explicit say hooks.
   305 	 */
   306 	if (g_ChatTriggers.WasFloodedMessage())
   307 	{
   308 		return;
   309 	}
   310 
   311 	cell_t result = Pl_Continue;
   312 	int args = command.ArgC() - 1;
   313 
   314 	List<CmdHook *>::iterator iter;
   315 	CmdHook *pHook;
   316 
   317 	/* Execute server-only commands if viable */
   318 	if (client == 0 && pInfo->srvhooks.size())
   319 	{
   320 		cell_t tempres = result;
   321 		for (iter=pInfo->srvhooks.begin();
   322 			 iter!=pInfo->srvhooks.end();
   323 			 iter++)
   324 		{
   325 			pHook = (*iter);
   326 			if (!pHook->pf->IsRunnable())
   327 			{
   328 				continue;
   329 			}
   330 			pHook->pf->PushCell(args);
   331 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   332 			{
   333 				if (tempres > result)
   334 				{
   335 					result = tempres;
   336 				}
   337 				if (result == Pl_Stop)
   338 				{
   339 					break;
   340 				}
   341 			}
   342 		}
   343 	
   344 		/* Check if there's an early stop */
   345 		if (result >= Pl_Stop)
   346 		{
   347 			if (!pInfo->sourceMod)
   348 			{
   349 				RETURN_META(MRES_SUPERCEDE);
   350 			}
   351 			return;
   352 		}
   353 	}
   354 
   355 	/* Execute console commands */
   356 	if (pInfo->conhooks.size())
   357 	{
   358 		cell_t tempres = result;
   359 		for (iter=pInfo->conhooks.begin();
   360 			iter!=pInfo->conhooks.end();
   361 			iter++)
   362 		{
   363 			pHook = (*iter);
   364 			if (!pHook->pf->IsRunnable())
   365 			{
   366 				continue;
   367 			}
   368 			if (client 
   369 				&& pHook->pAdmin
   370 				&& !CheckAccess(client, cmd, pHook->pAdmin))
   371 			{
   372 				if (result < Pl_Handled)
   373 				{
   374 					result = Pl_Handled;
   375 				}
   376 				continue;
   377 			}
   378 
   379 			/* On a listen server, sometimes the server host's client index can be set as 0.
   380 			 * So index 1 is passed to the command callback to correct this potential problem.
   381 			 */
   382 			if (client == 0 && !engine->IsDedicatedServer())
   383 			{
   384 				pHook->pf->PushCell(1);
   385 			} else {
   386 				pHook->pf->PushCell(client);
   387 			}
   388 
   389 			pHook->pf->PushCell(args);
   390 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   391 			{
   392 				if (tempres > result)
   393 				{
   394 					result = tempres;
   395 				}
   396 				if (result == Pl_Stop)
   397 				{
   398 					break;
   399 				}
   400 			}
   401 		}
   402 	}
   403 
   404 	if (result >= Pl_Handled)
   405 	{
   406 		if (!pInfo->sourceMod)
   407 		{
   408 			RETURN_META(MRES_SUPERCEDE);
   409 		}
   410 		return;
   411 	}
   412 }
   413 
   414 bool ConCmdManager::CheckCommandAccess(int client, const char *cmd, FlagBits cmdflags)
   415 {
   416 	if (cmdflags == 0 || client == 0)
   417 	{
   418 		return true;
   419 	}
   420 
   421 	/* If running listen server, then client 1 is the server host and should have 'root' access */
   422 	if (client == 1 && !engine->IsDedicatedServer())
   423 	{
   424 		return true;
   425 	}
   426 
   427 	CPlayer *player = g_Players.GetPlayerByIndex(client);
   428 	if (!player 
   429 		|| player->GetEdict() == NULL
   430 		|| player->IsFakeClient())
   431 	{
   432 		return false;
   433 	}
   434 
   435 	AdminId adm = player->GetAdminId();
   436 	if (adm != INVALID_ADMIN_ID)
   437 	{
   438 		FlagBits bits = g_Admins.GetAdminFlags(adm, Access_Effective);
   439 
   440 		/* root knows all, WHOA */
   441 		if ((bits & ADMFLAG_ROOT) == ADMFLAG_ROOT)
   442 		{
   443 			return true;
   444 		}
   445 
   446 		/* Check for overrides
   447 		 * :TODO: is it worth optimizing this?
   448 		 */
   449 		unsigned int groups = g_Admins.GetAdminGroupCount(adm);
   450 		GroupId gid;
   451 		OverrideRule rule;
   452 		bool override = false;
   453 		for (unsigned int i=0; i<groups; i++)
   454 		{
   455 			gid = g_Admins.GetAdminGroup(adm, i, NULL);
   456 			/* First get group-level override */
   457 			override = g_Admins.GetGroupCommandOverride(gid, cmd, Override_CommandGroup, &rule);
   458 			/* Now get the specific command override */
   459 			if (g_Admins.GetGroupCommandOverride(gid, cmd, Override_Command, &rule))
   460 			{
   461 				override = true;
   462 			}
   463 			if (override)
   464 			{
   465 				if (rule == Command_Allow)
   466 				{
   467 					return true;
   468 				}
   469 				else if (rule == Command_Deny)
   470 				{
   471 					return false;
   472 				}
   473 			}
   474 		}
   475 
   476 		/* See if our other flags match */
   477 		if ((bits & cmdflags) == cmdflags)
   478 		{
   479 			return true;
   480 		}
   481 	}
   482 
   483 	return false;
   484 }
   485 
   486 bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmin)
   487 {
   488 	if (CheckCommandAccess(client, cmd, pAdmin->eflags))
   489 	{
   490 		return true;
   491 	}
   492 
   493 	edict_t *pEdict = engine->PEntityOfEntIndex(client);
   494 	
   495 	/* If we got here, the command failed... */
   496 	char buffer[128];
   497 	if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
   498 		!= Trans_Okay)
   499 	{
   500 		UTIL_Format(buffer, sizeof(buffer), "You do not have access to this command");
   501 	}
   502 
   503 	unsigned int replyto = g_ChatTriggers.GetReplyTo();
   504 	if (replyto == SM_REPLY_CONSOLE)
   505 	{
   506 		char fullbuffer[192];
   507 		UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
   508 		engine->ClientPrintf(pEdict, fullbuffer);
   509 	}
   510 	else if (replyto == SM_REPLY_CHAT)
   511 	{
   512 		char fullbuffer[192];
   513 		UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
   514 		g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
   515 	}
   516 	
   517 	return false;
   518 }
   519 
   520 bool ConCmdManager::AddConsoleCommand(IPluginFunction *pFunction, 
   521 									   const char *name, 
   522 									   const char *description, 
   523 									   int flags)
   524 {
   525 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   526 
   527 	if (!pInfo)
   528 	{
   529 		return false;
   530 	}
   531 
   532 	CmdHook *pHook = new CmdHook();
   533 
   534 	pHook->pf = pFunction;
   535 	if (description && description[0])
   536 	{
   537 		pHook->helptext.assign(description);
   538 	}
   539 	pInfo->conhooks.push_back(pHook);
   540 
   541 	/* Add to the plugin */
   542 	CmdList *pList;
   543 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   544 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   545 	{
   546 		pList = new CmdList();
   547 		pPlugin->SetProperty("CommandList", pList);
   548 	}
   549 	PlCmdInfo info;
   550 	info.pInfo = pInfo;
   551 	info.type = Cmd_Console;
   552 	info.pHook = pHook;
   553 	AddToPlCmdList(pList, info);
   554 
   555 	return true;
   556 }
   557 
   558 bool ConCmdManager::AddAdminCommand(IPluginFunction *pFunction, 
   559 									 const char *name, 
   560 									 const char *group,
   561 									 int adminflags,
   562 									 const char *description, 
   563 									 int flags)
   564 {
   565 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   566 
   567 	if (!pInfo)
   568 	{
   569 		return false;
   570 	}
   571 
   572 	CmdHook *pHook = new CmdHook();
   573 	AdminCmdInfo *pAdmin = new AdminCmdInfo();
   574 
   575 	pHook->pf = pFunction;
   576 	if (description && description[0])
   577 	{
   578 		pHook->helptext.assign(description);
   579 	}
   580 	pHook->pAdmin = pAdmin;
   581 
   582 	void *object;
   583 	int grpid;
   584 	if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
   585 	{
   586 		grpid = m_Strings.AddString(group);
   587 		sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
   588 	}
   589 	else
   590 	{
   591 		grpid = (int)object;
   592 	}
   593 
   594 	pAdmin->cmdGrpId = grpid;
   595 	pAdmin->flags = adminflags;
   596 
   597 	/* First get the command group override, if any */
   598 	bool override = g_Admins.GetCommandOverride(group, 
   599 		Override_CommandGroup,
   600 		&(pAdmin->eflags));
   601 
   602 	/* Next get the command override, if any */
   603 	if (g_Admins.GetCommandOverride(name,
   604 		Override_Command,
   605 		&(pAdmin->eflags)))
   606 	{
   607 		override = true;
   608 	}
   609 
   610 	/* Assign normal flags if there were no overrides */
   611 	if (!override)
   612 	{
   613 		pAdmin->eflags = pAdmin->flags;
   614 	}
   615 
   616 	/* Finally, add the hook */
   617 	pInfo->conhooks.push_back(pHook);
   618 	pInfo->admin = *(pHook->pAdmin);
   619 	pInfo->is_admin_set = true;
   620 
   621 	/* Now add to the plugin */
   622 	CmdList *pList;
   623 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   624 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   625 	{
   626 		pList = new CmdList();
   627 		pPlugin->SetProperty("CommandList", pList);
   628 	}
   629 	PlCmdInfo info;
   630 	info.pInfo = pInfo;
   631 	info.type = Cmd_Admin;
   632 	info.pHook = pHook;
   633 	AddToPlCmdList(pList, info);
   634 
   635 	return true;
   636 }
   637 
   638 bool ConCmdManager::AddServerCommand(IPluginFunction *pFunction, 
   639 									  const char *name, 
   640 									  const char *description, 
   641 									  int flags)
   642 
   643 {
   644 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   645 
   646 	if (!pInfo)
   647 	{
   648 		return false;
   649 	}
   650 
   651 	CmdHook *pHook = new CmdHook();
   652 
   653 	pHook->pf = pFunction;
   654 	if (description && description[0])
   655 	{
   656 		pHook->helptext.assign(description);
   657 	}
   658 
   659 	pInfo->srvhooks.push_back(pHook);
   660 
   661 	/* Add to the plugin */
   662 	CmdList *pList;
   663 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   664 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   665 	{
   666 		pList = new CmdList();
   667 		pPlugin->SetProperty("CommandList", pList);
   668 	}
   669 	PlCmdInfo info;
   670 	info.pInfo = pInfo;
   671 	info.type = Cmd_Server;
   672 	info.pHook = pHook;
   673 	AddToPlCmdList(pList, info);
   674 
   675 	return true;
   676 }
   677 
   678 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
   679 {
   680 	CmdList::iterator iter = pList->begin();
   681 	bool inserted = false;
   682 	const char *orig = NULL;
   683 
   684 	orig = info.pInfo->pCmd->GetName();
   685 
   686 	/* Insert this into the help list, SORTED alphabetically. */
   687 	while (iter != pList->end())
   688 	{
   689 		PlCmdInfo &obj = (*iter);
   690 		const char *cmd = obj.pInfo->pCmd->GetName();
   691 		if (strcmp(orig, cmd) < 0)
   692 		{
   693 			pList->insert(iter, info);
   694 			inserted = true;
   695 			break;
   696 		}
   697 		iter++;
   698 	}
   699 
   700 	if (!inserted)
   701 	{
   702 		pList->push_back(info);
   703 	}
   704 }
   705 
   706 void ConCmdManager::AddToCmdList(ConCmdInfo *info)
   707 {
   708 	List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
   709 	ConCmdInfo *pInfo;
   710 	bool inserted = false;
   711 	const char *orig = NULL;
   712 
   713 	orig = info->pCmd->GetName();
   714 
   715 	/* Insert this into the help list, SORTED alphabetically. */
   716 	while (iter != m_CmdList.end())
   717 	{
   718 		const char *cmd = NULL;
   719 		pInfo = (*iter);
   720 		cmd = pInfo->pCmd->GetName();
   721 		if (strcmp(orig, cmd) < 0)
   722 		{
   723 			m_CmdList.insert(iter, info);
   724 			inserted = true;
   725 			break;
   726 		}
   727 		iter++;
   728 	}
   729 
   730 	if (!inserted)
   731 	{
   732 		m_CmdList.push_back(info);
   733 	}
   734 }
   735 
   736 void ConCmdManager::UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits, bool remove)
   737 {
   738 	ConCmdInfo *pInfo;
   739 
   740 	if (type == Override_Command)
   741 	{
   742 		if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   743 		{
   744 			return;
   745 		}
   746 
   747 		List<CmdHook *>::iterator iter;
   748 		CmdHook *pHook;
   749 
   750 		for (iter=pInfo->conhooks.begin(); iter!=pInfo->conhooks.end(); iter++)
   751 		{
   752 			pHook = (*iter);
   753 			if (pHook->pAdmin)
   754 			{
   755 				if (!remove)
   756 				{
   757 					pHook->pAdmin->eflags = bits;
   758 				} else {
   759 					pHook->pAdmin->eflags = pHook->pAdmin->flags;
   760 				}
   761 				pInfo->admin = *(pHook->pAdmin);
   762 			}
   763 		}
   764 		pInfo->is_admin_set = true;
   765 	}
   766 	else if (type == Override_CommandGroup)
   767 	{
   768 		void *object;
   769 		if (!sm_trie_retrieve(m_pCmdGrps, cmd, &object))
   770 		{
   771 			return;
   772 		}
   773 		int grpid = (int)object;
   774 
   775 		/* This is bad :( loop through all commands */
   776 		List<ConCmdInfo *>::iterator iter;
   777 		CmdHook *pHook;
   778 		for (iter=m_CmdList.begin(); iter!=m_CmdList.end(); iter++)
   779 		{
   780 			pInfo = (*iter);
   781 			for (List<CmdHook *>::iterator citer=pInfo->conhooks.begin();
   782 				 citer!=pInfo->conhooks.end();
   783 				 citer++)
   784 			{
   785 				pHook = (*citer);
   786 				if (pHook->pAdmin && pHook->pAdmin->cmdGrpId == grpid)
   787 				{
   788 					if (remove)
   789 					{
   790 						pHook->pAdmin->eflags = bits;
   791 					} else {
   792 						pHook->pAdmin->eflags = pHook->pAdmin->flags;
   793 					}
   794 					pInfo->admin = *(pHook->pAdmin);
   795 				}
   796 			}
   797 		}
   798 		pInfo->is_admin_set = true;
   799 	}
   800 }
   801 
   802 void ConCmdManager::RemoveConCmd(ConCmdInfo *info, const char *name, bool is_read_safe, bool untrack)
   803 {
   804 	/* Remove from the trie */
   805 	sm_trie_delete(m_pCmds, name);
   806 
   807 	/* Remove console-specific information
   808 	 * This should always be true as of right now
   809 	 */
   810 	if (info->pCmd)
   811 	{
   812 		if (info->sourceMod)
   813 		{
   814 			/* Unlink from SourceMM */
   815 			g_SMAPI->UnregisterConCommandBase(g_PLAPI, info->pCmd);
   816 			/* Delete the command's memory */
   817 			char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
   818 			char *new_name = const_cast<char *>(info->pCmd->GetName());
   819 			delete [] new_help;
   820 			delete [] new_name;
   821 			delete info->pCmd;
   822 		}
   823 		else
   824 		{
   825 			if (is_read_safe)
   826 			{
   827 				/* Remove the external hook */
   828 				SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
   829 			}
   830 			if (untrack)
   831 			{
   832 				UntrackConCommandBase(info->pCmd, this);
   833 			}
   834 		}
   835 	}
   836 	
   837 	/* Remove from list */
   838 	m_CmdList.remove(info);
   839 
   840 	delete info;
   841 }
   842 
   843 bool ConCmdManager::LookForSourceModCommand(const char *cmd)
   844 {
   845 	ConCmdInfo *pInfo;
   846 
   847 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   848 	{
   849 		return false;
   850 	}
   851 
   852 	return pInfo->sourceMod && (pInfo->conhooks.size() > 0);
   853 }
   854 
   855 bool ConCmdManager::LookForCommandAdminFlags(const char *cmd, FlagBits *pFlags)
   856 {
   857 	ConCmdInfo *pInfo;
   858 
   859 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   860 	{
   861 		return false;
   862 	}
   863 
   864 	*pFlags = pInfo->admin.eflags;
   865 
   866 	return pInfo->is_admin_set;
   867 }
   868 
   869 ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
   870 {
   871 	ConCmdInfo *pInfo;
   872 	if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
   873 	{
   874 		pInfo = new ConCmdInfo();
   875 		/* Find the commandopan */
   876 		ConCommandBase *pBase = icvar->GetCommands();
   877 		ConCommand *pCmd = NULL;
   878 		while (pBase)
   879 		{
   880 			if (strcmp(pBase->GetName(), name) == 0)
   881 			{
   882 				/* Don't want to return convar with same name */
   883 				if (!pBase->IsCommand())
   884 				{
   885 					return NULL;
   886 				}
   887 
   888 				pCmd = (ConCommand *)pBase;
   889 				break;
   890 			}
   891 			pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   892 		}
   893 
   894 		if (!pCmd)
   895 		{
   896 			/* Note that we have to duplicate because the source might not be 
   897 			 * a static string, and these expect static memory.
   898 			 */
   899 			if (!description)
   900 			{
   901 				description = "";
   902 			}
   903 			char *new_name = sm_strdup(name);
   904 			char *new_help = sm_strdup(description);
   905 			pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
   906 			pInfo->sourceMod = true;
   907 		}
   908 		else
   909 		{
   910 			TrackConCommandBase(pCmd, this);
   911 			SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
   912 		}
   913 
   914 		pInfo->pCmd = pCmd;
   915 		pInfo->is_admin_set = false;
   916 
   917 		sm_trie_insert(m_pCmds, name, pInfo);
   918 		AddToCmdList(pInfo);
   919 	}
   920 
   921 	return pInfo;
   922 }
   923 
   924 void ConCmdManager::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
   925 {
   926 	if (command.ArgC() >= 3)
   927 	{
   928 		const char *text = command.Arg(2);
   929 
   930 		CPlugin *pPlugin = g_PluginSys.FindPluginByConsoleArg(text);
   931 
   932 		if (!pPlugin)
   933 		{
   934 			g_RootMenu.ConsolePrint("[SM] Plugin \"%s\" was not found.", text);
   935 			return;
   936 		}
   937 
   938 		const sm_plugininfo_t *plinfo = pPlugin->GetPublicInfo();
   939 		const char *plname = IS_STR_FILLED(plinfo->name) ? plinfo->name : pPlugin->GetFilename();
   940 
   941 		CmdList *pList;
   942 		if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   943 		{
   944 			g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
   945 			return;
   946 		}
   947 		if (!pList->size())
   948 		{
   949 			g_RootMenu.ConsolePrint("[SM] No commands found for: %s", plname);
   950 			return;
   951 		}
   952 
   953 		CmdList::iterator iter;
   954 		const char *type = NULL;
   955 		const char *name;
   956 		const char *help;
   957 		g_RootMenu.ConsolePrint("[SM] Listing %d commands for: %s", pList->size(), plname);
   958 		g_RootMenu.ConsolePrint("  %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
   959 		for (iter=pList->begin();
   960 			 iter!=pList->end();
   961 			 iter++)
   962 		{
   963 			PlCmdInfo &cmd = (*iter);
   964 			if (cmd.type == Cmd_Server)
   965 			{
   966 				type = "server";
   967 			}
   968 			else if (cmd.type == Cmd_Console)
   969 			{
   970 				type = "console";
   971 			}
   972 			else if (cmd.type == Cmd_Admin)
   973 			{
   974 				type = "admin";
   975 			}
   976 			name = cmd.pInfo->pCmd->GetName();
   977 			if (cmd.pHook->helptext.size())
   978 			{
   979 				help = cmd.pHook->helptext.c_str();
   980 			}
   981 			else
   982 			{
   983 				help = cmd.pInfo->pCmd->GetHelpText();
   984 			}
   985 			g_RootMenu.ConsolePrint("  %-17.16s %-12.11s %s", name, type, help);		
   986 		}
   987 
   988 		return;
   989 	}
   990 
   991 	g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
   992 }