core/CConCmdManager.cpp
author David Anderson <dvander@alliedmods.net>
Sat Feb 17 07:54:20 2007 +0000 (2007-02-17)
changeset 513 0c299b5ad420
parent 504 1d6072ba774e
child 514 4c02b38c26ed
permissions -rw-r--r--
rewrote console command implementation internally
admin commands on the way but still not done
removed weird restriction
     1 /**
     2 * ===============================================================
     3 * SourceMod (C)2004-2007 AlliedModders LLC.  All rights reserved.
     4 * ===============================================================
     5 *
     6 * This file is not open source and may not be copied without explicit
     7 * written permission of AlliedModders LLC.  This file may not be redistributed 
     8 * in whole or significant part.
     9 * For information, see LICENSE.txt or http://www.sourcemod.net/license.php
    10 *
    11 * Version: $Id$
    12 */
    13 
    14 #include "CConCmdManager.h"
    15 #include "sm_srvcmds.h"
    16 #include "AdminCache.h"
    17 #include "sm_stringutil.h"
    18 
    19 CConCmdManager g_ConCmds;
    20 
    21 SH_DECL_HOOK0_void(ConCommand, Dispatch, SH_NOATTRIB, false);
    22 SH_DECL_HOOK1_void(IServerGameClients, SetCommandClient, SH_NOATTRIB, false, int);
    23 
    24 struct PlCmdInfo
    25 {
    26 	ConCmdInfo *pInfo;
    27 	CmdHook *pHook;
    28 	CmdType type;
    29 };
    30 typedef List<PlCmdInfo> CmdList;
    31 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info);
    32 
    33 CConCmdManager::CConCmdManager() : m_Strings(1024)
    34 {
    35 	m_pCmds = sm_trie_create();
    36 	m_pCmdGrps = sm_trie_create();
    37 }
    38 
    39 CConCmdManager::~CConCmdManager()
    40 {
    41 	sm_trie_destroy(m_pCmds);
    42 	sm_trie_destroy(m_pCmdGrps);
    43 }
    44 
    45 void CConCmdManager::OnSourceModAllInitialized()
    46 {
    47 	g_PluginSys.AddPluginsListener(this);
    48 	g_RootMenu.AddRootConsoleCommand("cmds", "List console commands", this);
    49 	SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false);
    50 }
    51 
    52 void CConCmdManager::OnSourceModShutdown()
    53 {
    54 	/* All commands should already be removed by the time we're done */
    55 	SH_REMOVE_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &CConCmdManager::SetCommandClient, false);
    56 	g_RootMenu.RemoveRootConsoleCommand("cmds", this);
    57 	g_PluginSys.RemovePluginsListener(this);
    58 }
    59 
    60 void CConCmdManager::RemoveConCmds(List<CmdHook *> &cmdlist, IPluginContext *pContext)
    61 {
    62 	List<CmdHook *>::iterator iter = cmdlist.begin();
    63 	CmdHook *pHook;
    64 
    65 	while (iter != cmdlist.end())
    66 	{
    67 		pHook = (*iter);
    68 		if (pHook->pf->GetParentContext() == pContext)
    69 		{
    70 			delete pHook->pAdmin;
    71 			delete pHook;
    72 			iter = cmdlist.erase(iter);
    73 		} else {
    74 			iter++;
    75 		}
    76 	}
    77 }
    78 
    79 void CConCmdManager::OnPluginDestroyed(IPlugin *plugin)
    80 {
    81 	CmdList *pList;
    82 	List<ConCmdInfo *> removed;
    83 	if (plugin->GetProperty("CommandList", (void **)&pList, true))
    84 	{
    85 		IPluginContext *pContext = plugin->GetBaseContext();
    86 		CmdList::iterator iter;
    87 		/* First pass!
    88 	 	 * Only bother if there's an actual command list on this plugin... 
    89 	 	 */
    90 		for (iter=pList->begin();
    91 			iter!=pList->end();
    92 			iter++)
    93 		{
    94 			PlCmdInfo &cmd = (*iter);
    95 			ConCmdInfo *pInfo = cmd.pInfo;
    96 
    97 			/* Has this chain already been fully cleaned/removed? */
    98 			if (removed.find(pInfo) != removed.end())
    99 			{
   100 				continue;
   101 			}
   102 
   103 			/* Remove any hooks from us on this command */
   104 			RemoveConCmds(pInfo->conhooks, pContext);
   105 			RemoveConCmds(pInfo->srvhooks, pContext);
   106 
   107 			/* See if there are still hooks */
   108 			if (pInfo->srvhooks.size())
   109 			{
   110 				continue;
   111 			}
   112 			if (pInfo->conhooks.size())
   113 			{
   114 				continue;
   115 			}
   116 
   117 			/* Remove the command, it should be safe now */
   118 			RemoveConCmd(pInfo);
   119 			removed.push_back(pInfo);
   120 		}
   121 		delete pList;
   122 	}
   123 }
   124 
   125 void CommandCallback()
   126 {
   127 	g_ConCmds.InternalDispatch();
   128 }
   129 
   130 void CConCmdManager::SetCommandClient(int client)
   131 {
   132 	m_CmdClient = client + 1;
   133 }
   134 
   135 ResultType CConCmdManager::DispatchClientCommand(int client, ResultType type)
   136 {
   137 	const char *cmd = engine->Cmd_Argv(0);
   138 	int args = engine->Cmd_Argc() - 1;
   139 
   140 	ConCmdInfo *pInfo;
   141 	if (sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   142 	{
   143 		cell_t result = type;
   144 		cell_t tempres = result;
   145 		List<CmdHook *>::iterator iter;
   146 		CmdHook *pHook;
   147 		for (iter=pInfo->conhooks.begin();
   148 			 iter!=pInfo->conhooks.end();
   149 			 iter++)
   150 		{
   151 			pHook = (*iter);
   152 			if (pHook->pAdmin
   153 				&& pHook->pAdmin->eflags)
   154 			{
   155 				/* :TODO: admin calculations */
   156 			}
   157 			pHook->pf->PushCell(client);
   158 			pHook->pf->PushCell(args);
   159 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   160 			{
   161 				if (tempres > result)
   162 				{
   163 					result = tempres;
   164 				}
   165 				if (result == Pl_Stop)
   166 				{
   167 					break;
   168 				}
   169 			}
   170 		}
   171 		type = (ResultType)result;
   172 	}
   173 
   174 	return type;
   175 }
   176 
   177 void CConCmdManager::InternalDispatch()
   178 {
   179 	/**
   180 	 * Note: Console commands will EITHER go through IServerGameDLL::ClientCommand,
   181 	 * OR this dispatch.  They will NEVER go through both.
   182 	 * -- 
   183 	 * Whether or not it goes through the callback is determined by FCVAR_GAMEDLL
   184 	 */
   185 
   186 	const char *cmd = engine->Cmd_Argv(0);
   187 	ConCmdInfo *pInfo;
   188 	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
   189 	{
   190 		return;
   191 	}
   192 
   193 	cell_t result = Pl_Continue;
   194 	int args = engine->Cmd_Argc() - 1;
   195 
   196 	List<CmdHook *>::iterator iter;
   197 	CmdHook *pHook;
   198 
   199 	/* Execute server-only commands if viable */
   200 	if (m_CmdClient == 0 && pInfo->srvhooks.size())
   201 	{
   202 		cell_t tempres = result;
   203 		for (iter=pInfo->srvhooks.begin();
   204 			 iter!=pInfo->srvhooks.end();
   205 			 iter++)
   206 		{
   207 			pHook = (*iter);
   208 			pHook->pf->PushCell(args);
   209 			if (pHook->pf->Execute(&tempres) == SP_ERROR_NONE)
   210 			{
   211 				if (tempres > result)
   212 				{
   213 					result = tempres;
   214 				}
   215 				if (result == Pl_Stop)
   216 				{
   217 					break;
   218 				}
   219 			}
   220 		}
   221 	
   222 		/* Check if there's an early stop */
   223 		if (result >= Pl_Stop)
   224 		{
   225 			if (!pInfo->sourceMod)
   226 			{
   227 				RETURN_META(MRES_SUPERCEDE);
   228 			}
   229 			return;
   230 		}
   231 	}
   232 
   233 	/* Execute console commands */
   234 	if (pInfo->conhooks.size())
   235 	{
   236 		cell_t tempres = result;
   237 		for (iter=pInfo->conhooks.begin();
   238 			iter!=pInfo->conhooks.end();
   239 			iter++)
   240 		{
   241 			pHook = (*iter);
   242 			if (m_CmdClient 
   243 				&& pHook->pAdmin
   244 				&& pHook->pAdmin->eflags)
   245 			{
   246 				/* :TODO: check admin stuff */
   247 			}
   248 			pHook->pf->PushCell(m_CmdClient);
   249 			pHook->pf->PushCell(args);
   250 			if (pHook->pf->Execute(&tempres) != SP_ERROR_NONE)
   251 			{
   252 				if (tempres > result)
   253 				{
   254 					result = tempres;
   255 				}
   256 				if (result == Pl_Stop)
   257 				{
   258 					break;
   259 				}
   260 			}
   261 		}
   262 	}
   263 
   264 	if (result >= Pl_Handled)
   265 	{
   266 		if (!pInfo->sourceMod)
   267 		{
   268 			RETURN_META(MRES_SUPERCEDE);
   269 		}
   270 		return;
   271 	}
   272 }
   273 
   274 ResultType RunAdminCommand(ConCmdInfo *pInfo, int client, int args)
   275 {
   276 	return Pl_Continue;
   277 }
   278 
   279 void CConCmdManager::AddConsoleCommand(IPluginFunction *pFunction, 
   280 									   const char *name, 
   281 									   const char *description, 
   282 									   int flags)
   283 {
   284 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   285 	CmdHook *pHook = new CmdHook();
   286 
   287 	pHook->pf = pFunction;
   288 	if (description && description[0])
   289 	{
   290 		pHook->helptext.assign(description);
   291 	}
   292 	pInfo->conhooks.push_back(pHook);
   293 
   294 	/* Add to the plugin */
   295 	CmdList *pList;
   296 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   297 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   298 	{
   299 		pList = new CmdList();
   300 		pPlugin->SetProperty("CommandList", pList);
   301 	}
   302 	PlCmdInfo info;
   303 	info.pInfo = pInfo;
   304 	info.type = Cmd_Console;
   305 	info.pHook = pHook;
   306 	AddToPlCmdList(pList, info);
   307 }
   308 
   309 bool CConCmdManager::AddAdminCommand(IPluginFunction *pFunction, 
   310 									 const char *name, 
   311 									 const char *group,
   312 									 int adminflags,
   313 									 const char *description, 
   314 									 int flags)
   315 {
   316 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   317 
   318 	CmdHook *pHook = new CmdHook();
   319 	AdminCmdInfo *pAdmin = new AdminCmdInfo();
   320 
   321 	pHook->pf = pFunction;
   322 	if (description && description[0])
   323 	{
   324 		pHook->helptext.assign(description);
   325 	}
   326 	pHook->pAdmin = pAdmin;
   327 
   328 	void *object;
   329 	int grpid;
   330 	if (!sm_trie_retrieve(m_pCmdGrps, group, (void **)&object))
   331 	{
   332 		grpid = m_Strings.AddString(group);
   333 		sm_trie_insert(m_pCmdGrps, group, (void *)grpid);
   334 	} else {
   335 		grpid = (int)object;
   336 	}
   337 
   338 	pAdmin->cmdGrpId = grpid;
   339 	pAdmin->flags = flags;
   340 
   341 	/* First get the command group override, if any */
   342 	bool override = g_Admins.GetCommandOverride(group, 
   343 		Override_CommandGroup,
   344 		&(pAdmin->eflags));
   345 
   346 	/* Next get the command override, if any */
   347 	if (g_Admins.GetCommandOverride(name,
   348 		Override_Command,
   349 		&(pAdmin->eflags)))
   350 	{
   351 		override = true;
   352 	}
   353 
   354 	/* Assign normal flags if there were no overrides */
   355 	if (!override)
   356 	{
   357 		pAdmin->eflags = pAdmin->flags;
   358 	}
   359 
   360 	/* Finally, add the hook */
   361 	pInfo->conhooks.push_back(pHook);
   362 
   363 	/* Now add to the plugin */
   364 	CmdList *pList;
   365 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   366 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   367 	{
   368 		pList = new CmdList();
   369 		pPlugin->SetProperty("CommandList", pList);
   370 	}
   371 	PlCmdInfo info;
   372 	info.pInfo = pInfo;
   373 	info.type = Cmd_Admin;
   374 	info.pHook = pHook;
   375 	AddToPlCmdList(pList, info);
   376 
   377 	return true;
   378 }
   379 
   380 void CConCmdManager::AddServerCommand(IPluginFunction *pFunction, 
   381 									  const char *name, 
   382 									  const char *description, 
   383 									  int flags)
   384 
   385 {
   386 	ConCmdInfo *pInfo = AddOrFindCommand(name, description, flags);
   387 	CmdHook *pHook = new CmdHook();
   388 
   389 	pHook->pf = pFunction;
   390 	if (description && description[0])
   391 	{
   392 		pHook->helptext.assign(description);
   393 	}
   394 
   395 	pInfo->srvhooks.push_back(pHook);
   396 
   397 	/* Add to the plugin */
   398 	CmdList *pList;
   399 	IPlugin *pPlugin = g_PluginSys.GetPluginByCtx(pFunction->GetParentContext()->GetContext());
   400 	if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   401 	{
   402 		pList = new CmdList();
   403 		pPlugin->SetProperty("CommandList", pList);
   404 	}
   405 	PlCmdInfo info;
   406 	info.pInfo = pInfo;
   407 	info.type = Cmd_Server;
   408 	info.pHook = pHook;
   409 	AddToPlCmdList(pList, info);
   410 }
   411 
   412 void AddToPlCmdList(CmdList *pList, const PlCmdInfo &info)
   413 {
   414 	CmdList::iterator iter = pList->begin();
   415 	bool inserted = false;
   416 	const char *orig = NULL;
   417 
   418 	orig = info.pInfo->pCmd->GetName();
   419 
   420 	/* Insert this into the help list, SORTED alphabetically. */
   421 	while (iter != pList->end())
   422 	{
   423 		PlCmdInfo &obj = (*iter);
   424 		const char *cmd = obj.pInfo->pCmd->GetName();
   425 		if (strcmp(orig, cmd) < 0)
   426 		{
   427 			pList->insert(iter, info);
   428 			inserted = true;
   429 			break;
   430 		}
   431 		iter++;
   432 	}
   433 
   434 	if (!inserted)
   435 	{
   436 		pList->push_back(info);
   437 	}
   438 }
   439 
   440 void CConCmdManager::AddToCmdList(ConCmdInfo *info)
   441 {
   442 	List<ConCmdInfo *>::iterator iter = m_CmdList.begin();
   443 	ConCmdInfo *pInfo;
   444 	bool inserted = false;
   445 	const char *orig = NULL;
   446 
   447 	orig = info->pCmd->GetName();
   448 
   449 	/* Insert this into the help list, SORTED alphabetically. */
   450 	while (iter != m_CmdList.end())
   451 	{
   452 		const char *cmd = NULL;
   453 		pInfo = (*iter);
   454 		cmd = pInfo->pCmd->GetName();
   455 		if (strcmp(orig, cmd) < 0)
   456 		{
   457 			m_CmdList.insert(iter, info);
   458 			inserted = true;
   459 			break;
   460 		}
   461 		iter++;
   462 	}
   463 
   464 	if (!inserted)
   465 	{
   466 		m_CmdList.push_back(info);
   467 	}
   468 }
   469 
   470 void CConCmdManager::RemoveConCmd(ConCmdInfo *info)
   471 {
   472 	/* Remove console-specific information
   473 	 * This should always be true as of right now
   474 	 */
   475 	if (info->pCmd)
   476 	{
   477 		/* Remove from the trie */
   478 		sm_trie_delete(m_pCmds, info->pCmd->GetName());
   479 
   480 		if (info->sourceMod)
   481 		{
   482 			/* Unlink from SourceMM */
   483 			g_SMAPI->UnregisterConCmdBase(g_PLAPI, info->pCmd);
   484 			/* Delete the command's memory */
   485 			char *new_help = const_cast<char *>(info->pCmd->GetHelpText());
   486 			char *new_name = const_cast<char *>(info->pCmd->GetName());
   487 			delete [] new_help;
   488 			delete [] new_name;
   489 			delete info->pCmd;
   490 		} else {
   491 			/* Remove the external hook */
   492 			SH_REMOVE_HOOK_STATICFUNC(ConCommand, Dispatch, info->pCmd, CommandCallback, false);
   493 		}
   494 	}
   495 	
   496 	/* Remove from list */
   497 	m_CmdList.remove(info);
   498 
   499 	delete info;
   500 }
   501 
   502 ConCmdInfo *CConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
   503 {
   504 	ConCmdInfo *pInfo;
   505 	if (!sm_trie_retrieve(m_pCmds, name, (void **)&pInfo))
   506 	{
   507 		pInfo = new ConCmdInfo();
   508 		/* Find the commandopan */
   509 		ConCommandBase *pBase = icvar->GetCommands();
   510 		ConCommand *pCmd = NULL;
   511 		while (pBase)
   512 		{
   513 			if (pBase->IsCommand()
   514 				&& (strcmp(pBase->GetName(), name) == 0))
   515 			{
   516 				pCmd = (ConCommand *)pBase;
   517 				break;
   518 			}
   519 			pBase = const_cast<ConCommandBase *>(pBase->GetNext());
   520 		}
   521 
   522 		if (!pCmd)
   523 		{
   524 			/* Note that we have to duplicate because the source might not be 
   525 			 * a static string, and these expect static memory.
   526 			 */
   527 			if (!description)
   528 			{
   529 				description = "";
   530 			}
   531 			char *new_name = sm_strdup(name);
   532 			char *new_help = sm_strdup(description);
   533 			pCmd = new ConCommand(new_name, CommandCallback, new_help, flags);
   534 			pInfo->sourceMod = true;
   535 		} else {
   536 			SH_ADD_HOOK_STATICFUNC(ConCommand, Dispatch, pCmd, CommandCallback, false);
   537 		}
   538 
   539 		pInfo->pCmd = pCmd;
   540 
   541 		sm_trie_insert(m_pCmds, name, pInfo);
   542 		AddToCmdList(pInfo);
   543 	}
   544 
   545 	return pInfo;
   546 }
   547 
   548 void CConCmdManager::OnRootConsoleCommand(const char *command, unsigned int argcount)
   549 {
   550 	if (argcount >= 3)
   551 	{
   552 		const char *text = engine->Cmd_Argv(2);
   553 		int id = atoi(text);
   554 		CPlugin *pPlugin = g_PluginSys.GetPluginByOrder(id);
   555 
   556 		if (!pPlugin)
   557 		{
   558 			g_RootMenu.ConsolePrint("[SM] Plugin index %d not found.", id);
   559 			return;
   560 		}
   561 
   562 		CmdList *pList;
   563 		if (!pPlugin->GetProperty("CommandList", (void **)&pList))
   564 		{
   565 			g_RootMenu.ConsolePrint("[SM] No commands found for %s", pPlugin->GetFilename());
   566 			return;
   567 		}
   568 		if (!pList->size())
   569 		{
   570 			g_RootMenu.ConsolePrint("[SM] No commands found for %s", pPlugin->GetFilename());
   571 			return;
   572 		}
   573 
   574 		CmdList::iterator iter;
   575 		const char *type = NULL;
   576 		const char *name;
   577 		const char *help;
   578 		g_RootMenu.ConsolePrint(" %-17.16s %-8.7s %s", "[Name]", "[Type]", "[Help]");
   579 		for (iter=pList->begin();
   580 			 iter!=pList->end();
   581 			 iter++)
   582 		{
   583 			PlCmdInfo &cmd = (*iter);
   584 			if (cmd.type == Cmd_Server)
   585 			{
   586 				type = "server";
   587 			} else if (cmd.type == Cmd_Console) {
   588 				type = "console";
   589 			} else if (cmd.type == Cmd_Admin) {
   590 				type = "admin";
   591 			}
   592 			name = cmd.pInfo->pCmd->GetName();
   593 			if (cmd.pHook->helptext.size())
   594 			{
   595 				help = cmd.pHook->helptext.c_str();
   596 			} else {
   597 				help = cmd.pInfo->pCmd->GetHelpText();
   598 			}
   599 			g_RootMenu.ConsolePrint(" %-17.16s %-12.11s %s", name, type, help);		
   600 		}
   601 
   602 		return;
   603 	}
   604 
   605 	g_RootMenu.ConsolePrint("[SM] Usage: sm cmds <plugin #>");
   606 }