#pragma semicolon 1
#pragma newdecls required

#include <sourcemod>
#include <sdktools>
#include <PlayerManager>

#define PLUGIN_AUTHOR "jenz"
#define PLUGIN_VERSION "1.1"

public Plugin myinfo =
{
        name = "nosteam bhop blocker",
        author = PLUGIN_AUTHOR,
        description = "handicaps nosteamers from bhopping (they mostly cheat) ",
        version = PLUGIN_VERSION,
        url = ""
};

bool bhop_restricted_nosteamer[MAXPLAYERS + 1];
int buttons_old[MAXPLAYERS + 1];
int flags_old[MAXPLAYERS + 1];

Database g_hDatabase;

ConVar g_hCvar_TriggerVelocity;
ConVar g_hCvar_SlowedVelocity;

public void OnPluginStart()
{
	g_hCvar_TriggerVelocity = CreateConVar("sm_nosteam_bhop_trigger_velocity", "300", "Horizontal velocity at which nosteamers will be slowed during bhop.", FCVAR_NONE, true, 0.0);
	g_hCvar_SlowedVelocity = CreateConVar("sm_nosteam_bhop_slowed_velocity", "200", "Horizontal velocity which nosteamers will be slowed to.", FCVAR_NONE, true, 0.0);

	AutoExecConfig();
}

public void OnConfigsExecuted()
{
    if (!g_hDatabase)
        Database.Connect(SQL_OnDatabaseConnect, "bot_surfing");
}

public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
	if(!db || strlen(error))
	{
		LogError("Database error: %s", error);
		return;
	}

	g_hDatabase = db;

	char sQuery[512];
	Format(sQuery, sizeof(sQuery), "CREATE TABLE IF NOT EXISTS bhop_whitelist (`steam_auth` VARCHAR(32) NOT NULL, `name` VARCHAR(64) NOT NULL, PRIMARY KEY (`steam_auth`))");
	g_hDatabase.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;
	}

	for(int i = 1; i <= MaxClients; i++)
	{
		if (IsClientConnected(i) && IsClientAuthorized(i))
		{
			char sAuthID[32];
			GetClientAuthId(i, AuthId_Steam2, sAuthID, sizeof(sAuthID));
			OnClientAuthorized(i, sAuthID);
		}
	}
}

public void OnClientAuthorized(int client, const char[] sAuthID)
{
	if (!IsFakeClient(client) && !IsClientSourceTV(client) && !PM_IsPlayerSteam(client))
	{
		bhop_restricted_nosteamer[client] = true;
		buttons_old[client] = 0;
		flags_old[client] = 0;

		char sQuery[512];
		Format(sQuery, sizeof(sQuery), "SELECT * FROM bhop_whitelist WHERE steam_auth = '%s'", sAuthID);
		g_hDatabase.Query(SQL_OnQueryCompleted, sQuery, GetClientSerial(client));
	}
	else
		bhop_restricted_nosteamer[client] = false;
}

public void SQL_OnQueryCompleted(Database db, DBResultSet results, const char[] error, any data)
{
	if (!db || strlen(error))
	{
		LogError("Query error: %s", error);
		return;
	}

	int client;
	if ((client = GetClientFromSerial(data)) == 0)
		return;

	if (results.RowCount && results.FetchRow())
	{
		int iFieldNum;
		results.FieldNameToNum("name", iFieldNum);
		char sName[MAX_NAME_LENGTH];
		results.FetchString(iFieldNum, sName, sizeof(sName));

		bhop_restricted_nosteamer[client] = false;
		LogMessage("%L was found as \'%s\' on the whitelist and therefore will be allowed to bhop", client, sName);
	}
}

public void OnClientDisconnect(int client)
{
	bhop_restricted_nosteamer[client] = false;
	buttons_old[client] = 0;
	flags_old[client] = 0;
}

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) || !IsPlayerAlive(client) || !bhop_restricted_nosteamer[client]) return;
	if (!(buttons_old[client] & IN_JUMP) && (!(buttons & IN_JUMP)))
	{
		flags_old[client] = GetEntityFlags(client);
		return;
	}
	if (buttons_old[client] & IN_JUMP)
	{
		if (!(buttons & IN_JUMP))
			if (GetEntityFlags(client) & FL_ONGROUND)
				buttons_old[client] = buttons;
		return;
	}
	if (!(flags_old[client] & FL_ONGROUND))
		return;

	float vVel[3];
	GetEntPropVector(client, Prop_Data, "m_vecVelocity", vVel);
	float fVelocity = SquareRoot(Pow(vVel[0], 2.0) + Pow(vVel[1], 2.0));

	if (fVelocity > g_hCvar_TriggerVelocity.FloatValue)
	{
		float fNormalized[3];
		fNormalized[0] = vVel[0] / fVelocity;
		fNormalized[1] = vVel[1] / fVelocity;
		fNormalized[2] = 0.0;

		float fTargetVelocity = g_hCvar_SlowedVelocity.FloatValue;
		float fFinal[3];
		fFinal[0] = fNormalized[0] * fTargetVelocity;
		fFinal[1] = fNormalized[1] * fTargetVelocity;
		fFinal[2] = vVel[2];
		TeleportEntity(client, NULL_VECTOR, NULL_VECTOR, fFinal);
	}

	buttons_old[client] = buttons;
}

stock int IsValidClient(int client, bool nobots = true)
{
	if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (nobots && IsFakeClient(client)))
		return false;

	return IsClientInGame(client);
}