251cced1f8
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 --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401971
452 lines
12 KiB
C++
452 lines
12 KiB
C++
/**
|
|
* vim: set ts=4 :
|
|
* =============================================================================
|
|
* SourceMod
|
|
* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
|
|
* =============================================================================
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, version 3.0, as published by the
|
|
* Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* As a special exception, AlliedModders LLC gives you permission to link the
|
|
* code of this program (as well as its derivative works) to "Half-Life 2," the
|
|
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
|
|
* by the Valve Corporation. You must obey the GNU General Public License in
|
|
* all respects for all other code used. Additionally, AlliedModders LLC grants
|
|
* this exception to all derivative works. AlliedModders LLC defines further
|
|
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
|
|
* or <http://www.sourcemod.net/license.php>.
|
|
*
|
|
* Version: $Id$
|
|
*/
|
|
|
|
#include <ITextParsers.h>
|
|
#include "ChatTriggers.h"
|
|
#include "sm_stringutil.h"
|
|
#include "ConCmdManager.h"
|
|
#include "PlayerManager.h"
|
|
#include "Translator.h"
|
|
#include "HalfLife2.h"
|
|
|
|
/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does.
|
|
* While the OB build only runs on MM:S 1.6.0+ (SH 5+), the older one
|
|
* can technically be compiled against any MM:S version after 1.4.2.
|
|
*/
|
|
#if defined ORANGEBOX_BUILD
|
|
extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate1<const CCommand &, void>);
|
|
extern int __SourceHook_FHAddConCommandDispatch(void *, ISourceHook::AddHookMode, bool, class fastdelegate::FastDelegate1<const CCommand &, void>);
|
|
#else
|
|
extern bool __SourceHook_FHRemoveConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
|
|
#if SH_IMPL_VERSION >= 5
|
|
extern int __SourceHook_FHAddConCommandDispatch(void *, ISourceHook::AddHookMode, bool, class fastdelegate::FastDelegate0<void>);
|
|
#elif SH_IMPL_VERSION == 4
|
|
extern int __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
|
|
#elif SH_IMPL_VERSION == 3
|
|
extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegate::FastDelegate0<void>);
|
|
#endif //SH_IMPL_VERSION
|
|
#endif //ORANGEBOX_BUILD
|
|
|
|
ChatTriggers g_ChatTriggers;
|
|
bool g_bSupressSilentFails = false;
|
|
CPhraseFile *g_pFloodPhrases = NULL;
|
|
|
|
ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false),
|
|
m_bTriggerWasSilent(false), m_ReplyTo(SM_REPLY_CONSOLE)
|
|
{
|
|
m_PubTrigger = sm_strdup("!");
|
|
m_PrivTrigger = sm_strdup("/");
|
|
m_PubTriggerSize = 1;
|
|
m_PrivTriggerSize = 1;
|
|
m_bIsChatTrigger = false;
|
|
}
|
|
|
|
ChatTriggers::~ChatTriggers()
|
|
{
|
|
delete [] m_PubTrigger;
|
|
m_PubTrigger = NULL;
|
|
delete [] m_PrivTrigger;
|
|
m_PrivTrigger = NULL;
|
|
}
|
|
|
|
ConfigResult ChatTriggers::OnSourceModConfigChanged(const char *key,
|
|
const char *value,
|
|
ConfigSource source,
|
|
char *error,
|
|
size_t maxlength)
|
|
{
|
|
if (strcmp(key, "PublicChatTrigger") == 0)
|
|
{
|
|
delete [] m_PubTrigger;
|
|
m_PubTrigger = sm_strdup(value);
|
|
m_PubTriggerSize = strlen(m_PubTrigger);
|
|
return ConfigResult_Accept;
|
|
}
|
|
else if (strcmp(key, "SilentChatTrigger") == 0)
|
|
{
|
|
delete [] m_PrivTrigger;
|
|
m_PrivTrigger = sm_strdup(value);
|
|
m_PrivTriggerSize = strlen(m_PrivTrigger);
|
|
return ConfigResult_Accept;
|
|
}
|
|
else if (strcmp(key, "SilentFailSuppress") == 0)
|
|
{
|
|
g_bSupressSilentFails = strcmp(value, "yes") == 0;
|
|
return ConfigResult_Accept;
|
|
}
|
|
|
|
return ConfigResult_Ignore;
|
|
}
|
|
|
|
void ChatTriggers::OnSourceModAllInitialized()
|
|
{
|
|
m_pShouldFloodBlock = g_Forwards.CreateForward("OnClientFloodCheck", ET_Event, 1, NULL, Param_Cell);
|
|
m_pDidFloodBlock = g_Forwards.CreateForward("OnClientFloodResult", ET_Event, 2, NULL, Param_Cell, Param_Cell);
|
|
}
|
|
|
|
void ChatTriggers::OnSourceModAllInitialized_Post()
|
|
{
|
|
unsigned int file_id;
|
|
|
|
file_id = g_Translator.FindOrAddPhraseFile("antiflood.phrases.txt");
|
|
g_pFloodPhrases = g_Translator.GetFileByIndex(file_id);
|
|
}
|
|
|
|
void ChatTriggers::OnSourceModGameInitialized()
|
|
{
|
|
unsigned int total = 2;
|
|
ConCommandBase *pCmd = icvar->GetCommands();
|
|
const char *name;
|
|
while (pCmd)
|
|
{
|
|
if (pCmd->IsCommand())
|
|
{
|
|
name = pCmd->GetName();
|
|
if (!m_pSayCmd && strcmp(name, "say") == 0)
|
|
{
|
|
m_pSayCmd = (ConCommand *)pCmd;
|
|
if (--total == 0)
|
|
{
|
|
break;
|
|
}
|
|
} else if (!m_pSayTeamCmd && strcmp(name, "say_team") == 0) {
|
|
m_pSayTeamCmd = (ConCommand *)pCmd;
|
|
if (--total == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
pCmd = const_cast<ConCommandBase *>(pCmd->GetNext());
|
|
}
|
|
|
|
if (m_pSayCmd)
|
|
{
|
|
SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
|
SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
|
}
|
|
if (m_pSayTeamCmd)
|
|
{
|
|
SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
|
SH_ADD_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
|
}
|
|
}
|
|
|
|
void ChatTriggers::OnSourceModShutdown()
|
|
{
|
|
if (m_pSayTeamCmd)
|
|
{
|
|
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
|
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayTeamCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
|
}
|
|
if (m_pSayCmd)
|
|
{
|
|
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
|
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Pre, false);
|
|
}
|
|
|
|
g_Forwards.ReleaseForward(m_pShouldFloodBlock);
|
|
g_Forwards.ReleaseForward(m_pDidFloodBlock);
|
|
}
|
|
|
|
#if defined ORANGEBOX_BUILD
|
|
void ChatTriggers::OnSayCommand_Pre(const CCommand &command)
|
|
{
|
|
#else
|
|
void ChatTriggers::OnSayCommand_Pre()
|
|
{
|
|
CCommand command;
|
|
#endif
|
|
int client;
|
|
CPlayer *pPlayer;
|
|
|
|
client = g_ConCmds.GetCommandClient();
|
|
m_bIsChatTrigger = false;
|
|
m_bWasFloodedMessage = false;
|
|
|
|
/* The server console cannot do this */
|
|
if (client == 0 || (pPlayer = g_Players.GetPlayerByIndex(client)) == NULL)
|
|
{
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
/* We guarantee the client is connected */
|
|
if (!pPlayer->IsConnected())
|
|
{
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
const char *args = command.ArgS();
|
|
|
|
if (!args)
|
|
{
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
/* Check if we need to block this message from being sent */
|
|
if (ClientIsFlooding(client))
|
|
{
|
|
char buffer[128];
|
|
|
|
/* :TODO: log an error? */
|
|
if (g_Translator.CoreTransEx(g_pFloodPhrases,
|
|
client,
|
|
buffer,
|
|
sizeof(buffer),
|
|
"Flooding the server",
|
|
NULL,
|
|
NULL)
|
|
!= Trans_Okay)
|
|
{
|
|
UTIL_Format(buffer, sizeof(buffer), "You are flooding the server!");
|
|
}
|
|
|
|
/* :TODO: we should probably kick people who spam too much. */
|
|
|
|
char fullbuffer[192];
|
|
UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s", buffer);
|
|
g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
|
|
|
|
m_bWasFloodedMessage = true;
|
|
|
|
RETURN_META(MRES_SUPERCEDE);
|
|
}
|
|
|
|
/* Handle quoted string sets */
|
|
bool is_quoted = false;
|
|
if (args[0] == '"')
|
|
{
|
|
args++;
|
|
is_quoted = true;
|
|
}
|
|
|
|
bool is_trigger = false;
|
|
bool is_silent = false;
|
|
|
|
/* Check for either trigger */
|
|
if (m_PubTriggerSize && strncmp(args, m_PubTrigger, m_PubTriggerSize) == 0)
|
|
{
|
|
is_trigger = true;
|
|
args = &args[m_PubTriggerSize];
|
|
}
|
|
else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0)
|
|
{
|
|
is_trigger = true;
|
|
is_silent = true;
|
|
args = &args[m_PrivTriggerSize];
|
|
}
|
|
|
|
if (!is_trigger)
|
|
{
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
/**
|
|
* Test if this is actually a command!
|
|
*/
|
|
if (!PreProcessTrigger(engine->PEntityOfEntIndex(client), args, is_quoted))
|
|
{
|
|
CPlayer *pPlayer;
|
|
if (is_silent
|
|
&& g_bSupressSilentFails
|
|
&& client != 0
|
|
&& (pPlayer = g_Players.GetPlayerByIndex(client)) != NULL
|
|
&& pPlayer->GetAdminId() != INVALID_ADMIN_ID)
|
|
{
|
|
RETURN_META(MRES_SUPERCEDE);
|
|
}
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
m_bIsChatTrigger = true;
|
|
|
|
/**
|
|
* We'll execute it in post.
|
|
*/
|
|
m_bWillProcessInPost = true;
|
|
m_bTriggerWasSilent = is_silent;
|
|
|
|
/* If we're silent, block */
|
|
if (is_silent)
|
|
{
|
|
RETURN_META(MRES_SUPERCEDE);
|
|
}
|
|
|
|
/* Otherwise, let the command continue */
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
#if defined ORANGEBOX_BUILD
|
|
void ChatTriggers::OnSayCommand_Post(const CCommand &command)
|
|
#else
|
|
void ChatTriggers::OnSayCommand_Post()
|
|
#endif
|
|
{
|
|
m_bIsChatTrigger = false;
|
|
m_bWasFloodedMessage = false;
|
|
if (m_bWillProcessInPost)
|
|
{
|
|
/* Reset this for re-entrancy */
|
|
m_bWillProcessInPost = false;
|
|
|
|
/* Execute the cached command */
|
|
int client = g_ConCmds.GetCommandClient();
|
|
unsigned int old = SetReplyTo(SM_REPLY_CHAT);
|
|
serverpluginhelpers->ClientCommand(engine->PEntityOfEntIndex(client), m_ToExecute);
|
|
SetReplyTo(old);
|
|
}
|
|
}
|
|
|
|
bool ChatTriggers::PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted)
|
|
{
|
|
/* Extract a command. This is kind of sloppy. */
|
|
char cmd_buf[64];
|
|
size_t cmd_len = 0;
|
|
const char *inptr = args;
|
|
while (*inptr != '\0'
|
|
&& !textparsers->IsWhitespace(inptr)
|
|
&& *inptr != '"'
|
|
&& cmd_len < sizeof(cmd_buf) - 1)
|
|
{
|
|
cmd_buf[cmd_len++] = *inptr++;
|
|
}
|
|
cmd_buf[cmd_len] = '\0';
|
|
|
|
if (cmd_len == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* See if we have this registered */
|
|
bool prepended = false;
|
|
if (!g_ConCmds.LookForSourceModCommand(cmd_buf))
|
|
{
|
|
/* Check if we had an "sm_" prefix */
|
|
if (strncmp(cmd_buf, "sm_", 3) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* Now, prepend. Don't worry about the buffers. This will
|
|
* work because the sizes are limited from earlier.
|
|
*/
|
|
char new_buf[80];
|
|
strcpy(new_buf, "sm_");
|
|
strncopy(&new_buf[3], cmd_buf, sizeof(new_buf)-3);
|
|
|
|
/* Recheck */
|
|
if (!g_ConCmds.LookForSourceModCommand(new_buf))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
prepended = true;
|
|
}
|
|
|
|
/* See if we need to do extra string manipulation */
|
|
if (is_quoted || prepended)
|
|
{
|
|
size_t len;
|
|
|
|
/* Check if we need to prepend sm_ */
|
|
if (prepended)
|
|
{
|
|
len = UTIL_Format(m_ToExecute, sizeof(m_ToExecute), "sm_%s", args);
|
|
} else {
|
|
len = strncopy(m_ToExecute, args, sizeof(m_ToExecute));
|
|
}
|
|
|
|
/* Check if we need to strip a quote */
|
|
if (is_quoted)
|
|
{
|
|
if (m_ToExecute[len-1] == '"')
|
|
{
|
|
m_ToExecute[--len] = '\0';
|
|
}
|
|
}
|
|
} else {
|
|
strncopy(m_ToExecute, args, sizeof(m_ToExecute));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int ChatTriggers::SetReplyTo(unsigned int reply)
|
|
{
|
|
unsigned int old = m_ReplyTo;
|
|
|
|
m_ReplyTo = reply;
|
|
|
|
return old;
|
|
}
|
|
|
|
unsigned int ChatTriggers::GetReplyTo()
|
|
{
|
|
return m_ReplyTo;
|
|
}
|
|
|
|
bool ChatTriggers::IsChatTrigger()
|
|
{
|
|
return m_bIsChatTrigger;
|
|
}
|
|
|
|
bool ChatTriggers::ClientIsFlooding(int client)
|
|
{
|
|
bool is_flooding = false;
|
|
|
|
if (m_pShouldFloodBlock->GetFunctionCount() != 0)
|
|
{
|
|
cell_t res = 0;
|
|
|
|
m_pShouldFloodBlock->PushCell(client);
|
|
m_pShouldFloodBlock->Execute(&res);
|
|
|
|
if (res != 0)
|
|
{
|
|
is_flooding = true;
|
|
}
|
|
}
|
|
|
|
if (m_pDidFloodBlock->GetFunctionCount() != 0)
|
|
{
|
|
m_pDidFloodBlock->PushCell(client);
|
|
m_pDidFloodBlock->PushCell(is_flooding ? 1 : 0);
|
|
m_pDidFloodBlock->Execute(NULL);
|
|
}
|
|
|
|
return is_flooding;
|
|
}
|
|
|
|
bool ChatTriggers::WasFloodedMessage()
|
|
{
|
|
return m_bWasFloodedMessage;
|
|
}
|