#pragma semicolon 1
#define DEBUG
#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.6"
#define g_dLength 400
#define g_dIndex 65
#include <sourcemod>
#include <colorvariables>
#include <clientprefs>
#include <unloze_zones>
#include <unloze_racetimer_specialmaps>
#include <unloze_racetimer_antizones>
#include <cstrike>
#include <sdktools>
#include <sdkhooks>
#include <outputinfo>
#pragma newdecls required
char g_cMapname[g_dLength];
char g_cSpecialMapStart[g_dLength];
char g_cSpecialMapEnd[g_dLength];
static char g_sConfigzones[PLATFORM_MAX_PATH];
float g_fStartTime[MAXPLAYERS + 1];
char g_csTime_record[MAXPLAYERS + 1][65];
float g_fClientVectors[MAXPLAYERS + 1][3];
int g_iClientFrames[MAXPLAYERS + 1];
int g_iClientSpeedInterval[MAXPLAYERS + 1];
int g_iClientChecking[MAXPLAYERS + 1];
bool g_bDisplaySpecial;
bool g_bHumansAllowedTime[MAXPLAYERS + 1];
bool g_bHideTimer[MAXPLAYERS + 1];
bool g_bEventBool = false;
Handle g_hClientCookie = INVALID_HANDLE;
Database g_dDatabase;
Handle hText;

int player_stage[MAXPLAYERS + 1];

public Plugin myinfo = 
{
	name = "UNLOZE_racetimer_css",
	author = PLUGIN_AUTHOR,
	description = "tracers times on race maps",
	version = PLUGIN_VERSION,
	url = "www.unloze.com"
};

//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
	//cmds
	RegConsoleCmd("sm_toptime", cmd_timerCheckTop, "checking top 10");
	RegConsoleCmd("sm_mytime", cmd_timerCheckSelf, "checking your personal time");
	RegConsoleCmd("sm_stages", cmd_timerCheckStage, "Checking race stages");
	RegConsoleCmd("sm_hidetimer", cmd_hideTimerHUD, "Hides timer HUD");
	RegAdminCmd("sm_cleantime", Cmd_timeReset, ADMFLAG_GENERIC);
	//hooks
	HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy);
	HookEntityOutput("trigger_multiple", "OnTrigger", Trigger_Multiple);
        HookEntityOutput("trigger_multiple", "OnStartTouch", Trigger_Multiple);
        HookEntityOutput("trigger_teleport", "OnTrigger", trigger_teleport);
        HookEntityOutput("trigger_teleport", "OnStartTouch", trigger_teleport);
	//HUD
	hText = CreateHudSynchronizer();
	//cookies
	g_hClientCookie = RegClientCookie("hide_timer_cookie", "Hides the timer HUD", CookieAccess_Private);
	for (int i = MaxClients; i > 0; --i)
	{
		if (!AreClientCookiesCached(i))
			continue;
		OnClientCookiesCached(i);
	}
}

public void trigger_teleport(const char[] output, int entity_index, int client, float delay)
{
        if (IsValidEdict(entity_index) && IsValidClient(client)  && g_bHumansAllowedTime[client])
	{
		if (StrContains(g_cMapname, "surf", false) == -1)
		{
			g_bHumansAllowedTime[client] = false;
			resetClientVectors(client);
			if (GetClientTeam(client) == 3)
			{
				PrintToChat(client, "Disabled timer due to potential teleport abuse, if this is wrong annoy jenz on discord");
			}
		}
	}
}

public void Trigger_Multiple(const char[] output, int entity_index, int client, float delay)
{
        if (IsValidEdict(entity_index) && origin_command_check(entity_index) && IsValidClient(client) && g_bHumansAllowedTime[client])
        {
		if (StrContains(g_cMapname, "surf", false) == -1)
                {
			g_bHumansAllowedTime[client] = false;
			resetClientVectors(client);
			PrintToChat(client, "Disabled timer due to potential teleport abuse, if this is wrong annoy jenz on discord");
		}
        }
}


public bool origin_command_check(int entity_index)
{
        int count_trigger = GetOutputCount(entity_index, "m_OnTrigger");
        int count_starttouch = GetOutputCount(entity_index, "m_OnStartTouch");
        for     (int i = 0; i < count_trigger; i++)
        {
                char buffer[g_dLength];
                GetOutputParameter(entity_index, "m_OnTrigger", i, buffer, sizeof(buffer));
                if (StrContains(buffer, "origin", true) != -1)
                        return true;
        }
        for     (int i = 0; i < count_starttouch; i++)
        {
                char buffer[g_dLength];
                GetOutputParameter(entity_index, "m_OnStartTouch", i, buffer, sizeof(buffer));
                if (StrContains(buffer, "origin", true) != -1)
                        return true;
        }
        return false;
}

