/*	
	Aimbot Detection v0.8
		by bugsy

CVAR
 - amx_aimattempts   If bot is spawned, not shot at all, [cvar#] times, the watch on player is removed.
 - amx_aimautowatch  Enable\Disable aimbot auto watch
 - amx_aimenable     Enable\Disable plugin

Change Log:
  v0.8
  - No check was in code for FORCE_SHOOT option so force shoot was occurring whether option was set or not. Fixed.
  - Added check to see if watched player is at different height than victim and if players are close to eachother. If both
    conditions are met then the bot will not be spawned. This is to avoid false detections where if the watched player is
    standing below\above and close to the victim. An example of this is if a victim is standing on a box while the watched 
    player is on the ground aiming up and shooting. Under this situation, the watched player would end up shooting the bot 
    even if not using an aimbot since they would already be aiming up in the direction of the spawned bot.
  - Removed kick from amxban punishment
  - Added remembering of SHOOT_BLANKS punishment (using steamid for now, via nvault)
  - Added LOG_ONLY as default punishment method
  
  v0.7
  - Removed log_amx when player is kicked during punishment. The only log will be to plugin log file.
  - Added stomach as hitzone for bot bodyhit location; it was accidentally left out in original code.
  - Added constant variables for auto watch ratio points. Plugin is now configurable entirely through constants below
  - Added 2 admin access flag constants. One for notification of plugin actions and one for using amx_aimwatch command
  - Added AMXBAN to punishments
  - Removed usage of 1 admin (g_AdminRequest) for notification. Now notifies all online admins with NOTIFY access flag
  - Added usage of command amx_aimwatch with no parameters. This will return currently watched player (if any)
  - Added auto-shoot on watched player when bot is spawned. This can help catch players who are careful with the shoot button
  - Result from amx_aimwatch command will now print to console instead of chat. If a watch is enabled\disabled on a player, 
    all admins with NOTIFY flag will be notified in chat
  - Fixed bug where if player was previously detected, auto-watch was still getting applied.
  - Added instant detection if bot is shot with any pistol or sniper rifles (experimental)
  
  v0.6
  - Changed point values
  - Added ban by IP punishment
  - Added separate log file for aimbot detections. 
  - Removed cstrike module, replaced with fakemeta
  - Fixed bug where player shoots bot enough to be detected but no detection or notification to admin occurs.
  
  v0.5
  - Added variable to keep track of players who pass [amx_aimattempts] detection tests so they do not get re-watched. 
    This variable gets reset at map change.
  - Adjusted the auto-watch detection
  - Added detection of grenade damage to prevent false aimbot detection if damage inflicted via grenade
  - Increased kill count at which auto-watch will begin monitoring. 5 kills if 0 deaths, otherwise 10 kills.
  - Added bot hitzone check along with point system. If hits head, immediately punish. Other body parts, just add points.
    if points reach 5, we punish player. Head = 5pts, mid-body = 3pts, legs = 1pt. 
  - If the watched player kills the detector bot there is no longer a deathmsg shown.
  - Added list of random names to use. Will eventually support reading from text file so users can create their own names.
  - Added variable to keep track of players who have been detected. This is really only for use if a punishment other then
    kick\ban&kick is used so a player doesn't get auto-watched after already being detected as aimbotter.
  - When assigning an admin to notify (for auto-watch), it will only select admin(s) that have RCON access.
  - Misc fixes\adjustments
  
  v0.4
  - Added enable\disable cvar 'amx_aimenable'
  - Adjusted ratios for autowatch
  - Added logging if aimbot detected. [Name, SteamID, IP, Map]
  - Added punishments (Kick, Kick & Ban, Shoot Blanks)
  - Added multi-line kick function to provide detailed reason for being kicked\banned
  
  v0.3
  - Added disable of aimbot detection when watched player disconnects. Also, if an admin requests 
    a watch on a player and that admin disconnects, notification gets handed off to another online admin if available.
  - Added notification to admin when aimbot detected
  - Made bot appear as a spectator instead of appearing on the enemy team
  - Added auto player watching based on performance statistics (See AutoWatchCheck)
  
  v0.2
  - Added detection for full server
  - Added enable\disable via amx_aimwatch <player> <1=enabled 0=disable>
  - Added event to kick bot after 2 seconds if not shot
  - Added ability for watched player to successfully pass test and have watch removed (via cvar)
*/

