#pragma semicolon 1 #define DEBUG #define PLUGIN_AUTHOR "jenz" #define PLUGIN_VERSION "2.0" #define g_dLength 400 #define g_dQueryLength 2048 #include #include #include #include #include #include #include #include #include #include #include #include #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(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(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; }