core/ChatTriggers.cpp
author Scott Ehlert <ds@alliedmods.net>
Wed Aug 15 06:19:30 2007 +0000 (2007-08-15)
changeset 1330 ab94316bb52b
parent 1231 17badaf66ce4
child 1377 723de5e67879
permissions -rw-r--r--
Updated license headers on virtually all files with extremely minor changes in the name of some sort of strange consistency.
All plugin and include file headers also have been changed to say about GPL3 instead of GPL2.

(This day shall henceforth be known as the Eighty Column Massacre of '07)
dvander@1231
     1
/**
dvander@1231
     2
 * vim: set ts=4 :
ds@1330
     3
 * =============================================================================
dvander@1231
     4
 * SourceMod
dvander@1231
     5
 * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
ds@1330
     6
 * =============================================================================
dvander@1231
     7
 *
ds@1330
     8
 * This program is free software; you can redistribute it and/or modify it under
ds@1330
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
ds@1330
    10
 * Free Software Foundation.
dvander@1231
    11
 * 
ds@1330
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
ds@1330
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
ds@1330
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
ds@1330
    15
 * details.
dvander@1231
    16
 *
ds@1330
    17
 * You should have received a copy of the GNU General Public License along with
ds@1330
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@1231
    19
 *
ds@1330
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
ds@1330
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
ds@1330
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
ds@1330
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
ds@1330
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
ds@1330
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
ds@1330
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
ds@1330
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@1231
    28
 *
dvander@1231
    29
 * Version: $Id$
dvander@1231
    30
 */
dvander@1231
    31
dvander@893
    32
#include "ChatTriggers.h"
dvander@893
    33
#include "sm_stringutil.h"
dvander@893
    34
#include "TextParsers.h"
dvander@893
    35
#include "ConCmdManager.h"
dvander@893
    36
dvander@893
    37
/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does */
dvander@893
    38
extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
dvander@904
    39
ds@908
    40
#if SH_IMPL_VERSION >= 4
dvander@893
    41
extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
dvander@904
    42
#else
dvander@904
    43
extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
dvander@904
    44
#endif
dvander@893
    45
dvander@893
    46
ChatTriggers g_ChatTriggers;
dvander@893
    47
dvander@893
    48
ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false), 
dvander@1103
    49
	m_bTriggerWasSilent(false), m_ReplyTo(SM_REPLY_CONSOLE)
dvander@893
    50
{
dvander@893
    51
	m_PubTrigger = sm_strdup("!");
dvander@893
    52
	m_PrivTrigger = sm_strdup("/");
dvander@893
    53
	m_PubTriggerSize = 1;
dvander@893
    54
	m_PrivTriggerSize = 1;
dvander@893
    55
}
dvander@893
    56
dvander@897
    57
ChatTriggers::~ChatTriggers()
dvander@897
    58
{
dvander@897
    59
	delete [] m_PubTrigger;
dvander@897
    60
	m_PubTrigger = NULL;
dvander@897
    61
	delete [] m_PrivTrigger;
dvander@897
    62
	m_PrivTrigger = NULL;
dvander@897
    63
}
dvander@896
    64
dvander@896
    65
ConfigResult ChatTriggers::OnSourceModConfigChanged(const char *key, 
dvander@896
    66
													const char *value, 
dvander@896
    67
													ConfigSource source,
dvander@896
    68
													char *error, 
dvander@896
    69
													size_t maxlength)
dvander@896
    70
{
dvander@896
    71
	if (strcmp(key, "PublicChatTrigger") == 0)
dvander@896
    72
	{
dvander@896
    73
		delete [] m_PubTrigger;
dvander@896
    74
		m_PubTrigger = sm_strdup(value);
dvander@896
    75
		m_PubTriggerSize = strlen(m_PubTrigger);
dvander@896
    76
		return ConfigResult_Accept;
dvander@896
    77
	} else if (strcmp(key, "SilentChatTrigger") == 0) {
dvander@896
    78
		delete [] m_PrivTrigger;
dvander@896
    79
		m_PrivTrigger = sm_strdup(value);
dvander@896
    80
		m_PrivTriggerSize = strlen(m_PrivTrigger);
dvander@896
    81
		return ConfigResult_Accept;
dvander@896
    82
	}
dvander@896
    83
dvander@896
    84
	return ConfigResult_Ignore;
dvander@896
    85
}
dvander@896
    86
dvander@893
    87
