core/ChatTriggers.cpp
author David Anderson <dvander@alliedmods.net>
Wed Jun 06 22:16:15 2007 +0000 (2007-06-06)
changeset 893 a3bc816549a8
child 896 e30eef2642ba
permissions -rw-r--r--
- added experimental (so far) support for SourceMod chat triggers
- added ReplyToCommand() for replying to triggers nicely
     1 #include "ChatTriggers.h"
     2 #include "sm_stringutil.h"
     3 #include "TextParsers.h"
     4 #include "ConCmdManager.h"
     5 
     6 /* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does */
     7 extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
     8 extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
     9 
    10 ChatTriggers g_ChatTriggers;
    11 
    12 ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false), 
    13 	m_ReplyTo(SM_REPLY_CONSOLE)
    14 {
    15 	m_PubTrigger = sm_strdup("!");
    16 	m_PrivTrigger = sm_strdup("/");
    17 	m_PubTriggerSize = 1;
    18 	m_PrivTriggerSize = 1;
    19 }
    20 
    21 void ChatTriggers::OnSourceModGameInitialized()
    22 {
    23 	ConCommandBase *pCmd = icvar->GetCommands();
    24 	const char *name;
    25 	while (pCmd)
    26 	{
    27 		if (pCmd->IsCommand())
    28 		{
    29 			name = pCmd->GetName();
    30 			if (strcmp(name, "say") == 0)
    31 			{
    32 				m_pSayCmd = (ConCommand *)pCmd;
    33 				break;
    34 			}
    35 		}
    36 		pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
    37 	}
    38 
    39 	if (m_pSayCmd)
    40 	{
    41 		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
    42 		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
    43 	}
    44 }
    45 
    46 void ChatTriggers::OnSourceModShutdown()
    47 {
    48 	if (m_pSayCmd)
    49 	{
    50 		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
    51 		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
    52 	}
    53 }
    54 
    55 void ChatTriggers::OnSayCommand_Pre()
    56 {
    57 	int client = g_ConCmds.GetCommandClient();
    58 
    59 	/* The server console cannot do this */
    60 	if (client == 0)
    61 	{
    62 		RETURN_META(MRES_IGNORED);
    63 	}
    64 
    65 	const char *args = engine->Cmd_Args();
    66 
    67 	/* Handle quoted string sets */
    68 	bool is_quoted = false;
    69 	if (args[0] == '"')
    70 	{
    71 		args++;
    72 		is_quoted = true;
    73 	}
    74 
    75 	bool is_trigger = false;
    76 	bool is_silent = false;
    77 
    78 	/* Check for either trigger */
    79 	if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0)
    80 	{
    81 		is_trigger = true;
    82 	} else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) {
    83 		is_trigger = true;
    84 		is_silent = true;
    85 	}
    86 
    87 	if (!is_trigger)
    88 	{
    89 		RETURN_META(MRES_IGNORED);
    90 	}
    91 
    92 	/* If we're a public command, process later */
    93 	if (!is_silent)
    94 	{
    95 		/* We have to process this in _post_ instead.  Darn. */
    96 		m_bWillProcessInPost = true;
    97 		RETURN_META(MRES_IGNORED);
    98 	}
    99 
   100 	/* Otherwise, process now */
   101 	if (ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PrivTriggerSize], is_quoted))
   102 	{
   103 		/* If we succeed, block the original say! */
   104 		RETURN_META(MRES_SUPERCEDE);
   105 	}
   106 
   107 	RETURN_META(MRES_IGNORED);
   108 }
   109 
   110 void ChatTriggers::OnSayCommand_Post()
   111 {
   112 	if (m_bWillProcessInPost)
   113 	{
   114 		/* Reset this for re-entrancy */
   115 		m_bWillProcessInPost = false;
   116 
   117 		/* Get our arguments */
   118 		const char *args = engine->Cmd_Args();
   119 
   120 		/* Handle quotations */
   121 		bool is_quoted = false;
   122 		if (args[0] == '"')
   123 		{
   124 			args++;
   125 			is_quoted = true;
   126 		}
   127 
   128 		int client = g_ConCmds.GetCommandClient();
   129 
   130 		ProcessTrigger(engine->PEntityOfEntIndex(client), &args[m_PubTriggerSize], is_quoted);
   131 	}
   132 }
   133 
   134 bool ChatTriggers::ProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted)
   135 {
   136 	/* Eat up whitespace */
   137 	while (*args != '\0' && IsWhitespace(args))
   138 	{
   139 		args++;
   140 	}
   141 
   142 	/* Check if we're still valid */
   143 	if (*args == '\0')
   144 	{
   145 		return false;
   146 	}
   147 
   148 	/* Extract a command.  This is kind of sloppy. */
   149 	char cmd_buf[64];
   150 	size_t cmd_len = 0;
   151 	const char *inptr = args;
   152 	while (*inptr != '\0' 
   153 			&& !IsWhitespace(inptr) 
   154 			&& cmd_len < sizeof(cmd_buf) - 1)
   155 	{
   156 		cmd_buf[cmd_len++] = *inptr++;
   157 	}
   158 	cmd_buf[cmd_len] = '\0';
   159 
   160 	/* See if we have this registered */
   161 	bool prepended = false;
   162 	if (!g_ConCmds.LookForSourceModCommand(cmd_buf))
   163 	{
   164 		/* Check if we had an "sm_" prefix */
   165 		if (strncmp(cmd_buf, "sm_", 3) == 0)
   166 		{
   167 			return false;
   168 		}
   169 
   170 		/* Now, prepend.  Don't worry about the buffers.  This will 
   171 		 * work because the sizes are limited from earlier.
   172 		 */
   173 		char new_buf[80];
   174 		strcpy(new_buf, "sm_");
   175 		strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3);
   176 
   177 		/* Recheck */
   178 		if (!g_ConCmds.LookForSourceModCommand(new_buf))
   179 		{
   180 			return false;
   181 		}
   182 		
   183 		prepended = true;
   184 	}
   185 
   186 	/* See if we need to do extra string manipulation */
   187 	if (is_quoted || prepended)
   188 	{
   189 		static char buffer[300];
   190 		size_t len;
   191 
   192 		/* Check if we need to prepend sm_ */
   193 		if (prepended)
   194 		{
   195 			len = UTIL_Format(buffer, sizeof(buffer), "sm_%s", args);
   196 		} else {
   197 			len = strncopy(buffer, args, sizeof(buffer));
   198 		}
   199 		
   200 		/* Check if we need to strip a quote */
   201 		if (is_quoted)
   202 		{
   203 			if (buffer[len-1] == '"')
   204 			{
   205 				buffer[--len] = '\0';
   206 			}
   207 		}
   208 
   209 		args = buffer;
   210 	}
   211 
   212 	/* Finally, execute! */
   213 	unsigned int old = SetReplyTo(SM_REPLY_CHAT);
   214 	serverpluginhelpers->ClientCommand(pEdict, args);
   215 	SetReplyTo(old);
   216 
   217 	return true;
   218 }
   219 
   220 unsigned int ChatTriggers::SetReplyTo(unsigned int reply)
   221 {
   222 	unsigned int old = m_ReplyTo;
   223 
   224 	m_ReplyTo = reply;
   225 
   226 	return old;
   227 }
   228 
   229 unsigned int ChatTriggers::GetReplyTo()
   230 {
   231 	return m_ReplyTo;
   232 }