/* To Do: * Add cfg option to disable hyperscroll detection...maybe if jumps is set to 0? * Code in natives to ignore a client. This would allow other plugins to ignore them, give them bhop hacks, later turn off hacks, then re-enable this plugin checking them. */ #pragma semicolon 1 #define PLUGIN_VERSION "1.10.1" //changelog at bottom #define TAG "[TOGs Jump Stats] " #define CSGO_RED "\x07" #define CSS_RED "\x07FF0000" #include #include #include #include #undef REQUIRE_PLUGIN #include #pragma newdecls required public Plugin myinfo = { name = "TOGs Jump Stats", author = "That One Guy (based on code from Inami)", description = "Player bhop method analysis.", version = PLUGIN_VERSION, url = "http://www.togcoding.com" } ConVar g_hEnableAdmNotifications = null; ConVar g_hEnableLogs = null; ConVar g_hReqMultRoundsHyp = null; ConVar g_hAboveNumber = null; ConVar g_hAboveNumberFlags = null; ConVar g_hHypPerf = null; ConVar g_hHacksPerf = null; ConVar g_hCooldown = null; ConVar g_hPatCount = null; ConVar g_hStatsFlag = null; char g_sStatsFlag[30]; ConVar g_hAdminFlag = null; char g_sAdminFlag[30]; ConVar g_hRelogDiff = null; ConVar g_hFPSMaxMinValue = null; ConVar g_hBanHacks = null; ConVar g_hBanPat = null; ConVar g_hBanHyp = null; ConVar g_hBanFPSMax = null; float ga_fAvgJumps[MAXPLAYERS + 1] = {1.0, ...}; float ga_fAvgSpeed[MAXPLAYERS + 1] = {250.0, ...}; float ga_fVel[MAXPLAYERS + 1][3]; float ga_fLastPos[MAXPLAYERS + 1][3]; float ga_fAvgPerfJumps[MAXPLAYERS + 1] = {0.3333, ...}; float ga_fMaxPerf[MAXPLAYERS + 1] = {0.0, ...}; bool ga_bFlagged[MAXPLAYERS + 1]; bool ga_bFlagHypCurrentRound[MAXPLAYERS + 1]; bool ga_bFlagHypLastRound[MAXPLAYERS + 1]; bool ga_bFlagHypTwoRoundsAgo[MAXPLAYERS + 1]; bool ga_bSurfCheck[MAXPLAYERS + 1]; bool ga_bNotificationsPaused[MAXPLAYERS + 1] = {false, ...}; char g_sHypPath[PLATFORM_MAX_PATH]; char g_sHacksPath[PLATFORM_MAX_PATH]; char g_sPatPath[PLATFORM_MAX_PATH]; int ga_iJumps[MAXPLAYERS + 1] = {0, ...}; int ga_iPattern[MAXPLAYERS + 1] = {0, ...}; int ga_iPatternhits[MAXPLAYERS + 1] = {0, ...}; int ga_iAutojumps[MAXPLAYERS + 1] = {0, ...}; int ga_iIgnoreCount[MAXPLAYERS + 1]; int ga_iLastPos[MAXPLAYERS + 1] = {0, ...}; int ga_iNumberJumpsAbove[MAXPLAYERS + 1]; int gaa_iLastJumps[MAXPLAYERS + 1][30]; int g_iTickCount = 1; bool g_bDisableAdminMsgs = false; bool g_bCSGO = false; public void OnPluginStart() { LoadTranslations("common.phrases"); AutoExecConfig_SetFile("togsjumpstats"); AutoExecConfig_CreateConVar("tjs_version", PLUGIN_VERSION, "TOGs Jump Stats Version", FCVAR_NOTIFY|FCVAR_DONTRECORD); g_hCooldown = AutoExecConfig_CreateConVar("tjs_gen_cooldown", "60", "Cooldown time between chat notifications to admins for any given clients that is flagged.", FCVAR_NONE, true, 0.0); g_hStatsFlag = AutoExecConfig_CreateConVar("tjs_gen_flag", "", "Players with this flag will be able to check stats. Set to \"public\" to let everyone use it."); g_hStatsFlag.AddChangeHook(OnCVarChange); g_hStatsFlag.GetString(g_sStatsFlag, sizeof(g_sStatsFlag)); g_hAdminFlag = AutoExecConfig_CreateConVar("tjs_adm_flag", "b", "Players with this flag will see notifications when players are flagged. Set to \"public\" to let everyone use it."); g_hAdminFlag.AddChangeHook(OnCVarChange); g_hAdminFlag.GetString(g_sAdminFlag, sizeof(g_sAdminFlag)); g_hRelogDiff = AutoExecConfig_CreateConVar("tjs_flag_relogdiff", "0.05", "Players are re-logged in the same map if they are flagged with a perf that is this much higher than the previous one.", FCVAR_NONE, true, 0.0, true, 1.0); g_hFPSMaxMinValue = AutoExecConfig_CreateConVar("tjs_fpsmax_minvalue", "60.0", "Minimum value of fps_max to enforce. Players below this will be flagged (other than zero).", FCVAR_NONE, true, 0.0, true, 1.0); g_hEnableAdmNotifications = AutoExecConfig_CreateConVar("tjs_gen_notifications", "1", "Enable admin chat notifications when a player is flagged (0 = Disabled, 1 = Enabled).", FCVAR_NONE, true, 0.0, true, 1.0); g_hEnableLogs = AutoExecConfig_CreateConVar("tjs_gen_log", "1", "Enable logging player jump stats if a player is flagged (0 = Disabled, 1 = Enabled).", FCVAR_NONE, true, 0.0, true, 1.0); g_hReqMultRoundsHyp = AutoExecConfig_CreateConVar("tjs_hyp_mult_rounds", "1", "Clients will not be flagged (in logs and admin notifications) for hyperscrolling until they are noted 3 rounds in a row (0 = Disabled, 1 = Enabled).", FCVAR_NONE, true, 0.0, true, 1.0); g_hAboveNumber = AutoExecConfig_CreateConVar("tjs_hyp_numjumps", "16", "Number of jump commands to use as a threshold for flagging hyperscrollers.", FCVAR_NONE, true, 1.0); g_hAboveNumberFlags = AutoExecConfig_CreateConVar("tjs_hyp_threshold", "16", "Out of the last 30 jumps, the number of jumps that must be above tjs_numjumps to flag player for hyperscrolling.", FCVAR_NONE, true, 1.0); g_hHypPerf = AutoExecConfig_CreateConVar("tjs_hyp_perf", "0.6", "Above this perf ratio (in combination with the other hyperscroll cvars), players will be flagged for hyperscrolling.", FCVAR_NONE, true, 0.0, true, 1.0); g_hHacksPerf = AutoExecConfig_CreateConVar("tjs_hacks_perf", "0.8", "Above this perf ratio (ratios range between 0.0 - 1.0), players will be flagged for hacks.", FCVAR_NONE, true, 0.0, true, 1.0); g_hPatCount = AutoExecConfig_CreateConVar("tjs_pat_count", "18", "Number of jump out of the last 30 that must match to be flagged for pattern jumps (scripts).", FCVAR_NONE, true, 1.0); g_hBanHacks = AutoExecConfig_CreateConVar("tjs_ban_hacks", "0", "Ban length in minutes (0 = perm, -1 = disabled) for hacks detection.", FCVAR_NONE, true, -1.0); g_hBanPat = AutoExecConfig_CreateConVar("tjs_ban_pat", "-1", "Ban length in minutes (0 = perm, -1 = disabled) for pattern jumps detection.", FCVAR_NONE, true, -1.0); g_hBanHyp = AutoExecConfig_CreateConVar("tjs_ban_hyp", "-1", "Ban length in minutes (0 = perm, -1 = disabled) for hyperscroll detection.", FCVAR_NONE, true, -1.0); g_hBanFPSMax = AutoExecConfig_CreateConVar("tjs_ban_fpsmax", "-1", "Ban length in minutes (0 = perm, -1 = disabled) for FPS Max abuse detection.", FCVAR_NONE, true, -1.0); HookEvent("player_jump", Event_PlayerJump, EventHookMode_Post); BuildPath(Path_SM, g_sHypPath, sizeof(g_sHypPath), "logs/togsjumpstats/hyperscrollers.log"); BuildPath(Path_SM, g_sHacksPath, sizeof(g_sHacksPath), "logs/togsjumpstats/hacks.log"); BuildPath(Path_SM, g_sPatPath, sizeof(g_sPatPath), "logs/togsjumpstats/patterns.log"); RegConsoleCmd("sm_jumps", Command_Jumps, "Gives statistics for player jumps."); RegConsoleCmd("sm_stopmsgs", Command_StopAdminMsgs, "Stops admin chat notifications when players are flagged for current map."); RegConsoleCmd("sm_enablemsgs", Command_EnableAdminMsgs, "Re-enables admin chat notifications when players are flagged."); RegConsoleCmd("sm_msgstatus", Command_MsgStatus, "Check enabled/disabled status of admin chat notifications."); RegConsoleCmd("sm_resetjumps", Command_ResetJumps, "Reset statistics for a player."); AutoExecConfig_ExecuteFile(); AutoExecConfig_CleanFile(); char sGame[32]; GetGameFolderName(sGame, sizeof(sGame)); if(StrEqual(sGame, "csgo", false)) { g_bCSGO = true; } else { g_bCSGO = false; } HookEvent("round_start", Event_RoundStart, EventHookMode_Pre); for(int i = 1; i <= MaxClients; i++) //late load handler { if(IsValidClient(i)) { OnClientPutInServer(i); } } char sBuffer[PLATFORM_MAX_PATH]; BuildPath(Path_SM, sBuffer, sizeof(sBuffer), "logs/togsjumpstats/"); if(!DirExists(sBuffer)) { CreateDirectory(sBuffer, 777); } } public void OnCVarChange(ConVar hCVar, const char[] sOldValue, const char[] sNewValue) { if(hCVar == g_hStatsFlag) { g_hStatsFlag.GetString(g_sStatsFlag, sizeof(g_sStatsFlag)); } else if(hCVar == g_hAdminFlag) { g_hAdminFlag.GetString(g_sAdminFlag, sizeof(g_sAdminFlag)); } } public Action Event_RoundStart(Handle hEvent, const char[] sName, bool bDontBroadcast) { if(g_hReqMultRoundsHyp.IntValue) { for(int i = 1; i <= MaxClients; i++) { if(ga_bFlagHypLastRound[i]) { ga_bFlagHypTwoRoundsAgo[i] = true; } else { ga_bFlagHypTwoRoundsAgo[i] = false; } if(ga_bFlagHypCurrentRound[i]) { ga_bFlagHypLastRound[i] = true; } else { ga_bFlagHypLastRound[i] = false; } ga_bFlagHypCurrentRound[i] = false; } } for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i)) { QueryClientConVar(i, "fps_max", ClientConVar, i); } } } public int ClientConVar(QueryCookie cookie, int client, ConVarQueryResult result, const char[] sCVarName, const char[] sCVarValue) { float fValue = StringToFloat(sCVarValue); if((fValue < g_hFPSMaxMinValue.FloatValue) && fValue) //if non-zero and less { char sMsg[32]; Format(sMsg, sizeof(sMsg), "fps_max-%s", sCVarValue); LogFlag(client, sMsg); if(!g_bDisableAdminMsgs && g_hEnableAdmNotifications.BoolValue) { NotifyAdmins(client, sMsg); } } } public void OnClientPutInServer(int client) { ga_bNotificationsPaused[client] = false; ga_bFlagged[client] = false; ga_bFlagHypCurrentRound[client] = false; ga_bFlagHypLastRound[client] = false; ga_bFlagHypTwoRoundsAgo[client] = false; } public void OnClientPostAdminCheck(int client) { if(HasFlags(client, g_sAdminFlag)) { CreateTimer(30.0, TimerCB_CheckForFlags, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } } public Action TimerCB_CheckForFlags(Handle hTimer, any iUserID) { int client = GetClientOfUserId(iUserID); int iCount = 0; if(IsValidClient(client)) { for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i)) { if(ga_bFlagged[i]) { iCount++; } } } if(iCount) { PrintToChat(client, "%s%s%i players have been flagged for jump stats! Please check everyone's stats!", TAG, g_bCSGO ? CSGO_RED : CSS_RED, iCount); } } } public void Event_PlayerJump(Handle hEvent, const char[] sName, bool bDontBroadcast) { int client = GetClientOfUserId(GetEventInt(hEvent, "userid")); if(!IsValidClient(client)) { return; } ga_fAvgJumps[client] = (ga_fAvgJumps[client] * 9.0 + float(ga_iJumps[client])) / 10.0; float a_fVelVectors[3]; GetEntPropVector(client, Prop_Data, "m_vecVelocity", a_fVelVectors); a_fVelVectors[2] = 0.0; float speed = GetVectorLength(a_fVelVectors); ga_fAvgSpeed[client] = (ga_fAvgSpeed[client] * 9.0 + speed) / 10.0; gaa_iLastJumps[client][ga_iLastPos[client]] = ga_iJumps[client]; ga_iLastPos[client]++; if(ga_iLastPos[client] == 30) { ga_iLastPos[client] = 0; } if(ga_fAvgJumps[client] > 15.0) { if((ga_iPatternhits[client] > 0) && (ga_iJumps[client] == ga_iPattern[client])) { ga_iPatternhits[client]++; if(ga_iPatternhits[client] > g_hPatCount.IntValue) { if(!ga_bNotificationsPaused[client]) { if(!g_bDisableAdminMsgs && g_hEnableAdmNotifications.BoolValue) { NotifyAdmins(client, "Pattern Jumps"); } } if((ga_fAvgPerfJumps[client] - g_hRelogDiff.FloatValue) > ga_fMaxPerf[client]) { LogFlag(client, "pattern jumps", ga_bFlagged[client]); ga_fMaxPerf[client] = ga_fAvgPerfJumps[client]; } } } else if((ga_iPatternhits[client] > 0) && (ga_iJumps[client] != ga_iPattern[client])) { ga_iPatternhits[client] -= 2; } else { ga_iPattern[client] = ga_iJumps[client]; ga_iPatternhits[client] = 2; } } if(ga_fAvgJumps[client] > 14.0) { //check if more than 8 of the last 30 jumps were above 12 ga_iNumberJumpsAbove[client] = 0; for(int i = 0; i < 29; i++) //count { if((gaa_iLastJumps[client][i]) > (g_hAboveNumber.IntValue - 1)) //threshhold for # jump commands { ga_iNumberJumpsAbove[client]++; } } if((ga_iNumberJumpsAbove[client] > (g_hAboveNumberFlags.IntValue - 1)) && (ga_fAvgPerfJumps[client] >= g_hHypPerf.FloatValue)) //if more than # { if(g_hReqMultRoundsHyp.IntValue) { if(ga_bFlagHypTwoRoundsAgo[client] && ga_bFlagHypLastRound[client]) { if(!ga_bNotificationsPaused[client]) { if(!g_bDisableAdminMsgs && g_hEnableAdmNotifications.BoolValue) { NotifyAdmins(client, "Hyperscroll (3 rounds in a row)"); } } if((ga_fAvgPerfJumps[client] - g_hRelogDiff.FloatValue) > ga_fMaxPerf[client]) { LogFlag(client, "hyperscroll (3 rounds in a row)", ga_bFlagged[client]); ga_fMaxPerf[client] = ga_fAvgPerfJumps[client]; } } else { ga_bFlagHypCurrentRound[client] = true; } } else { if(!ga_bNotificationsPaused[client]) { if(!g_bDisableAdminMsgs && g_hEnableAdmNotifications.BoolValue) { NotifyAdmins(client, "Hyperscroll"); } } if((ga_fAvgPerfJumps[client] - g_hRelogDiff.FloatValue) > ga_fMaxPerf[client]) { LogFlag(client, "hyperscroll", ga_bFlagged[client]); ga_fMaxPerf[client] = ga_fAvgPerfJumps[client]; } } } } else if(ga_iJumps[client] > 1) { ga_iAutojumps[client] = 0; } ga_iJumps[client] = 0; float a_fTempVectors[3]; a_fTempVectors = ga_fLastPos[client]; GetEntPropVector(client, Prop_Send, "m_vecOrigin", ga_fLastPos[client]); float len = GetVectorDistance(ga_fLastPos[client], a_fTempVectors, true); if(len < 30.0) { ga_iIgnoreCount[client] = 2; } if(ga_fAvgPerfJumps[client] >= g_hHacksPerf.FloatValue) { if(!ga_bNotificationsPaused[client]) { if(!g_bDisableAdminMsgs && g_hEnableAdmNotifications.BoolValue) { NotifyAdmins(client, "Hacks"); } } if((ga_fAvgPerfJumps[client] - g_hRelogDiff.FloatValue) > ga_fMaxPerf[client]) { LogFlag(client, "hacks", ga_bFlagged[client]); ga_fMaxPerf[client] = ga_fAvgPerfJumps[client]; } } } public Action Command_StopAdminMsgs(int client, int iArgs) { if(!HasFlags(client, g_sAdminFlag) && IsValidClient(client)) { ReplyToCommand(client, "%sYou do not have access to this command!", TAG); return Plugin_Handled; } StopMsgs(client); return Plugin_Handled; } public Action Command_MsgStatus(int client, int iArgs) { if(!HasFlags(client, g_sAdminFlag) && IsValidClient(client)) { ReplyToCommand(client, "%sYou do not have access to this command!", TAG); return Plugin_Handled; } if(g_bDisableAdminMsgs) { ReplyToCommand(client, "%sAdmin chat notifications for flagged players is currently disabled!", TAG); } else { ReplyToCommand(client, "%sAdmin chat notifications for flagged players is currently enabled.", TAG); } return Plugin_Handled; } void StopMsgs(any client) { g_bDisableAdminMsgs = true; for(int i = 1; i <= MaxClients; i++) { if(IsClientInGame(i) && CheckCommandAccess(i, "sm_admin", ADMFLAG_GENERIC, true) && !IsFakeClient(i)) { if(i > 0) { CPrintToChat(i, "%s%s%N has disabled admin notices for bhop cheats until map changes!", TAG, g_bCSGO ? CSGO_RED : CSS_RED, client); } } } } void EnableMsgs(any client) { g_bDisableAdminMsgs = false; for(int i = 1; i <= MaxClients; i++) { if(IsClientInGame(i) && CheckCommandAccess(i, "sm_admin", ADMFLAG_GENERIC, true) && !IsFakeClient(i)) { if(i > 0) { CPrintToChat(i, "%s%s%N has re-enabled admin notices for bhop cheats!", TAG, g_bCSGO ? CSGO_RED : CSS_RED, client); } } } } public Action Command_EnableAdminMsgs(int client, int iArgs) { if(!HasFlags(client, g_sAdminFlag) && IsValidClient(client)) { ReplyToCommand(client, "%sYou do not have access to this command!", TAG); return Plugin_Handled; } EnableMsgs(client); return Plugin_Handled; } public void OnMapStart() { g_bDisableAdminMsgs = false; for(int i = 1; i <= MaxClients; i++) { if(IsClientInGame(i)) { ga_bNotificationsPaused[i] = false; ga_bFlagHypCurrentRound[i] = false; ga_bFlagHypLastRound[i] = false; ga_bFlagHypTwoRoundsAgo[i] = false; } } } void NotifyAdmins(int client, char[] sFlagType) { if(IsValidClient(client)) { if(StrContains(sFlagType, "fps_max", false) == -1) { for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i) && CheckCommandAccess(i, "sm_admin", ADMFLAG_GENERIC, true)) { CPrintToChat(i, "%s%s'%N' has been flagged for '%s'! Please check their jump stats!", TAG, g_bCSGO ? CSGO_RED : CSS_RED, client, sFlagType); PerformStats(i, client); } } ga_bNotificationsPaused[client] = true; CreateTimer(g_hCooldown.FloatValue, UnPause_TimerMonitor, GetClientUserId(client), TIMER_FLAG_NO_MAPCHANGE); } else { char a_sTempArray[2][32]; ExplodeString(sFlagType, "-", a_sTempArray, sizeof(a_sTempArray), sizeof(a_sTempArray[])); for(int i = 1; i <= MaxClients; i++) { if(IsValidClient(i) && CheckCommandAccess(i, "sm_admin", ADMFLAG_GENERIC, true)) { CPrintToChat(i, "%s%s'%N' has been flagged for having fps_max set to %s! Please enforce a minimum value of %5.1f.", TAG, g_bCSGO ? CSGO_RED : CSS_RED, client, a_sTempArray[1], g_hFPSMaxMinValue.FloatValue); PerformStats(i, client); } } } } } public Action UnPause_TimerMonitor(Handle hTimer, any iUserID) { int client = GetClientOfUserId(iUserID); if(IsValidClient(client)) { ga_bNotificationsPaused[client] = false; } return Plugin_Continue; } public void OnClientDisconnect(int client) { ga_iJumps[client] = 0; ga_fAvgJumps[client] = 5.0; ga_fAvgSpeed[client] = 250.0; ga_fAvgPerfJumps[client] = 0.3333; ga_iPattern[client] = 0; ga_iPatternhits[client] = 0; ga_iAutojumps[client] = 0; ga_iIgnoreCount[client] = 0; ga_bFlagged[client] = false; ga_bFlagHypCurrentRound[client] = false; ga_bFlagHypLastRound[client] = false; ga_bFlagHypTwoRoundsAgo[client] = false; ga_fVel[client][2] = 0.0; int i; while(i < 30) { gaa_iLastJumps[client][i] = 0; i++; } } public void OnGameFrame() { if(g_iTickCount > 1*MaxClients) { g_iTickCount = 1; } else { if(g_iTickCount % 1 == 0) { int client = g_iTickCount / 1; if(ga_bSurfCheck[client] && IsClientInGame(client) && IsPlayerAlive(client)) { GetEntPropVector(client, Prop_Data, "m_vecVelocity", ga_fVel[client]); if(ga_fVel[client][2] < -290) { ga_iIgnoreCount[client] = 2; } } } g_iTickCount++; } } void LogFlag(int client, const char[] sType, bool bAlreadyFlagged = false) { if(IsValidClient(client)) { char sStats[256], sLogMsg[300]; GetClientStats(client, sStats, sizeof(sStats)); Format(sLogMsg, sizeof(sLogMsg), "%s %s%s", sStats, sType, (bAlreadyFlagged ? " (already flagged this map)" : "")); if(StrEqual(sType, "hacks", false)) { if(g_hEnableLogs.BoolValue) { LogToFileEx(g_sHacksPath, sLogMsg); } if(g_hBanHacks.IntValue != -1) { if(LibraryExists("sourcebans")) { SBBanPlayer(0, client, g_hBanHacks.IntValue, sLogMsg); } else { BanClient(client, g_hBanHacks.IntValue, BANFLAG_AUTO, sLogMsg, "You have been banned for bhop hacks!", "jumpstats", 0); } } } else if(StrEqual(sType, "pattern jumps", false)) { if(g_hEnableLogs.BoolValue) { LogToFileEx(g_sPatPath, sLogMsg); } if(g_hBanPat.IntValue != -1) { if(LibraryExists("sourcebans")) { SBBanPlayer(0, client, g_hBanPat.IntValue, sLogMsg); } else { BanClient(client, g_hBanPat.IntValue, BANFLAG_AUTO, sLogMsg, "You have been banned for bhop hacks!", "jumpstats", 0); } } } else if(StrEqual(sType, "hyperscroll", false) || StrEqual(sType, "hyperscroll (3 rounds in a row)", false)) { if(g_hEnableLogs.BoolValue) { LogToFileEx(g_sHypPath, sLogMsg); } if(g_hBanHyp.IntValue != -1) { if(LibraryExists("sourcebans")) { SBBanPlayer(0, client, g_hBanHyp.IntValue, sLogMsg); } else { BanClient(client, g_hBanHyp.IntValue, BANFLAG_AUTO, sLogMsg, "You have been banned for bhop hacks!", "jumpstats", 0); } } } else if(StrContains(sType, "fps_max", false) != -1) { char a_sTempArray[2][32]; ExplodeString(sType, "-", a_sTempArray, sizeof(a_sTempArray), sizeof(a_sTempArray[])); Format(sLogMsg, sizeof(sLogMsg), "%L has fps_max set to %s (min. accepted value set to %i)! This can be used as a glitch to get high perfect percentages!", client, a_sTempArray[1], g_hFPSMaxMinValue.IntValue); if(g_hEnableLogs.BoolValue) { LogToFileEx(g_sHacksPath, sLogMsg); } if(g_hBanFPSMax.IntValue != -1) { if(LibraryExists("sourcebans")) { SBBanPlayer(0, client, g_hBanFPSMax.IntValue, sLogMsg); } else { BanClient(client, g_hBanFPSMax.IntValue, BANFLAG_AUTO, sLogMsg, "You have been banned for bhop hacks!", "jumpstats", 0); } } } ga_bFlagged[client] = true; } } public Action Command_Jumps(int client, int iArgs) { if(iArgs != 1) { ReplyToCommand(client, "%sUsage: sm_jumps <#userid|name|@all>", TAG); return Plugin_Handled; } if(IsValidClient(client)) { if(!HasFlags(client, g_sStatsFlag)) { ReplyToCommand(client, "%sYou do not have access to this command!", TAG); return Plugin_Handled; } } char sArg[65]; GetCmdArg(1, sArg, sizeof(sArg)); char sTargetName[MAX_TARGET_LENGTH]; int a_iTargets[MAXPLAYERS], iTargetCount; bool bTN_Is_ML; if((iTargetCount = ProcessTargetString(sArg, client, a_iTargets, MAXPLAYERS, COMMAND_FILTER_NO_IMMUNITY, sTargetName, sizeof(sTargetName), bTN_Is_ML)) <= 0) { ReplyToCommand(client, "Not found or invalid parameter."); return Plugin_Handled; } SortedStats(client, a_iTargets, iTargetCount); if(IsValidClient(client)) { ReplyToCommand(client, "%sCheck console for output!", TAG); } return Plugin_Handled; } public Action Command_ResetJumps(int client, int iArgs) { if(iArgs != 1) { ReplyToCommand(client, "%sUsage: sm_resetjumps <#userid|name|@all>", TAG); return Plugin_Handled; } if(IsValidClient(client)) { if(!HasFlags(client, g_sAdminFlag) && IsValidClient(client)) { ReplyToCommand(client, "%sYou do not have access to this command!", TAG); return Plugin_Handled; } } char sArg[65]; GetCmdArg(1, sArg, sizeof(sArg)); char sTargetName[MAX_TARGET_LENGTH]; int a_iTargets[MAXPLAYERS], iTargetCount; bool bTN_Is_ML; if((iTargetCount = ProcessTargetString(sArg, client, a_iTargets, MAXPLAYERS, COMMAND_FILTER_NO_IMMUNITY, sTargetName, sizeof(sTargetName), bTN_Is_ML)) <= 0) { ReplyToCommand(client, "Not found or invalid parameter."); return Plugin_Handled; } for(int i = 0; i < iTargetCount; i++) { int target = a_iTargets[i]; if(IsValidClient(target)) { ResetJumps(target); ReplyToCommand(client, "%sStats are now reset for player %N.", TAG, target); } } return Plugin_Handled; } void ResetJumps(int target) { for(int i = 0; i < 29; i++) { gaa_iLastJumps[target][i] = 0; } } void PerformStats(int client, int target) { char sStats[300]; GetClientStats(target, sStats, sizeof(sStats)); if(IsValidClient(client)) { PrintToConsole(client, "Flagged: %i || %s", ga_bFlagged[target], sStats); } else { PrintToServer("Flagged: %i || %s", ga_bFlagged[target], sStats); } } void SortedStats(int client, int[] a_iTargets, int iCount) { float[][] a_fPerfs = new float[iCount][2]; int iValidCount = 0; for(int i = 0; i < iCount; i++) { if(IsValidClient(a_iTargets[i])) { a_fPerfs[i][0] = ga_fAvgPerfJumps[a_iTargets[i]] * 1000; iValidCount++; } else { a_fPerfs[i][0] = -1.0; } a_fPerfs[i][1] = float(a_iTargets[i]); } SortCustom2D(a_fPerfs, iCount, SortPerfs); char[][] a_sStats = new char[iValidCount][300]; int k = 0; char sMsg[300]; for(int j = 0; j < iCount; j++) { int target = RoundFloat(a_fPerfs[j][1]); if(IsValidClient(target) && (a_fPerfs[j][0] != -1.0)) { //save to another array to display them in order, since the get stats takes time and therefor they can sometimes come out of order slightly char sStats[300]; GetClientStats(target, sStats, sizeof(sStats)); Format(sMsg, sizeof(sMsg), "Flagged: %d || %s", ga_bFlagged[target], sStats); strcopy(a_sStats[k], 300, sMsg); k++; } } if(IsValidClient(client)) { for(int m = 0; m < iValidCount; m++) { PrintToConsole(client, a_sStats[m]); } } else { for(int m = 0; m < iValidCount; m++) { PrintToServer(a_sStats[m]); } } } public int SortPerfs(int[] x, int[] y, const int[][] aArray, Handle hHndl) { if(view_as(x[0]) > view_as(y[0])) { return -1; } return view_as(x[0]) < view_as(y[0]); } void GetClientStats(int client, char[] sStats, int iLength) { char sMap[128]; GetCurrentMap(sMap, sizeof(sMap)); Format(sStats, iLength, "Perf: %4.1f%% || Avg: %-4.1f / %5.1f || %L || Map: %s || Last: ", ga_fAvgPerfJumps[client]*100, ga_fAvgJumps[client], ga_fAvgSpeed[client], client, sMap); Format(sStats, iLength, "%s%i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i", sStats, gaa_iLastJumps[client][0], gaa_iLastJumps[client][1], gaa_iLastJumps[client][2], gaa_iLastJumps[client][3], gaa_iLastJumps[client][4], gaa_iLastJumps[client][5], gaa_iLastJumps[client][6], gaa_iLastJumps[client][7], gaa_iLastJumps[client][8], gaa_iLastJumps[client][9], gaa_iLastJumps[client][10], gaa_iLastJumps[client][11], gaa_iLastJumps[client][12], gaa_iLastJumps[client][13], gaa_iLastJumps[client][14], gaa_iLastJumps[client][15], gaa_iLastJumps[client][16], gaa_iLastJumps[client][17], gaa_iLastJumps[client][18], gaa_iLastJumps[client][19], gaa_iLastJumps[client][20], gaa_iLastJumps[client][21], gaa_iLastJumps[client][22], gaa_iLastJumps[client][23], gaa_iLastJumps[client][24], gaa_iLastJumps[client][25], gaa_iLastJumps[client][26], gaa_iLastJumps[client][27], gaa_iLastJumps[client][28], gaa_iLastJumps[client][29]); } public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float a_fVel[3], float a_fAngles[3], int &weapon) { if(IsPlayerAlive(client)) { static bool bHoldingJump[MAXPLAYERS + 1]; static bLastOnGround[MAXPLAYERS + 1]; if(buttons & IN_JUMP) { if(!bHoldingJump[client]) { bHoldingJump[client] = true;//started pressing +jump ga_iJumps[client]++; if(bLastOnGround[client] && (GetEntityFlags(client) & FL_ONGROUND)) { ga_fAvgPerfJumps[client] = (ga_fAvgPerfJumps[client] * 9.0 + 0) / 10.0; } else if(!bLastOnGround[client] && (GetEntityFlags(client) & FL_ONGROUND)) { ga_fAvgPerfJumps[client] = (ga_fAvgPerfJumps[client] * 9.0 + 1) / 10.0; } } } else if(bHoldingJump[client]) { bHoldingJump[client] = false;//released (-jump) } bLastOnGround[client] = GetEntityFlags(client) & FL_ONGROUND; } return Plugin_Continue; } bool HasFlags(int client, char[] sFlags) { if(StrEqual(sFlags, "public", false) || StrEqual(sFlags, "", false)) { return true; } else if(StrEqual(sFlags, "none", false)) //useful for some plugins { return false; } else if(!client) //if rcon { return true; } else if(CheckCommandAccess(client, "sm_not_a_command", ADMFLAG_ROOT, true)) { return true; } AdminId id = GetUserAdmin(client); if(id == INVALID_ADMIN_ID) { return false; } int flags, clientflags; clientflags = GetUserFlagBits(client); if(StrContains(sFlags, ";", false) != -1) //check if multiple strings { int i = 0, iStrCount = 0; while(sFlags[i] != '\0') { if(sFlags[i++] == ';') { iStrCount++; } } iStrCount++; //add one more for stuff after last comma char[][] a_sTempArray = new char[iStrCount][30]; ExplodeString(sFlags, ";", a_sTempArray, iStrCount, 30); bool bMatching = true; for(i = 0; i < iStrCount; i++) { bMatching = true; flags = ReadFlagString(a_sTempArray[i]); for(int j = 0; j <= 20; j++) { if(bMatching) //if still matching, continue loop { if(flags & (1< include. * Changed g_iDisableAdminMsgs to boolean, since it was being used like one (only two options). * Replaced global cache of game folder name (for checking if CS:GO) with global cached boolean, thus not needing to check the game name each time, but rather check boolean value. * Cleaned up variable names all throughout the plugin and did general cleanup, deleting unneccesary code (havent touched this plugin in a long time). 1.9.1.nm * Broke apart GetClientStats formatting function to enforce 32 arg max (it had 35). * Converted to new syntax. 1.9.2 * Made admin notification after connecting only show if a player has been flagged. * Added code to create log folder if it doesn't exist. 1.9.3 * Fixed logs indication regarding whether a player has "already been flagged this map". * Changed console stats output from using %d to use %i for the "flagged" boolean output. Shouldn't make a difference as I can see, but made the change due to a report of the flag not functioning properly. 1.9.4 * Added cvar for admin notification flags. 1.9.5 * Minor edit to low fps_max detection - zero values were supposed to be allowed, but slipped through due to float decimals extending past string compared against. Fixed. 1.10.0 * Added options to use sourcebans to ban for detections (defaults to bans for hacks only - scripts, hyperscroll, and fps_max abuse default to no ban). 1.10.1 * Added alternative if sourcebans is not enabled. Renamed ban length CVars to no longer imply sourcebans (SB). */