1811 lines
66 KiB
SourcePawn
Executable File
1811 lines
66 KiB
SourcePawn
Executable File
#pragma semicolon 1
|
|
#define DEBUG
|
|
#define PLUGIN_AUTHOR "jenz"
|
|
#define PLUGIN_VERSION "2.0"
|
|
#define g_dLength 400
|
|
#define g_dQueryLength 2048
|
|
#include <sourcemod>
|
|
#include <morecolors>
|
|
#include <clientprefs>
|
|
#include <unloze_zones>
|
|
#include <unloze_racetimer_specialmaps>
|
|
#include <unloze_racetimer_antizones>
|
|
#include <cstrike>
|
|
#include <sdktools>
|
|
#include <sdkhooks>
|
|
#include <PlayerManager>
|
|
#include <commandfilters>
|
|
#include <outputinfo>
|
|
|
|
#pragma newdecls required
|
|
char g_cMapname[g_dLength];
|
|
char g_cSpecialMapStart[g_dLength];
|
|
char g_cSpecialMapEnd[g_dLength];
|
|
char g_csSID[MAXPLAYERS + 1][65];
|
|
char g_sSelectedMap[MAXPLAYERS + 1][512];
|
|
|
|
float g_fStartTime[MAXPLAYERS + 1];
|
|
float g_fClientVectors[MAXPLAYERS + 1][3];
|
|
float g_fClient_End_time[MAXPLAYERS + 1];
|
|
|
|
int g_iClientFrames[MAXPLAYERS + 1];
|
|
int player_stage[MAXPLAYERS + 1];
|
|
int g_iSelectedStage[MAXPLAYERS + 1];
|
|
|
|
bool g_bClientsIgnoring[MAXPLAYERS + 1];
|
|
bool g_bHumansAllowedTime[MAXPLAYERS + 1];
|
|
bool g_bHideTimer[MAXPLAYERS + 1];
|
|
bool g_bClient_allowed_to_leave_again[MAXPLAYERS + 1];
|
|
bool g_bClientTimesFetched[MAXPLAYERS + 1];
|
|
|
|
Handle g_hClientCookie = INVALID_HANDLE;
|
|
Handle hText;
|
|
|
|
Database g_dDatabase;
|
|
|
|
ArrayList g_hMapStageCategoryCvars;
|
|
enum struct MapStageCategoryCvars
|
|
{
|
|
char map[512];
|
|
char serverCvars[4096];
|
|
int stage;
|
|
int CvarsHash;
|
|
}
|
|
|
|
CurrentMapClientRecords g_CurrentClientRaceZone[MAXPLAYERS +1];
|
|
ArrayList g_hCurrentMapClientRecords;
|
|
enum struct CurrentMapClientRecords
|
|
{
|
|
int client;
|
|
char csTime_record[MAXPLAYERS + 1];
|
|
int stage;
|
|
int CvarsHash;
|
|
int position;
|
|
int recordedAt; // Unix timestamp - fetched via UNIX_TIMESTAMP(recorded_at)
|
|
int categoryNumber;
|
|
}
|
|
|
|
ArrayList g_hCurrentMapAllTopRecords;
|
|
enum struct CurrentMapAllTopRecords
|
|
{
|
|
char sName[MAX_NAME_LENGTH];
|
|
char csTime_record[MAXPLAYERS + 1];
|
|
int stage;
|
|
int CvarsHash;
|
|
int recordedAt; // Unix timestamp - fetched via UNIX_TIMESTAMP(recorded_at)
|
|
}
|
|
|
|
public Plugin myinfo =
|
|
{
|
|
name = "UNLOZE_racetimer_css",
|
|
author = PLUGIN_AUTHOR,
|
|
description = "tracks times on race maps on ze",
|
|
version = PLUGIN_VERSION,
|
|
url = "www.unloze.com"
|
|
};
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void OnPluginStart()
|
|
{
|
|
//cmds
|
|
RegConsoleCmd("sm_ignoretimer", cmd_timerIgnoreTime, "All racetimer will be ignored for you");
|
|
RegConsoleCmd("sm_ignoretime", cmd_timerIgnoreTime, "All racetimer will be ignored for you");
|
|
RegConsoleCmd("sm_hidetimer", cmd_hideTimerHUD, "Hides timer HUD");
|
|
|
|
//just repeating many commands leading to the same overall navigation menu
|
|
RegConsoleCmd("sm_toptime", cmd_OverallMapsMenu, "checking top times for results");
|
|
RegConsoleCmd("sm_mytime", cmd_OverallMapsMenu, "checking your personal time");
|
|
RegConsoleCmd("sm_time", cmd_OverallMapsMenu, "Checking race stages");
|
|
RegConsoleCmd("sm_times", cmd_OverallMapsMenu, "Checking race stages");
|
|
RegConsoleCmd("sm_timer", cmd_OverallMapsMenu, "Checking race stages");
|
|
RegConsoleCmd("sm_timers", cmd_OverallMapsMenu, "Checking race stages");
|
|
|
|
//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);
|
|
|
|
g_hCurrentMapClientRecords = new ArrayList(sizeof(CurrentMapClientRecords));
|
|
g_hCurrentMapAllTopRecords = new ArrayList(sizeof(CurrentMapAllTopRecords));
|
|
g_hMapStageCategoryCvars = new ArrayList(sizeof(MapStageCategoryCvars));
|
|
|
|
//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);
|
|
}
|
|
|
|
//admin commands to monitor for the sake of stopping a timer from being cheated
|
|
AddCommandListener(CommandAdminMonitor, "sm_slap");
|
|
AddCommandListener(CommandAdminMonitor, "sm_noclip");
|
|
AddCommandListener(CommandAdminMonitor, "sm_gravity");
|
|
AddCommandListener(CommandAdminMonitor, "sm_speed");
|
|
AddCommandListener(CommandAdminMonitor, "sm_resize");
|
|
AddCommandListener(CommandAdminMonitor, "sm_modelscale");
|
|
AddCommandListener(CommandAdminMonitor, "sm_bring");
|
|
AddCommandListener(CommandAdminMonitor, "sm_goto");
|
|
}
|
|
|
|
public Action CommandAdminMonitor(int client, const char[] command, int argc)
|
|
{
|
|
char sArgs[64];
|
|
char sTargetName[MAX_TARGET_LENGTH];
|
|
int iTargets[MAXPLAYERS];
|
|
bool bIsML;
|
|
GetCmdArg(1, sArgs, sizeof(sArgs));
|
|
|
|
if ((ProcessTargetString(sArgs, client, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_BOTS, sTargetName, sizeof(sTargetName), bIsML)) <= 0)
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
int target = iTargets[0];
|
|
if (!g_bHumansAllowedTime[target])
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
PrintToChat(target, "Command Monitor: Disabled your timer due to admin interferrence caused by admin %N.", client);
|
|
g_bHumansAllowedTime[target] = false;
|
|
ResetCurrentClientRaceZone(target);
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
public Action allow_leaving_again(Handle hTimer, int Serial)
|
|
{
|
|
int client;
|
|
if ((client = GetClientFromSerial(Serial)) == 0)
|
|
{
|
|
return Plugin_Handled;
|
|
}
|
|
if (IsValidClient(client))
|
|
{
|
|
g_bClient_allowed_to_leave_again[client] = true;
|
|
}
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public void trigger_teleport(const char[] output, int entity_index, int client, float delay)
|
|
{
|
|
if (IsValidEdict(entity_index) && IsValidClient(client) && g_bHumansAllowedTime[client])
|
|
{
|
|
//if its surf maps there most likely are needed teleports
|
|
if (StrContains(g_cMapname, "surf", false) == -1)
|
|
{
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
resetClientVectors(client);
|
|
g_bClient_allowed_to_leave_again[client] = false;
|
|
PrintToChat(client, "Disabled timer due to potential teleport abuse, type 1.");
|
|
CreateTimer(1.0, allow_leaving_again, GetClientSerial(client));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
ResetCurrentClientRaceZone(client);
|
|
resetClientVectors(client);
|
|
g_bClient_allowed_to_leave_again[client] = false;
|
|
PrintToChat(client, "Disabled timer due to potential teleport abuse, type 1.");
|
|
CreateTimer(1.0, allow_leaving_again, GetClientSerial(client));
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
for(int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (IsValidClient(i))
|
|
{
|
|
OnClientPostAdminCheck(i);
|
|
}
|
|
}
|
|
OnMapStart();
|
|
}
|
|
|
|
public void SQL_FinishedQuery(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
delete results;
|
|
if (!db || strlen(error))
|
|
{
|
|
char sQuery[g_dQueryLength];
|
|
ResetPack(data);
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
LogError("Query error 3: %s", error);
|
|
LogError("actual query: %s", sQuery);
|
|
}
|
|
delete data;
|
|
}
|
|
|
|
public void OnMapStart()
|
|
{
|
|
PrecacheSound("unloze/4echo_finish.wav");
|
|
AddFileToDownloadsTable("sound/unloze/4echo_finish.wav");
|
|
|
|
if (!g_dDatabase)
|
|
{
|
|
Database.Connect(SQL_OnDatabaseConnect, "racetimercss_2026");
|
|
return;
|
|
}
|
|
GetCurrentMap(g_cMapname, sizeof(g_cMapname));
|
|
|
|
Format(g_cSpecialMapStart, sizeof(g_cSpecialMapStart), "");
|
|
Format(g_cSpecialMapEnd, sizeof(g_cSpecialMapEnd), "");
|
|
g_hCurrentMapClientRecords.Clear();
|
|
g_hCurrentMapAllTopRecords.Clear();
|
|
g_hMapStageCategoryCvars.Clear();
|
|
FillMapStageCategoryCvars();
|
|
}
|
|
|
|
public void FillMapStageCategoryCvars()
|
|
{
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "select zc.map_name, zc.stage, zc.cvars_hash, zc.server_cvars from `zone_categories` zc where zc.is_invalid = 0 order by zc.map_name, zc.stage, zc.cvars_hash");
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
g_dDatabase.Query(SQL_FinishedQueryFillMapStageCategoryCvars, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
public void SQL_FinishedQueryFillMapStageCategoryCvars(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
if (!db || strlen(error))
|
|
{
|
|
char sQuery[g_dQueryLength];
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
LogError("Query error 1: %s", error);
|
|
LogError("actual query: %s", sQuery);
|
|
delete data;
|
|
delete results;
|
|
return;
|
|
}
|
|
delete data;
|
|
while (results.RowCount && results.FetchRow())
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
results.FetchString(0, entry.map, sizeof(entry.map));
|
|
entry.stage = results.FetchInt(1);
|
|
entry.CvarsHash = results.FetchInt(2);
|
|
results.FetchString(3, entry.serverCvars, sizeof(entry.serverCvars));
|
|
g_hMapStageCategoryCvars.PushArray(entry);
|
|
}
|
|
delete results;
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
GetArrayArray(g_hMapStageCategoryCvars, i, entry);
|
|
if (StrEqual(entry.map, g_cMapname))
|
|
{
|
|
//each stage/category combo has the top 200 results ordered by best times.
|
|
GetAllCurrentMapTopRecords(entry.CvarsHash, entry.stage);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void GetAllCurrentMapTopRecords(int CvarsHash, int stage)
|
|
{
|
|
char serverTag[64];
|
|
FormatServerTag(serverTag);
|
|
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "select ti2.steam_name, ti.time_value, zc.stage, zc.cvars_hash, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where zc.map_name = '%s' and zc.server_tag = '%s' and zc.stage = '%i' and zc.cvars_hash = '%i' and ti.is_invalid = 0 and zc.is_invalid = 0 order by ti.time_value asc limit 200", g_cMapname, serverTag, stage, CvarsHash);
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
g_dDatabase.Query(SQL_FinishedQueryCurrentMapTopRecords, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
public void SQL_FinishedQueryCurrentMapTopRecords(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
if (!db || strlen(error))
|
|
{
|
|
char sQuery[g_dQueryLength];
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
LogError("Query error 1: %s", error);
|
|
LogError("actual query: %s", sQuery);
|
|
delete data;
|
|
delete results;
|
|
return;
|
|
}
|
|
delete data;
|
|
while (results.RowCount && results.FetchRow())
|
|
{
|
|
CurrentMapAllTopRecords entry;
|
|
results.FetchString(0, entry.sName, sizeof(entry.sName));
|
|
results.FetchString(1, entry.csTime_record, sizeof(entry.csTime_record));
|
|
entry.stage = results.FetchInt(2);
|
|
entry.CvarsHash = results.FetchInt(3);
|
|
entry.recordedAt = results.FetchInt(4);
|
|
g_hCurrentMapAllTopRecords.PushArray(entry);
|
|
}
|
|
delete results;
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void Event_RoundStart(Handle event, const char[] name, bool dontBroadcast)
|
|
{
|
|
Format(g_cSpecialMapStart, sizeof(g_cSpecialMapStart), "");
|
|
Format(g_cSpecialMapEnd, sizeof(g_cSpecialMapEnd), "");
|
|
|
|
int race_zone_count = GetTotalRaceZones();
|
|
int l_iZoneCount = unloze_zoneCount();
|
|
if (!race_zone_count)
|
|
return;
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (IsValidClient(i) && !IsFakeClient(i))
|
|
{
|
|
g_fClient_End_time[i] = 0.0;
|
|
resetClientVectors(i);
|
|
char first_zone[512];
|
|
ZoneNameBasedOnIndex(0, first_zone);
|
|
g_bClient_allowed_to_leave_again[i] = true;
|
|
g_bHumansAllowedTime[i] = false;
|
|
ResetCurrentClientRaceZone(i);
|
|
if (l_iZoneCount == 1 && race_zone_count == 1)
|
|
{
|
|
g_bHumansAllowedTime[i] = true;
|
|
PrintToChat(i, "Started timer: There is only a finish zone");
|
|
g_fStartTime[i] = GetEngineTime();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void OnClientPostAdminCheck(int client)
|
|
{
|
|
if (IsFakeClient(client))
|
|
{
|
|
return;
|
|
}
|
|
GetClientAuthId(client, AuthId_Steam2, g_csSID[client], sizeof(g_csSID[]));
|
|
resetClient(client);
|
|
InsertPlayer(client);
|
|
client_ignoring_racetimer(client);
|
|
mysql_get_player_times(client);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void mysql_get_player_times(int client)
|
|
{
|
|
char serverTag[64];
|
|
FormatServerTag(serverTag);
|
|
|
|
char query[g_dQueryLength];
|
|
Format(query, sizeof(query), "select ti.time_value, zc.cvars_hash, zc.stage, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at, (select count(*) + 1 from `timer_records` ti3 where ti3.zone_category_id = ti.zone_category_id and ti3.time_value < ti.time_value and ti3.is_invalid = 0) as position from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where ti2.steam_auth = '%s' and zc.map_name = '%s' and zc.server_tag = '%s' and ti.time_value = (select min(ti4.time_value) from `timer_records` ti4 inner join `timer_improvements` ti5 on ti4.improvement_id = ti5.id where ti5.steam_auth = ti2.steam_auth and ti4.zone_category_id = ti.zone_category_id and ti4.is_invalid = 0 and zc.is_invalid = 0) order by zc.stage asc", g_csSID[client], g_cMapname, serverTag);
|
|
//only return the best timevalue for a clients category.
|
|
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_dQueryLength];
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
LogError("Query error 1: %s", error);
|
|
LogError("actual query: %s", sQuery);
|
|
delete data;
|
|
delete results;
|
|
return;
|
|
}
|
|
delete data;
|
|
int client;
|
|
if ((client = GetClientFromSerial(client_serial)) == 0)
|
|
{
|
|
delete results;
|
|
return;
|
|
}
|
|
while (results.RowCount && results.FetchRow())
|
|
{
|
|
CurrentMapClientRecords entry;
|
|
entry.client = client;
|
|
results.FetchString(0, entry.csTime_record, sizeof(entry.csTime_record));
|
|
entry.CvarsHash = results.FetchInt(1);
|
|
entry.stage = results.FetchInt(2); //just redundantly the same stage each time
|
|
entry.recordedAt = results.FetchInt(3);
|
|
entry.position = results.FetchInt(4);
|
|
g_hCurrentMapClientRecords.PushArray(entry);
|
|
}
|
|
delete results;
|
|
g_bClientTimesFetched[client] = true;
|
|
}
|
|
|
|
public void InsertPlayer(int client)
|
|
{
|
|
char sName[MAX_NAME_LENGTH];
|
|
EscapeName(client, sName);
|
|
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "INSERT IGNORE INTO `players` (`steam_auth`, `name`) VALUES ('%s', '%s')", g_csSID[client], sName);
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
public void client_ignoring_racetimer(int client)
|
|
{
|
|
char query[g_dQueryLength];
|
|
Format(query, sizeof(query), "SELECT is_ignoring FROM `players` where steam_auth = '%s'", g_csSID[client]);
|
|
g_dDatabase.Query(SQL_OnQueryCompleted_ignoring, query, GetClientSerial(client));
|
|
}
|
|
|
|
public void SQL_OnQueryCompleted_ignoring(Database db, DBResultSet results, const char[] error, int Serial)
|
|
{
|
|
if (!db || strlen(error))
|
|
{
|
|
LogError("error resolving zone_categories: %s", error);
|
|
delete results;
|
|
return;
|
|
}
|
|
int client;
|
|
if ((client = GetClientFromSerial(Serial)) == 0)
|
|
{
|
|
delete results;
|
|
return;
|
|
}
|
|
if (!IsValidClient(client))
|
|
{
|
|
delete results;
|
|
return;
|
|
}
|
|
if (results.RowCount && results.FetchRow())
|
|
{
|
|
g_bClientsIgnoring[client] = results.FetchInt(0) == 0 ? false : true;
|
|
}
|
|
else
|
|
{
|
|
g_bClientsIgnoring[client] = false;
|
|
}
|
|
delete results;
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// 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)
|
|
{
|
|
Format(g_csSID[client], sizeof(g_csSID[]), "");
|
|
resetClient(client);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void resetClient(int client)
|
|
{
|
|
if (0 < client <= MaxClients)
|
|
{
|
|
g_bClientTimesFetched[client] = false;
|
|
g_bClientsIgnoring[client] = false;
|
|
g_bHumansAllowedTime[client] = false;
|
|
g_fClient_End_time[client] = 0.0;
|
|
g_bClient_allowed_to_leave_again[client] = false;
|
|
resetClientVectors(client);
|
|
g_fStartTime[client] = 0.0;
|
|
player_stage[client] = 0;
|
|
|
|
for (int j = g_hCurrentMapClientRecords.Length - 1; j >= 0; j--)
|
|
{
|
|
CurrentMapClientRecords entry;
|
|
GetArrayArray(g_hCurrentMapClientRecords, j, entry);
|
|
if (entry.client == client)
|
|
{
|
|
g_hCurrentMapClientRecords.Erase(j);
|
|
}
|
|
}
|
|
ResetCurrentClientRaceZone(client);
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// 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_bHumansAllowedTime[client])
|
|
return;
|
|
|
|
if (!IsPlayerAlive(client))
|
|
{
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
resetClientVectors(client);
|
|
PrintToChat(client, "Disabled timer due to dying.");
|
|
return;
|
|
}
|
|
if (GetClientTeam(client) == CS_TEAM_CT)
|
|
{
|
|
float clientVectors[3];
|
|
GetClientAbsOrigin(client, clientVectors);
|
|
if (checkClientOrigin(g_fClientVectors[client], clientVectors, client))
|
|
{
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
resetClientVectors(client);
|
|
g_bClient_allowed_to_leave_again[client] = false;
|
|
PrintToChat(client, "Disabled timer due to potential teleport abuse, type 2.");
|
|
CreateTimer(1.0, allow_leaving_again, GetClientSerial(client));
|
|
return;
|
|
}
|
|
int frameCap = 11;
|
|
if (g_iClientFrames[client] >= frameCap)
|
|
{
|
|
if (GetEntityMoveType(client) == MOVETYPE_NOCLIP)
|
|
{
|
|
PrintToChat(client, "Disabled your Timer due to Noclip mode.");
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
resetClientVectors(client);
|
|
return;
|
|
}
|
|
g_iClientFrames[client] = 0;
|
|
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);
|
|
if (total_time != -1.0)
|
|
{
|
|
//if position is -1 the client has no record yet for this stage category
|
|
if (g_CurrentClientRaceZone[client].client != 0)
|
|
{
|
|
ShowSyncHudText(client, hText, "Time: %06.3f\nRecord: %s\nPosition: %i\nMap: %s\nStage: %i\nCategory: %i", total_time, g_CurrentClientRaceZone[client].csTime_record, g_CurrentClientRaceZone[client].position, g_cMapname, player_stage[client], g_CurrentClientRaceZone[client].categoryNumber);
|
|
}
|
|
else
|
|
{
|
|
ShowSyncHudText(client, hText, "Time: %06.3f\nRecord: None\nMap: %s\nStage: %i", total_time, g_cMapname, player_stage[client]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
g_iClientFrames[client]++;
|
|
}
|
|
else
|
|
{
|
|
//is zm
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
//called if teleported to some place far away or teleported from inside start zone to end zone
|
|
resetClientVectors(client);
|
|
PrintToChat(client, "Disabled timer due to ZM infection.");
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
public float client_current_race_time(int i_client)
|
|
{
|
|
if (g_fStartTime[i_client] == 0.0)
|
|
return -1.0;
|
|
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);
|
|
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)
|
|
{
|
|
if (g_bClientsIgnoring[client])
|
|
{
|
|
return;
|
|
}
|
|
int zoneIndex = RetrieveZoneIndex(zone);
|
|
int race_zone_count = GetTotalRaceZones();
|
|
if (!(StrEqual(zone, g_cSpecialMapEnd)) && !race_zone_count)
|
|
return;
|
|
|
|
int l_iZoneCount = unloze_zoneCount();
|
|
|
|
if (GetClientTeam(client) == CS_TEAM_CT && (g_bHumansAllowedTime[client] || StrEqual(zone, g_cSpecialMapEnd)))
|
|
{
|
|
if ((StrContains(zone, "ZONE_PREFIX_RACE") > -1) || StrEqual(zone, g_cSpecialMapEnd))
|
|
{
|
|
if (l_iZoneCount < 2)
|
|
player_stage[client] = 0;
|
|
if (player_stage[client] == (zoneIndex / 2) || l_iZoneCount < 2)
|
|
{
|
|
g_fClient_End_time[client] = client_current_race_time(client);
|
|
g_iClientFrames[client] = 0;
|
|
resetClientVectors(client);
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
FinishedRaceZone(client);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void unloze_zoneLeave(int client, char[] zone)
|
|
{
|
|
//only maps with multiple zones need ZONE_PREFIX_START
|
|
if (g_bClientsIgnoring[client])
|
|
{
|
|
return;
|
|
}
|
|
int race_zone_count = GetTotalRaceZones();
|
|
if (!(StrEqual(zone, g_cSpecialMapStart)) && !race_zone_count)
|
|
return;
|
|
|
|
if (GetClientTeam(client) == CS_TEAM_CT)
|
|
{
|
|
if ((StrContains(zone, "ZONE_PREFIX_START") > -1) || StrEqual(zone, g_cSpecialMapStart))
|
|
{
|
|
if (!PM_IsPlayerSteam(client))
|
|
{
|
|
PrintToChat(client, "Not starting timer due to being listed as nosteamer");
|
|
return;
|
|
}
|
|
if (!g_bClient_allowed_to_leave_again[client])
|
|
{
|
|
PrintToChat(client, "Did not start timer due to teleport cooldown.");
|
|
return;
|
|
}
|
|
|
|
if (!g_bClientTimesFetched[client])
|
|
{
|
|
PrintToChat(client, "Not starting timer because your records have not been loaded from the database yet!");
|
|
return;
|
|
}
|
|
|
|
g_fStartTime[client] = GetEngineTime();
|
|
float notRounded = float(RetrieveZoneIndex(zone));
|
|
player_stage[client] = RoundToCeil(notRounded / 2);
|
|
g_bHumansAllowedTime[client] = true;
|
|
|
|
char serverTag[32];
|
|
FormatServerTag(serverTag);
|
|
char serverCvars[4096];
|
|
int Hash = FormatServerCvarHash(serverCvars, sizeof(serverCvars), serverTag, client);
|
|
|
|
bool found = false;
|
|
int categoryNumber = 0;
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
g_hMapStageCategoryCvars.GetArray(i, entry);
|
|
if (StrEqual(entry.map, g_cMapname) && entry.stage == player_stage[client])
|
|
{
|
|
categoryNumber++;
|
|
if (entry.CvarsHash == Hash)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//its a new category
|
|
if (!found)
|
|
{
|
|
categoryNumber++;
|
|
}
|
|
CPrintToChat(client, "Timer started for Stage: %i. Category: %i", player_stage[client], categoryNumber);
|
|
|
|
for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++)
|
|
{
|
|
CurrentMapClientRecords entry;
|
|
g_hCurrentMapClientRecords.GetArray(i, entry);
|
|
if (entry.client == client && entry.stage == player_stage[client] && entry.CvarsHash == Hash)
|
|
{
|
|
entry.categoryNumber = categoryNumber;
|
|
//client already has a previous record entry, we display its recordtime and position.
|
|
g_CurrentClientRaceZone[client] = entry;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// 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])
|
|
{
|
|
g_bHumansAllowedTime[client] = false;
|
|
ResetCurrentClientRaceZone(client);
|
|
PrintToChat(client, "Disabled Timer due to using illegal shortcut");
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// 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[MAXPLAYERS + 1];
|
|
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]);
|
|
}
|
|
|
|
public void mysql_get_client_position(CurrentMapClientRecords entry, float client_time)
|
|
{
|
|
char serverTag[32];
|
|
FormatServerTag(serverTag);
|
|
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "select count(*) + 1 from `timer_records` ti3 inner join `zone_categories` zc2 on ti3.zone_category_id = zc2.id where zc2.map_name = '%s' and zc2.server_tag = '%s' and zc2.stage = '%i' and zc2.cvars_hash = '%i' and ti3.time_value < %06.3f and ti3.is_invalid = 0 and zc2.is_invalid = 0", g_cMapname, serverTag, entry.stage, entry.CvarsHash, client_time);
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
hDataPack.WriteCell(GetClientSerial(entry.client));
|
|
hDataPack.WriteCell(entry.stage);
|
|
hDataPack.WriteCell(entry.CvarsHash);
|
|
g_dDatabase.Query(SQL_FetchPlayersPosition, sQuery, hDataPack, DBPrio_High);
|
|
}
|
|
|
|
public void SQL_FetchPlayersPosition(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
char sQuery[g_dQueryLength];
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
if (!db || strlen(error))
|
|
{
|
|
LogError("Query error resolving zone_categories: %s. actual sQuery: %s", error, sQuery);
|
|
delete results;
|
|
delete data;
|
|
return;
|
|
}
|
|
|
|
int clientSerial = data.ReadCell();
|
|
int client;
|
|
if ((client = GetClientFromSerial(clientSerial)) == 0)
|
|
{
|
|
delete results;
|
|
delete data;
|
|
return;
|
|
}
|
|
if (results.RowCount && results.FetchRow())
|
|
{
|
|
int stage = data.ReadCell();
|
|
int cvarsHash = data.ReadCell();
|
|
for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++)
|
|
{
|
|
CurrentMapClientRecords entry;
|
|
g_hCurrentMapClientRecords.GetArray(i, entry);
|
|
if (entry.client == client && entry.stage == stage && entry.CvarsHash == cvarsHash)
|
|
{
|
|
entry.position = results.FetchInt(0);
|
|
g_hCurrentMapClientRecords.SetArray(i, entry);
|
|
|
|
//we potentially also have a new entry to add to the arraylist for map top times
|
|
if (entry.position < 200)
|
|
{
|
|
if (entry.position < 10)
|
|
{
|
|
CPrintToChatAll("{green}[UNLOZE] Player %N just got a top10 record for Stage %i", client, player_stage[client]);
|
|
//hehe
|
|
EmitSoundToAll("unloze/4echo_finish.wav", .entity=client, .volume=1.0);
|
|
}
|
|
bool pushedEntry = false;
|
|
int categoryPosition = 0;
|
|
for (int j = 0; j < g_hCurrentMapAllTopRecords.Length; j++)
|
|
{
|
|
CurrentMapAllTopRecords entry1;
|
|
g_hCurrentMapAllTopRecords.GetArray(j, entry1);
|
|
//correct stage, correct category, first entry with a higher position than our newly added one
|
|
if (entry1.stage == stage && entry1.CvarsHash == cvarsHash)
|
|
{
|
|
categoryPosition++;
|
|
//this should put in the new position and move the old one up.
|
|
if (categoryPosition >= entry.position)
|
|
{
|
|
//we now have more than 200 entries for the maps continued duration in the stage category.
|
|
g_hCurrentMapAllTopRecords.ShiftUp(j);
|
|
|
|
CurrentMapAllTopRecords newEntry;
|
|
EscapeName(client, newEntry.sName);
|
|
newEntry.stage = stage;
|
|
newEntry.CvarsHash = cvarsHash;
|
|
Format(newEntry.csTime_record, sizeof(newEntry.csTime_record), entry.csTime_record);
|
|
newEntry.recordedAt = entry.recordedAt;
|
|
g_hCurrentMapAllTopRecords.SetArray(j, newEntry);
|
|
pushedEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//maybe the category is empty or has to be put at the very end when less than 200 results
|
|
if (g_hCurrentMapAllTopRecords.Length == 0 || !pushedEntry)
|
|
{
|
|
CurrentMapAllTopRecords newEntry;
|
|
EscapeName(client, newEntry.sName);
|
|
newEntry.stage = stage;
|
|
newEntry.CvarsHash = cvarsHash;
|
|
Format(newEntry.csTime_record, sizeof(newEntry.csTime_record), entry.csTime_record);
|
|
newEntry.recordedAt = entry.recordedAt;
|
|
g_hCurrentMapAllTopRecords.PushArray(newEntry);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
delete results;
|
|
delete data;
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public void FinishedRaceZone(int client)
|
|
{
|
|
float client_time = g_fClient_End_time[client];
|
|
if (client_time == -1.0)
|
|
{
|
|
//if client disconnected we dont want to cary timer over to other client
|
|
return;
|
|
}
|
|
|
|
if (StrEqual(g_csSID[client], "STEAM_ID_STOP_IGNORING_RETVALS") || StrEqual(g_csSID[client], "STEAM_ID_PENDING"))
|
|
{
|
|
PrintToChat(client, "Your steam ID is not working, not updating timer");
|
|
return;
|
|
}
|
|
|
|
if (!g_dDatabase)
|
|
{
|
|
PrintToChat(client, "Sorry but the database connection is dead so your time cannot be saved.");
|
|
return;
|
|
}
|
|
|
|
char serverTag[32];
|
|
FormatServerTag(serverTag);
|
|
char serverCvars[4096];
|
|
int Hash = FormatServerCvarHash(serverCvars, sizeof(serverCvars), serverTag, client);
|
|
|
|
float old_client_time = 0.0;
|
|
char old_client_timeS[64];
|
|
int ClientRecordOldIndex = -1;
|
|
for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++)
|
|
{
|
|
CurrentMapClientRecords entry;
|
|
g_hCurrentMapClientRecords.GetArray(i, entry);
|
|
if (entry.client == client && entry.stage == player_stage[client] && entry.CvarsHash == Hash)
|
|
{
|
|
ClientRecordOldIndex = i;
|
|
if (StrContains(entry.csTime_record, ":") != -1)
|
|
{
|
|
int generic_length = 65;
|
|
char[][] sPart = new char[2][generic_length];
|
|
ExplodeString(entry.csTime_record, ":", sPart, 2, generic_length);
|
|
old_client_time = (StringToFloat(sPart[0]) * 60) + StringToFloat(sPart[1]);
|
|
}
|
|
else
|
|
{
|
|
old_client_time = StringToFloat(entry.csTime_record);
|
|
}
|
|
Format(old_client_timeS, sizeof(old_client_timeS), entry.csTime_record);
|
|
break;
|
|
}
|
|
}
|
|
|
|
char extraInfo[64];
|
|
int categoryNumber = 0;
|
|
bool found = false;
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
g_hMapStageCategoryCvars.GetArray(i, entry);
|
|
if (!StrEqual(entry.map, g_cMapname) || entry.stage != player_stage[client])
|
|
{
|
|
continue;
|
|
}
|
|
categoryNumber++;
|
|
if (entry.CvarsHash == Hash)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//we got a new category added right now, put it into the arraylist
|
|
if (!found)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
Format(entry.map, sizeof(entry.map), g_cMapname);
|
|
entry.stage = player_stage[client];
|
|
entry.CvarsHash = Hash;
|
|
Format(entry.serverCvars, sizeof(entry.serverCvars), serverCvars);
|
|
g_hMapStageCategoryCvars.PushArray(entry);
|
|
categoryNumber++;
|
|
}
|
|
|
|
Format(extraInfo, sizeof(extraInfo), "Category: %i", categoryNumber);
|
|
CPrintToChat(client, "{green}[UNLOZE] Stage: %i %s\nTime: %06.3f", player_stage[client], extraInfo, client_time);
|
|
|
|
if (client_time >= old_client_time && old_client_time > 0.0)
|
|
{
|
|
CPrintToChat(client, "{green}[UNLOZE] Did not improve your personal best time of %s.", old_client_timeS);
|
|
return;
|
|
}
|
|
|
|
CurrentMapClientRecords entry;
|
|
//we got an entry for the client arraylist to add or replace
|
|
//the client array only has unique entries meanwhile the Arraylist for toptimes may have client duplicates (worse and better times for same player)
|
|
if (ClientRecordOldIndex == -1)
|
|
{
|
|
entry.client = client;
|
|
Format(entry.csTime_record, sizeof(entry.csTime_record), "%06.3f", client_time);
|
|
entry.CvarsHash = Hash;
|
|
entry.stage = player_stage[client];
|
|
entry.recordedAt = GetTime();
|
|
g_hCurrentMapClientRecords.PushArray(entry);
|
|
}
|
|
else
|
|
{
|
|
g_hCurrentMapClientRecords.GetArray(ClientRecordOldIndex, entry);
|
|
Format(entry.csTime_record, sizeof(entry.csTime_record), "%06.3f", client_time);
|
|
entry.recordedAt = GetTime();
|
|
g_hCurrentMapClientRecords.SetArray(ClientRecordOldIndex, entry);
|
|
}
|
|
mysql_get_client_position(entry, client_time);
|
|
|
|
if (old_client_time == 0.0)
|
|
{
|
|
CPrintToChat(client, "{green}[UNLOZE] Your record: None yet. Command: !timer");
|
|
}
|
|
else
|
|
{
|
|
CPrintToChat(client, "{green}[UNLOZE] You beat your personal record of %s! Command: !timer", old_client_timeS);
|
|
}
|
|
|
|
InsertZoneCategory(client);
|
|
}
|
|
|
|
public void EscapeName(int client, char[] sNameOutput)
|
|
{
|
|
GetClientName(client, sNameOutput, MAX_NAME_LENGTH);
|
|
int size2 = 2 * strlen(sNameOutput) + 1;
|
|
g_dDatabase.Escape(sNameOutput, sNameOutput, size2 + 1);
|
|
}
|
|
|
|
public void FormatServerTag(char[] serverTag)
|
|
{
|
|
int i_port = GetConVarInt(FindConVar("hostport"));
|
|
if (i_port == 27015)
|
|
{
|
|
Format(serverTag, 32, "ze1");
|
|
}
|
|
else if (i_port == 27035)
|
|
{
|
|
Format(serverTag, 32, "ze2");
|
|
}
|
|
else if (i_port == 27019)
|
|
{
|
|
Format(serverTag, 32, "dev");
|
|
}
|
|
}
|
|
|
|
public int FormatServerCvarHash(char[] serverCvarsOutput, int size, char[] serverTag, int client)
|
|
{
|
|
int sv_gravity = GetConVarInt(FindConVar("sv_gravity"));
|
|
int sv_enablebunnyhopping = GetConVarInt(FindConVar("sv_enablebunnyhopping"));
|
|
int sv_autobunnyhopping = GetConVarInt(FindConVar("sv_autobunnyhopping"));
|
|
int sv_accelerate = GetConVarInt(FindConVar("sv_accelerate"));
|
|
int sv_friction = GetConVarInt(FindConVar("sv_friction"));
|
|
int sv_airaccelerate = GetConVarInt(FindConVar("sv_airaccelerate"));
|
|
//< 0.014 = 100 tick
|
|
int tickrate = GetConVarFloat(FindConVar("sm_interval_per_tick")) < 0.014? 100 : 66;
|
|
float client_speed = GetEntPropFloat(client, Prop_Data, "m_flLaggedMovementValue");
|
|
float client_gravity = GetEntPropFloat(client, Prop_Data, "m_flGravity");
|
|
if (client_gravity == 0.0)
|
|
{
|
|
client_gravity = 1.0;
|
|
}
|
|
|
|
Format(serverCvarsOutput, size, "sv_gravity %i, sv_enablebunnyhopping %i, sv_autobunnyhopping %i, sv_accelerate %i, sv_airaccelerate %i, tickrate %i, sv_friction %i, client_speed %04.2f, client_gravity %04.2f", sv_gravity, sv_enablebunnyhopping, sv_autobunnyhopping, sv_accelerate, sv_airaccelerate, tickrate, sv_friction, client_speed, client_gravity);
|
|
|
|
char sHashInput[4096];
|
|
Format(sHashInput, sizeof(sHashInput), "%s %s", serverCvarsOutput, serverTag);
|
|
return FNV1aHash(sHashInput);
|
|
}
|
|
|
|
public void InsertZoneCategory(int client)
|
|
{
|
|
char sName[MAX_NAME_LENGTH];
|
|
EscapeName(client, sName);
|
|
|
|
char sSID[64];
|
|
Format(sSID, sizeof(sSID), g_csSID[client]);
|
|
|
|
char serverTag[32];
|
|
FormatServerTag(serverTag);
|
|
|
|
char serverCvars[4096];
|
|
int Hash = FormatServerCvarHash(serverCvars, sizeof(serverCvars), serverTag, client);
|
|
//LogMessage("Hash: %i", Hash);
|
|
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "INSERT IGNORE INTO `zone_categories` (`map_name`, `stage`, `server_tag`, `server_cvars`, `cvars_hash`) VALUES ('%s', %i, '%s', '%s', %i)", g_cMapname, player_stage[client], serverTag, serverCvars, Hash);
|
|
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
|
|
// not taking any chances so just carrying all this information along
|
|
float client_time = g_fClient_End_time[client];
|
|
int client_stage = player_stage[client];
|
|
char mapname[64];
|
|
Format(mapname, sizeof(mapname), g_cMapname);
|
|
hDataPack.WriteFloat(client_time);
|
|
hDataPack.WriteString(mapname);
|
|
hDataPack.WriteCell(client_stage);
|
|
hDataPack.WriteString(serverTag);
|
|
hDataPack.WriteCell(Hash);
|
|
hDataPack.WriteString(sName);
|
|
hDataPack.WriteString(sSID);
|
|
g_dDatabase.Query(SQL_OnCategoryInserted, sQuery, hDataPack, DBPrio_High);
|
|
}
|
|
|
|
public void SQL_OnCategoryInserted(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
char sQuery[g_dQueryLength];
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
if (!db || strlen(error))
|
|
{
|
|
LogError("Query error resolving zone_categories: %s. actual sQuery: %s", error, sQuery);
|
|
delete results;
|
|
delete data;
|
|
return;
|
|
}
|
|
|
|
float client_time = data.ReadFloat();
|
|
char mapname[128];
|
|
data.ReadString(mapname, sizeof(mapname));
|
|
int client_stage = data.ReadCell();
|
|
char serverTag[32];
|
|
data.ReadString(serverTag, sizeof(serverTag));
|
|
int Hash = data.ReadCell();
|
|
char sName[MAX_NAME_LENGTH];
|
|
data.ReadString(sName, sizeof(sName));
|
|
char sSID[64];
|
|
data.ReadString(sSID, sizeof(sSID));
|
|
delete results;
|
|
delete data;
|
|
SubmitTimeImprovement(client_time, mapname, client_stage, serverTag, Hash, sName, sSID);
|
|
}
|
|
|
|
public void SubmitTimeImprovement(float client_time, char[] mapname, int client_stage, char[] serverTag, int Hash, char[] sName, char[] sSID)
|
|
{
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "INSERT INTO `timer_improvements` (`steam_auth`, `steam_name`) VALUES ('%s', '%s')", sSID, sName);
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
// not taking any chances so just carrying all this information along
|
|
hDataPack.WriteFloat(client_time);
|
|
hDataPack.WriteString(mapname);
|
|
hDataPack.WriteCell(client_stage);
|
|
hDataPack.WriteString(serverTag);
|
|
hDataPack.WriteCell(Hash);
|
|
hDataPack.WriteString(sSID);
|
|
g_dDatabase.Query(SQL_OnTimerImprovementsInserted, sQuery, hDataPack, DBPrio_High);
|
|
}
|
|
|
|
public void SQL_OnTimerImprovementsInserted(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
char sQuery[g_dQueryLength];
|
|
data.ReadString(sQuery, sizeof(sQuery));
|
|
if (!db || strlen(error))
|
|
{
|
|
LogError("Query error timer improvement insert: %s. actual sQuery: %s", error, sQuery);
|
|
delete results;
|
|
delete data;
|
|
return;
|
|
}
|
|
|
|
float client_time = data.ReadFloat();
|
|
char mapname[128];
|
|
data.ReadString(mapname, sizeof(mapname));
|
|
int client_stage = data.ReadCell();
|
|
char serverTag[32];
|
|
data.ReadString(serverTag, sizeof(serverTag));
|
|
int Hash = data.ReadCell();
|
|
char sSID[64];
|
|
data.ReadString(sSID, sizeof(sSID));
|
|
|
|
delete results;
|
|
delete data;
|
|
SubmitTimeRecord(client_time, mapname, client_stage, serverTag, Hash, sSID);
|
|
}
|
|
|
|
public void SubmitTimeRecord(float client_time, char[] mapname, int client_stage, char[] serverTag, int Hash, char[] sSID)
|
|
{
|
|
//there is a small window for a race condition theoretically if the client would finish another racetimer inside 2-3 seconds of the query running
|
|
//as the last selected row would point to a more recently inserted row. We expect that not to happen in practise.
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "INSERT INTO `timer_records` (`improvement_id`, `zone_category_id`, `time_value`) VALUES ((select id from `timer_improvements` where steam_auth = '%s' order by recorded_at desc limit 1), (select id from `zone_categories` where map_name = '%s' and stage = '%i' and server_tag = '%s' and cvars_hash = '%i'), %06.3f)", sSID, mapname, client_stage, serverTag, Hash, client_time);
|
|
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
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 Action cmd_timerIgnoreTime(int client, int args)
|
|
{
|
|
update_client_ignoring_racetimer(client);
|
|
return Plugin_Handled;
|
|
}
|
|
public void update_client_ignoring_racetimer(int client)
|
|
{
|
|
if (!IsValidClient(client))
|
|
return;
|
|
|
|
if (StrEqual(g_csSID[client], "STEAM_ID_STOP_IGNORING_RETVALS") || StrEqual(g_csSID[client], "STEAM_ID_PENDING"))
|
|
{
|
|
PrintToChat(client, "Your steam ID is not working, not updating settings.");
|
|
return;
|
|
}
|
|
|
|
if (g_bClientsIgnoring[client])
|
|
{
|
|
g_bClientsIgnoring[client] = false;
|
|
g_bHideTimer[client] = false;
|
|
SetClientCookie(client, g_hClientCookie, "0");
|
|
PrintToChat(client, "You are now not ignoring the racetimer");
|
|
}
|
|
else
|
|
{
|
|
g_bClientsIgnoring[client] = true;
|
|
g_bHideTimer[client] = true;
|
|
SetClientCookie(client, g_hClientCookie, "1");
|
|
PrintToChat(client, "You are now ignoring the racetimer");
|
|
}
|
|
char sQuery[g_dQueryLength];
|
|
if (!g_dDatabase)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Format(sQuery, sizeof(sQuery), "update `players` set `is_ignoring` = '%i' where steam_auth = '%s' ON DUPLICATE KEY UPDATE `is_ignoring` = '%i'", g_bClientsIgnoring[client], g_csSID[client], g_bClientsIgnoring[client]);
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteString(sQuery);
|
|
g_dDatabase.Query(SQL_FinishedQuery, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// 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"); }
|
|
return Plugin_Handled;
|
|
}
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
public Action cmd_OverallMapsMenu(int client, int args)
|
|
{
|
|
OverAllMapsMenu(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public void OverAllMapsMenu(int client)
|
|
{
|
|
Menu menu = new Menu(MenuHandler_SelectMap);
|
|
menu.SetTitle("[UNLOZE Racetimer] Select a map you want to see times for");
|
|
|
|
// current map first, if it has any recorded categories at all
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
g_hMapStageCategoryCvars.GetArray(i, entry);
|
|
if (StrEqual(entry.map, g_cMapname))
|
|
{
|
|
menu.AddItem(entry.map, entry.map);
|
|
break; // only need to add the current map once
|
|
}
|
|
}
|
|
|
|
// then every other distinct map, in whatever order the array has them
|
|
char iteratedMap[512];
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
g_hMapStageCategoryCvars.GetArray(i, entry);
|
|
if (!StrEqual(entry.map, iteratedMap) && !StrEqual(entry.map, g_cMapname))
|
|
{
|
|
menu.AddItem(entry.map, entry.map);
|
|
Format(iteratedMap, sizeof(iteratedMap), entry.map);
|
|
}
|
|
}
|
|
|
|
if (menu.ItemCount == 0)
|
|
{
|
|
menu.AddItem("-1", "No maps with recorded times yet", ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 0);
|
|
}
|
|
|
|
public int MenuHandler_SelectMap(Menu menu, MenuAction action, int client, int selection)
|
|
{
|
|
switch (action)
|
|
{
|
|
case MenuAction_Select:
|
|
{
|
|
char selectedMap[512];
|
|
menu.GetItem(selection, selectedMap, sizeof(selectedMap));
|
|
strcopy(g_sSelectedMap[client], sizeof(g_sSelectedMap[]), selectedMap);
|
|
OpenStageMenu(client, selectedMap);
|
|
}
|
|
case MenuAction_End:
|
|
{
|
|
delete menu;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public void OpenStageMenu(int client, const char[] mapName)
|
|
{
|
|
Menu menu = new Menu(MenuHandler_SelectStage);
|
|
menu.SetTitle("[UNLOZE Racetimer] Stages on: %s", mapName);
|
|
menu.ExitBackButton = true;
|
|
|
|
ArrayList stages = new ArrayList();
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
g_hMapStageCategoryCvars.GetArray(i, entry);
|
|
if (!StrEqual(entry.map, mapName))
|
|
{
|
|
continue;
|
|
}
|
|
if (stages.FindValue(entry.stage) != -1)
|
|
{
|
|
continue;
|
|
}
|
|
stages.Push(entry.stage);
|
|
}
|
|
stages.Sort(Sort_Ascending, Sort_Integer);
|
|
|
|
for (int i = 0; i < stages.Length; i++)
|
|
{
|
|
int stage = stages.Get(i);
|
|
char info[16];
|
|
IntToString(stage, info, sizeof(info));
|
|
char display[32];
|
|
Format(display, sizeof(display), "Stage %i", stage);
|
|
menu.AddItem(info, display);
|
|
}
|
|
delete stages;
|
|
|
|
if (menu.ItemCount == 0)
|
|
{
|
|
menu.AddItem("-1", "No stages found", ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 0);
|
|
}
|
|
|
|
public int MenuHandler_SelectStage(Menu menu, MenuAction action, int client, int selection)
|
|
{
|
|
switch (action)
|
|
{
|
|
case MenuAction_Select:
|
|
{
|
|
char info[16];
|
|
menu.GetItem(selection, info, sizeof(info));
|
|
int stage = StringToInt(info);
|
|
OpenCategoryMenu(client, g_sSelectedMap[client], stage);
|
|
}
|
|
case MenuAction_Cancel:
|
|
{
|
|
if (selection == MenuCancel_ExitBack)
|
|
{
|
|
OverAllMapsMenu(client);
|
|
}
|
|
}
|
|
case MenuAction_End:
|
|
{
|
|
delete menu;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public void OpenCategoryMenu(int client, const char[] mapName, int stage)
|
|
{
|
|
g_iSelectedStage[client] = stage;
|
|
|
|
Menu menu = new Menu(MenuHandler_SelectCategory);
|
|
menu.SetTitle("[UNLOZE Racetimer] %s - Stage %i", mapName, stage);
|
|
menu.ExitBackButton = true;
|
|
menu.Pagination = 4; //otherwise the text cuts off.
|
|
|
|
int categoryNumber = 0;
|
|
for (int i = 0; i < g_hMapStageCategoryCvars.Length; i++)
|
|
{
|
|
MapStageCategoryCvars entry;
|
|
g_hMapStageCategoryCvars.GetArray(i, entry);
|
|
if (!StrEqual(entry.map, mapName) || entry.stage != stage)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
categoryNumber++;
|
|
char info[32];
|
|
Format(info, sizeof(info), "%i:%i", categoryNumber, entry.CvarsHash);
|
|
|
|
char header[32];
|
|
Format(header, sizeof(header), "Category %i", categoryNumber);
|
|
menu.AddItem(info, header);
|
|
menu.AddItem("-1", entry.serverCvars, ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
if (categoryNumber == 0)
|
|
{
|
|
menu.AddItem("-1", "No categories found", ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 0);
|
|
}
|
|
|
|
public int MenuHandler_SelectCategory(Menu menu, MenuAction action, int client, int selection)
|
|
{
|
|
switch (action)
|
|
{
|
|
case MenuAction_Select:
|
|
{
|
|
char info[32];
|
|
menu.GetItem(selection, info, sizeof(info));
|
|
|
|
char parts[2][16];
|
|
ExplodeString(info, ":", parts, 2, sizeof(parts[]));
|
|
int categoryNumber = StringToInt(parts[0]);
|
|
int cvarsHash = StringToInt(parts[1]);
|
|
|
|
OpenCategoryRecordsMenu(client, g_sSelectedMap[client], g_iSelectedStage[client], categoryNumber, cvarsHash);
|
|
}
|
|
case MenuAction_Cancel:
|
|
{
|
|
if (selection == MenuCancel_ExitBack)
|
|
{
|
|
OpenStageMenu(client, g_sSelectedMap[client]);
|
|
}
|
|
}
|
|
case MenuAction_End:
|
|
{
|
|
delete menu;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public void OpenCategoryRecordsMenu(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash)
|
|
{
|
|
if (StrEqual(mapName, g_cMapname))
|
|
{
|
|
OpenCurrentMapCategoryRecordsMenu(client, mapName, stage, categoryNumber, cvarsHash);
|
|
}
|
|
else
|
|
{
|
|
FetchOtherMapCategoryRecords(client, mapName, stage, categoryNumber, cvarsHash);
|
|
}
|
|
}
|
|
|
|
public void OpenCurrentMapCategoryRecordsMenu(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash)
|
|
{
|
|
char ownTime[MAXPLAYERS + 1];
|
|
char formatted[32];
|
|
int ownPosition = -1;
|
|
for (int i = 0; i < g_hCurrentMapClientRecords.Length; i++)
|
|
{
|
|
CurrentMapClientRecords entry;
|
|
g_hCurrentMapClientRecords.GetArray(i, entry);
|
|
if (entry.client == client && entry.stage == stage && entry.CvarsHash == cvarsHash)
|
|
{
|
|
strcopy(ownTime, sizeof(ownTime), entry.csTime_record);
|
|
ownPosition = entry.position;
|
|
FormatTime(formatted, sizeof(formatted), "%m/%d/%Y %H:%M", entry.recordedAt);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Menu menu = new Menu(MenuHandler_RecordsInfo);
|
|
menu.ExitBackButton = true;
|
|
if (ownPosition != -1)
|
|
{
|
|
menu.SetTitle("%s - Stage %i - Category %i\nYour time: %s (#%i): %s", mapName, stage, categoryNumber, ownTime, ownPosition, formatted);
|
|
}
|
|
else
|
|
{
|
|
menu.SetTitle("%s - Stage %i - Category %i\nYou have no record here yet", mapName, stage, categoryNumber);
|
|
}
|
|
|
|
int position = 0;
|
|
for (int i = 0; i < g_hCurrentMapAllTopRecords.Length; i++)
|
|
{
|
|
CurrentMapAllTopRecords entry;
|
|
g_hCurrentMapAllTopRecords.GetArray(i, entry);
|
|
if (entry.stage != stage || entry.CvarsHash != cvarsHash)
|
|
{
|
|
continue;
|
|
}
|
|
position++;
|
|
FormatTime(formatted, sizeof(formatted), "%m/%d/%Y %H:%M", entry.recordedAt);
|
|
char display[g_dLength];
|
|
Format(display, sizeof(display), "#%i: %s - %s: %s", position, entry.csTime_record, entry.sName, formatted);
|
|
menu.AddItem("-1", display, ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
if (g_hCurrentMapAllTopRecords.Length == 0)
|
|
{
|
|
menu.AddItem("-1", "No records yet", ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 0);
|
|
}
|
|
|
|
public void FetchOtherMapCategoryRecords(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash)
|
|
{
|
|
char serverTag[64];
|
|
FormatServerTag(serverTag);
|
|
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery),"select ti2.steam_name, ti.time_value, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where zc.map_name = '%s' and zc.stage = '%i' and zc.cvars_hash = '%i' and zc.server_tag = '%s' and ti.is_invalid = 0 and zc.is_invalid = 0 order by ti.time_value asc limit 200", mapName, stage, cvarsHash, serverTag);
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteCell(GetClientSerial(client));
|
|
hDataPack.WriteString(mapName);
|
|
hDataPack.WriteCell(stage);
|
|
hDataPack.WriteCell(categoryNumber);
|
|
hDataPack.WriteCell(cvarsHash);
|
|
g_dDatabase.Query(SQL_OnOtherMapTopRecordsFetched, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
public void SQL_OnOtherMapTopRecordsFetched(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
int clientSerial = data.ReadCell();
|
|
char mapName[512];
|
|
data.ReadString(mapName, sizeof(mapName));
|
|
int stage = data.ReadCell();
|
|
int categoryNumber = data.ReadCell();
|
|
int cvarsHash = data.ReadCell();
|
|
delete data;
|
|
|
|
int client;
|
|
if ((client = GetClientFromSerial(clientSerial)) == 0)
|
|
{
|
|
delete results;
|
|
return;
|
|
}
|
|
|
|
if (!db || strlen(error))
|
|
{
|
|
LogError("Query error fetching other-map top records: %s", error);
|
|
delete results;
|
|
PrintToChat(client, "Failed to load records, try again.");
|
|
return;
|
|
}
|
|
|
|
ArrayList topRecords = new ArrayList(sizeof(CurrentMapAllTopRecords));
|
|
while (results.RowCount && results.FetchRow())
|
|
{
|
|
CurrentMapAllTopRecords entry;
|
|
results.FetchString(0, entry.sName, sizeof(entry.sName));
|
|
results.FetchString(1, entry.csTime_record, sizeof(entry.csTime_record));
|
|
entry.stage = stage;
|
|
entry.CvarsHash = cvarsHash;
|
|
entry.recordedAt = results.FetchInt(2);
|
|
topRecords.PushArray(entry);
|
|
}
|
|
delete results;
|
|
|
|
FetchOtherMapOwnPosition(client, mapName, stage, categoryNumber, cvarsHash, topRecords);
|
|
}
|
|
|
|
public void FetchOtherMapOwnPosition(int client, const char[] mapName, int stage, int categoryNumber, int cvarsHash, ArrayList topRecords)
|
|
{
|
|
char serverTag[64];
|
|
FormatServerTag(serverTag);
|
|
|
|
char sQuery[g_dQueryLength];
|
|
Format(sQuery, sizeof(sQuery), "select ti.time_value, (select count(*) + 1 from `timer_records` ti3 inner join `zone_categories` zc2 on ti3.zone_category_id = zc2.id where zc2.map_name = '%s' and zc2.stage = %i and zc2.cvars_hash = %i and zc2.server_tag = '%s' and ti3.time_value < ti.time_value and ti3.is_invalid = 0 and zc2.is_invalid = 0) as position, UNIX_TIMESTAMP(ti2.recorded_at) as recorded_at from `timer_records` ti inner join `timer_improvements` ti2 on ti.improvement_id = ti2.id inner join `zone_categories` zc on ti.zone_category_id = zc.id where ti2.steam_auth = '%s' and zc.map_name = '%s' and zc.stage = %i and zc.cvars_hash = %i and ti.is_invalid = 0 and zc.is_invalid = 0 order by ti.time_value asc limit 1", mapName, stage, cvarsHash, serverTag, g_csSID[client], mapName, stage, cvarsHash);
|
|
|
|
DataPack hDataPack = new DataPack();
|
|
hDataPack.WriteCell(GetClientSerial(client));
|
|
hDataPack.WriteString(mapName);
|
|
hDataPack.WriteCell(stage);
|
|
hDataPack.WriteCell(categoryNumber);
|
|
hDataPack.WriteCell(view_as<int>(topRecords));
|
|
g_dDatabase.Query(SQL_OnOtherMapOwnPositionFetched, sQuery, hDataPack, DBPrio_Normal);
|
|
}
|
|
|
|
public void SQL_OnOtherMapOwnPositionFetched(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
ResetPack(data);
|
|
int clientSerial = data.ReadCell();
|
|
char mapName[512];
|
|
data.ReadString(mapName, sizeof(mapName));
|
|
int stage = data.ReadCell();
|
|
int categoryNumber = data.ReadCell();
|
|
ArrayList topRecords = view_as<ArrayList>(data.ReadCell());
|
|
delete data;
|
|
|
|
int client;
|
|
if ((client = GetClientFromSerial(clientSerial)) == 0)
|
|
{
|
|
delete results;
|
|
delete topRecords;
|
|
return;
|
|
}
|
|
|
|
bool hasOwnRecord = false;
|
|
char ownTime[32];
|
|
int ownPosition = 0;
|
|
char formattedOwn[32];
|
|
if (!db || strlen(error))
|
|
{
|
|
LogError("Query error fetching own position: %s", error);
|
|
}
|
|
else if (results.RowCount && results.FetchRow())
|
|
{
|
|
results.FetchString(0, ownTime, sizeof(ownTime));
|
|
ownPosition = results.FetchInt(1);
|
|
FormatTime(formattedOwn, sizeof(formattedOwn), "%m/%d/%Y %H:%M", results.FetchInt(2));
|
|
hasOwnRecord = true;
|
|
}
|
|
delete results;
|
|
|
|
Menu menu = new Menu(MenuHandler_RecordsInfo);
|
|
menu.ExitBackButton = true;
|
|
if (hasOwnRecord)
|
|
{
|
|
menu.SetTitle("%s - Stage %i - Category %i\nYour time: %s (#%i): %s", mapName, stage, categoryNumber, ownTime, ownPosition, formattedOwn);
|
|
}
|
|
else
|
|
{
|
|
menu.SetTitle("%s - Stage %i - Category %i\nYou have no record here yet", mapName, stage, categoryNumber);
|
|
}
|
|
|
|
for (int i = 0; i < topRecords.Length; i++)
|
|
{
|
|
CurrentMapAllTopRecords entry;
|
|
topRecords.GetArray(i, entry);
|
|
char formatted[32];
|
|
FormatTime(formatted, sizeof(formatted), "%m/%d/%Y %H:%M", entry.recordedAt);
|
|
char display[g_dLength];
|
|
Format(display, sizeof(display), "#%i: %s - %s: %s", i + 1, entry.csTime_record, entry.sName, formatted);
|
|
menu.AddItem("-1", display, ITEMDRAW_DISABLED);
|
|
}
|
|
if (topRecords.Length == 0)
|
|
{
|
|
menu.AddItem("-1", "No records yet", ITEMDRAW_DISABLED);
|
|
}
|
|
delete topRecords;
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 0);
|
|
}
|
|
|
|
public int MenuHandler_RecordsInfo(Menu menu, MenuAction action, int client, int selection)
|
|
{
|
|
if (action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
else if (action == MenuAction_Cancel)
|
|
{
|
|
if (selection == MenuCancel_ExitBack)
|
|
{
|
|
OpenCategoryMenu(client, g_sSelectedMap[client], g_iSelectedStage[client]);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//----------------------------------------------------------------------------------------------------
|
|
stock bool IsValidClient(int client)
|
|
{
|
|
if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
stock int FNV1aHash(const char[] str)
|
|
{
|
|
int hash = 0x811C9DC5; // FNV offset basis
|
|
int len = strlen(str);
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
hash ^= str[i];
|
|
hash *= 16777619; // FNV prime
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
public Action OnClientCommand(int client, int argc)
|
|
{
|
|
if (!IsValidClient(client) || argc < 1)
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
char sArgs[64];
|
|
char sTargetName[MAX_TARGET_LENGTH];
|
|
int iTargets[MAXPLAYERS];
|
|
bool bIsML;
|
|
GetCmdArg(1, sArgs, sizeof(sArgs));
|
|
|
|
if ((ProcessTargetString(sArgs, client, iTargets, MAXPLAYERS, COMMAND_FILTER_NO_BOTS, sTargetName, sizeof(sTargetName), bIsML)) <= 0)
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
int target = iTargets[0];
|
|
if (!g_bHumansAllowedTime[target])
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
if (CheckCommandAccess(client, "", ADMFLAG_GENERIC, false))
|
|
{
|
|
PrintToChat(target, "Disabled your timer due to admin interferrence caused by admin %N.", client);
|
|
g_bHumansAllowedTime[target] = false;
|
|
ResetCurrentClientRaceZone(target);
|
|
}
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
void ResetCurrentClientRaceZone(int client)
|
|
{
|
|
g_CurrentClientRaceZone[client].client = 0;
|
|
g_CurrentClientRaceZone[client].csTime_record[0] = '\0';
|
|
g_CurrentClientRaceZone[client].stage = 0;
|
|
g_CurrentClientRaceZone[client].CvarsHash = 0;
|
|
g_CurrentClientRaceZone[client].position = 0;
|
|
g_CurrentClientRaceZone[client].recordedAt = 0;
|
|
}
|