#include <amxmodx>
#include <amxmisc>
#include <fakemeta>
#include <hamsandwich>
#include <csx>
#include <fun>
#include <nvault>

//|||||||||||||||| Configuration ||||||||||||||||

#define BOT_STAY_TIME	1.5      //Amount of time bot stays in air waiting to be shot by aimbot player

//Force watched player to shoot at bot (1 enable, 0 disable)
#define FORCE_SHOOT	1

//****AutoWatch detection ratios****
#define KILLDEATH      2.0      //Ratio of kills to deaths
#define HITSSHOTS      0.5      //Ratio of hits to shots (based on round stats only)
#define HEADSHOTKILL   0.5      //Ratio of headshot kills to all kills (based on round stats only)
#define HEADTOBODY     0.4      //Ratio of shots to the head vs anywhere on body shots (based on round stats only)
//   AutoWatch ratio points
#define KDRATIO_PTS	1	//Points added if kill\death ratio met
#define HITSHOTS_PTS	1	//Points added if hits\shots ratio met
#define HSKILL_PTS	2	//Points added if headshot kills\all kills ratio met
#define HEADBODY_PTS	2	//Points added if shots to head vs other body parts ratio met
//   Points needed to apply auto-watch
#define POINTSNEEDED	4	//Total points needed to apply auto-watch on player

//****Bot hit points required for detection****
#define HEADHITS	2	//Points added if bot gets hit in the head
#define MIDBODYHITS	2	// " " " " " " " " "  arms/stomach/chest
#define LEGHITS		1	// " " " " " " " " "  legs
//   Hits needed to punish player
#define HITSNEEDED	2	//Total hit points needed for player to get punished

//****Punish methods****
enum {
	KICK_ONLY=0, 
	KICK_AND_BANID,
	KICK_AND_BANIP,
	KICK_AND_AMXBAN,
	SHOOT_BLANKS,
	LOG_ONLY }
//   The punishment to use
KICK_AND_BANID,
KICK_AND_BANIP,
KICK_AND_AMXBAN,

//****Admin Flags****
#define FLAG_NOTIFY	ADMIN_RCON	//Admin level required to be notified of plugin actions
#define FLAG_COMMAND	ADMIN_RCON	//  ... .............. to use amx_aimwatch command

//|||||||||||||| End Configuration ||||||||||||||

//****Const****
//Credit to arkshine for detecting grenade damage in Ham_TakeDamage
#define DMG_GRENADE ( 1 << 24 )

//cstrike to fakemeta constants
#define OFFSET_TEAM		114
#define OFFSET_INTERNALMODEL	126

//****Global vars****
new g_BotID		//Global bot playerID

//Bot names that will be used. One will be picked at random
new g_BotNames[20][] = {
			"`sG .  Kimbo Slice",
			"ti.Zelda",
			"RT|chyEaaaa",
			"Crazykilla94",
			"SmOk[eD]wow~",
			"|Nova| <PUA>",
			"lmd [b]easT",
			"nck",
			"Goroncity.net",
			"chocolate man`",
			"countryboy",
			"{o_O} yak",
			"clay561",
			"|vvI| `HitEmUp:",
			"Method. pokemon",
			"BimBo",
			"cG.WARmachine",
			"eye poke",
			"proj.evo | givy",
			"Pekinglowping"}
			
