/* Plugin generated by AMXX-Studio */

/*
Anti Speedhack v1.8
This script will detect and ban speedhackers.

You can adjust the sensetivity values by the constants; 
#define MAXOFFENCE and #define SECDIST and FFF

If you want to try it on, you might want to use GHW_Chronic's Legal Speed Hack 

The works:
The plugin checks your position every second and measures the distance in units to your position
one second ago. This allows the plugin to check how fast you have been movin the latest seconds.
If a player have jumped 2000 units over a perod of half a second then there is a good chance we
have a speedhacker.

CVARS
amx_sh_sens | def. 3 - Sensetivity for autokick, should be around 2-3 i think
amx_sh_autokick | 1=on, 0=off | def. 1 - Auto-kick speedhackers if no admin is online?

Requires: 
#include <amxmodx>
#include <amxmisc>
#include <cstrike>
#include <engine>

Credits: 
to EKS for giving me ideas from the spinhack detector script - also "borrowed" some code  
Spin Hack Detector can be found here 
* All ppl on the forum here from where i found various code snippets and information 
* Zenith77 for annoying me to revise pluggin to v1.1 
* ... and also a little bit of creds to me 

Caution! 
If this pluggin runs amok, kills your dog and steals your girlfriend away from you, dont ask me why and dont blaim me. Use this at own risk. Known side effects may be that your server may randomly ban people without warning IF you mess upp with the sens values. If you experience these negative effects, try increasing these values - NOT decreasing! 

Todo
 * Improve when recived feedback
 * Clean up code

Version History 
 1.8   -  Fixed bug : Previously the script banns YOU - not the speedhacker
 
 1.7   -  Fixed bug : Silly me. Fixed a bug that dissabled the autokicker

 1.6 -    Improved  : Now this plugin will logg speedhackers so admins can se what have been going on.
                      Also, added funtion to automaticly kick speedhackers if no admin is online.
		      Added cvars amx_sh_sens and amx_sh_autokick
		      
 1.5 -    Fixed bug : I wont say that i have got this running fully yet, since i have constantly been
                      proven otherwise when saying so. I have taken a new approach on the problems though
                      and it seams the problems were caused when anyone gets banned it does not reset.

          Improved  : I have also removed the autoban system since admins prefferably would like some
		      controll over who to be banned. This script may now work with teleports and even speed
		      since any admin may choose to ignore the speedhack alerts.
		      
 1.4 -    Fixed bug : Finaly found the root of the problems (i think). Now buyzones (commonly at spawnpoints)
                      are neutral freezones to clear at spawn. It seamed to work on D2, have not checket
		      every single map though. Would be glad over some feedback :) - Removed FFF

 1.3 -    Fixed bug : random important bugs fixed, more stable now and does not take forever to ban a violator 

 1.2 -    Fixed bug : server does not ban as soon as is suspects speedhacker 

 1.1 -    Does not run on kz and surf type maps 
          Lowered the sens values after some tests  

	  
If you find any bugs, please let me know:
mathias.frendin@telia.com : mail/msn
-DarkSnow
*/

#include <amxmodx>
#include <amxmisc>
#include <cstrike>
#include <engine>

#pragma tabsize 0

#define PLUGIN "Speedhack Protection"
#define VERSION "1.8"
#define AUTHOR "DarkSnow"

#define MAXPLAYERS 32
#define UseAMXBANS 0
#define DEBUG 0		//1 to activate debug mode
//Max offence is the max value of secdist+ before we can be 100% sure its speedhack
//Secdist is the distance between the intervals that is concidered maximum of normal travel speed,
//normaly you run just under 300 units, but counting in that you might jump we use about 350 just to be sure
#define MAXOFFENCE 700 //Sensitivity, may need to be adjusted
#define SECDIST 230	//Sensitivity, may need to be adjusted
new mSpeedhackaction // Menu
new mcbSpeedhackaction // Menu Callback
new suspect_id
new SusCNT[MAXPLAYERS+1] //Suspect Counter


new g_OldPos[3][MAXPLAYERS+1]
new g_MaxPlayers
new g_Detections[MAXPLAYERS+1] 
new g_Warning[MAXPLAYERS+1]

public plugin_init() {
	new mapname[64] 
	mSpeedhackaction = menu_create("Speedhack action", "mh_Speedhackaction")
	mcbSpeedhackaction = menu_makecallback("mcb_Speedhackaction")
	menu_additem(mSpeedhackaction, "Ban", "ma_Speedhackaction", ADMIN_BAN, mcbSpeedhackaction)
	menu_additem(mSpeedhackaction, "Spectate", "ma_Speedhackaction", ADMIN_BAN, mcbSpeedhackaction)
	/* Menu End */

	register_plugin(PLUGIN, VERSION, AUTHOR)
	
	get_mapname(mapname,63) 
	if (containi(mapname,"surf_")!=-1)
		return PLUGIN_HANDLED_MAIN
	if (containi(mapname,"kz_")!=-1)
		return PLUGIN_HANDLED_MAIN

	g_MaxPlayers = get_maxplayers()
	set_task(1.0, "checkSpeedHack",127,_,_,"b")
	register_logevent("clearAlertValue",2,"0=World triggered","1=Round_Start")
	register_logevent( "clearAlertValue",2,"0=World triggered","1=Round_End")
	register_cvar("amx_sh_sens","3")
	register_cvar("amx_sh_autokick","1")
	return PLUGIN_CONTINUE
}

