658 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
			
		
		
	
	
			658 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 10
 | 
						|
#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;
 | 
						|
 | 
						|
char g_sStats[4096];
 | 
						|
 | 
						|
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(IsClientInGame(client))
 | 
						|
				OnClientPutInServer(client);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Api
 | 
						|
	g_hOnClientDetected = CreateGlobalForward("AntiBhopCheat_OnClientDetected", ET_Ignore, Param_Cell, Param_String, Param_String);
 | 
						|
}
 | 
						|
 | 
						|
public void OnClientPutInServer(int client)
 | 
						|
{
 | 
						|
	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 = iTick;
 | 
						|
 | 
						|
					DoStats(Player, CurStreak, hJump);
 | 
						|
				}
 | 
						|
				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 client = Player.iClient;
 | 
						|
	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);
 | 
						|
 | 
						|
	int iStreakJumps = CurStreak.iJumps;
 | 
						|
	if(iStreakJumps >= 6)
 | 
						|
	{
 | 
						|
		float HackRatio = CurStreak.iHackJumps / float(iStreakJumps);
 | 
						|
		if(HackRatio >= 0.85 && !Player.bFlagged)
 | 
						|
		{
 | 
						|
			Player.bFlagged = true;
 | 
						|
			NotifyAdmins(client, "bhop hack streak");
 | 
						|
//			KickClient(client, "Turn off your hack!");
 | 
						|
			LimitBhop(client, true);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		float HyperRatio = CurStreak.iHyperJumps / float(iStreakJumps);
 | 
						|
		if(HyperRatio >= 0.85 && !Player.bFlagged)
 | 
						|
		{
 | 
						|
			Player.bFlagged = true;
 | 
						|
			NotifyAdmins(client, "hyperscroll streak");
 | 
						|
			CPrintToChat(client, "{green}[SM]{default} Turn off your bhop macro/script or hyperscroll!");
 | 
						|
			CPrintToChat(client, "{green}[SM]{default} Your bhop has been {red}turned off{default} until the end of the map.");
 | 
						|
			LimitBhop(client, true);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	int iGlobalJumps = Player.iJumps;
 | 
						|
	if(iGlobalJumps >= 25)
 | 
						|
	{
 | 
						|
		float HackRatio = Player.iHackJumps / float(iGlobalJumps);
 | 
						|
		if(HackRatio >= 0.65 && !Player.bFlagged)
 | 
						|
		{
 | 
						|
			Player.bFlagged = true;
 | 
						|
			NotifyAdmins(client, "bhop hack global");
 | 
						|
			//KickClient(client, "Turn off your hack!");
 | 
						|
			LimitBhop(client, true);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		float HyperRatio = Player.iHyperJumps / float(iGlobalJumps);
 | 
						|
		if(HyperRatio >= 0.50 && !Player.bFlagged)
 | 
						|
		{
 | 
						|
			Player.bFlagged = true;
 | 
						|
			NotifyAdmins(client, "hyperscroll global");
 | 
						|
			CPrintToChat(client, "{green}[SM]{default} Turn off your bhop macro/script or hyperscroll!");
 | 
						|
			CPrintToChat(client, "{green}[SM]{default} Your bhop has been {red}turned off{default} until the end of the map.");
 | 
						|
			LimitBhop(client, true);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
			PrintStats(i, client);
 | 
						|
			PrintStreak(i, client, -1, true);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	Forward_OnDetected(client, sReason, g_sStats)
 | 
						|
}
 | 
						|
 | 
						|
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++)
 | 
						|
	{
 | 
						|
		PrintStats(client, iTargets[i]);
 | 
						|
	}
 | 
						|
 | 
						|
	return Plugin_Handled;
 | 
						|
}
 | 
						|
 | 
						|
void PrintStats(int client, int iTarget)
 | 
						|
{
 | 
						|
	PrintToConsole(client, "[SM] Bunnyhop stats for %L", iTarget);
 | 
						|
 | 
						|
	CPlayer Player = g_aPlayers[iTarget];
 | 
						|
	ArrayList hStreaks = Player.hStreaks;
 | 
						|
	CStreak hStreak = Player.hStreak;
 | 
						|
	int iStreaks = hStreaks.Length;
 | 
						|
 | 
						|
	// Try showing latest valid streak
 | 
						|
	if(!hStreak.bValid)
 | 
						|
	{
 | 
						|
		if(iStreaks)
 | 
						|
			hStreak = hStreaks.Get(iStreaks - 1);
 | 
						|
	}
 | 
						|
 | 
						|
	int iGlobalJumps = Player.iJumps;
 | 
						|
	float HyperRatio = Player.iHyperJumps / float(iGlobalJumps);
 | 
						|
	float HackRatio = Player.iHackJumps / float(iGlobalJumps);
 | 
						|
 | 
						|
	PrintToConsole(client, "Global jumps: %d | Hyper?: %.1f%% | Hack?: %.1f%%",
 | 
						|
		iGlobalJumps, HyperRatio * 100.0, HackRatio * 100.0);
 | 
						|
 | 
						|
 | 
						|
	int aGlobalJumps[3];
 | 
						|
	Player.GetJumps(aGlobalJumps);
 | 
						|
 | 
						|
	PrintToConsole(client, "Global jumps perf group (1 2 +): %1.f%%  %1.f%%  %1.f%%",
 | 
						|
		(aGlobalJumps[0] / float(iGlobalJumps)) * 100.0,
 | 
						|
		(aGlobalJumps[1] / float(iGlobalJumps)) * 100.0,
 | 
						|
		(aGlobalJumps[2] / float(iGlobalJumps)) * 100.0);
 | 
						|
 | 
						|
 | 
						|
	PrintToConsole(client, "more to come...");
 | 
						|
}
 | 
						|
 | 
						|
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++)
 | 
						|
	{
 | 
						|
		PrintStreak(client, iTargets[i], iStreak);
 | 
						|
	}
 | 
						|
 | 
						|
	return Plugin_Handled;
 | 
						|
}
 | 
						|
 | 
						|
void PrintStreak(int client, int iTarget, int iStreak, bool bDetected=false)
 | 
						|
{
 | 
						|
	g_sStats = "";
 | 
						|
 | 
						|
	PrintToConsole(client, "[SM] Bunnyhop streak %d for %L", iStreak, iTarget);
 | 
						|
 | 
						|
	if (bDetected)
 | 
						|
		Format(g_sStats, sizeof(g_sStats), "%sBunnyhop streak %d for %L\n",
 | 
						|
		g_sStats, 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)
 | 
						|
		{
 | 
						|
			ReplyToCommand(client, "[SM] Streak is out of bounds (max. %d)!", MAX_STREAKS);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		int iIndex = iStreaks - iStreak;
 | 
						|
		if(iIndex < 0)
 | 
						|
		{
 | 
						|
			ReplyToCommand(client, "[SM] Only %d streaks are available for this player right now!", iStreaks);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		hStreak = hStreaks.Get(iIndex);
 | 
						|
	}
 | 
						|
 | 
						|
	int iStreakJumps = hStreak.iJumps;
 | 
						|
	float HyperRatio = hStreak.iHyperJumps / float(iStreakJumps);
 | 
						|
	float HackRatio = hStreak.iHackJumps / float(iStreakJumps);
 | 
						|
 | 
						|
	PrintToConsole(client, "Streak jumps: %d | Hyper?: %.1f%% | Hack?: %.1f%%",
 | 
						|
		iStreakJumps, HyperRatio * 100.0, HackRatio * 100.0);
 | 
						|
 | 
						|
	if (bDetected)
 | 
						|
		Format(g_sStats, sizeof(g_sStats), "%sStreak jumps: %d | Hyper?: %.1f%% | Hack?: %.1f%%\n",
 | 
						|
		g_sStats, iStreakJumps, HyperRatio * 100.0, HackRatio * 100.0);
 | 
						|
 | 
						|
	int aStreakJumps[3];
 | 
						|
	hStreak.GetJumps(aStreakJumps);
 | 
						|
 | 
						|
	PrintToConsole(client, "Streak jumps perf group (1 2 +): %1.f%%  %1.f%%  %1.f%%",
 | 
						|
		(aStreakJumps[0] / float(iStreakJumps)) * 100.0,
 | 
						|
		(aStreakJumps[1] / float(iStreakJumps)) * 100.0,
 | 
						|
		(aStreakJumps[2] / float(iStreakJumps)) * 100.0);
 | 
						|
 | 
						|
	if (bDetected)
 | 
						|
		Format(g_sStats, sizeof(g_sStats), "%sStreak jumps perf group (1 2 +): %1.f%%  %1.f%%  %1.f%%\n",
 | 
						|
		g_sStats,
 | 
						|
		(aStreakJumps[0] / float(iStreakJumps)) * 100.0,
 | 
						|
		(aStreakJumps[1] / float(iStreakJumps)) * 100.0,
 | 
						|
		(aStreakJumps[2] / float(iStreakJumps)) * 100.0);
 | 
						|
 | 
						|
	PrintToConsole(client, "#%2s %5s %7s %7s %5s %5s %8s %4s %6s   %s",
 | 
						|
		"id", " diff", "  invel", " outvel", " gain", " comb", " avgdist", " num", " avg+-", "pattern");
 | 
						|
 | 
						|
	if (bDetected)
 | 
						|
		Format(g_sStats, sizeof(g_sStats), "%s#%2s %5s %7s %7s %5s %5s %8s %4s %6s   %s\n",
 | 
						|
		g_sStats, "id", " diff", "  invel", " outvel", " gain", " comb", " 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[256];
 | 
						|
		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;
 | 
						|
 | 
						|
		PrintToConsole(client, "#%2d %4d%% %7.1f %7.1f %4d%% %4d%% %8.2f %4d %6.2f   %s",
 | 
						|
			i,
 | 
						|
			fPrevVel == 0.0 ? 100 : RoundFloat((fInVel / fPrevVel) * 100.0 - 100.0),
 | 
						|
			fInVel,
 | 
						|
			fOutVel,
 | 
						|
			fInVel == 0.0 ? 100 : RoundFloat((fOutVel / fInVel) * 100.0 - 100.0),
 | 
						|
			fPrevVel == 0.0 ? 100 : RoundFloat((fOutVel / fPrevVel) * 100.0 - 100.0),
 | 
						|
			fAvgDist,
 | 
						|
			iPresses,
 | 
						|
			fAvgDownUp,
 | 
						|
			sPattern);
 | 
						|
 | 
						|
		if (bDetected)
 | 
						|
			Format(g_sStats, sizeof(g_sStats), "%s#%2d %4d%% %7.1f %7.1f %4d%% %4d%% %8.2f %4d %6.2f   %s\n",
 | 
						|
			g_sStats,
 | 
						|
			i,
 | 
						|
			fPrevVel == 0.0 ? 100 : RoundFloat((fInVel / fPrevVel) * 100.0 - 100.0),
 | 
						|
			fInVel,
 | 
						|
			fOutVel,
 | 
						|
			fInVel == 0.0 ? 100 : RoundFloat((fOutVel / fInVel) * 100.0 - 100.0),
 | 
						|
			fPrevVel == 0.0 ? 100 : RoundFloat((fOutVel / fPrevVel) * 100.0 - 100.0),
 | 
						|
			fAvgDist,
 | 
						|
			iPresses,
 | 
						|
			fAvgDownUp,
 | 
						|
			sPattern);
 | 
						|
 | 
						|
		iPrevEndTick = iEndTick;
 | 
						|
		fPrevVel = fOutVel;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
 | 
						|
	g_sStats = "";
 | 
						|
} |