new g_MaxPlayers	//Max players server allows
new g_PlayerToWatch	//id of player being watched
new g_AimPasses		//Keeps track of # times player does not shoot bot. If reaches amx_aimattempts, aimbot watch removed
new g_PassedTest[33]	//Keeps track of players who were watched and passed [amx_aimattempts] detection tests.
new g_Detected[33]	//Keeps track of players that were detected. (for when kick\kick&ban not used)
new g_HitPoints		//Counts hit areas of bot; head = 5, mid-body = 3, legs = 1. 5+ = punishment
new g_NvID		//nVault file handle
new g_PutInServer[33]

//****CVAR pointers****
new g_pcvar_attempts
new g_pcvar_autowatch
new g_pcvar_enabled

enum CsTeams 
{
	CS_TEAM_UNASSIGNED = 0,
	CS_TEAM_T,
	CS_TEAM_CT,
	CS_TEAM_SPECTATOR }


public plugin_init() 
{
	register_plugin("AimBot Detection", "0.8" , "bugsy")

	register_concmd("amx_aimwatch", "WatchPlayer" , FLAG_COMMAND  , "<player> <1=On 0=Off> - Detect if player using aimbot.")

	RegisterHam(Ham_Killed,     "player", "func_Ham_Killed_Player")
	RegisterHam(Ham_TakeDamage, "player", "func_Ham_TakeDamage")
	
	register_logevent("eRoundEnd", 2, "1=Round_End")
	
	register_message(get_user_msgid("DeathMsg"), "func_DeathMsg")

	g_pcvar_attempts  = register_cvar( "amx_aimattempts"  , "3")
	g_pcvar_autowatch = register_cvar( "amx_aimautowatch" , "1")
	g_pcvar_enabled   = register_cvar( "amx_aimenable"    , "1")
}

public plugin_cfg()
{
	g_MaxPlayers = get_maxplayers()
	g_NvID = nvault_open("aimbot_blanks")
}


public client_disconnect(id)
{
	if( !g_PlayerToWatch || is_user_bot(id) || !get_pcvar_num(g_pcvar_enabled) )
		return PLUGIN_CONTINUE

	g_PassedTest[id] = 0
	g_Detected[id]   = 0
	g_PutInServer[id] = 0
	
	//Our watched player has disconnected. Stop watching and reset variables
	if( id == g_PlayerToWatch )
	{
		new szName[33]
		get_user_name( id , szName , 32)

		for( new i = 1 ; i <= g_MaxPlayers ; i++)
			if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
				client_print( i , print_chat , "* [Aimbot Detector] %s has disconnected. Aimbot detection has been stopped." , szName ) 
		
		
		if( g_BotID)
		{
			new szName[33]
			get_user_name( g_BotID , szName, 32)
			server_cmd("kick ^"%s^"" , szName)
			g_BotID = 0
		}

		g_PlayerToWatch = 0
		g_AimPasses = 0
		g_HitPoints = 0
		
		return PLUGIN_CONTINUE
	}

	return PLUGIN_CONTINUE
}


public client_putinserver(id)
{
	if( is_user_bot(id) || !is_user_connected(id) )
		return PLUGIN_CONTINUE
	
	if( is_user_admin(id) && g_PlayerToWatch )
	{
		new szName[33]
		get_user_name( g_PlayerToWatch , szName , 32 )
		client_print( id , print_chat , "* [Aimbot Detector] %s is currently being watched for an aimbot." , szName ) 
	}
	
	g_PutInServer[id] = 1
	
	//User was detected when authorized, apply punishment
	if( g_Detected[id] )
		set_user_hitzones( id , 0 , 0 )
		
	return PLUGIN_CONTINUE
}


public client_authorized(id)
{
	new szAuthID[35]
	new szVal[2]
	new ts
		
	get_user_authid( id , szAuthID , 34 )
	
	//User found in vault, re-apply shoot blanks punishment
	if( nvault_lookup( g_NvID , szAuthID , szVal , 1 , ts ) )
	{
		new szName[33]
		get_user_name( id , szName , 32 )
		
		g_Detected[id] = 1
		
		for( new i = 1 ; i <= g_MaxPlayers ; i++)
			if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
				client_print( i , print_chat , "* [Aimbot Detector] %s has connected and shoot blanks punishment has been applied." , szName ) 
		
		//I was getting an error when trying to set_user_hitzones before client_putinserver was called so I had
		//to do a work-around.
		if( g_PutInServer[id] )
			set_user_hitzones( id , 0 , 0 )
	}
}