//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
	if(!db || strlen(error))
	{
		LogError("Database error: %s", error);
		return;
	}
	g_dDatabase = db;
	//create tables
        char sQuery[g_dLength];
        Format(sQuery, sizeof(sQuery), "CREATE TABLE IF NOT EXISTS `zetimer_table` (`steam_auth` VARCHAR(254) NOT NULL, `name` VARCHAR(254) NOT NULL, PRIMARY KEY (`steam_auth`))");
	g_dDatabase.Query(SQL_OnConnectFinished, sQuery, _, DBPrio_High);
}

public void SQL_OnConnectFinished(Database db, DBResultSet results, const char[] error, any data)
{
        if(!db || strlen(error))
        {
                LogError("Database error: %s", error);
                return;
        }
	static Handle hHostName;
        if((hHostName = FindConVar("hostname")) == INVALID_HANDLE)
            return;
        char line[g_dLength];
        GetConVarString(hHostName, line, sizeof(line));
        if (StrContains(line, "EVENT", false) > -1)
                g_bEventBool = true;
	else
		g_bEventBool = false;
        g_bDisplaySpecial = unloze_gBSpecialMapDisplay();
        GetCurrentMap(g_cMapname, sizeof(g_cMapname));
        startTimer();

	for(int i = 1; i <= MaxClients; i++)
		if (IsValidClient(i))
			OnClientPostAdminCheck(i);
}

public void MYSQLCheckMapEntry()
{
        int l_iRaceCount;
        int l_iZoneCount = unloze_zoneCount();
        char sQuery[g_dLength];
        char l_cZoneIndexName[g_dIndex][g_dLength];
	if (l_iZoneCount == 1)
	{
		Format(sQuery, sizeof(sQuery), "ALTER TABLE `zetimer_table` ADD COLUMN IF NOT EXISTS `%s` TEXT DEFAULT '0.000' NOT NULL", g_cMapname);
		DataPack hDataPack = new DataPack();
        	hDataPack.WriteString(sQuery);
		g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_High);
	}
	else
	{
		for (int iterator = 0; iterator <= l_iZoneCount; iterator++)
		{
			if (IsCorrectZone(iterator, l_cZoneIndexName[iterator][g_dLength -1], "ZONE_PREFIX_RACE"))
			{
				l_iRaceCount++;
				Format(sQuery, sizeof(sQuery), "ALTER TABLE `zetimer_table` ADD COLUMN IF NOT EXISTS `%sS%i` TEXT DEFAULT '0.000' NOT NULL", g_cMapname, l_iRaceCount);
				DataPack hDataPack = new DataPack();
        			hDataPack.WriteString(sQuery);
				g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_High);
			}
		}
	}
}

public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, DataPack data)
{
	if (!db || strlen(error))
	{
		char sQuery[g_dLength];
		ResetPack(data);
		data.ReadString(sQuery, sizeof(sQuery));
		LogError("Query error 3: %s", error); 
		LogError("actual query: %s", sQuery);
	}
	delete data;
}