void ChatTriggers::OnSourceModGameInitialized()
dvander@893
    88
{
dvander@897
    89
	unsigned int total = 2;
dvander@893
    90
	ConCommandBase *pCmd = icvar->GetCommands();
dvander@893
    91
	const char *name;
dvander@893
    92
	while (pCmd)
dvander@893
    93
	{
dvander@893
    94
		if (pCmd->IsCommand())
dvander@893
    95
		{
dvander@893
    96
			name = pCmd->GetName();
dvander@897
    97
			if (!m_pSayCmd && strcmp(name, "say") == 0)
dvander@893
    98
			{
dvander@893
    99
				m_pSayCmd = (ConCommand *)pCmd;
dvander@897
   100
				if (--total == 0)
dvander@897
   101
				{
dvander@897
   102
					break;
dvander@897
   103
				}
dvander@897
   104
			} else if (!m_pSayTeamCmd && strcmp(name, "say_team") == 0) {
dvander@897
   105
				m_pSayTeamCmd = (ConCommand *)pCmd;
dvander@897
   106
				if (--total == 0)
dvander@897
   107
				{
dvander@897
   108
					break;
dvander@897
   109
				}
dvander@893
   110
			}
dvander@893
   111
		}
dvander@893
   112
		pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
dvander@893
   113
	}
dvander@893
   114
dvander@893
   115
	if (m_pSayCmd)
dvander@893
   116
	{
dvander@893
   117
		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
dvander@893
   118
		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
dvander@893
   119
	}
dvander@897
   120
	if (m_pSayTeamCmd)
dvander@897
   121
	{
dvander@897
   122
		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
dvander@897
   123
		SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true);
dvander@897
   124
	}
dvander@893
   125
}
dvander@893
   126
dvander@893
   127
void ChatTriggers::OnSourceModShutdown()
dvander@893
   128
{
dvander@897
   129
	if (m_pSayTeamCmd)
dvander@897
   130
	{
dvander@897
   131
		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true);
dvander@897
   132
		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
dvander@897
   133
	}
dvander@893
   134
	if (m_pSayCmd)
dvander@893
   135
	{
dvander@893
   136
		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
dvander@893
   137
		SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
dvander@893
   138
	}
dvander@893
   139
}
dvander@893
   140
dvander@893
   141
void ChatTriggers::OnSayCommand_Pre()
dvander@893
   142
{
dvander@893
   143
	int client = g_ConCmds.GetCommandClient();
dvander@893
   144
dvander@893
   145
	/* The server console cannot do this */
dvander@893
   146
	if (client == 0)
dvander@893
   147
	{
dvander@893
   148
		RETURN_META(MRES_IGNORED);
dvander@893
   149
	}
dvander@893
   150
dvander@893
   151
	const char *args = engine->Cmd_Args();
ds@1139
   152
	
ds@1139
   153
	if (!args)
ds@1139
   154
	{
ds@1139
   155
		RETURN_META(MRES_IGNORED);
ds@1139
   156
	}
dvander@893
   157
dvander@893
   158
	/* Handle quoted string sets */
dvander@893
   159
	bool is_quoted = false;
dvander@893
   160
	if (args[0] == '"')
dvander@893
   161
	{
dvander@893
   162
		args++;
dvander@893
   163
		is_quoted = true;
dvander@893
   164
	}
dvander@893
   165
dvander@893
   166
	bool is_trigger = false;
dvander@893
   167
	bool is_silent = false;
dvander@893
   168
dvander@893
   169
	/* Check for either trigger */
dvander@893
   170
	if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0)
dvander@893
   171
	{
dvander@893
   172
		is_trigger = true;
dvander@1103
   173
		args = &args[m_PubTriggerSize];
dvander@893
   174
	} else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) {
dvander@893
   175
		is_trigger = true;
dvander@893
   176
		is_silent = true;
dvander@1103
   177
		args = &args[m_PrivTriggerSize];
dvander@893
   178
	}
dvander@893
   179
dvander@893
   180
	if (!is_trigger)
dvander@893
   181
	{
dvander@893
   182
		RETURN_META(MRES_IGNORED);
dvander@893
   183
	}
dvander@893
   184
dvander@1103
   185
	/**
dvander@1103
   186
	 * Test if this is actually a command!
dvander@1103
   187
	 */
dvander@1103
   188
	if (!PreProcessTrigger(engine->PEntityOfEntIndex(client), args, is_quoted))
dvander@893
   189
	{
dvander@893
   190
		RETURN_META(MRES_IGNORED);
dvander@893
   191
	}
dvander@893
   192
dvander@1103
   193
	/**
dvander@1103
   194
	 * We'll execute it in post.
dvander@1103
   195
	 */
dvander@1103
   196
	m_bWillProcessInPost = true;
dvander@1103
   197
	m_bTriggerWasSilent = is_silent;
dvander@1103
   198
dvander@1103
   199
	/* If we're silent, block */
dvander@1103
   200
	if (is_silent)
dvander@893
   201
	{
dvander@893
   202
		RETURN_META(MRES_SUPERCEDE);
dvander@893
   203
	}
dvander@893
   204
dvander@1103
   205
	/* Otherwise, let the command continue */
dvander@893
   206
	RETURN_META(MRES_IGNORED);
dvander@893
   207
}
dvander@893
   208
dvander@893
   209