public eRoundEnd(id)
{
	if( g_PlayerToWatch || !get_pcvar_num(g_pcvar_autowatch) || !get_pcvar_num(g_pcvar_enabled) )
		return PLUGIN_HANDLED
		
	new iPlayers[32]
	new iNum
	
	get_players(iPlayers, iNum , "c")
	
	for( new i = 0 ; i < iNum ; i++ )
	{
		if( !g_PassedTest[iPlayers[i]] && !g_Detected[iPlayers[i]] )
		{
			AutoWatchCheck( iPlayers[i] )

			if( g_PlayerToWatch )
				break
		}
	}
	
	return PLUGIN_CONTINUE
}



public AddBot()
{		
	new szTeam[2]
	new szName[33]
	format(szName , 32 , "%s" , g_BotNames[random_num(0 , 19)] )
	
	new bot = engfunc(EngFunc_CreateFakeClient, szName)
	
	if(!bot) 
		return PLUGIN_HANDLED
	
	engfunc(EngFunc_FreeEntPrivateData, bot)

	static szRejectReason[128]
	dllfunc(DLLFunc_ClientConnect, bot, szName, "127.0.0.1", szRejectReason)
	
	if(!is_user_connected(bot)) 
		return PLUGIN_HANDLED

	dllfunc(DLLFunc_ClientPutInServer, bot)
	set_pev(bot,pev_spawnflags, pev(bot, pev_spawnflags) | FL_CLIENT)
	set_pev(bot,pev_flags, pev(bot,pev_flags) | FL_CLIENT)

	//Bot created, assign to appropriate team. 
	if( fm_get_user_team( g_PlayerToWatch ) == CS_TEAM_T )	
		format( szTeam , 1, "%d" , CS_TEAM_CT ) 
	else
		format( szTeam , 1, "%d" , CS_TEAM_T ) 
		
	engclient_cmd(bot, "jointeam", szTeam)
	engclient_cmd(bot, "joinclass", "1")
	
	//Spawn bot
	fm_user_spawn(bot)
	
	//Make bot invisible
	set_user_rendering(bot, kRenderFxNone, 0, 0, 0, kRenderTransAlpha,0)
	
	//Make our bot appear as a spectator on scoreboard
	message_begin(MSG_ALL, get_user_msgid("TeamInfo") , _, 0)
	write_byte(bot)
	write_string("SPECTATOR")
	message_end()
	
	//Assign newly created bots id to global bot id
	g_BotID = bot
	
	return PLUGIN_HANDLED
}


public RemoveBot( arg[1] )
{
	if ( g_BotID )
	{
		//1 was passed so this means the player did not shoot the bot or he shot at a different player
		//while the bot existed. This adds to the variable that counts how many times the test was passed.
		//Once the cvar is reached, we remove the aimbot watch from the player.
		if( arg[0] )
		{
			g_AimPasses++

			if( g_AimPasses == get_pcvar_num(g_pcvar_attempts) )
			{
				new szName[33]
				get_user_name( g_PlayerToWatch , szName , 32 )
				for( new i = 1 ; i <= g_MaxPlayers ; i++)
					if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
						client_print( i , print_chat , "* [Aimbot Detector] It is unlikely that %s is using an aimbot. No longer monitoring player." , szName)	
			
				g_PlayerToWatch = 0
				g_AimPasses = 0
				g_HitPoints = 0
			}
		}
		
		new szName[33]
		get_user_name(g_BotID , szName ,32)
		server_cmd( "kick ^"%s^"" , szName )
		g_BotID = 0	
	}
}