public void OnMapStart()
{	
	if (!g_dDatabase)
		Database.Connect(SQL_OnDatabaseConnect, "racetimercss");
	else
	{
		static Handle hHostName;
		if((hHostName = FindConVar("hostname")) == INVALID_HANDLE)
		    return;
		char line[g_dLength];
		GetConVarString(hHostName, line, sizeof(line));
		if (StrContains(line, "EVENT", false) > -1)
			g_bEventBool = true;
		else
			g_bEventBool = false;
		g_bDisplaySpecial = unloze_gBSpecialMapDisplay();
		GetCurrentMap(g_cMapname, sizeof(g_cMapname));
		startTimer();
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void OnPluginEnd()
{
	CloseHandle(hText);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void startTimer()
{
	char line[g_dLength];
	Handle zonefile = INVALID_HANDLE;
	BuildPath(Path_SM, g_sConfigzones, sizeof(g_sConfigzones), "configs/unloze_zones/%s.zones.txt", g_cMapname);
	zonefile = OpenFile(g_sConfigzones, "r");
	if (zonefile != INVALID_HANDLE)
	{
		while (!IsEndOfFile(zonefile) && ReadFileLine(zonefile, line, sizeof(line)))
		{
			if (StrContains(line, "ZONE_PREFIX_RACE", false) > -1 || g_bDisplaySpecial)
			{
				MYSQLCheckMapEntry();
				break;
			}
		}
	}
	delete zonefile;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void Event_RoundStart(Handle event, const char[] name, bool dontBroadcast)
{
	int l_iZoneCount = unloze_zoneCount();
	if (!l_iZoneCount)
		return;
	for (int i = 1; i <= MaxClients; i++)
		if (IsValidClient(i) && !IsFakeClient(i))
		{
			resetClientVectors(i);
			if (l_iZoneCount != 1)
				g_bHumansAllowedTime[i] = false;
			else
			{
				mysql_get_player_time(i, 0);
				g_bHumansAllowedTime[i] = true;
				g_fStartTime[i] = GetEngineTime();
			}
		}
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void OnClientPostAdminCheck(int client)
{
	resetClient(client);
	insertPlayerMYSQL(client);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void OnClientCookiesCached(int client)
{
	char sValue[8];
	GetClientCookie(client, g_hClientCookie, sValue, sizeof(sValue));
	g_bHideTimer[client] = (sValue[0] != '\0' && !!StringToInt(sValue));
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
	resetClient(client);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void resetClient(int client)
{
	if (0 < client <= MaxClients)
	{
		g_iClientChecking[client] = 0; 
		g_bHumansAllowedTime[client] = false;
		resetClientVectors(client);
		player_stage[client] = 0;
		Format(g_csTime_record[client], sizeof(g_csTime_record[]), "0.000");
	}
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
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 (!IsValidClient(client))
		return;
	if (g_bEventBool)
	{
		g_bHumansAllowedTime[client] = false;
		return;
	}	
	if (g_bHumansAllowedTime[client] && (GetClientTeam(client) == CS_TEAM_CT) && IsPlayerAlive(client))
	{
		int frameCap = 11;
		if (g_iClientFrames[client] >= frameCap)
		{
			g_iClientFrames[client] = 0;
			float clientVectors[3];
			GetClientAbsOrigin(client, clientVectors);
			if (checkClientOrigin(g_fClientVectors[client], clientVectors, client))
			{
				g_bHumansAllowedTime[client] = false;
				resetClientVectors(client);
				PrintToChat(client, "Disabled timer due to potential teleport abuse");
				return;
			}
			int speedCheckerCap = 10;
			if (g_iClientSpeedInterval[client] > speedCheckerCap)
			{
				g_iClientSpeedInterval[client] = 0;
				bool bNoclip = (GetEntityMoveType(client) == MOVETYPE_NOCLIP);
				if (bNoclip)
				{
					g_bHumansAllowedTime[client] = false;
					resetClientVectors(client);
					PrintToChat(client, "Disabled timer due to Noclip");
					return;
				}
				float speed = GetEntPropFloat(client, Prop_Data, "m_flLaggedMovementValue");
				if (speed > 1.0)
				{
					if (StrContains(g_cMapname, "surf", false) == -1)
					{
						g_bHumansAllowedTime[client] = false;
						resetClientVectors(client);
						PrintToChat(client, "Disabled timer due to modified run speed");
						return;
					}
				}
				float client_gravity = GetEntityGravity(client);
				ConVar gravity = FindConVar("sv_gravity");
				float gravityFloat = gravity.FloatValue;
				int minimalPermitedGravity = 610;
				//PrintToChat(client, "client_gravity: %f\ngravityFloat: %f", client_gravity, gravityFloat);
				if (((client_gravity > 1.3 || client_gravity < 0.6000) && client_gravity != 0.000000) || gravityFloat < minimalPermitedGravity)
				{
					//PrintToChat(client, "client_gravity: %f\ngravityFloat: %f", client_gravity, gravityFloat);
					g_bHumansAllowedTime[client] = false;
					resetClientVectors(client);
					PrintToChat(client, "Disabled timer due to modified gravity");
					return;
				}
			}
			g_fClientVectors[client] = clientVectors;
			if (hText != INVALID_HANDLE && !g_bHideTimer[client])
			{
				SetHudTextParams(0.35, 0.85, 0.1, 125, 255, 255, 85);
				float total_time = client_current_race_time(client);
				char sTime[32];
				FormatPlayerTime(total_time, sTime, sizeof(sTime), false, 1);
				ShowSyncHudText(client, hText, "%N Time: %s\nRecord: %s\nMap: %s\nCourse: %i", client, sTime, g_csTime_record[client], g_cMapname, player_stage[client]);
			}
			g_iClientSpeedInterval[client]++;
		}
		g_iClientFrames[client]++;
	}
	return;
}

public float client_current_race_time(int i_client)
{
	return GetEngineTime() - g_fStartTime[i_client];
}

//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void resetClientVectors(int client)
{
	g_fClientVectors[client][0] = 0.000000;
	g_fClientVectors[client][1] = 0.000000;
	g_fClientVectors[client][2] = 0.000000;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public bool checkClientOrigin(float oldVals[3], float newVals[3], int client)
{
	float zero = 0.000000;
	if ((oldVals[0] == zero && oldVals[1] == zero && oldVals[2] == zero) || (newVals[0] == zero && newVals[1] == zero && newVals[2] == zero))
	{
		return false;
	}
	float teleport_range = 1090450.0;
	int velocityCap = 625;
	float distance = GetVectorDistance(oldVals, newVals, true);
	//PrintToChatAll("distance: %f", distance);
	bool bInAir = (GetEntPropEnt(client, Prop_Send, "m_hGroundEntity") == -1);
	if (distance > teleport_range)
	{
		if (StrContains(g_cMapname, "surf", false) != -1)
			return false;
		float fVelocity[3];
		GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVelocity);
		float currentspeed = SquareRoot(Pow(fVelocity[0],2.0)+Pow(fVelocity[1],2.0));
		//PrintToChat(client, "currentspeed: %f", currentspeed);
		if (bInAir && currentspeed > velocityCap)
			return false;
		return true;
	}
	return false;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void unloze_zoneEntry(int client, char[] zone)
{
	int zoneIndex = RetrieveZoneIndex(zone);
	int l_iZoneCount = unloze_zoneCount();
	if (((GetClientTeam(client) == CS_TEAM_CT && g_bHumansAllowedTime[client]) && StrContains(zone, "ZONE_PREFIX_RACE") > -1) || StrEqual(zone, g_cSpecialMapEnd))
	{
		if (IsClientAuthorized(client))
		{
			if (l_iZoneCount < 2)
				player_stage[client] = 0;
			if (player_stage[client] == (zoneIndex / 2) || l_iZoneCount < 2)
				FinishedStageRaceZone(client);
		}
		else
			PrintToChat(client, "You are not authorized with steam!!");
		g_bHumansAllowedTime[client] = false;
	}
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void unloze_zoneLeave(int client, char[] zone)
{
	//only maps with multiple zones need ZONE_PREFIX_START
	int l_iZoneCount = unloze_zoneCount();
	if (((GetClientTeam(client) == CS_TEAM_CT) && StrContains(zone, "ZONE_PREFIX_START") > -1) || StrEqual(zone, g_cSpecialMapStart))
	{
		if (!l_iZoneCount)
			return;
		resetClientVectors(client);
		g_fStartTime[client] = GetEngineTime();
		float notRounded = float(RetrieveZoneIndex(zone));
		player_stage[client] = RoundToCeil(notRounded / 2);
		mysql_get_player_time(client, player_stage[client]);
		g_bHumansAllowedTime[client] = true;
		CPrintToChat(client, "Timer started for Course: %i",  player_stage[client]);
	}
}

stock void FormatPlayerTime(float Time, char[] result, int maxlength, bool showDash, int precision)
{
	if(Time <= 0.0 && showDash == true)
	{
		Format(result, maxlength, "-");
		return;
	}
	int hours 	= RoundToFloor(Time/3600);
	Time         -= hours*3600;
	int minutes 	= RoundToFloor(Time/60);
	Time         -= minutes*60;
	float seconds = Time;
	
	char sPrecision[16];
	
	if(precision == 0)
		Format(sPrecision, sizeof(sPrecision), (hours > 0 || minutes > 0)?"%04.1f":"%.1f", seconds);
	else if(precision == 1)
		Format(sPrecision, sizeof(sPrecision), (hours > 0 || minutes > 0)?"%06.3f":"%.3f", seconds);
	else if(precision == 2)
		Format(sPrecision, sizeof(sPrecision), (hours > 0 || minutes > 0)?"%09.6f":"%.6f", seconds);
	
	if(hours > 0)
		Format(result, maxlength, "%d:%02d:%s", hours, minutes, sPrecision);
	else if(minutes > 0)
		Format(result, maxlength, "%d:%s", minutes, sPrecision);
	else
		Format(result, maxlength, "%s", sPrecision);
}


//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void unloze_zoneCreated()
{
	startTimer();
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void CheckIfSpecialRoundZones(char[] resultstart, char[] resultend)
{
	Format(g_cSpecialMapStart, sizeof(g_cSpecialMapStart), resultstart);
	Format(g_cSpecialMapEnd, sizeof(g_cSpecialMapEnd), resultend);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void CheckifAntiZones(int client, bool reset)
{
	if (reset)
		g_bHumansAllowedTime[client] = false;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public int RetrieveZoneIndex(char[] zone)
{
	//if you leave zone_2 you want the corresponding racezone to be zone_3
	int iterator = strlen(zone) - 1;
	if (iterator == -1)
		return 1;
	char l_sZone[g_dIndex];
	Format(l_sZone, sizeof(l_sZone), zone);
	while (IsCharNumeric(l_sZone[iterator]))
		iterator--;
	iterator++;
	strcopy(l_sZone, sizeof(l_sZone), l_sZone[iterator]);
	return StringToInt(l_sZone[iterator]);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void FinishedStageRaceZone(int client)
{
	char sSID[g_dIndex];
        char sName[MAX_NAME_LENGTH];
        GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID));
        GetClientName(client, sName, sizeof(sName));
        int size2 = 2 * strlen(sName) + 1;
        char[] sEscapedName = new char[size2 + 1];

        g_dDatabase.Escape(sName, sEscapedName, size2 + 1);

        if (StrEqual(sSID, "STEAM_ID_STOP_IGNORING_RETVALS") || StrEqual(sSID, "STEAM_ID_PENDING"))
        {
                PrintToChat(client, "Your steam ID is not working, not updating timer");
                return;
        }

	int l_iZoneCount = unloze_zoneCount();
	float client_time = client_current_race_time(client);
        char sTime[32];
        FormatPlayerTime(client_time, sTime, sizeof(sTime), false, 1);
	if (l_iZoneCount > 1)
                CPrintToChat(client, "{green}[UNLOZE] Stage: %i", player_stage[client]);
        CPrintToChat(client, "{green}[UNLOZE] Client: %N Time: %s", client, sTime);
	int stage = player_stage[client];
	char sQuery[g_dLength];
	if (StrEqual(g_csTime_record[client], "0.000"))
	{
		CPrintToChat(client, "Your record: None yet\nCommand: !toptime !mytime !stages");
	}
	else
	{
		CPrintToChat(client, "Your record: %s\nCommand: !toptime !mytime !stages", g_csTime_record[client]);
	}
	if (l_iZoneCount < 2)
		Format(sQuery, sizeof(sQuery), "UPDATE `zetimer_table` SET `%s` = '%s', name = '%s' WHERE steam_auth = '%s'", g_cMapname, sTime, sEscapedName, sSID);
	else
		Format(sQuery, sizeof(sQuery), "UPDATE `zetimer_table` SET `%sS%i` = '%s', name = '%s' WHERE steam_auth = '%s'", g_cMapname, stage, sTime, sEscapedName, sSID);
	int generic_length = 32;
	char[][] sPart = new char[2][generic_length];
	float old_client_time = 0.0;
	if (StrContains(g_csTime_record[client], ":") != -1)
	{
		ExplodeString(g_csTime_record[client], ":", sPart, 2, generic_length);
		old_client_time = (StringToFloat(sPart[0]) * 60) + StringToFloat(sPart[1]);
	}
	else
	{
		old_client_time = StringToFloat(g_csTime_record[client]);
	}
	if (client_time >= old_client_time && old_client_time > 0.0)
	{
		return;
	}
        DataPack hDataPack = new DataPack();
        hDataPack.WriteString(sQuery);
        g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_High);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void mysql_get_player_time(int client, int stage)
{
	char query[g_dLength];
	char steam_auth[g_dIndex];
	GetClientAuthId(client, AuthId_Steam2, steam_auth, sizeof(steam_auth));
	if (!stage)
		Format(query, sizeof(query), "SELECT `%s` FROM `zetimer_table` where steam_auth = '%s'", g_cMapname, steam_auth);
	else
		Format(query, sizeof(query), "SELECT `%sS%i` FROM `zetimer_table` where steam_auth = '%s'", g_cMapname, stage, steam_auth);
	DataPack hDataPack = new DataPack();
	hDataPack.WriteCell(GetClientSerial(client));
        hDataPack.WriteString(query);
	g_dDatabase.Query(SQL_OnQueryCompleted, query, hDataPack);
}

public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] error, DataPack data)
{
        ResetPack(data);
        int client_serial = data.ReadCell();
        if (!db || strlen(error))
        {
                char sQuery[g_dLength]; 
                data.ReadString(sQuery, sizeof(sQuery));
                LogError("Query error 1: %s", error);
                LogError("actual query: %s", sQuery);
		delete data;
                return;
        }
	delete data;
        int client; 
        if ((client = GetClientFromSerial(client_serial)) == 0)
                return;
        if (results.RowCount && results.FetchRow())
                results.FetchString(0, g_csTime_record[client], sizeof(g_csTime_record[]));
}

//----------------------------------------------------------------------------------------------------
// Purpose: TODO implement if needed
//----------------------------------------------------------------------------------------------------
public int GetTotalRaceZones()
{
	int l_iZoneCount = unloze_zoneCount();
	char l_cIndexName[g_dLength];
	int l_iCountRace;
	for (int iterator = 0; iterator < l_iZoneCount; iterator++)
	{
		ZoneNameBasedOnIndex(iterator, l_cIndexName[iterator]);
		if (StrContains(l_cIndexName[iterator], "ZONE_PREFIX_RACE") > -1)
			l_iCountRace++;
	}
	return l_iCountRace;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void insertPlayerMYSQL(int client)
{
	if (!IsValidClient(client))
		return;
	char sSID[g_dIndex];
        char sQuery[g_dLength];
        char sName[MAX_NAME_LENGTH];
        GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID));
        GetClientName(client, sName, sizeof(sName));
        int size2 = 2 * strlen(sName) + 1;
        char[] sEscapedName = new char[size2 + 1];
	if (!g_dDatabase)
	{
                Database.Connect(SQL_OnDatabaseConnect, "racetimercss");
		return;
	}
        g_dDatabase.Escape(sName, sEscapedName, size2 + 1);
	if (StrEqual(sSID, "STEAM_ID_STOP_IGNORING_RETVALS") || StrEqual(sSID, "STEAM_ID_PENDING"))
        {
                PrintToChat(client, "Your steam ID is not working, not updating timer");
                return;
        }
	Format(sQuery, sizeof(sQuery), "INSERT INTO `zetimer_table` (`steam_auth`, `name`) VALUES ('%s', '%s') ON DUPLICATE KEY UPDATE `name` = '%s'", sSID, sEscapedName, sEscapedName);
	DataPack hDataPack = new DataPack();
        hDataPack.WriteString(sQuery);
        g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_High);
}

//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public Action cmd_timerCheckTop(int client, int args)
{	
	CheckTop(client, 0, 0);
	return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void CheckTop(int client, int index, int autismstate)
{
	int l_iZoneCount = unloze_zoneCount();
	char sQuery[g_dLength];
	//PrintToChatAll("checktop l_iZoneCount: %i", l_iZoneCount);
	if (l_iZoneCount < 1)
	{
		PrintToChat(client, "No zones active on this map");
		return;
	}
	if (!autismstate)
	{
		CheckStagesOnMap(client, 0);
		return;
	}
	//we have index now from menu selection
	if (l_iZoneCount < 2)
		Format(sQuery, sizeof(sQuery), "SELECT name, `%s` FROM `zetimer_table` WHERE `%s` != 0.000 ORDER BY LENGTH(`%s`) ASC, `%s` ASC LIMIT 10", g_cMapname, g_cMapname, g_cMapname, g_cMapname);
	else
		Format(sQuery, sizeof(sQuery), "SELECT name, `%sS%i` FROM `zetimer_table` WHERE `%sS%i` != 0.000 ORDER BY LENGTH(`%sS%i`) ASC, `%sS%i` ASC LIMIT 10", g_cMapname, index, g_cMapname, index,  g_cMapname, index, g_cMapname, index);
	DataPack hDataPack = new DataPack();
	hDataPack.WriteCell(GetClientSerial(client));
	hDataPack.WriteString(sQuery);
	g_dDatabase.Query(SQL_Select_Top_Callback, sQuery, hDataPack);
}

public void SQL_Select_Top_Callback(Database db, DBResultSet results, const char[] error, DataPack data)
{
	ResetPack(data);
	int client_serial = data.ReadCell();
	if (!db || strlen(error))
        {
                char sQuery[g_dLength];
                data.ReadString(sQuery, sizeof(sQuery));
		LogError("Query error 2: %s", error);   
                LogError("actual query: %s", sQuery);
		delete data;
		return;
        }
	delete data;
	int iclient;
	if ((iclient = GetClientFromSerial(client_serial)) == 0)
		return;
        int l_iPosition;
	char sTime[g_dLength];
        //Player Name
        char[] g_cPlayerName = new char[MAX_NAME_LENGTH];
        char g_cContent[g_dLength];
        Menu menu = new Menu(MenuHandler1);
        menu.SetTitle("Maptimer: %s", g_cMapname);
        if (results != INVALID_HANDLE)
        {
		while (results.RowCount > 0 && results.FetchRow())
                {
                        l_iPosition++;
			results.FetchString(0, g_cPlayerName, MAX_NAME_LENGTH);
			results.FetchString(1, sTime, sizeof(sTime));
                        Format(g_cContent, sizeof(g_cContent), "#%i: Time: %s - %s", l_iPosition, sTime, g_cPlayerName);
                        menu.AddItem("-1", g_cContent, ITEMDRAW_DISABLED);
		 }
		if (!l_iPosition)
                {
                        menu.AddItem("-1", "No results. Commands: !toptime !stages", ITEMDRAW_DISABLED);
                }
                menu.ExitButton = true;
                menu.Display(iclient, 0);
        }
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------	
public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2)
{
	if (action == MenuAction_End)
		delete menu;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public Action Cmd_timeReset(int client, int args)
{
	if (!IsValidClient(client))
		return Plugin_Handled;
	if (args != 2)
	{
		ReplyToCommand(client, "[SM] Usage cleantime <target> <course>");
		return Plugin_Handled;
	}
	char sTarget[65], steam2[64];
	GetCmdArg(1, sTarget, sizeof(sTarget));
	int targetID = FindTarget(client, sTarget, false);
	if(targetID == -1)
		return Plugin_Handled;
	GetClientAuthId(targetID, AuthId_Steam2, steam2, sizeof(steam2));
	GetCmdArg(2, sTarget, sizeof(sTarget));
	deleteClientTime(steam2, StringToInt(sTarget));
	return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------	
public Action cmd_hideTimerHUD(int client, int args)
{
	if (!g_bHideTimer[client])
	{
		g_bHideTimer[client] = true;
		SetClientCookie(client, g_hClientCookie, "1");
		PrintToChat(client, "Disabled timer HUD");
	} else { g_bHideTimer[client] = false; PrintToChat(client, "Enabled timer HUD"); SetClientCookie(client, g_hClientCookie, "0"); }
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------	
public Action cmd_timerCheckStage(int client, int args)
{
	CheckStagesOnMap(client, 0);
	return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public void CheckStagesOnMap(int client, int state)
{
	int l_iCount;
	int l_iZoneCount = unloze_zoneCount();
	Menu StageMenu = CreateMenu(Stage_menu);
	char l_cZoneIndexName[g_dIndex][g_dLength];
	char l_cMenuContent[g_dLength];
	if (!l_iZoneCount)
	{
		CPrintToChat(client, "[UNLOZE] Map does not support racestage timer");
		return;
	}
	//state 0 == toptime, state 1 == own time
	g_iClientChecking[client] = state;
	StageMenu.SetTitle("Stages on: %s", g_cMapname);
	if (g_bDisplaySpecial)
	{
		l_iCount++;
		Format(l_cMenuContent, sizeof(l_cMenuContent), "Stage: %i", l_iCount);
		StageMenu.AddItem("", l_cMenuContent);
	}
	else
	{
		for (int Iterator = 0; Iterator <= l_iZoneCount; Iterator++)
		{
			if (IsCorrectZone(Iterator, l_cZoneIndexName[Iterator][g_dLength -1], "ZONE_PREFIX_RACE"))
			{
				l_iCount++;
				Format(l_cMenuContent, sizeof(l_cMenuContent), "Stage: %i", l_iCount);
				StageMenu.AddItem("", l_cMenuContent);
			}
		}
	}
	StageMenu.ExitButton = true;
	StageMenu.Display(client, 0);
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public bool IsCorrectZone(int index, char[] zoneIndexName, char[] zone_prefix)
{
	ZoneNameBasedOnIndex(index, zoneIndexName);
	if (StrContains(zoneIndexName, zone_prefix) > -1)
	{
		return true;
	}
	return false;
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------
public int Stage_menu(Menu menu, MenuAction action, int client, int selection)
{
	if (action == MenuAction_Select && IsValidClient(client))
	{ 
		selection++;
		if (!g_iClientChecking[client])
		{
			CheckTop(client, selection, 1);
		}
		else
		{
			CheckStageSelf(client, selection);
		}
	}
	else if (action == MenuAction_End) 
	{
		delete(menu);
	}
}
//----------------------------------------------------------------------------------------------------
// Purpose: 
//----------------------------------------------------------------------------------------------------	
public Action cmd_timerCheckSelf(int client, int args)
{
	Checkself(client);
	return Plugin_Handled;
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void deleteClientTime(char[] steam2, int stage)
{
	char l_cQuery[g_dLength];
	int l_iZoneCount = unloze_zoneCount();
	if (l_iZoneCount > 1)
		Format(l_cQuery, sizeof(l_cQuery), "UPDATE `zetimer_table` SET `%sS%i` = '0.000' WHERE steam_auth = '%s'", g_cMapname, stage, steam2);
	else
		Format(l_cQuery, sizeof(l_cQuery), "UPDATE `zetimer_table` SET `%s` = '0.000' WHERE steam_auth = '%s'", g_cMapname, steam2);
	DataPack hDataPack = new DataPack();
        hDataPack.WriteString(l_cQuery);
	g_dDatabase.Query(SQL_FinishedQuery, l_cQuery, hDataPack, DBPrio_High);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void Checkself(int client)
{
	int l_iZoneCount = unloze_zoneCount();
	char l_cQuery[g_dLength];
	char l_cSID[g_dIndex];
	GetClientAuthId(client, AuthId_Steam2, l_cSID, sizeof(l_cSID));
	if (l_iZoneCount < 1)
	{
		PrintToChat(client, "No zones active on this map");
		return;
	}
	if (l_iZoneCount < 2)
	{
		Format(l_cQuery, sizeof(l_cQuery), "SELECT name, `%s` FROM `zetimer_table` WHERE steam_auth = '%s'", g_cMapname, l_cSID);
		DataPack hDataPack = new DataPack();
		hDataPack.WriteCell(GetClientSerial(client));
        	hDataPack.WriteString(l_cQuery);
		g_dDatabase.Query(SQL_CheckSelf, l_cQuery, hDataPack);
	}
	else
		CheckStagesOnMap(client, 1);
}
//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void CheckStageSelf(int client, int selection)
{
	char l_cQuery[g_dLength];
	char l_cSID[g_dIndex];
	GetClientAuthId(client, AuthId_Steam2, l_cSID, sizeof(l_cSID));
	Format(l_cQuery, sizeof(l_cQuery), "SELECT name, `%sS%i` FROM `zetimer_table` WHERE steam_auth = '%s'", g_cMapname, selection, l_cSID);
	DataPack hDataPack = new DataPack();
	hDataPack.WriteCell(GetClientSerial(client));
	hDataPack.WriteString(l_cQuery);
	g_dDatabase.Query(SQL_CheckSelf, l_cQuery, hDataPack);
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void SQL_CheckSelf(Database db, DBResultSet results, const char[] error, DataPack data)
{
	ResetPack(data);
        int client_serial = data.ReadCell();
	if (!db || strlen(error))
        {
                char sQuery[g_dLength];
                data.ReadString(sQuery, sizeof(sQuery));
		LogError("Query error 4: %s", error);   
                LogError("actual query: %s", sQuery);
		delete data;
                return;
        }
	delete data;
	char sTime[g_dLength];
        char l_cMessageContent[g_dLength];
        char[] l_cPlayerName = new char[MAX_NAME_LENGTH];
        int iclient;
	if ((iclient = GetClientFromSerial(client_serial)) == 0)
		return;
	if (results.RowCount && results.FetchRow())
	{
		results.FetchString(0, l_cPlayerName, MAX_NAME_LENGTH);
		results.FetchString(1, sTime, sizeof(sTime));
		if (StrEqual(sTime, "0.000", false))
                {
                        CPrintToChat(iclient, "You have no time yet!");
                        return;
                }
                Format(l_cMessageContent, sizeof(l_cMessageContent), "%s - %s", sTime, l_cPlayerName);
                CPrintToChat(iclient, "Your best time: %s", l_cMessageContent);
	}
	else
                CPrintToChat(iclient, "You have no time yet!");
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool IsValidClient(int client)
{
	if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
		return true;
	return false;
}