//Clear all previous detections so the new player does not get booted if someone previously banned
//for speedhack hapened to have the same id
public client_connect(id)
{
	g_Detections[id] = 0
	g_Warning[id] = 0
	g_OldPos[0][id] = 0
	g_OldPos[1][id] = 0
	g_OldPos[2][id] = 0
	SusCNT[id] = 0
	return PLUGIN_CONTINUE
}

public checkSpeedHack()
{
	for(new i=1;i<=g_MaxPlayers;i++)
	{
		if(is_user_alive(i)) //rather pointless otherwise, dont you think?
		{
			new origin[3] 
			new oldorigin[3]
			new dist
			
			
			//Get origins
			get_user_origin(i, origin, 0)
			oldorigin[0] = g_OldPos[0][i]
			oldorigin[1] = g_OldPos[1][i]
			oldorigin[2] = g_OldPos[2][i]
			
			dist = get_distance(origin, oldorigin)
			if (dist > SECDIST)
				g_Detections[i] = g_Detections[i] + dist - SECDIST
			if (cs_get_user_buyzone(i))
				clearSpecificAlertValue(i)
			if (g_Detections[i] > SECDIST)
			{
				g_Warning[i]++
				if (g_Warning[i] > 1)
					if (g_Detections[i] > MAXOFFENCE)
						RegisterOffense(i)
			}
			
			g_OldPos[0][i] = origin[0]
			g_OldPos[1][i] = origin[1]
			g_OldPos[2][i] = origin[2]
		}
		else
		{
			clearSpecificAlertValue(i)
		}
	}
   
}

//Clear on each round and connect so people dont get booted for spawning
// ... since when you spawn you teleports whitch the script may otherwise enterprate as speedhack
public clearAlertValue()
{
	for(new i=1;i<=g_MaxPlayers;i++)
	{
		g_Detections[i] = 0
		g_Warning[i] = 0
	}
}

public clearSpecificAlertValue(id)
{
	g_Detections[id] = 0
	g_Warning[id] = 0
}

public client_putinserver ( id )
{
	clearSpecificAlertValue(id)
	SusCNT[id] = 0
	return PLUGIN_CONTINUE
}

public client_disconnect ( id )
{
	clearSpecificAlertValue(id)
	SusCNT[id] = 0
	return PLUGIN_CONTINUE
}

//Boot the bugger from the server
public RegisterOffense(id)
{
	new admCnt=0
	new Authid[35],Name[32],CurrentTime[29],Map[32],ping,loss,Message[256]
	suspect_id = id
	get_user_name(id,Name,31)
	get_user_authid(id,Authid,34)
	get_mapname(Map,31)
	get_user_ping(id,ping,loss)
	get_time("%d/%m-%Y - %H:%M:%S",CurrentTime,29)

#if DEBUG == 1
	client_print(0, print_chat, "%i detections, %i warnings",g_Detections[id],g_Warning[id])
#endif
	clearSpecificAlertValue(id)

	for(new i=1;i<=g_MaxPlayers;i++)
	{
		if (is_user_admin(i))
		{
			format(Message,255,"[Anti Speed Hack %s - %s] %s<%s> suspected of using speedhack (Ping: %d )",CurrentTime,Map,Name,Authid,ping)
			
			client_print(i, print_chat, "[SHS] %s suspected of speedhack",Name)
			log_amx(Message)
			menu_display(i, mSpeedhackaction, 0)
			admCnt++
		}
	}

	//No admins online?
	if (admCnt == 0)
		if (get_cvar_num("amx_sh_autokick") == 1)
			SusCNT[id]++
		

	if (SusCNT[id] > get_cvar_num("amx_sh_sens"))
	{
		format(Message,255,"[Anti Speed Hack %s - %s] %s<%s> suspected of using speedhack (Ping: %d ) - No admin online, kicked from server",CurrentTime,Map,Name,Authid,ping)
		client_print(0, print_chat, "[SHS] %s suspected of speedhack - kick from server",Name)
		log_amx(Message)
		server_cmd("kick #%d Kick for using cheats",get_user_userid(id))
	}
	return PLUGIN_CONTINUE
}


/* Menu Speedhack action */

public mh_Speedhackaction(id, menu, item) {
	/* This event is called when someone presses a key on this menu */
	new Authid[35],Name[32],CurrentTime[29],Map[32],AdminName[32]
	get_user_name(suspect_id,Name,31)
	get_user_name(id,AdminName,31)
	get_user_authid(suspect_id,Authid,34)
	get_mapname(Map,31)
	get_time("%d/%m-%Y - %H:%M:%S",CurrentTime,29)

	if (item == 0)
	{
		new Message[256]
		server_cmd("kick #%d Banned becuse of cheats",get_user_userid(suspect_id))
		server_cmd("banid 0.0 %s",Authid)
		server_cmd("writeid")

		format(Message,255,"[Anti Speed Hack %s - %s] %s<%s> Banned by admin %s - Speedhack",CurrentTime,Map,Name,Authid,AdminName)
		log_amx(Message)

	}
	if (item == 1)
	{
		cs_set_user_team(id, 3)
		user_silentkill(id)
	}
		
}

public ma_Speedhackaction(id) {
	/* This event is called when an item was selected */
}

public mcb_Speedhackaction(id, menu, item) {
	/* This is the callback-event, here you can set items enabled or disabled. */
	/* If you want to enable an item, use: return ITEM_ENABLED */
	/* If you want to disable an item, use: return ITEM_DISABLED */
}