public WatchPlayer(id,level,cid)
{
	if ( !cmd_access(id,level,cid,1) )
		return PLUGIN_HANDLED
	
	if( !get_pcvar_num(g_pcvar_enabled) )
	{
		console_print( id , "* [Aimbot Detector] Plugin currently disabled.")
		return PLUGIN_HANDLED
	}
	
	new szName[33]
	new iArgs = read_argc()
	if( iArgs == 1 )
	{
		if( g_PlayerToWatch )
		{
			get_user_name( g_PlayerToWatch , szName , 32 )
			console_print( id , "* [Aimbot Detector] Currently watching %s." , szName)
		}
		else
		{
			console_print( id , "* [Aimbot Detector] No players are being watched at this time." , szName)
		}
		return PLUGIN_HANDLED
	}
	else if( (iArgs != 1 ) && (read_argc() != 3) )
	{
		console_print( id , "* [Aimbot Detector] Proper usage: amx_aimwatch <player> <0=Off/1=On> OR amx_aimwatch with no parameters")
		return PLUGIN_HANDLED;
	}
	
	new szVal[2]
	
	//Read player name and enable\disable value
	read_argv(1, szName, 32)
	read_argv(2, szVal, 1)
	
	new iPlayer = cmd_target( id , szName , 8 )
	if( !iPlayer)
		return PLUGIN_HANDLED
		
	get_user_name( iPlayer , szName , 32)
	
	//Enable monitoring on player
	if( szVal[0] == '1' )
	{
		if( g_PlayerToWatch == iPlayer )
		{
			console_print( id , "* [Aimbot Detector] Already monitoring %s for aimbot.", szName)
			return PLUGIN_HANDLED
		}
		
		if( g_Detected[iPlayer] )
		{
			console_print( id , "* [Aimbot Detector] This player has already been detected and punished.")
			return PLUGIN_HANDLED
		}
		
		console_print( id , "* [Aimbot Detector] Now monitoring %s for aimbot.", szName)
		
		for( new i = 1 ; i <= g_MaxPlayers ; i++)
			if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
				client_print( i , print_chat , "* [Aimbot Detector] Now monitoring %s for aimbot." , szName)	
			
		g_PlayerToWatch = iPlayer
	}
	else if( szVal[0] == '0' )
	{
		if( !g_PlayerToWatch )
		{
			console_print( id , "* [Aimbot Detector] No players are being monitored for aimbot.")
			return PLUGIN_HANDLED
		}
		
		if( iPlayer != g_PlayerToWatch )
		{
			console_print( id , "* [Aimbot Detector] %s is not currently being monitored for aimbot.", szName)
			return PLUGIN_HANDLED
		}
			
		console_print( id , "* [Aimbot Detector] No longer monitoring %s for aimbot.", szName)

		new szName[33]
		get_user_name( g_PlayerToWatch , szName , 32 )
		for( new i = 1 ; i <= g_MaxPlayers ; i++)
			if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
				client_print( i , print_chat , "* [Aimbot Detector] No longer monitoring %s for aimbot." , szName)	
			
		g_PlayerToWatch = 0
	}
	
	g_HitPoints = 0
	g_AimPasses = 0

	return PLUGIN_HANDLED
}