void ChatTriggers::OnSayCommand_Post()
dvander@893
   210
{
dvander@893
   211
	if (m_bWillProcessInPost)
dvander@893
   212
	{
dvander@893
   213
		/* Reset this for re-entrancy */
dvander@893
   214
		m_bWillProcessInPost = false;
dvander@1103
   215
		
dvander@1103
   216
		/* Execute the cached command */
dvander@893
   217
		int client = g_ConCmds.GetCommandClient();
dvander@1103
   218
		unsigned int old = SetReplyTo(SM_REPLY_CHAT);
dvander@1103
   219
		serverpluginhelpers->ClientCommand(engine->PEntityOfEntIndex(client), m_ToExecute);
dvander@1103
   220
		SetReplyTo(old);
dvander@893
   221
	}
dvander@893
   222
}
dvander@893
   223
dvander@1103
   224
bool ChatTriggers::PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted)
dvander@893
   225
{
dvander@893
   226
	/* Eat up whitespace */
dvander@893
   227
	while (*args != '\0' && IsWhitespace(args))
dvander@893
   228
	{
dvander@893
   229
		args++;
dvander@893
   230
	}
dvander@893
   231
dvander@893
   232
	/* Check if we're still valid */
dvander@893
   233
	if (*args == '\0')
dvander@893
   234
	{
dvander@893
   235
		return false;
dvander@893
   236
	}
dvander@893
   237
dvander@893
   238
	/* Extract a command.  This is kind of sloppy. */
dvander@893
   239
	char cmd_buf[64];
dvander@893
   240
	size_t cmd_len = 0;
dvander@893
   241
	const char *inptr = args;
dvander@893
   242
	while (*inptr != '\0' 
dvander@893
   243
			&& !IsWhitespace(inptr) 
dvander@935
   244
			&& *inptr != '"'
dvander@893
   245
			&& cmd_len < sizeof(cmd_buf) - 1)
dvander@893
   246
	{
dvander@893
   247
		cmd_buf[cmd_len++] = *inptr++;
dvander@893
   248
	}
dvander@893
   249
	cmd_buf[cmd_len] = '\0';
dvander@893
   250
dvander@893
   251
	/* See if we have this registered */
dvander@893
   252
	bool prepended = false;
dvander@893
   253
	if (!g_ConCmds.LookForSourceModCommand(cmd_buf))
dvander@893
   254
	{
dvander@893
   255
		/* Check if we had an "sm_" prefix */
dvander@893
   256
		if (strncmp(cmd_buf, "sm_", 3) == 0)
dvander@893
   257
		{
dvander@893
   258
			return false;
dvander@893
   259
		}
dvander@893
   260
dvander@893
   261
		/* Now, prepend.  Don't worry about the buffers.  This will 
dvander@893
   262
		 * work because the sizes are limited from earlier.
dvander@893
   263
		 */
dvander@893
   264
		char new_buf[80];
dvander@893
   265
		strcpy(new_buf, "sm_");
dvander@893
   266
		strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3);
dvander@893
   267
dvander@893
   268
		/* Recheck */
dvander@893
   269
		if (!g_ConCmds.LookForSourceModCommand(new_buf))
dvander@893
   270
		{
dvander@893
   271
			return false;
dvander@893
   272
		}
dvander@893
   273
		
dvander@893
   274
		prepended = true;
dvander@893
   275
	}
dvander@893
   276
dvander@893
   277
	/* See if we need to do extra string manipulation */
dvander@893
   278
	if (is_quoted || prepended)
dvander@893
   279
	{
dvander@893
   280
		size_t len;
dvander@893
   281
dvander@893
   282
		/* Check if we need to prepend sm_ */
dvander@893
   283
		if (prepended)
dvander@893
   284
		{
dvander@1103
   285
			len = UTIL_Format(m_ToExecute, sizeof(m_ToExecute), "sm_%s", args);
dvander@893
   286
		} else {
dvander@1103
   287
			len = strncopy(m_ToExecute, args, sizeof(m_ToExecute));
dvander@893
   288
		}
dvander@893
   289
		
dvander@893
   290
		/* Check if we need to strip a quote */
dvander@893
   291
		if (is_quoted)
dvander@893
   292
		{
dvander@1103
   293
			if (m_ToExecute[len-1] == '"')
dvander@893
   294
			{
dvander@1103
   295
				m_ToExecute[--len] = '\0';
dvander@893
   296
			}
dvander@893
   297
		}
dvander@1103
   298
	} else {
dvander@1103
   299
		strncopy(m_ToExecute, args, sizeof(m_ToExecute));
dvander@893
   300
	}
dvander@893
   301
dvander@893
   302
	return true;
dvander@893
   303
}
dvander@893
   304
dvander@893
   305
unsigned int ChatTriggers::SetReplyTo(unsigned int reply)
dvander@893
   306
{
dvander@893
   307
	unsigned int old = m_ReplyTo;
dvander@893
   308
dvander@893
   309
	m_ReplyTo = reply;
dvander@893
   310
dvander@893
   311
	return old;
dvander@893
   312
}
dvander@893
   313
dvander@893
   314
unsigned int ChatTriggers::GetReplyTo()
dvander@893
   315
{
dvander@893
   316
	return m_ReplyTo;
dvander@893
   317
}