- added experimental (so far) support for SourceMod chat triggers
authorDavid Anderson <dvander@alliedmods.net>
Wed Jun 06 22:16:15 2007 +0000 (2007-06-06)
changeset 893a3bc816549a8
parent 892 0702fda305f0
child 894 e54c2e1d96f2
- added experimental (so far) support for SourceMod chat triggers
- added ReplyToCommand() for replying to triggers nicely
core/ChatTriggers.cpp
core/ChatTriggers.h
core/ConCmdManager.cpp
core/ConCmdManager.h
core/msvc8/sourcemod_mm.vcproj
core/sm_globals.h
core/smn_console.cpp
core/smn_halflife.cpp
core/sourcemm_api.cpp
core/sourcemod.cpp
core/sourcemod.h
plugins/include/console.inc
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/core/ChatTriggers.cpp	Wed Jun 06 22:16:15 2007 +0000
     1.3 @@ -0,0 +1,232 @@
     1.4 +#include "ChatTriggers.h"
     1.5 +#include "sm_stringutil.h"
     1.6 +#include "TextParsers.h"
     1.7 +#include "ConCmdManager.h"
     1.8 +
     1.9 +/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does */
    1.10 +extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
    1.11 +extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
    1.12 +
    1.13 +ChatTriggers g_ChatTriggers;
    1.14 +
    1.15 +ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false), 
    1.16 +	m_ReplyTo(SM_REPLY_CONSOLE)
    1.17 +{
    1.18 +	m_PubTrigger = sm_strdup("!");
    1.19 +	m_PrivTrigger = sm_strdup("/");
    1.20 +	m_PubTriggerSize = 1;
    1.21 +	m_PrivTriggerSize = 1;
    1.22 +}
    1.23 +
    1.24 +void ChatTriggers::OnSourceModGameInitialized()
    1.25 +{
    1.26 +	ConCommandBase *pCmd = icvar->GetCommands();
    1.27 +	const char *name;
    1.28 +	while (pCmd)
    1.29 +	{
    1.30 +		if (pCmd->IsCommand())
    1.31 +		{
    1.32 +			name = pCmd->GetName();
    1.33 +			if (strcmp(name, "say") == 0)
    1.34 +			{
    1.35 +				m_pSayCmd = (ConCommand *)pCmd;
    1.36 +				break;
    1.37 +			}
    1.38 +		}
    1.39 +		pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
    1.40 +	}
    1.41 +
    1.42 +	if (m_pSayCmd)
    1.43 +	{
    1.44 +		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
    1.45 +		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
    1.46 +	}
    1.47 +}
    1.48 +
    1.49 +void ChatTriggers::OnSourceModShutdown()
    1.50 +{
    1.51 +	if (m_pSayCmd)
    1.52 +	{
    1.53 +		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
    1.54 +		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
    1.55 +	}
    1.56 +}
    1.57 +
    1.58 +void ChatTriggers::OnSayCommand_Pre()
    1.59 +{
    1.60 +	int client = g_ConCmds.GetCommandClient();
    1.61 +
    1.62 +	/* The server console cannot do this */
    1.63 +	if (client == 0)
    1.64 +	{
    1.65 +		RETURN_META(MRES_IGNORED);
    1.66 +	}
    1.67 +
    1.68 +	const char *args = engine->Cmd_Args();
    1.69 +
    1.70 +	/* Handle quoted string sets */
    1.71 +	bool is_quoted = false;
    1.72 +	if (args[0] == '"')
    1.73 +	{
    1.74 +		args++;
    1.75 +		is_quoted = true;
    1.76 +	}
    1.77 +
    1.78 +	bool is_trigger = false;
    1.79 +	bool is_silent = false;
    1.80 +
    1.81 +	/* Check for either trigger */
    1.82 +	if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0)
    1.83 +	{
    1.84 +		is_trigger = true;
    1.85 +	} else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) {
    1.86 +		is_trigger = true;
    1.87 +		is_silent = true;
    1.88 +	}
    1.89 +
    1.90 +	if (!is_trigger)
    1.91 +	{
    1.92 +		RETURN_META(MRES_IGNORED);
    1.93 +	}
    1.94 +
    1.95 +	/* If we're a public command, process later */
    1.96 +	if (!is_silent)
    1.97 +	{
    1.98 +		/* We have to process this in _post_ instead.  Darn. */
    1.99 +		m_bWillProcessInPost = true;
   1.100 +		RETURN_META(MRES_IGNORED);
   1.101 +	}
   1.102 +
   1.103 +	/* Otherwise, process now */
   1.104 +	if (ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PrivTriggerSize], is_quoted))
   1.105 +	{
   1.106 +		/* If we succeed, block the original say! */
   1.107 +		RETURN_META(MRES_SUPERCEDE);
   1.108 +	}
   1.109 +
   1.110 +	RETURN_META(MRES_IGNORED);
   1.111 +}
   1.112 +
   1.113 +void ChatTriggers::OnSayCommand_Post()
   1.114 +{
   1.115 +	if (m_bWillProcessInPost)
   1.116 +	{
   1.117 +		/* Reset this for re-entrancy */
   1.118 +		m_bWillProcessInPost = false;
   1.119 +
   1.120 +		/* Get our arguments */
   1.121 +		const char *args = engine->Cmd_Args();
   1.122 +
   1.123 +		/* Handle quotations */
   1.124 +		bool is_quoted = false;
   1.125 +		if (args[0] == '"')
   1.126 +		{
   1.127 +			args++;
   1.128 +			is_quoted = true;
   1.129 +		}
   1.130 +
   1.131 +		int client = g_ConCmds.GetCommandClient();
   1.132 +
   1.133 +		ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PubTriggerSize], is_quoted);
   1.134 +	}
   1.135 +}
   1.136 +
   1.137 +bool ChatTriggers::ProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted)
   1.138 +{
   1.139 +	/* Eat up whitespace */
   1.140 +	while (*args != '\0' && IsWhitespace(args))
   1.141 +	{
   1.142 +		args++;
   1.143 +	}
   1.144 +
   1.145 +	/* Check if we're still valid */
   1.146 +	if (*args == '\0')
   1.147 +	{
   1.148 +		return false;
   1.149 +	}
   1.150 +
   1.151 +	/* Extract a command.  This is kind of sloppy. */
   1.152 +	char cmd_buf[64];
   1.153 +	size_t cmd_len = 0;
   1.154 +	const char *inptr = args;
   1.155 +	while (*inptr != '\0' 
   1.156 +			&& !IsWhitespace(inptr) 
   1.157 +			&& cmd_len < sizeof(cmd_buf) - 1)
   1.158 +	{
   1.159 +		cmd_buf[cmd_len++] = *inptr++;
   1.160 +	}
   1.161 +	cmd_buf[cmd_len] = '\0';
   1.162 +
   1.163 +	/* See if we have this registered */
   1.164 +	bool prepended = false;
   1.165 +	if (!g_ConCmds.LookForSourceModCommand(cmd_buf))
   1.166 +	{
   1.167 +		/* Check if we had an "sm_" prefix */
   1.168 +		if (strncmp(cmd_buf, "sm_", 3) == 0)
   1.169 +		{
   1.170 +			return false;
   1.171 +		}
   1.172 +
   1.173 +		/* Now, prepend.  Don't worry about the buffers.  This will 
   1.174 +		 * work because the sizes are limited from earlier.
   1.175 +		 */
   1.176 +		char new_buf[80];
   1.177 +		strcpy(new_buf, "sm_");
   1.178 +		strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3);
   1.179 +
   1.180 +		/* Recheck */
   1.181 +		if (!g_ConCmds.LookForSourceModCommand(new_buf))
   1.182 +		{
   1.183 +			return false;
   1.184 +		}
   1.185 +		
   1.186 +		prepended = true;
   1.187 +	}
   1.188 +
   1.189 +	/* See if we need to do extra string manipulation */
   1.190 +	if (is_quoted || prepended)
   1.191 +	{
   1.192 +		static char buffer[300];
   1.193 +		size_t len;
   1.194 +
   1.195 +		/* Check if we need to prepend sm_ */
   1.196 +		if (prepended)
   1.197 +		{
   1.198 +			len = UTIL_Format(buffer, sizeof(buffer), "sm_%s", args);
   1.199 +		} else {
   1.200 +			len = strncopy(buffer, args, sizeof(buffer));
   1.201 +		}
   1.202 +		
   1.203 +		/* Check if we need to strip a quote */
   1.204 +		if (is_quoted)
   1.205 +		{
   1.206 +			if (buffer[len-1] == '"')
   1.207 +			{
   1.208 +				buffer[--len] = '\0';
   1.209 +			}
   1.210 +		}
   1.211 +
   1.212 +		args = buffer;
   1.213 +	}
   1.214 +
   1.215 +	/* Finally, execute! */
   1.216 +	unsigned int old = SetReplyTo(SM_REPLY_CHAT);
   1.217 +	serverpluginhelpers->ClientCommand(pEdict, args);
   1.218 +	SetReplyTo(old);
   1.219 +
   1.220 +	return true;
   1.221 +}
   1.222 +
   1.223 +unsigned int ChatTriggers::SetReplyTo(unsigned int reply)
   1.224 +{
   1.225 +	unsigned int old = m_ReplyTo;
   1.226 +
   1.227 +	m_ReplyTo = reply;
   1.228 +
   1.229 +	return old;
   1.230 +}
   1.231 +
   1.232 +unsigned int ChatTriggers::GetReplyTo()
   1.233 +{
   1.234 +	return m_ReplyTo;
   1.235 +}
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/core/ChatTriggers.h	Wed Jun 06 22:16:15 2007 +0000
     2.3 @@ -0,0 +1,37 @@
     2.4 +#ifndef _INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_
     2.5 +#define _INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_
     2.6 +
     2.7 +#include "sm_globals.h"
     2.8 +#include "sourcemm_api.h"
     2.9 +
    2.10 +#define SM_REPLY_CONSOLE	0
    2.11 +#define SM_REPLY_CHAT		1
    2.12 +
    2.13 +class ChatTriggers : public SMGlobalClass
    2.14 +{
    2.15 +public:
    2.16 +	ChatTriggers();
    2.17 +public: //SMGlobalClass
    2.18 +	void OnSourceModGameInitialized();
    2.19 +	void OnSourceModShutdown();
    2.20 +private: //ConCommand
    2.21 +	void OnSayCommand_Pre();
    2.22 +	void OnSayCommand_Post();
    2.23 +public:
    2.24 +	unsigned int GetReplyTo();
    2.25 +	unsigned int SetReplyTo(unsigned int reply);
    2.26 +private:
    2.27 +	bool ProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted);
    2.28 +private:
    2.29 +	ConCommand *m_pSayCmd;
    2.30 +	char *m_PubTrigger;
    2.31 +	size_t m_PubTriggerSize;
    2.32 +	char *m_PrivTrigger;
    2.33 +	size_t m_PrivTriggerSize;
    2.34 +	bool m_bWillProcessInPost;
    2.35 +	unsigned int m_ReplyTo;
    2.36 +};
    2.37 +
    2.38 +extern ChatTriggers g_ChatTriggers;
    2.39 +
    2.40 +#endif //_INCLUDE_SOURCEMOD_CHAT_TRIGGERS_H_
     3.1 --- a/core/ConCmdManager.cpp	Wed Jun 06 20:51:17 2007 +0000
     3.2 +++ b/core/ConCmdManager.cpp	Wed Jun 06 22:16:15 2007 +0000
     3.3 @@ -58,13 +58,17 @@
     3.4  	SH_ADD_HOOK_MEMFUNC(IServerGameClients, SetCommandClient, serverClients, this, &ConCmdManager::SetCommandClient, false);
     3.5  
     3.6  	ConCommandBase *pCmd = icvar->GetCommands();
     3.7 +	const char *name;
     3.8  	while (pCmd)
     3.9  	{
    3.10 -		if (pCmd->IsCommand() 
    3.11 -			&& strcmp(pCmd->GetName(), "exec") == 0)
    3.12 +		if (pCmd->IsCommand())
    3.13  		{
    3.14 -			m_pExecCmd = (ConCommand *)pCmd;
    3.15 -			break;
    3.16 +			name = pCmd->GetName();
    3.17 +			if (strcmp(name, "exec") == 0)
    3.18 +			{
    3.19 +				m_pExecCmd = (ConCommand *)pCmd;
    3.20 +				break;
    3.21 +			}
    3.22  		}
    3.23  		pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
    3.24  	}
    3.25 @@ -756,6 +760,18 @@
    3.26  	delete info;
    3.27  }
    3.28  
    3.29 +bool ConCmdManager::LookForSourceModCommand(const char *cmd)
    3.30 +{
    3.31 +	ConCmdInfo *pInfo;
    3.32 +
    3.33 +	if (!sm_trie_retrieve(m_pCmds, cmd, (void **)&pInfo))
    3.34 +	{
    3.35 +		return false;
    3.36 +	}
    3.37 +
    3.38 +	return pInfo->sourceMod && (pInfo->conhooks.size() > 0);
    3.39 +}
    3.40 +
    3.41  ConCmdInfo *ConCmdManager::AddOrFindCommand(const char *name, const char *description, int flags)
    3.42  {
    3.43  	ConCmdInfo *pInfo;
     4.1 --- a/core/ConCmdManager.h	Wed Jun 06 20:51:17 2007 +0000
     4.2 +++ b/core/ConCmdManager.h	Wed Jun 06 22:16:15 2007 +0000
     4.3 @@ -101,6 +101,7 @@
     4.4  	ResultType DispatchClientCommand(int client, ResultType type);
     4.5  	void UpdateAdminCmdFlags(const char *cmd, OverrideType type, FlagBits bits);
     4.6  	void NotifyExecDone(const char *file);
     4.7 +	bool LookForSourceModCommand(const char *cmd);
     4.8  private:
     4.9  	void InternalDispatch();
    4.10  	ResultType RunAdminCommand(ConCmdInfo *pInfo, int client, int args);
     5.1 --- a/core/msvc8/sourcemod_mm.vcproj	Wed Jun 06 20:51:17 2007 +0000
     5.2 +++ b/core/msvc8/sourcemod_mm.vcproj	Wed Jun 06 22:16:15 2007 +0000
     5.3 @@ -190,6 +190,10 @@
     5.4  				>
     5.5  			</File>
     5.6  			<File
     5.7 +				RelativePath="..\ChatTriggers.cpp"
     5.8 +				>
     5.9 +			</File>
    5.10 +			<File
    5.11  				RelativePath="..\ConCmdManager.cpp"
    5.12  				>
    5.13  			</File>
    5.14 @@ -312,6 +316,10 @@
    5.15  				>
    5.16  			</File>
    5.17  			<File
    5.18 +				RelativePath="..\ChatTriggers.h"
    5.19 +				>
    5.20 +			</File>
    5.21 +			<File
    5.22  				RelativePath="..\ConCmdManager.h"
    5.23  				>
    5.24  			</File>
     6.1 --- a/core/sm_globals.h	Wed Jun 06 20:51:17 2007 +0000
     6.2 +++ b/core/sm_globals.h	Wed Jun 06 22:16:15 2007 +0000
     6.3 @@ -121,6 +121,13 @@
     6.4  	virtual void OnSourceModVSPReceived(IServerPluginCallbacks *iface)
     6.5  	{
     6.6  	}
     6.7 +
     6.8 +	/**
     6.9 +	 * @brief Called once all MM:S plugins are loaded.
    6.10 +	 */
    6.11 +	virtual void OnSourceModGameInitialized()
    6.12 +	{
    6.13 +	}
    6.14  private:
    6.15  	SMGlobalClass *m_pGlobalClassNext;
    6.16  	static SMGlobalClass *head;
     7.1 --- a/core/smn_console.cpp	Wed Jun 06 20:51:17 2007 +0000
     7.2 +++ b/core/smn_console.cpp	Wed Jun 06 22:16:15 2007 +0000
     7.3 @@ -21,6 +21,7 @@
     7.4  #include "PluginSys.h"
     7.5  #include "sm_stringutil.h"
     7.6  #include "PlayerManager.h"
     7.7 +#include "ChatTriggers.h"
     7.8  
     7.9  enum ConVarBounds
    7.10  {
    7.11 @@ -28,6 +29,8 @@
    7.12  	ConVarBound_Lower
    7.13  };
    7.14  
    7.15 +#define HUD_PRINTTALK		3
    7.16 +
    7.17  static cell_t sm_CreateConVar(IPluginContext *pContext, const cell_t *params)
    7.18  {
    7.19  	char *name, *defaultVal, *helpText;
    7.20 @@ -632,6 +635,77 @@
    7.21  	return 1;
    7.22  }
    7.23  
    7.24 +
    7.25 +static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
    7.26 +{
    7.27 +	CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
    7.28 +
    7.29 +	if (!pPlayer)
    7.30 +	{
    7.31 +		return pContext->ThrowNativeError("Player %d is not a valid player", params[1]);
    7.32 +	}
    7.33 +
    7.34 +	if (!pPlayer->IsConnected())
    7.35 +	{
    7.36 +		return pContext->ThrowNativeError("Player %d is not connected", params[1]);
    7.37 +	}
    7.38 +
    7.39 +	char buffer[256];
    7.40 +	g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
    7.41 +
    7.42 +	unsigned int old = g_ChatTriggers.SetReplyTo(SM_REPLY_CONSOLE);
    7.43 +	serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer);
    7.44 +	g_ChatTriggers.SetReplyTo(old);
    7.45 +
    7.46 +	return 1;
    7.47 +}
    7.48 +
    7.49 +static cell_t ReplyToCommand(IPluginContext *pContext, const cell_t *params)
    7.50 +{
    7.51 +	/* Build the format string */
    7.52 +	char buffer[1024];
    7.53 +	size_t len = g_SourceMod.FormatString(buffer, sizeof(buffer)-2, pContext, params, 2);
    7.54 +
    7.55 +	/* If we're printing to the server, shortcut out */
    7.56 +	if (params[1] == 0)
    7.57 +	{
    7.58 +		/* Print */
    7.59 +		buffer[len++] = '\n';
    7.60 +		buffer[len] = '\0';
    7.61 +		META_CONPRINT(buffer);
    7.62 +		return 1;
    7.63 +	}
    7.64 +
    7.65 +	CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
    7.66 +
    7.67 +	if (!pPlayer)
    7.68 +	{
    7.69 +		return pContext->ThrowNativeError("Client index %d is invalid", params[1]);
    7.70 +	}
    7.71 +
    7.72 +	if (!pPlayer->IsConnected())
    7.73 +	{
    7.74 +		return pContext->ThrowNativeError("Client %d is not connected", params[1]);
    7.75 +	}
    7.76 +
    7.77 +	unsigned int replyto = g_ChatTriggers.GetReplyTo();
    7.78 +	if (replyto == SM_REPLY_CONSOLE)
    7.79 +	{
    7.80 +		buffer[len++] = '\n';
    7.81 +		buffer[len] = '\0';
    7.82 +		engine->ClientPrintf(pPlayer->GetEdict(), buffer);
    7.83 +	} else if (replyto == SM_REPLY_CHAT) {
    7.84 +		if (len >= 191)
    7.85 +		{
    7.86 +			len = 191;
    7.87 +		}
    7.88 +		buffer[len] = '\0';
    7.89 +		g_HL2.TextMsg(params[1], HUD_PRINTTALK, buffer);
    7.90 +	}
    7.91 +
    7.92 +	return 1;
    7.93 +}
    7.94 +
    7.95  REGISTER_NATIVES(consoleNatives)
    7.96  {
    7.97  	{"CreateConVar",		sm_CreateConVar},
    7.98 @@ -665,5 +739,7 @@
    7.99  	{"InsertServerCommand",	sm_InsertServerCommand},
   7.100  	{"ServerExecute",		sm_ServerExecute},
   7.101  	{"ClientCommand",		sm_ClientCommand},
   7.102 +	{"FakeClientCommand",	FakeClientCommand},
   7.103 +	{"ReplyToCommand",		ReplyToCommand},
   7.104  	{NULL,					NULL}
   7.105  };
     8.1 --- a/core/smn_halflife.cpp	Wed Jun 06 20:51:17 2007 +0000
     8.2 +++ b/core/smn_halflife.cpp	Wed Jun 06 22:16:15 2007 +0000
     8.3 @@ -233,28 +233,6 @@
     8.4  	return enginesound->IsSoundPrecached(sample) ? 1 : 0;
     8.5  }
     8.6  
     8.7 -static cell_t FakeClientCommand(IPluginContext *pContext, const cell_t *params)
     8.8 -{
     8.9 -	CPlayer *pPlayer = g_Players.GetPlayerByIndex(params[1]);
    8.10 -
    8.11 -	if (!pPlayer)
    8.12 -	{
    8.13 -		return pContext->ThrowNativeError("Player %d is not a valid player", params[1]);
    8.14 -	}
    8.15 -
    8.16 -	if (!pPlayer->IsConnected())
    8.17 -	{
    8.18 -		return pContext->ThrowNativeError("Player %d is not connected", params[1]);
    8.19 -	}
    8.20 -
    8.21 -	char buffer[256];
    8.22 -	g_SourceMod.FormatString(buffer, sizeof(buffer), pContext, params, 2);
    8.23 -
    8.24 -	serverpluginhelpers->ClientCommand(pPlayer->GetEdict(), buffer);
    8.25 -
    8.26 -	return 1;
    8.27 -}
    8.28 -
    8.29  static cell_t smn_CreateDialog(IPluginContext *pContext, const cell_t *params)
    8.30  {
    8.31  	KeyValues *pKV;
    8.32 @@ -354,7 +332,6 @@
    8.33  	{"IsGenericPrecached",		IsGenericPrecached},
    8.34  	{"PrecacheSound",			PrecacheSound},
    8.35  	{"IsSoundPrecached",		IsSoundPrecached},
    8.36 -	{"FakeClientCommand",		FakeClientCommand},
    8.37  	{"CreateDialog",			smn_CreateDialog},
    8.38  	{"PrintToChat",				PrintToChat},
    8.39  	{"PrintCenterText",			PrintCenterText},
     9.1 --- a/core/sourcemm_api.cpp	Wed Jun 06 20:51:17 2007 +0000
     9.2 +++ b/core/sourcemm_api.cpp	Wed Jun 06 22:16:15 2007 +0000
     9.3 @@ -88,6 +88,7 @@
     9.4  
     9.5  void SourceMod_Core::AllPluginsLoaded()
     9.6  {
     9.7 +	g_SourceMod.AllPluginsLoaded();
     9.8  }
     9.9  
    9.10  const char *SourceMod_Core::GetAuthor()
    10.1 --- a/core/sourcemod.cpp	Wed Jun 06 20:51:17 2007 +0000
    10.2 +++ b/core/sourcemod.cpp	Wed Jun 06 22:16:15 2007 +0000
    10.3 @@ -656,6 +656,16 @@
    10.4  	return g_pVM;
    10.5  }
    10.6  
    10.7 +void SourceModBase::AllPluginsLoaded()
    10.8 +{
    10.9 +	SMGlobalClass *base = SMGlobalClass::head;
   10.10 +	while (base)
   10.11 +	{
   10.12 +		base->OnSourceModGameInitialized();
   10.13 +		base = base->m_pGlobalClassNext;
   10.14 +	}
   10.15 +}
   10.16 +
   10.17  SMGlobalClass *SMGlobalClass::head = NULL;
   10.18  
   10.19  SMGlobalClass::SMGlobalClass()
    11.1 --- a/core/sourcemod.h	Wed Jun 06 20:51:17 2007 +0000
    11.2 +++ b/core/sourcemod.h	Wed Jun 06 22:16:15 2007 +0000
    11.3 @@ -97,6 +97,7 @@
    11.4  	const char *GetGameFolderName() const;
    11.5  	ISourcePawnEngine *GetScriptingEngine();
    11.6  	IVirtualMachine *GetScriptingVM();
    11.7 +	void AllPluginsLoaded();
    11.8  private:
    11.9  	/**
   11.10  	 * @brief Loading plugins
    12.1 --- a/plugins/include/console.inc	Wed Jun 06 20:51:17 2007 +0000
    12.2 +++ b/plugins/include/console.inc	Wed Jun 06 22:16:15 2007 +0000
    12.3 @@ -133,7 +133,7 @@
    12.4  /**
    12.5   * Sends a message to a client's console.
    12.6   *
    12.7 - * @param client		Player index.
    12.8 + * @param client		Client index.
    12.9   * @param format		Formatting rules.
   12.10   * @param ...			Variable number of format parameters.
   12.11   * @noreturn
   12.12 @@ -142,6 +142,21 @@
   12.13  native PrintToConsole(client, const String:format[], any:...);
   12.14  
   12.15  /**
   12.16 + * Reples to a message in a command.
   12.17 + *
   12.18 + * A client index of 0 will use PrintToServer().
   12.19 + * If the command was from the console, PrintToConsole() is used.
   12.20 + * If the command was from chat, PrintToChat() is used.
   12.21 + *
   12.22 + * @param client		Client index, or 0 for server.
   12.23 + * @param format		Formatting rules.
   12.24 + * @param ...			Variable number of format parameters.
   12.25 + * @noreturn
   12.26 + * @error				If the client is not connected or invalid.
   12.27 + */
   12.28 +native ReplyToCommand(client, const String:fornmat[], any:...);
   12.29 +
   12.30 +/**
   12.31   * Called when a server-only command is invoked.  
   12.32   *
   12.33   * @params args			Number of arguments that were in the argument string.