public AutoWatchCheck(id)
{	
	if( !is_user_connected(id) )
		return PLUGIN_HANDLED

/*	 0 - kills 
	1 - deaths 
	2 - headshots 
	4 - shots 
	5 - hits 

	0 - Generic 
	1 - Head     
	2 - Chest
	3 - Stomach 
	4 - LeftArm 
	5 - RightArm 
	6 - LeftLeg 
	7 - RightLeg */

	new iPoints = 0
	new iBodyHits = 0
	new iStats[8]
	new iBHits[8]
	new szName[33]
	new iKills = 0
	new iDeaths = 0
	new i
	
	get_user_rstats ( id, iStats, iBHits ) 
	iKills = get_user_frags(id)
	iDeaths = get_user_deaths(id)	

	//Kill/Death ratio
	if( (iKills >= 10) && iDeaths  )
	{
		if( float(iKills / iDeaths)  >=  KILLDEATH ) 
			iPoints+=KDRATIO_PTS
	}
	else if( ( iKills >= 5 )  && !iDeaths )
	{
		iPoints+=KDRATIO_PTS
	}

	//Hits/Shots ratio
	if(  iStats[5] && iStats[4]  )
	{
		if( float( iStats[5] / iStats[4] )  >=  HITSSHOTS ) 
			iPoints+=HITSHOTS_PTS
	}

	//Headshot-Kills/Kills ratio
	if(  iStats[2] && iStats[0]  )
	{
		if(   float(iStats[2] / iStats[0])  >=  HEADSHOTKILL )
			iPoints+=HSKILL_PTS
	}
	
	//Shots to the head/all body parts ratio
	for( i = 0 ; i < 8; i++)
		iBodyHits+= iBHits[i]
		
	if(  iBHits[1] && iBodyHits  )
	{
		if( float(iBHits[1] / iBodyHits)  >=  HEADTOBODY )
			iPoints+=HEADBODY_PTS
	}
	
	//If player has POINTSNEEDED or more watch points, apply aimbot watch to him.
	if( iPoints >= POINTSNEEDED )
	{
		//Watch player
		g_PlayerToWatch = id
		g_HitPoints = 0
		g_AimPasses = 0

		get_user_name( id , szName , 32 )

		for( new i = 1 ; i <= g_MaxPlayers ; i++)
			if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
				client_print( i , print_chat , "* [Aimbot Detector - AutoWatch] Now watching %s for aimbot." , szName )
	}
	
	return PLUGIN_HANDLED
}

public LogDetection( szLog[] )
{
	new szData[175]
	new szLogFile[64]
	new iHour, iMinute, iSecond
	new iYear, iMonth, iDay
	
	time(iHour, iMinute, iSecond ) 
	date( iYear, iMonth, iDay ) 
	iYear-=2000

	format( szData , 174, "[%02d/%02d/%02d] [%02d:%02d:%02d] %s" , iMonth, iDay , iYear , iHour, iMinute, iSecond, szLog) 
	
	get_localinfo( "amxx_logs" , szLogFile , 63 )
	format( szLogFile , 63 , "%s/aimbotdetections.log" , szLogFile )

	write_file(szLogFile ,szData ,-1)
}

