2008-03-30 09:00:22 +02:00
/**
2015-09-06 06:44:18 +02:00
* vim : set ts = 4 sw = 4 tw = 99 noet :
2008-03-30 09:00:22 +02:00
* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
* 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 .
2016-10-04 17:34:42 +02:00
*
2008-03-30 09:00:22 +02:00
* 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 $
*/
2020-05-17 08:15:32 +02:00
# include <memory>
2008-03-30 09:00:22 +02:00
# include <ITextParsers.h>
# include "ChatTriggers.h"
# include "sm_stringutil.h"
# include "ConCmdManager.h"
# include "PlayerManager.h"
# include "HalfLife2.h"
2010-05-15 04:43:53 +02:00
# include "logic_bridge.h"
2013-10-09 15:12:46 +02:00
# include "sourcemod.h"
2015-09-06 21:54:12 +02:00
# include "provider.h"
2016-10-04 17:34:42 +02:00
# include <bridge/include/ILogger.h>
2015-09-06 06:44:18 +02:00
# include <amtl/am-string.h>
2008-03-30 09:00:22 +02:00
ChatTriggers g_ChatTriggers ;
bool g_bSupressSilentFails = false ;
2016-10-04 17:34:42 +02:00
ChatTriggers : : ChatTriggers ( ) : m_bWillProcessInPost ( false ) ,
2014-02-28 05:06:02 +01:00
m_ReplyTo ( SM_REPLY_CONSOLE ) , m_ArgSBackup ( NULL )
2008-03-30 09:00:22 +02:00
{
2015-09-06 06:55:41 +02:00
m_PubTrigger = " ! " ;
m_PrivTrigger = " / " ;
2008-03-30 09:00:22 +02:00
m_bIsChatTrigger = false ;
2014-02-28 02:22:01 +01:00
m_bPluginIgnored = true ;
2013-08-05 17:46:29 +02:00
# if SOURCE_ENGINE == SE_EPISODEONE
m_bIsINS = false ;
# endif
2008-03-30 09:00:22 +02:00
}
ChatTriggers : : ~ ChatTriggers ( )
{
2014-02-28 05:06:02 +01:00
delete [ ] m_ArgSBackup ;
m_ArgSBackup = NULL ;
2008-03-30 09:00:22 +02:00
}
2016-10-04 17:34:42 +02:00
void ChatTriggers : : SetChatTrigger ( ChatTriggerType type , const char * value )
{
2020-05-17 08:15:32 +02:00
std : : unique_ptr < char [ ] > filtered ( new char [ strlen ( value ) + 1 ] ) ;
2016-10-04 17:34:42 +02:00
const char * src = value ;
char * dest = filtered . get ( ) ;
char c ;
while ( ( c = * src + + ) ! = ' \0 ' ) {
if ( c < = ' ' | | c = = ' " ' | | c = = ' \' ' | | ( c > = ' 0 ' & & c < = ' 9 ' ) | | c = = ' ; ' | | ( c > = ' A ' & & c < = ' Z ' ) | | c = = ' \\ ' | | ( c > = ' a ' & & c < = ' z ' ) | | c > = 0x7F ) {
logger - > LogError ( " Ignoring %s chat trigger character '%c', not in valid set: %s " , ( type = = ChatTrigger_Private ? " silent " : " public " ) , c , " !#$%&()*+,-./:<=>?@[]^_`{|}~ " ) ;
continue ;
}
* dest + + = c ;
}
* dest = ' \0 ' ;
if ( type = = ChatTrigger_Private ) {
m_PrivTrigger = filtered . get ( ) ;
} else {
m_PubTrigger = filtered . get ( ) ;
}
}
ConfigResult ChatTriggers : : OnSourceModConfigChanged ( const char * key ,
const char * value ,
2008-03-30 09:00:22 +02:00
ConfigSource source ,
2016-10-04 17:34:42 +02:00
char * error ,
2008-03-30 09:00:22 +02:00
size_t maxlength )
{
if ( strcmp ( key , " PublicChatTrigger " ) = = 0 )
{
2016-10-04 17:34:42 +02:00
SetChatTrigger ( ChatTrigger_Public , value ) ;
2008-03-30 09:00:22 +02:00
return ConfigResult_Accept ;
}
else if ( strcmp ( key , " SilentChatTrigger " ) = = 0 )
{
2016-10-04 17:34:42 +02:00
SetChatTrigger ( ChatTrigger_Private , value ) ;
2008-03-30 09:00:22 +02:00
return ConfigResult_Accept ;
}
else if ( strcmp ( key , " SilentFailSuppress " ) = = 0 )
{
g_bSupressSilentFails = strcmp ( value , " yes " ) = = 0 ;
return ConfigResult_Accept ;
}
return ConfigResult_Ignore ;
}
void ChatTriggers : : OnSourceModAllInitialized ( )
{
2013-10-09 14:43:08 +02:00
m_pShouldFloodBlock = forwardsys - > CreateForward ( " OnClientFloodCheck " , ET_Event , 1 , NULL , Param_Cell ) ;
m_pDidFloodBlock = forwardsys - > CreateForward ( " OnClientFloodResult " , ET_Event , 2 , NULL , Param_Cell , Param_Cell ) ;
m_pOnClientSayCmd = forwardsys - > CreateForward ( " OnClientSayCommand " , ET_Event , 3 , NULL , Param_Cell , Param_String , Param_String ) ;
m_pOnClientSayCmd_Post = forwardsys - > CreateForward ( " OnClientSayCommand_Post " , ET_Ignore , 3 , NULL , Param_Cell , Param_String , Param_String ) ;
2008-03-30 09:00:22 +02:00
}
void ChatTriggers : : OnSourceModAllInitialized_Post ( )
{
2010-05-15 04:43:53 +02:00
logicore . AddCorePhraseFile ( " antiflood.phrases " ) ;
2008-03-30 09:00:22 +02:00
}
void ChatTriggers : : OnSourceModGameInitialized ( )
{
2015-09-06 21:54:12 +02:00
ConCommand * say_team = FindCommand ( " say_team " ) ;
2015-09-06 23:55:51 +02:00
CommandHook : : Callback pre_hook = [ this ] ( int client , const ICommandArgs * args ) - > bool {
return this - > OnSayCommand_Pre ( client , args ) ;
2015-09-06 21:54:12 +02:00
} ;
2015-09-06 23:55:51 +02:00
CommandHook : : Callback post_hook = [ this ] ( int client , const ICommandArgs * args ) - > bool {
return this - > OnSayCommand_Post ( client , args ) ;
2015-09-06 21:54:12 +02:00
} ;
if ( ConCommand * say = FindCommand ( " say " ) ) {
2020-05-31 08:06:29 +02:00
hooks_ . push_back ( sCoreProviderImpl . AddCommandHook ( say , pre_hook ) ) ;
hooks_ . push_back ( sCoreProviderImpl . AddPostCommandHook ( say , post_hook ) ) ;
2008-03-30 09:00:22 +02:00
}
2015-09-06 21:54:12 +02:00
if ( ConCommand * say_team = FindCommand ( " say_team " ) ) {
2020-05-31 08:06:29 +02:00
hooks_ . push_back ( sCoreProviderImpl . AddCommandHook ( say_team , pre_hook ) ) ;
hooks_ . push_back ( sCoreProviderImpl . AddPostCommandHook ( say_team , post_hook ) ) ;
2008-03-30 09:00:22 +02:00
}
2013-08-05 17:46:29 +02:00
# if SOURCE_ENGINE == SE_EPISODEONE
2013-08-05 18:06:04 +02:00
m_bIsINS = ( strcmp ( g_SourceMod . GetGameFolderName ( ) , " insurgency " ) = = 0 ) ;
2015-09-06 21:54:12 +02:00
if ( m_bIsINS ) {
if ( ConCommand * say2 = FindCommand ( " say2 " ) ) {
2020-05-31 08:06:29 +02:00
hooks_ . push_back ( sCoreProviderImpl . AddCommandHook ( say2 , pre_hook ) ) ;
hooks_ . push_back ( sCoreProviderImpl . AddPostCommandHook ( say2 , post_hook ) ) ;
2013-08-05 17:46:29 +02:00
}
}
# elif SOURCE_ENGINE == SE_NUCLEARDAWN
2015-09-06 21:54:12 +02:00
if ( ConCommand * say_squad = FindCommand ( " say_squad " ) ) {
2020-05-31 08:06:29 +02:00
hooks_ . push_back ( sCoreProviderImpl . AddCommandHook ( say_squad , pre_hook ) ) ;
hooks_ . push_back ( sCoreProviderImpl . AddPostCommandHook ( say_squad , post_hook ) ) ;
2013-08-05 17:46:29 +02:00
}
# endif
2008-03-30 09:00:22 +02:00
}
void ChatTriggers : : OnSourceModShutdown ( )
{
2015-09-06 21:54:12 +02:00
hooks_ . clear ( ) ;
2008-03-30 09:00:22 +02:00
2013-10-09 14:43:08 +02:00
forwardsys - > ReleaseForward ( m_pShouldFloodBlock ) ;
forwardsys - > ReleaseForward ( m_pDidFloodBlock ) ;
forwardsys - > ReleaseForward ( m_pOnClientSayCmd ) ;
forwardsys - > ReleaseForward ( m_pOnClientSayCmd_Post ) ;
2008-03-30 09:00:22 +02:00
}
2015-09-06 23:55:51 +02:00
bool ChatTriggers : : OnSayCommand_Pre ( int client , const ICommandArgs * command )
2008-03-30 09:00:22 +02:00
{
m_bIsChatTrigger = false ;
m_bWasFloodedMessage = false ;
2014-02-28 02:22:01 +01:00
m_bPluginIgnored = true ;
2008-03-30 09:00:22 +02:00
2015-09-06 21:54:12 +02:00
const char * args = command - > ArgS ( ) ;
2014-02-28 02:22:01 +01:00
2013-08-23 13:31:04 +02:00
if ( ! args )
2015-09-06 21:54:12 +02:00
return false ;
2013-08-23 13:31:04 +02:00
2016-10-04 17:34:42 +02:00
/* Save these off for post hook as the command data returned from the engine in older engine versions
2013-08-23 13:31:04 +02:00
* can be NULL , despite the data still being there and valid . */
2015-09-06 21:54:12 +02:00
m_Arg0Backup = command - > Arg ( 0 ) ;
2014-02-28 05:06:02 +01:00
size_t len = strlen ( args ) ;
2014-02-28 16:38:36 +01:00
# if SOURCE_ENGINE == SE_EPISODEONE
if ( m_bIsINS )
2014-02-28 05:06:02 +01:00
{
2014-02-28 16:38:36 +01:00
if ( strcmp ( m_Arg0Backup , " say2 " ) = = 0 & & len > = 4 )
{
args + = 4 ;
len - = 4 ;
}
if ( len = = 0 )
2015-09-06 21:54:12 +02:00
return true ;
2014-02-28 05:06:02 +01:00
}
2014-02-28 16:38:36 +01:00
# endif
2014-02-28 05:06:02 +01:00
/* The first pair of quotes are stripped from client say commands, but not console ones.
* We do not want the forwards to differ from what is displayed .
* So only strip the first pair of quotes from client say commands . */
2014-02-28 16:38:36 +01:00
bool is_quoted = false ;
if (
# if SOURCE_ENGINE == SE_EPISODEONE
2016-10-04 17:34:42 +02:00
! m_bIsINS & &
2014-02-28 16:38:36 +01:00
# endif
client ! = 0 & & args [ 0 ] = = ' " ' & & args [ len - 1 ] = = ' " ' )
2014-02-28 05:06:02 +01:00
{
/* The server normally won't display empty say commands, but in this case it does.
* I don ' t think it ' s desired so let ' s block it . */
if ( len < = 2 )
2015-09-06 21:54:12 +02:00
return true ;
2014-02-28 05:06:02 +01:00
args + + ;
len - - ;
is_quoted = true ;
}
2014-02-28 16:38:36 +01:00
2014-02-28 05:06:02 +01:00
/* Some? engines strip the last quote when printing the string to chat.
* This results in having a double - quoted message passed to the OnClientSayCommand ( " message " ) forward ,
* but losing the last quote in the OnClientSayCommand_Post ( " message) forward.
* To compensate this , we copy the args into our own buffer where the engine won ' t mess with
* and strip the quotes . */
delete [ ] m_ArgSBackup ;
m_ArgSBackup = new char [ CCommand : : MaxCommandLength ( ) + 1 ] ;
memcpy ( m_ArgSBackup , args , len + 1 ) ;
/* Strip the quotes from the argument */
if ( is_quoted )
{
if ( m_ArgSBackup [ len - 1 ] = = ' " ' )
{
m_ArgSBackup [ - - len ] = ' \0 ' ;
}
}
2013-08-23 13:31:04 +02:00
2008-03-30 09:00:22 +02:00
/* The server console cannot do this */
2013-08-05 17:46:29 +02:00
if ( client = = 0 )
2008-03-30 09:00:22 +02:00
{
2014-02-28 02:22:01 +01:00
if ( CallOnClientSayCommand ( client ) > = Pl_Handled )
2015-09-06 21:54:12 +02:00
return true ;
2014-02-28 02:22:01 +01:00
2015-09-06 21:54:12 +02:00
return false ;
2008-03-30 09:00:22 +02:00
}
2013-08-05 17:46:29 +02:00
CPlayer * pPlayer = g_Players . GetPlayerByIndex ( client ) ;
2008-03-30 09:00:22 +02:00
/* We guarantee the client is connected */
2013-08-05 17:46:29 +02:00
if ( ! pPlayer | | ! pPlayer - > IsConnected ( ) )
2015-09-06 21:54:12 +02:00
return false ;
2008-03-30 09:00:22 +02:00
/* Check if we need to block this message from being sent */
if ( ClientIsFlooding ( client ) )
{
char buffer [ 128 ] ;
2010-05-15 04:43:53 +02:00
if ( ! logicore . CoreTranslate ( buffer , sizeof ( buffer ) , " %T " , 2 , NULL , " Flooding the server " , & client ) )
2015-09-06 06:47:33 +02:00
ke : : SafeSprintf ( buffer , sizeof ( buffer ) , " You are flooding the server! " ) ;
2008-03-30 09:00:22 +02:00
/* :TODO: we should probably kick people who spam too much. */
char fullbuffer [ 192 ] ;
2015-09-06 06:47:33 +02:00
ke : : SafeSprintf ( fullbuffer , sizeof ( fullbuffer ) , " [SM] %s " , buffer ) ;
2008-03-30 09:00:22 +02:00
g_HL2 . TextMsg ( client , HUD_PRINTTALK , fullbuffer ) ;
m_bWasFloodedMessage = true ;
2015-09-06 21:54:12 +02:00
return true ;
2008-03-30 09:00:22 +02:00
}
bool is_trigger = false ;
bool is_silent = false ;
2016-10-04 17:34:42 +02:00
// Prefer the silent trigger in case of clashes.
2020-05-20 21:35:26 +02:00
if ( strchr ( m_PrivTrigger . c_str ( ) , m_ArgSBackup [ 0 ] ) ) {
2008-03-30 09:00:22 +02:00
is_trigger = true ;
is_silent = true ;
2020-05-20 21:35:26 +02:00
} else if ( strchr ( m_PubTrigger . c_str ( ) , m_ArgSBackup [ 0 ] ) ) {
2016-10-04 17:34:42 +02:00
is_trigger = true ;
}
if ( is_trigger ) {
// Bump the args past the chat trigger - we only support single-character triggers now.
args = & m_ArgSBackup [ 1 ] ;
2008-03-30 09:00:22 +02:00
}
/**
* Test if this is actually a command !
*/
2014-02-28 05:06:02 +01:00
if ( is_trigger & & PreProcessTrigger ( PEntityOfEntIndex ( client ) , args ) )
2008-03-30 09:00:22 +02:00
{
2013-08-05 17:46:29 +02:00
m_bIsChatTrigger = true ;
/**
* We ' ll execute it in post .
*/
m_bWillProcessInPost = true ;
}
2014-02-28 02:22:01 +01:00
if ( is_silent & & ( m_bIsChatTrigger | | ( g_bSupressSilentFails & & pPlayer - > GetAdminId ( ) ! = INVALID_ADMIN_ID ) ) )
2015-09-06 21:54:12 +02:00
return true ;
2008-03-30 09:00:22 +02:00
2014-02-28 02:22:01 +01:00
if ( CallOnClientSayCommand ( client ) > = Pl_Handled )
2015-09-06 21:54:12 +02:00
return true ;
2008-03-30 09:00:22 +02:00
/* Otherwise, let the command continue */
2015-09-06 21:54:12 +02:00
return false ;
2008-03-30 09:00:22 +02:00
}
2015-09-06 23:55:51 +02:00
bool ChatTriggers : : OnSayCommand_Post ( int client , const ICommandArgs * command )
2013-08-15 16:18:27 +02:00
{
2013-08-23 13:28:07 +02:00
if ( m_bWillProcessInPost )
2008-03-30 09:00:22 +02:00
{
2013-08-23 13:28:07 +02:00
/* Reset this for re-entrancy */
m_bWillProcessInPost = false ;
2014-02-28 02:22:01 +01:00
2008-03-30 09:00:22 +02:00
/* Execute the cached command */
unsigned int old = SetReplyTo ( SM_REPLY_CHAT ) ;
2008-11-14 16:18:30 +01:00
serverpluginhelpers - > ClientCommand ( PEntityOfEntIndex ( client ) , m_ToExecute ) ;
2008-03-30 09:00:22 +02:00
SetReplyTo ( old ) ;
}
2013-08-05 17:46:29 +02:00
2014-02-28 02:22:01 +01:00
if ( ! m_bPluginIgnored & & m_pOnClientSayCmd_Post - > GetFunctionCount ( ) ! = 0 )
2013-08-05 17:46:29 +02:00
{
m_pOnClientSayCmd_Post - > PushCell ( client ) ;
2013-08-15 16:18:27 +02:00
m_pOnClientSayCmd_Post - > PushString ( m_Arg0Backup ) ;
m_pOnClientSayCmd_Post - > PushString ( m_ArgSBackup ) ;
2013-08-05 17:46:29 +02:00
m_pOnClientSayCmd_Post - > Execute ( NULL ) ;
}
m_bIsChatTrigger = false ;
m_bWasFloodedMessage = false ;
2015-09-06 21:54:12 +02:00
return false ;
2008-03-30 09:00:22 +02:00
}
2014-02-28 05:06:02 +01:00
bool ChatTriggers : : PreProcessTrigger ( edict_t * pEdict , const char * args )
2008-03-30 09:00:22 +02:00
{
2015-09-06 21:54:12 +02:00
/* Extract a command. This is kind of sloppy. */
2008-03-30 09:00:22 +02:00
char cmd_buf [ 64 ] ;
size_t cmd_len = 0 ;
const char * inptr = args ;
2016-10-04 17:34:42 +02:00
while ( * inptr ! = ' \0 '
& & ! textparsers - > IsWhitespace ( inptr )
2008-03-30 09:00:22 +02:00
& & * 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 ;
}
2016-10-04 17:34:42 +02:00
/* Now, prepend. Don't worry about the buffers. This will
2008-03-30 09:00:22 +02:00
* work because the sizes are limited from earlier .
*/
char new_buf [ 80 ] ;
strcpy ( new_buf , " sm_ " ) ;
2015-09-06 06:44:18 +02:00
ke : : SafeStrcpy ( & new_buf [ 3 ] , sizeof ( new_buf ) - 3 , cmd_buf ) ;
2008-03-30 09:00:22 +02:00
/* Recheck */
if ( ! g_ConCmds . LookForSourceModCommand ( new_buf ) )
{
return false ;
}
2014-02-28 02:22:01 +01:00
2008-03-30 09:00:22 +02:00
prepended = true ;
}
/* See if we need to do extra string manipulation */
2014-02-28 05:06:02 +01:00
if ( prepended )
2008-03-30 09:00:22 +02:00
{
size_t len ;
/* Check if we need to prepend sm_ */
if ( prepended )
{
2015-09-06 06:47:33 +02:00
len = ke : : SafeSprintf ( m_ToExecute , sizeof ( m_ToExecute ) , " sm_%s " , args ) ;
2008-03-30 09:00:22 +02:00
} else {
2015-09-06 06:44:18 +02:00
len = ke : : SafeStrcpy ( m_ToExecute , sizeof ( m_ToExecute ) , args ) ;
2008-03-30 09:00:22 +02:00
}
} else {
2015-09-06 06:44:18 +02:00
ke : : SafeStrcpy ( m_ToExecute , sizeof ( m_ToExecute ) , args ) ;
2008-03-30 09:00:22 +02:00
}
return true ;
}
2013-08-23 13:31:04 +02:00
cell_t ChatTriggers : : CallOnClientSayCommand ( int client )
{
cell_t res = Pl_Continue ;
2014-02-28 02:22:01 +01:00
if ( m_pOnClientSayCmd - > GetFunctionCount ( ) ! = 0 )
2013-08-24 00:48:06 +02:00
{
m_pOnClientSayCmd - > PushCell ( client ) ;
m_pOnClientSayCmd - > PushString ( m_Arg0Backup ) ;
m_pOnClientSayCmd - > PushString ( m_ArgSBackup ) ;
m_pOnClientSayCmd - > Execute ( & res ) ;
}
2014-02-28 02:22:01 +01:00
m_bPluginIgnored = ( res > = Pl_Stop ) ;
2013-08-23 13:31:04 +02:00
return res ;
}
2008-03-30 09:00:22 +02:00
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 ;
}