reworked antiflood. it now has some logic in core to take care of loading order nastiness, and to fully prevent trigger spamming
--HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401931
This commit is contained in:
parent
b10bf9d31a
commit
aed775162c
@ -34,6 +34,8 @@
|
|||||||
#include "sm_stringutil.h"
|
#include "sm_stringutil.h"
|
||||||
#include "ConCmdManager.h"
|
#include "ConCmdManager.h"
|
||||||
#include "PlayerManager.h"
|
#include "PlayerManager.h"
|
||||||
|
#include "Translator.h"
|
||||||
|
#include "HalfLife2.h"
|
||||||
|
|
||||||
/* :HACKHACK: We can't SH_DECL here because ConCmdManager.cpp does.
|
/* :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
|
* While the OB build only runs on MM:S 1.6.0+ (SH 5+), the older one
|
||||||
@ -55,6 +57,7 @@ extern bool __SourceHook_FHAddConCommandDispatch(void *, bool, class fastdelegat
|
|||||||
|
|
||||||
ChatTriggers g_ChatTriggers;
|
ChatTriggers g_ChatTriggers;
|
||||||
bool g_bSupressSilentFails = false;
|
bool g_bSupressSilentFails = false;
|
||||||
|
CPhraseFile *g_pFloodPhrases = NULL;
|
||||||
|
|
||||||
ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false),
|
ChatTriggers::ChatTriggers() : m_pSayCmd(NULL), m_bWillProcessInPost(false),
|
||||||
m_bTriggerWasSilent(false), m_ReplyTo(SM_REPLY_CONSOLE)
|
m_bTriggerWasSilent(false), m_ReplyTo(SM_REPLY_CONSOLE)
|
||||||
@ -103,6 +106,20 @@ ConfigResult ChatTriggers::OnSourceModConfigChanged(const char *key,
|
|||||||
return ConfigResult_Ignore;
|
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()
|
void ChatTriggers::OnSourceModGameInitialized()
|
||||||
{
|
{
|
||||||
unsigned int total = 2;
|
unsigned int total = 2;
|
||||||
@ -155,6 +172,9 @@ void ChatTriggers::OnSourceModShutdown()
|
|||||||
SH_REMOVE_HOOK_MEMFUNC(ConCommand, Dispatch, m_pSayCmd, this, &ChatTriggers::OnSayCommand_Post, true);
|
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);
|
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
|
#if defined ORANGEBOX_BUILD
|
||||||
@ -167,6 +187,7 @@ void ChatTriggers::OnSayCommand_Pre()
|
|||||||
#endif
|
#endif
|
||||||
int client = g_ConCmds.GetCommandClient();
|
int client = g_ConCmds.GetCommandClient();
|
||||||
m_bIsChatTrigger = false;
|
m_bIsChatTrigger = false;
|
||||||
|
m_bWasFloodedMessage = false;
|
||||||
|
|
||||||
/* The server console cannot do this */
|
/* The server console cannot do this */
|
||||||
if (client == 0)
|
if (client == 0)
|
||||||
@ -181,6 +202,35 @@ void ChatTriggers::OnSayCommand_Pre()
|
|||||||
RETURN_META(MRES_IGNORED);
|
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 */
|
/* Handle quoted string sets */
|
||||||
bool is_quoted = false;
|
bool is_quoted = false;
|
||||||
if (args[0] == '"')
|
if (args[0] == '"')
|
||||||
@ -197,7 +247,9 @@ void ChatTriggers::OnSayCommand_Pre()
|
|||||||
{
|
{
|
||||||
is_trigger = true;
|
is_trigger = true;
|
||||||
args = &args[m_PubTriggerSize];
|
args = &args[m_PubTriggerSize];
|
||||||
} else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0) {
|
}
|
||||||
|
else if (m_PrivTriggerSize && strncmp(args, m_PrivTrigger, m_PrivTriggerSize) == 0)
|
||||||
|
{
|
||||||
is_trigger = true;
|
is_trigger = true;
|
||||||
is_silent = true;
|
is_silent = true;
|
||||||
args = &args[m_PrivTriggerSize];
|
args = &args[m_PrivTriggerSize];
|
||||||
@ -250,6 +302,7 @@ void ChatTriggers::OnSayCommand_Post()
|
|||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
m_bIsChatTrigger = false;
|
m_bIsChatTrigger = false;
|
||||||
|
m_bWasFloodedMessage = false;
|
||||||
if (m_bWillProcessInPost)
|
if (m_bWillProcessInPost)
|
||||||
{
|
{
|
||||||
/* Reset this for re-entrancy */
|
/* Reset this for re-entrancy */
|
||||||
@ -355,3 +408,35 @@ bool ChatTriggers::IsChatTrigger()
|
|||||||
{
|
{
|
||||||
return m_bIsChatTrigger;
|
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;
|
||||||
|
}
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "sourcemm_api.h"
|
#include "sourcemm_api.h"
|
||||||
#include <IGameHelpers.h>
|
#include <IGameHelpers.h>
|
||||||
#include <compat_wrappers.h>
|
#include <compat_wrappers.h>
|
||||||
|
#include <IForwardSys.h>
|
||||||
|
|
||||||
class ChatTriggers : public SMGlobalClass
|
class ChatTriggers : public SMGlobalClass
|
||||||
{
|
{
|
||||||
@ -43,6 +44,8 @@ public:
|
|||||||
ChatTriggers();
|
ChatTriggers();
|
||||||
~ChatTriggers();
|
~ChatTriggers();
|
||||||
public: //SMGlobalClass
|
public: //SMGlobalClass
|
||||||
|
void OnSourceModAllInitialized();
|
||||||
|
void OnSourceModAllInitialized_Post();
|
||||||
void OnSourceModGameInitialized();
|
void OnSourceModGameInitialized();
|
||||||
void OnSourceModShutdown();
|
void OnSourceModShutdown();
|
||||||
ConfigResult OnSourceModConfigChanged(const char *key,
|
ConfigResult OnSourceModConfigChanged(const char *key,
|
||||||
@ -62,8 +65,10 @@ public:
|
|||||||
unsigned int GetReplyTo();
|
unsigned int GetReplyTo();
|
||||||
unsigned int SetReplyTo(unsigned int reply);
|
unsigned int SetReplyTo(unsigned int reply);
|
||||||
bool IsChatTrigger();
|
bool IsChatTrigger();
|
||||||
|
bool WasFloodedMessage();
|
||||||
private:
|
private:
|
||||||
bool PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted);
|
bool PreProcessTrigger(edict_t *pEdict, const char *args, bool is_quoted);
|
||||||
|
bool ClientIsFlooding(int client);
|
||||||
private:
|
private:
|
||||||
ConCommand *m_pSayCmd;
|
ConCommand *m_pSayCmd;
|
||||||
ConCommand *m_pSayTeamCmd;
|
ConCommand *m_pSayTeamCmd;
|
||||||
@ -74,8 +79,11 @@ private:
|
|||||||
bool m_bWillProcessInPost;
|
bool m_bWillProcessInPost;
|
||||||
bool m_bTriggerWasSilent;
|
bool m_bTriggerWasSilent;
|
||||||
bool m_bIsChatTrigger;
|
bool m_bIsChatTrigger;
|
||||||
|
bool m_bWasFloodedMessage;
|
||||||
unsigned int m_ReplyTo;
|
unsigned int m_ReplyTo;
|
||||||
char m_ToExecute[300];
|
char m_ToExecute[300];
|
||||||
|
IForward *m_pShouldFloodBlock;
|
||||||
|
IForward *m_pDidFloodBlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ChatTriggers g_ChatTriggers;
|
extern ChatTriggers g_ChatTriggers;
|
||||||
|
@ -299,6 +299,16 @@ void ConCmdManager::InternalDispatch(const CCommand &command)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This is a hack to prevent say triggers from firing on messages that were
|
||||||
|
* blocked because of flooding. We won't remove this, but the hack will get
|
||||||
|
* "nicer" when we expose explicit say hooks.
|
||||||
|
*/
|
||||||
|
if (META_RESULT_STATUS == MRES_SUPERCEDE
|
||||||
|
&& g_ChatTriggers.WasFloodedMessage())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cell_t result = Pl_Continue;
|
cell_t result = Pl_Continue;
|
||||||
int args = command.ArgC() - 1;
|
int args = command.ArgC() - 1;
|
||||||
|
|
||||||
@ -456,7 +466,9 @@ bool ConCmdManager::CheckCommandAccess(int client, const char *cmd, FlagBits cmd
|
|||||||
if (rule == Command_Allow)
|
if (rule == Command_Allow)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
} else if (rule == Command_Deny) {
|
}
|
||||||
|
else if (rule == Command_Deny)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,18 +498,20 @@ bool ConCmdManager::CheckAccess(int client, const char *cmd, AdminCmdInfo *pAdmi
|
|||||||
if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
|
if (g_Translator.CoreTrans(client, buffer, sizeof(buffer), "No Access", NULL, NULL)
|
||||||
!= Trans_Okay)
|
!= Trans_Okay)
|
||||||
{
|
{
|
||||||
snprintf(buffer, sizeof(buffer), "You do not have access to this command");
|
UTIL_Format(buffer, sizeof(buffer), "You do not have access to this command");
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int replyto = g_ChatTriggers.GetReplyTo();
|
unsigned int replyto = g_ChatTriggers.GetReplyTo();
|
||||||
if (replyto == SM_REPLY_CONSOLE)
|
if (replyto == SM_REPLY_CONSOLE)
|
||||||
{
|
{
|
||||||
char fullbuffer[192];
|
char fullbuffer[192];
|
||||||
snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
|
UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.\n", buffer);
|
||||||
engine->ClientPrintf(pEdict, fullbuffer);
|
engine->ClientPrintf(pEdict, fullbuffer);
|
||||||
} else if (replyto == SM_REPLY_CHAT) {
|
}
|
||||||
|
else if (replyto == SM_REPLY_CHAT)
|
||||||
|
{
|
||||||
char fullbuffer[192];
|
char fullbuffer[192];
|
||||||
snprintf(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
|
UTIL_Format(fullbuffer, sizeof(fullbuffer), "[SM] %s.", buffer);
|
||||||
g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
|
g_HL2.TextMsg(client, HUD_PRINTTALK, fullbuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -949,25 +949,19 @@ size_t Translator::Translate(char *buffer, size_t maxlength, void **params, cons
|
|||||||
return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
|
return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
TransError Translator::CoreTrans(int client,
|
TransError Translator::CoreTransEx(CPhraseFile *pFile,
|
||||||
char *buffer,
|
int client,
|
||||||
size_t maxlength,
|
char *buffer,
|
||||||
const char *phrase,
|
size_t maxlength,
|
||||||
void **params,
|
const char *phrase,
|
||||||
size_t *outlen)
|
void **params,
|
||||||
|
size_t *outlen)
|
||||||
{
|
{
|
||||||
/* :TODO: do language stuff here */
|
|
||||||
|
|
||||||
if (!g_pCorePhrases)
|
|
||||||
{
|
|
||||||
return Trans_BadPhraseFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
Translation trans;
|
Translation trans;
|
||||||
TransError err;
|
TransError err;
|
||||||
|
|
||||||
/* Using server lang temporarily until client lang stuff is implemented */
|
/* Using server lang temporarily until client lang stuff is implemented */
|
||||||
if ((err=g_pCorePhrases->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay)
|
if ((err = pFile->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay)
|
||||||
{
|
{
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -982,6 +976,21 @@ TransError Translator::CoreTrans(int client,
|
|||||||
return Trans_Okay;
|
return Trans_Okay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransError Translator::CoreTrans(int client,
|
||||||
|
char *buffer,
|
||||||
|
size_t maxlength,
|
||||||
|
const char *phrase,
|
||||||
|
void **params,
|
||||||
|
size_t *outlen)
|
||||||
|
{
|
||||||
|
if (!g_pCorePhrases)
|
||||||
|
{
|
||||||
|
return Trans_BadPhraseFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreTransEx(g_pCorePhrases, client, buffer, maxlength, phrase, params, outlen);
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int Translator::GetServerLanguage()
|
unsigned int Translator::GetServerLanguage()
|
||||||
{
|
{
|
||||||
return m_ServerLang;
|
return m_ServerLang;
|
||||||
|
@ -140,6 +140,13 @@ public:
|
|||||||
bool GetLanguageByName(const char *name, unsigned int *index);
|
bool GetLanguageByName(const char *name, unsigned int *index);
|
||||||
size_t Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans);
|
size_t Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans);
|
||||||
CPhraseFile *GetFileByIndex(unsigned int index);
|
CPhraseFile *GetFileByIndex(unsigned int index);
|
||||||
|
TransError CoreTransEx(CPhraseFile *pFile,
|
||||||
|
int client,
|
||||||
|
char *buffer,
|
||||||
|
size_t maxlength,
|
||||||
|
const char *phrase,
|
||||||
|
void **params,
|
||||||
|
size_t *outlen=NULL);
|
||||||
TransError CoreTrans(int client,
|
TransError CoreTrans(int client,
|
||||||
char *buffer,
|
char *buffer,
|
||||||
size_t maxlength,
|
size_t maxlength,
|
||||||
|
@ -51,10 +51,6 @@ new Handle:sm_flood_time; /* Handle to sm_flood_time convar */
|
|||||||
|
|
||||||
public OnPluginStart()
|
public OnPluginStart()
|
||||||
{
|
{
|
||||||
LoadTranslations("antiflood.phrases");
|
|
||||||
|
|
||||||
RegConsoleCmd("say", CheckChatFlood);
|
|
||||||
RegConsoleCmd("say_team", CheckChatFlood);
|
|
||||||
sm_flood_time = CreateConVar("sm_flood_time", "0.75", "Amount of time allowed between chat messages");
|
sm_flood_time = CreateConVar("sm_flood_time", "0.75", "Amount of time allowed between chat messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,41 +60,63 @@ public OnClientPutInServer(client)
|
|||||||
g_FloodTokens[client] = 0;
|
g_FloodTokens[client] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action:CheckChatFlood(client, args)
|
new Float:max_chat;
|
||||||
|
|
||||||
|
public bool:OnClientFloodCheck(client)
|
||||||
{
|
{
|
||||||
/* Chat from server console shouldn't be checked for flooding */
|
max_chat = GetConVarFloat(sm_flood_time);
|
||||||
if (client == 0)
|
|
||||||
|
if (max_chat <= 0.0
|
||||||
|
|| (GetUserFlagBits(client) & ADMFLAG_ROOT) == ADMFLAG_ROOT)
|
||||||
{
|
{
|
||||||
return Plugin_Continue;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
new Float:maxChat = GetConVarFloat(sm_flood_time);
|
PrintToServer("OCFC: %f %f %d", g_LastTime[client], GetGameTime(), g_FloodTokens[client]);
|
||||||
|
|
||||||
if (maxChat > 0.0)
|
if (g_LastTime[client] > GetGameTime())
|
||||||
{
|
{
|
||||||
new Float:curTime = GetGameTime();
|
/* If player has 3 or more flood tokens, block their message */
|
||||||
|
if (g_FloodTokens[client] >= 3)
|
||||||
if (g_LastTime[client] > curTime)
|
|
||||||
{
|
{
|
||||||
/* If player has 3 or more flood tokens, block their message */
|
return true;
|
||||||
if (g_FloodTokens[client] >= 3)
|
|
||||||
{
|
|
||||||
PrintToChat(client, "[SM] %t", "Flooding the server");
|
|
||||||
g_LastTime[client] = curTime + maxChat + 3.0;
|
|
||||||
|
|
||||||
return Plugin_Stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add one flood token when player goes over chat time limit */
|
|
||||||
g_FloodTokens[client]++;
|
|
||||||
} else if (g_FloodTokens[client]) {
|
|
||||||
/* Remove one flood token when player chats within time limit (slow decay) */
|
|
||||||
g_FloodTokens[client]--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Store last time of chat usage */
|
|
||||||
g_LastTime[client] = curTime + maxChat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Plugin_Continue;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnClientFloodResult(client, bool:blocked)
|
||||||
|
{
|
||||||
|
if (max_chat <= 0.0
|
||||||
|
|| (GetUserFlagBits(client) & ADMFLAG_ROOT) == ADMFLAG_ROOT)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Float:curTime = GetGameTime();
|
||||||
|
new Float:newTime = curTime + max_chat;
|
||||||
|
|
||||||
|
PrintToServer("OCFR: %f, %f", g_LastTime[client], GetGameTime());
|
||||||
|
|
||||||
|
if (g_LastTime[client] > curTime)
|
||||||
|
{
|
||||||
|
/* If the last message was blocked, update their time limit */
|
||||||
|
if (blocked)
|
||||||
|
{
|
||||||
|
newTime += 3.0;
|
||||||
|
}
|
||||||
|
/* Add one flood token when player goes over chat time limit */
|
||||||
|
else if (g_FloodTokens[client] < 3)
|
||||||
|
{
|
||||||
|
g_FloodTokens[client]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (g_FloodTokens[client] > 0)
|
||||||
|
{
|
||||||
|
/* Remove one flood token when player chats within time limit (slow decay) */
|
||||||
|
g_FloodTokens[client]--;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_LastTime[client] = newTime;
|
||||||
}
|
}
|
||||||
|
@ -505,6 +505,32 @@ native Handle:ReadMapList(Handle:array=INVALID_HANDLE,
|
|||||||
*/
|
*/
|
||||||
native SetMapListCompatBind(const String:name[], const String:file[]);
|
native SetMapListCompatBind(const String:name[], const String:file[]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a client has sent chat text. This must return either true or
|
||||||
|
* false to indicate that a client is or is not spamming the server.
|
||||||
|
*
|
||||||
|
* The return value is a hint only. Core or another plugin may decide
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
|
* @param client Client index. The server (0) will never be passed.
|
||||||
|
* @return True if client is spamming the server, false otherwise.
|
||||||
|
*/
|
||||||
|
forward bool:OnClientFloodCheck(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after a client's flood check has been computed. This can be used
|
||||||
|
* by antiflood algorithms to decay/increase flooding weights.
|
||||||
|
*
|
||||||
|
* Since the result from "OnClientFloodCheck" isn't guaranteed to be the
|
||||||
|
* final result, it is generally a good idea to use this to play with other
|
||||||
|
* algorithms nicely.
|
||||||
|
*
|
||||||
|
* @param client Client index. The server (0) will never be passed.
|
||||||
|
* @param blocked True if client flooded last "say", false otherwise.
|
||||||
|
* @noreturn
|
||||||
|
*/
|
||||||
|
forward OnClientFloodResult(client, bool:blocked);
|
||||||
|
|
||||||
#include <helpers>
|
#include <helpers>
|
||||||
#include <entity>
|
#include <entity>
|
||||||
#include <entity_prop_stocks>
|
#include <entity_prop_stocks>
|
||||||
|
Loading…
Reference in New Issue
Block a user