public func_Ham_TakeDamage(victim, inflictor, attacker, Float:fDamage, bitDamage) 
{     
	if( !g_PlayerToWatch  || is_user_bot(inflictor) || !get_pcvar_num(g_pcvar_enabled) )
		return PLUGIN_CONTINUE

	//If bot exists, the person issuing the damage is the watched player, and it is not by grenade
	if( g_BotID && (inflictor == g_PlayerToWatch) && !(bitDamage & DMG_GRENADE) )
	{
		new arg[1]
		
		//The victim is our bot which means he shot the bot immediately after killing a player
		//This player is using an aimbot.
		if( victim == g_BotID )
		{
			new iHitzone
			new iGun
			
			//Check where the watched player has shot the bot
			get_user_attacker( victim , iGun , iHitzone )
			
			//If user is using a sniper or pistol then very good chance is using aimbot so
			//player gets punished no matter what bodypart of bot is hit. If hit by a weapon
			//not listed below then we count body hits.
			new iWeaponUsed = get_user_weapon( inflictor )
			if( (iWeaponUsed == CSW_AWP) || (iWeaponUsed == CSW_G3SG1) || (iWeaponUsed == CSW_SG550) || (iWeaponUsed == CSW_SCOUT) ||
				(iWeaponUsed == CSW_GLOCK18) || (iWeaponUsed == CSW_DEAGLE   ) || (iWeaponUsed == CSW_P228) ||
				(iWeaponUsed == CSW_ELITE  ) || (iWeaponUsed == CSW_FIVESEVEN) || (iWeaponUsed == CSW_USP ) ) 
			{
				g_HitPoints=HITSNEEDED
			}
			else
			{
				//Accumulate hit points depending on which part of body is hit.
				if( iHitzone == HIT_HEAD )
					g_HitPoints+=HEADHITS
				else if( ( iHitzone == HIT_RIGHTARM ) || (iHitzone == HIT_STOMACH) || ( iHitzone == HIT_CHEST ) || ( iHitzone == HIT_LEFTARM ) )
					g_HitPoints+=MIDBODYHITS
				else
					g_HitPoints+=LEGHITS
			}
	
			//If we have enough hit points, punish player
			if( g_HitPoints >= HITSNEEDED )
			{
				new szName[33]
				new szAuthID[35]
				new szIP[22]
				new szMap[21]
				new szLog[143]
				
				if( is_user_alive(victim) )
					RemoveBot(arg)
				
				get_user_name( inflictor , szName , 32)
				get_user_authid( inflictor , szAuthID, 34)
				get_user_ip( inflictor , szIP , 21 )
				copy( szIP , strfind(szIP , ":") , szIP)
				get_mapname( szMap , 20)
				
				format(szLog, 142, "An aimbot was detected on %s [%s] [%s] [%s]" , szName , szAuthID , szIP  , szMap )
				LogDetection(szLog)
				
				for( new i = 1 ; i <= g_MaxPlayers ; i++)
					if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
						client_print( i , print_chat , "* [Aimbot Detector] %s is using an aimbot!!!" , szName )
			
				switch(PUNISHMENT)
				{
					case KICK_ONLY:
					{
						KickPlayer( inflictor , "[Aimbot Detector]", "An aimbot was detected on your system." , "You have been kicked" ) 
					}
					case KICK_AND_BANID:
					{
						server_cmd( "banid ^"0^" ^"%s^";wait;wait;writeid" , szAuthID )
						KickPlayer( inflictor , "[Aimbot Detector]", "An aimbot was detected on your system." , "You have been banned permanently" ) 
					}
					case KICK_AND_BANIP:
					{
						server_cmd( "addip ^"0^" ^"%s^";wait;wait;writeip" , szIP )
						KickPlayer( inflictor , "[Aimbot Detector]", "An aimbot was detected on your system." , "You have been banned permanently" ) 
					}
					case KICK_AND_AMXBAN:
					{ 
						server_cmd( "amx_ban ^"0^" ^"%s^" ^"Aimbot detected^"" , szAuthID)
					}
					case SHOOT_BLANKS:
					{	
						set_user_hitzones( inflictor , 0 , 0 )
						g_Detected[inflictor] = 1
						nvault_set( g_NvID , szAuthID , "1")
						
						for( new i = 1 ; i <= g_MaxPlayers ; i++)
							if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
								client_print( i , print_chat , "* %s will now shoot blanks.", szName )
					}
					case LOG_ONLY:
					{
						for( new i = 1 ; i <= g_MaxPlayers ; i++)
							if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
								client_print( i , print_chat , "* This detection has been logged to log file" , szName )
					}
				}
				
				g_PlayerToWatch = 0
				g_AimPasses = 0
				g_HitPoints = 0
			}
		}
		else
		{
			//The bot exists but our watched player has shot another player. This most likely means the
			//watched player is not using an aimbot because he shot a player and not our bot. We remove
			//the bot and pass a 1 to the bot_remove function to increase g_AimPasses variable. 
			arg[0] = 1
			RemoveBot(arg)
		}
	}
	
	return PLUGIN_CONTINUE
}			

	
public func_Ham_Killed_Player(victim, killer, shouldgib)
{
	if( !g_PlayerToWatch || is_user_bot(killer) || !get_pcvar_num(g_pcvar_enabled) )
		return PLUGIN_CONTINUE
	
	//Our 'hacker' died, kick the bot. We will keep watching the player and hopefully
	//catch him next round.
	if( g_BotID && ( victim == g_PlayerToWatch ) && ( 1 <= victim <= g_MaxPlayers ) ) 
	{
		new arg[1]
		arg[0] = 0
		RemoveBot(arg)
		
		return PLUGIN_CONTINUE
	}
	
	//If the killer is the person we are trying to catch hacking and we have not yet created a bot
	if( !g_BotID && (killer == g_PlayerToWatch) && ( 1 <= killer <= g_MaxPlayers) )
	{
		//Server currently full so we cannot add bot
		if( get_playersnum() == g_MaxPlayers )
		{
			
			for( new i = 1 ; i <= g_MaxPlayers ; i++)
				if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))			
					client_print( i , print_chat,"* [Aimbot Detector] Server currently full, cannot spawn aimbot detection bot.")
		
			return PLUGIN_CONTINUE
		}
		

		new iVictimOrigin[3]
		new iWatchedOrigin[3]
		
		//Get origin of watched player and victim
		get_user_origin(victim, iVictimOrigin)
		get_user_origin(g_PlayerToWatch, iWatchedOrigin)
		
		//Check to see if watched player is close to and above\below victim. If so we do not spawn the bot to avoid
		//false detection.
		if( (abs(iVictimOrigin[2]-iWatchedOrigin[2]) >= 60) && (get_distance(iVictimOrigin, iWatchedOrigin) <= 150 ) )
		{
			for( new i = 1 ; i <= g_MaxPlayers ; i++)
				if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))
					client_print( i , print_chat,"* [Aimbot Detector] Could not spawn bot because watched player too close to victim.")
			
			return PLUGIN_CONTINUE
		}
		
		AddBot()
		
		if( g_BotID )
		{
			iVictimOrigin[2]+= 100
			set_user_origin(g_BotID ,iVictimOrigin)
			
			//If the monitored player doesn't shoot the bot within [X] seconds, there's a good chance he won't
			//at all so we can remove the bot. We pass 1 to the RemoveBot function because this will increase
			//the passes variable to help remove aimbot watch from the player.
			new param[1] 
			param[0] = 1
	
			//To avoid warnings from compiler
			switch(FORCE_SHOOT)
			{
				case 1:
					set_task( BOT_STAY_TIME / 2.0 , "DelayShoot" )
			}
			
			set_task( BOT_STAY_TIME  , "RemoveBot"  , 0 , param  , 1 )
		}	
		else
		{
			for( new i = 1 ; i <= g_MaxPlayers ; i++)
				if( is_user_connected(i) && is_user_admin(i) && (get_user_flags(i) & FLAG_NOTIFY))			
					client_print( i , print_chat,"* [Aimbot Detector] Error spawning bot.")
		}
	}
	
	return PLUGIN_CONTINUE
}


