csgo-plugins/AntiBhopCheat/scripting/AntiBhopCheat.sp
2020-03-25 20:09:12 +02:00

653 lines
16 KiB
SourcePawn

#include <sourcemod>
#include <sdktools>
#include <multicolors>
#include <SelectiveBhop>
#include <basic>
#include "CJump.inc"
#include "CStreak.inc"
#include "CPlayer.inc"
#define MAX_STREAKS 5
#define VALID_MIN_JUMPS 3
#define VALID_MAX_TICKS 5
#define VALID_MIN_VELOCITY 250
int g_aButtons[MAXPLAYERS + 1];
bool g_bOnGround[MAXPLAYERS + 1];
bool g_bHoldingJump[MAXPLAYERS + 1];
bool g_bInJump[MAXPLAYERS + 1];
CPlayer g_aPlayers[MAXPLAYERS + 1];
// Api
Handle g_hOnClientDetected;
public Plugin myinfo =
{
name = "AntiBhopCheat",
author = "BotoX",
description = "Detect all kinds of bhop cheats",
version = "0.0",
url = ""
};
public void OnPluginStart()
{
LoadTranslations("common.phrases");
RegAdminCmd("sm_stats", Command_Stats, ADMFLAG_GENERIC, "sm_stats <#userid|name>");
RegAdminCmd("sm_streak", Command_Streak, ADMFLAG_GENERIC, "sm_streak <#userid|name> [streak]");
/* Handle late load */
for(int client = 1; client <= MaxClients; client++)
{
if(IsClientConnected(client))
{
if(IsClientConnected(client))
OnClientConnected(client);
}
}
// Api
g_hOnClientDetected = CreateGlobalForward("AntiBhopCheat_OnClientDetected", ET_Ignore, Param_Cell, Param_String, Param_String);
}
public void OnClientConnected(int client)
{
if(g_aPlayers[client])
{
g_aPlayers[client].Dispose();
g_aPlayers[client] = null;
}
g_aPlayers[client] = new CPlayer(client);
}
public void OnClientDisconnect(int client)
{
if(g_aPlayers[client])
{
g_aPlayers[client].Dispose();
g_aPlayers[client] = null;
}
g_bOnGround[client] = false;
g_bHoldingJump[client] = false;
g_bInJump[client] = false;
}
public Action OnPlayerRunCmd(int client, int &buttons)
{
g_aButtons[client] = buttons;
return Plugin_Continue;
}
public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float vel[3], const float angles[3], int weapon, int subtype, int cmdnum, int tickcount, int seed, const int mouse[2])
{
if(!IsPlayerAlive(client))
return;
CPlayer Player = g_aPlayers[client];
MoveType ClientMoveType = GetEntityMoveType(client);
bool bInWater = GetEntProp(client, Prop_Send, "m_nWaterLevel") >= 2;
bool bPrevOnGround = g_bOnGround[client];
bool bOnGround = !bInWater && GetEntityFlags(client) & FL_ONGROUND;
bool bPrevHoldingJump = g_bHoldingJump[client];
bool bHoldingJump = view_as<bool>(g_aButtons[client] & IN_JUMP);
bool bInJump = g_bInJump[client];
float fVecVelocity[3];
GetEntPropVector(client, Prop_Data, "m_vecAbsVelocity", fVecVelocity);
fVecVelocity[2] = 0.0;
float fVelocity = GetVectorLength(fVecVelocity);
if(bInJump && (bInWater || ClientMoveType == MOVETYPE_LADDER || ClientMoveType == MOVETYPE_NOCLIP))
bOnGround = true;
if(bOnGround)
{
if(!bPrevOnGround)
{
g_bOnGround[client] = true;
g_bInJump[client] = false;
if(bInJump)
OnTouchGround(Player, tickcount, fVelocity);
}
}
else
{
if(bPrevOnGround)
g_bOnGround[client] = false;
}
if(bHoldingJump)
{
if(!bPrevHoldingJump && !bOnGround && (bPrevOnGround || bInJump))
{
g_bHoldingJump[client] = true;
g_bInJump[client] = true;
OnPressJump(Player, tickcount, fVelocity, bPrevOnGround);
}
}
else
{
if(bPrevHoldingJump)
{
g_bHoldingJump[client] = false;
OnReleaseJump(Player, tickcount, fVelocity);
}
}
}
// TODO: Release after touch ground
void OnTouchGround(CPlayer Player, int iTick, float fVelocity)
{
//PrintToServer("%d - OnTouchGround", iTick);
CStreak CurStreak = Player.hStreak;
ArrayList hJumps = CurStreak.hJumps;
CJump hJump = hJumps.Get(hJumps.Length - 1);
hJump.iEndTick = iTick;
hJump.fEndVel = fVelocity;
int iLength = hJumps.Length;
if(iLength == VALID_MIN_JUMPS)
{
CurStreak.bValid = true;
// Current streak is valid, push onto hStreaks ArrayList
ArrayList hStreaks = Player.hStreaks;
if(hStreaks.Length == MAX_STREAKS)
{
// Keep the last 10 streaks
CStreak hStreak = hStreaks.Get(0);
hStreak.Dispose();
hStreaks.Erase(0);
}
hStreaks.Push(CurStreak);
for(int i = 0; i < iLength - 1; i++)
{
CJump hJump_ = hJumps.Get(i);
DoStats(Player, CurStreak, hJump_);
}
}
else if(iLength > VALID_MIN_JUMPS)
{
CJump hJump_ = hJumps.Get(hJumps.Length - 2);
DoStats(Player, CurStreak, hJump_);
}
}
void OnPressJump(CPlayer Player, int iTick, float fVelocity, bool bLeaveGround)
{
//PrintToServer("%d - OnPressJump %d", iTick, bLeaveGround);
CStreak CurStreak = Player.hStreak;
ArrayList hJumps = CurStreak.hJumps;
CJump hJump;
if(bLeaveGround)
{
int iPrevJump = -1;
// Check if we should start a new streak
if(hJumps.Length)
{
// Last jump was more than VALID_MAX_TICKS ticks ago or not valid and fVelocity < VALID_MIN_VELOCITY
hJump = hJumps.Get(hJumps.Length - 1);
if(hJump.iEndTick < iTick - VALID_MAX_TICKS || fVelocity < VALID_MIN_VELOCITY)
{
if(CurStreak.bValid)
{
CurStreak.iEndTick = hJump.iEndTick;
DoStats(Player, CurStreak, hJump);
CheckStats(Player, CurStreak);
}
else
CurStreak.Dispose();
CurStreak = new CStreak();
Player.hStreak = CurStreak;
hJumps = CurStreak.hJumps;
}
else
{
iPrevJump = iTick - hJump.iEndTick;
hJump.iNextJump = iPrevJump;
}
}
hJump = new CJump();
hJump.iStartTick = iTick;
hJump.fStartVel = fVelocity;
if(iPrevJump != -1)
hJump.iPrevJump = iPrevJump;
hJumps.Push(hJump);
}
else
hJump = hJumps.Get(hJumps.Length - 1);
ArrayList hPresses = hJump.hPresses;
hPresses.Push(iTick);
}
void OnReleaseJump(CPlayer Player, int iTick, float fVelocity)
{
//PrintToServer("%d - OnReleaseJump", iTick);
CStreak CurStreak = Player.hStreak;
ArrayList hJumps = CurStreak.hJumps;
CJump hJump = hJumps.Get(hJumps.Length - 1);
ArrayList hPresses = hJump.hPresses;
hPresses.Set(hPresses.Length - 1, iTick, 1);
}
void DoStats(CPlayer Player, CStreak CurStreak, CJump hJump)
{
int aJumps[3] = {0, 0, 0};
int iPresses = 0;
int iTicks = 0;
int iLastJunk = 0;
CurStreak.iJumps++;
Player.iJumps++;
ArrayList hPresses = hJump.hPresses;
int iStartTick = hJump.iStartTick;
int iEndTick = hJump.iEndTick;
int iPrevJump = hJump.iPrevJump;
int iNextJump = hJump.iNextJump;
if(iPrevJump > 0)
{
int iPerf = iPrevJump - 1;
if(iPerf > 2)
iPerf = 2;
aJumps[iPerf]++;
}
iPresses = hPresses.Length;
iTicks = iEndTick - iStartTick;
iLastJunk = iEndTick - hPresses.Get(iPresses - 1, 1);
float PressesPerTick = (iPresses * 4.0) / float(iTicks);
if(PressesPerTick >= 0.85)
{
CurStreak.iHyperJumps++;
Player.iHyperJumps++;
}
if(iNextJump != -1 && iNextJump <= 1 && (iLastJunk > 5 || iPresses <= 2) && hJump.fEndVel >= 285.0)
{
CurStreak.iHackJumps++;
Player.iHackJumps++;
}
int aGlobalJumps[3];
Player.GetJumps(aGlobalJumps);
aGlobalJumps[0] += aJumps[0];
aGlobalJumps[1] += aJumps[1];
aGlobalJumps[2] += aJumps[2];
Player.SetJumps(aGlobalJumps);
int aStreakJumps[3];
CurStreak.GetJumps(aStreakJumps);
aStreakJumps[0] += aJumps[0];
aStreakJumps[1] += aJumps[1];
aStreakJumps[2] += aJumps[2];
CurStreak.SetJumps(aStreakJumps);
}
void CheckStats(CPlayer Player, CStreak Streak)
{
int client = Player.iClient;
int iStreakJumps = Streak.iJumps;
if(iStreakJumps >= 6)
{
float HackRatio = Streak.iHackJumps / float(iStreakJumps);
if(HackRatio >= 0.80)
{
Player.iHackFlagged += 1;
char sBuffer[32];
Format(sBuffer, sizeof(sBuffer), "bhop hack streak %d\n", Player.iHackFlagged);
NotifyAdmins(client, sBuffer);
}
float HyperRatio = Streak.iHyperJumps / float(iStreakJumps);
if(HyperRatio >= 0.80)
{
Player.iHyperFlagged += 1;
CPrintToChat(client, "{green}[SM]{default} Turn off your bhop macro/script or hyperscroll!");
CPrintToChat(client, "{green}[SM]{default} Your bhop is {red}turned off{default} until you bhop legit again.");
LimitBhop(client, true);
}
else if(IsBhopLimited(client))
{
LimitBhop(client, false);
CPrintToChat(client, "{green}[SM]{default} Your bhop is {green}turned on{default} again.");
}
}
int iGlobalJumps = Player.iJumps;
if(iGlobalJumps >= 35)
{
float HackRatio = Player.iHackJumps / float(iGlobalJumps);
if(HackRatio >= 0.60 && !Player.bHackGlobal)
{
Player.bHackGlobal = true;
NotifyAdmins(client, "bhop hack global");
}
float HyperRatio = Player.iHyperJumps / float(iGlobalJumps);
if(HyperRatio >= 0.50)
{
if(!Player.bHyperGlobal)
{
Player.bHyperGlobal = true;
CPrintToChat(client, "{green}[SM]{default} Turn off your bhop macro/script or hyperscroll!");
CPrintToChat(client, "{green}[SM]{default} Your bhop is {red}turned off{default} until you bhop legit again.");
LimitBhop(client, true);
}
}
else if(Player.bHyperGlobal && IsBhopLimited(client))
{
LimitBhop(client, false);
CPrintToChat(client, "{green}[SM]{default} Your bhop is {green}turned on{default} again.");
}
}
}
NotifyAdmins(int client, const char[] sReason)
{
for(int i = 1; i <= MaxClients; i++)
{
if(IsClientInGame(i) && !IsFakeClient(i) && CheckCommandAccess(i, "sm_stats", ADMFLAG_GENERIC))
{
CPrintToChat(i, "{green}[SM]{default} %L has been detected for {red}%s{default}, please check your console!", client, sReason);
FormatStats(i, client);
PrintToConsole(client, "%s", "\n");
FormatStreak(i, client, 0);
}
}
char sBuffer[2000];
int len = FormatStats(-1, client, sBuffer, sizeof(sBuffer));
sBuffer[len++] = '\n';
len += FormatStreak(-1, client, 0, sBuffer[len], sizeof(sBuffer) - len);
Forward_OnDetected(client, sReason, sBuffer);
}
public Action Command_Stats(int client, int argc)
{
if(argc < 1 || argc > 2)
{
ReplyToCommand(client, "[SM] Usage: sm_stats <#userid|name>");
return Plugin_Handled;
}
char sArg[65];
char sTargetName[MAX_TARGET_LENGTH];
int iTargets[MAXPLAYERS];
int iTargetCount;
bool bIsML;
GetCmdArg(1, sArg, sizeof(sArg));
if((iTargetCount = ProcessTargetString(sArg, client, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_MULTI | COMMAND_FILTER_NO_IMMUNITY, sTargetName, sizeof(sTargetName), bIsML)) <= 0)
{
ReplyToTargetError(client, iTargetCount);
return Plugin_Handled;
}
for(int i = 0; i < iTargetCount; i++)
{
FormatStats(client, iTargets[i]);
PrintToConsole(client, "%s", "\n");
for(int j = 0; j < 3; j++)
{
FormatStreak(client, iTargets[i], j);
PrintToConsole(client, "%s", "\n");
}
}
return Plugin_Handled;
}
public Action Command_Streak(int client, int argc)
{
if(argc < 1 || argc > 2)
{
ReplyToCommand(client, "[SM] Usage: sm_streak <#userid|name> [streak]");
return Plugin_Handled;
}
char sArg[65];
char sArg2[8];
char sTargetName[MAX_TARGET_LENGTH];
int iTargets[MAXPLAYERS];
int iTargetCount;
bool bIsML;
int iStreak = -1;
GetCmdArg(1, sArg, sizeof(sArg));
if(argc == 2)
{
GetCmdArg(2, sArg2, sizeof(sArg2));
iStreak = StringToInt(sArg2);
}
if((iTargetCount = ProcessTargetString(sArg, client, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_MULTI | COMMAND_FILTER_NO_IMMUNITY, sTargetName, sizeof(sTargetName), bIsML)) <= 0)
{
ReplyToTargetError(client, iTargetCount);
return Plugin_Handled;
}
for(int i = 0; i < iTargetCount; i++)
{
FormatStreak(client, iTargets[i], iStreak);
}
return Plugin_Handled;
}
int FormatStats(int client, int iTarget, char[] sBuf=0, int len=0)
{
int iBuf = ConsoleFormat(client, sBuf, len, "[SM] Bunnyhop stats for %L\n", iTarget);
CPlayer Player = g_aPlayers[iTarget];
int iGlobalJumps = Player.iJumps;
float HyperRatio = Player.iHyperJumps / float(iGlobalJumps);
float HackRatio = Player.iHackJumps / float(iGlobalJumps);
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "Global jumps: %d | Hyper?: %.1f%% | Hack?: %.1f%%\n",
iGlobalJumps, HyperRatio * 100.0, HackRatio * 100.0);
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "bHackGlobal: %d | bHyperGlobal: %d | iHackFlagged: %d | iHyperFlagged: %d\n",
Player.bHackGlobal, Player.bHyperGlobal, Player.iHackFlagged, Player.iHyperFlagged);
int aGlobalJumps[3];
Player.GetJumps(aGlobalJumps);
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "Global jumps perf group (1 2 +): %1.f%% %1.f%% %1.f%%\n",
(aGlobalJumps[0] / float(iGlobalJumps)) * 100.0,
(aGlobalJumps[1] / float(iGlobalJumps)) * 100.0,
(aGlobalJumps[2] / float(iGlobalJumps)) * 100.0);
return iBuf;
}
int FormatStreak(int client, int iTarget, int iStreak, char[] sBuf=0, int len=0)
{
int iBuf = ConsoleFormat(client, sBuf, len, "[SM] Bunnyhop streak %d for %L\n", iStreak, iTarget);
CPlayer Player = g_aPlayers[iTarget];
ArrayList hStreaks = Player.hStreaks;
CStreak hStreak = Player.hStreak;
int iStreaks = hStreaks.Length;
// Try showing latest valid streak
if(iStreak <= 0 && !hStreak.bValid)
{
if(iStreaks)
hStreak = hStreaks.Get(iStreaks - 1);
}
else if(iStreak > 0)
{
if(iStreak > MAX_STREAKS)
{
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "[SM] Streak is out of bounds (max. %d)!\n", MAX_STREAKS);
return iBuf;
}
int iIndex = iStreaks - iStreak;
if(iIndex < 0)
{
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "[SM] Only %d streaks are available for this player right now!\n", iStreaks);
return iBuf;
}
hStreak = hStreaks.Get(iIndex);
}
int iStreakJumps = hStreak.iJumps;
float HyperRatio = hStreak.iHyperJumps / float(iStreakJumps);
float HackRatio = hStreak.iHackJumps / float(iStreakJumps);
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "Streak jumps: %d | Hyper?: %.1f%% | Hack?: %.1f%%\n",
iStreakJumps, HyperRatio * 100.0, HackRatio * 100.0);
int aStreakJumps[3];
hStreak.GetJumps(aStreakJumps);
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "Streak jumps perf group (1 2 +): %1.f%% %1.f%% %1.f%%\n",
(aStreakJumps[0] / float(iStreakJumps)) * 100.0,
(aStreakJumps[1] / float(iStreakJumps)) * 100.0,
(aStreakJumps[2] / float(iStreakJumps)) * 100.0);
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "#%2s %7s %5s %8s %4s %6s %s\n",
"id", " outvel", " gain", " avgdist", " num", " avg+-", "pattern");
ArrayList hJumps = hStreak.hJumps;
float fPrevVel = 0.0;
int iPrevEndTick = -1;
for(int i = 0; i < hJumps.Length; i++)
{
CJump hJump = hJumps.Get(i);
ArrayList hPresses = hJump.hPresses;
float fInVel = hJump.fStartVel;
float fOutVel = hJump.fEndVel;
int iEndTick = hJump.iEndTick;
static char sPattern[512];
int iPatternLen = 0;
int iPrevTick = -1;
int iTicks;
if(iPrevEndTick != -1)
{
iTicks = hJump.iStartTick - iPrevEndTick;
for(int k = 0; k < iTicks && k < 16; k++)
sPattern[iPatternLen++] = '|';
}
float fAvgDist = 0.0;
float fAvgDownUp = 0.0;
int iPresses = hPresses.Length;
for(int j = 0; j < iPresses; j++)
{
int aJunkJump[2];
hPresses.GetArray(j, aJunkJump);
if(iPrevTick != -1)
{
iTicks = aJunkJump[0] - iPrevTick;
for(int k = 0; k < iTicks && k < 16; k++)
sPattern[iPatternLen++] = '.';
fAvgDist += iTicks;
}
sPattern[iPatternLen++] = '^';
iTicks = aJunkJump[1] - aJunkJump[0];
for(int k = 0; k < iTicks && k < 16; k++)
sPattern[iPatternLen++] = ',';
fAvgDownUp += iTicks;
sPattern[iPatternLen++] = 'v';
iPrevTick = aJunkJump[1];
}
fAvgDist /= iPresses;
fAvgDownUp /= iPresses;
iTicks = iEndTick - iPrevTick;
for(int k = 0; k < iTicks && k < 16; k++)
sPattern[iPatternLen++] = '.';
sPattern[iPatternLen++] = '|';
sPattern[iPatternLen++] = '\0';
if(fPrevVel == 0.0)
fPrevVel = fInVel;
iBuf += ConsoleFormat(client, sBuf[iBuf], len-iBuf, "#%2d %7.1f %4d%% %8.2f %4d %6.2f %s\n",
i,
fOutVel,
fPrevVel == 0.0 ? 100 : RoundFloat((fOutVel / fPrevVel) * 100.0 - 100.0),
fAvgDist,
iPresses,
fAvgDownUp,
sPattern);
iPrevEndTick = iEndTick;
fPrevVel = fOutVel;
}
return iBuf;
}
bool Forward_OnDetected(int client, const char[] reason, const char[] stats)
{
Call_StartForward(g_hOnClientDetected);
Call_PushCell(client);
Call_PushString(reason);
Call_PushString(stats);
Call_Finish();
}
stock int ConsoleFormat(int client, char[] buffer, int maxlength, const char[] format, any ...)
{
if(client >= 1 && client <= MAXPLAYERS)
{
char sBuf[1024];
sBuf[VFormat(sBuf, sizeof(sBuf), format, 5) - 1] = 0;
PrintToConsole(client, "%s", sBuf);
}
if(maxlength > 0)
return VFormat(buffer, maxlength, format, 5);
return 0;
}