public DelayShoot(id)
{
	client_cmd( g_PlayerToWatch , "+attack;wait;wait;wait;-attack") 
}

public func_DeathMsg(msg_id, msg_dest, msg_entity)
{
	//If bot gets killed we block the deathmsg from being shown.
	if( get_msg_arg_int(2) == g_BotID )
	{
		new arg[1]
		arg[0] = 0
		RemoveBot(arg)
		
		return PLUGIN_HANDLED
	}

	return PLUGIN_CONTINUE
}


public KickPlayer( target , szReason[] , szLine2[] , szLine3[] ) 
{     
	//* Credit to Teyut from amx forums for multi-line kick message *
	
	new msg_content[1024];
	new pl_name[32];
	new pl_authid[32];         
    
	get_user_name(target, pl_name, 31);     
	get_user_authid(target, pl_authid, 31);         
	
	format(msg_content, 1023, "%s^n%s^n%s", szReason, szLine2, szLine3);     
	message_begin(MSG_ONE, 2 , {0,0,0}, target);     
	write_string(msg_content);     
	message_end();        
}

public CsTeams:fm_get_user_team(id)
{
	return CsTeams:get_pdata_int(id, OFFSET_TEAM);
}

public fm_user_spawn(id) 
{ 
	set_pev(id, pev_deadflag, DEAD_RESPAWNABLE);
	dllfunc(DLLFunc_Spawn, id);
	set_pev(id, pev_iuser1, 0);
}
