mirror of
https://github.com/shavitush/bhoptimer.git
synced 2025-12-07 10:28:26 +00:00
2720 lines
67 KiB
SourcePawn
2720 lines
67 KiB
SourcePawn
/*
|
|
* shavit's Timer - World Records
|
|
* by: shavit
|
|
*
|
|
* This file is part of shavit's Timer.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, version 3.0, as published by the
|
|
* Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <sourcemod>
|
|
#include <convar_class>
|
|
#include <dhooks>
|
|
|
|
#undef REQUIRE_PLUGIN
|
|
#include <shavit>
|
|
#include <adminmenu>
|
|
|
|
#pragma newdecls required
|
|
#pragma semicolon 1
|
|
|
|
// #define DEBUG
|
|
|
|
enum struct wrcache_t
|
|
{
|
|
int iLastStyle;
|
|
int iLastTrack;
|
|
int iPagePosition;
|
|
bool bForceStyle;
|
|
bool bPendingMenu;
|
|
char sClientMap[PLATFORM_MAX_PATH];
|
|
float fWRs[STYLE_LIMIT];
|
|
}
|
|
|
|
enum struct stagetimewrcp_t
|
|
{
|
|
float fTime;
|
|
int iAuth;
|
|
}
|
|
|
|
bool gB_Late = false;
|
|
bool gB_Rankings = false;
|
|
bool gB_Stats = false;
|
|
|
|
// forwards
|
|
Handle gH_OnWorldRecord = null;
|
|
Handle gH_OnFinish_Post = null;
|
|
Handle gH_OnWRDeleted = null;
|
|
Handle gH_OnWorstRecord = null;
|
|
Handle gH_OnFinishMessage = null;
|
|
Handle gH_OnWorldRecordsCached = null;
|
|
|
|
// database handle
|
|
Database2 gH_SQL = null;
|
|
bool gB_Connected = false;
|
|
|
|
// cache
|
|
wrcache_t gA_WRCache[MAXPLAYERS+1];
|
|
StringMap gSM_StyleCommands = null;
|
|
|
|
char gS_Map[PLATFORM_MAX_PATH];
|
|
ArrayList gA_ValidMaps = null;
|
|
|
|
// current wr stats
|
|
float gF_WRTime[STYLE_LIMIT][TRACKS_SIZE];
|
|
int gI_WRRecordID[STYLE_LIMIT][TRACKS_SIZE];
|
|
int gI_WRSteamID[STYLE_LIMIT][TRACKS_SIZE];
|
|
StringMap gSM_WRNames = null;
|
|
ArrayList gA_Leaderboard[STYLE_LIMIT][TRACKS_SIZE];
|
|
bool gB_LoadedCache[MAXPLAYERS+1];
|
|
float gF_PlayerRecord[MAXPLAYERS+1][STYLE_LIMIT][TRACKS_SIZE];
|
|
int gI_PlayerCompletion[MAXPLAYERS+1][STYLE_LIMIT][TRACKS_SIZE];
|
|
|
|
// admin menu
|
|
TopMenu gH_AdminMenu = null;
|
|
TopMenuObject gH_TimerCommands = INVALID_TOPMENUOBJECT;
|
|
|
|
// table prefix
|
|
char gS_MySQLPrefix[32];
|
|
|
|
// cvars
|
|
Convar gCV_RecordsLimit = null;
|
|
Convar gCV_RecentLimit = null;
|
|
Convar gCV_NewDBConnection = null;
|
|
|
|
// timer settings
|
|
int gI_Styles = 0;
|
|
stylestrings_t gS_StyleStrings[STYLE_LIMIT];
|
|
|
|
// chat settings
|
|
chatstrings_t gS_ChatStrings;
|
|
|
|
// stage times (wrs/pbs)
|
|
float gA_StageWR[STYLE_LIMIT][TRACKS_SIZE][MAX_STAGES]; // WR run's stage times
|
|
//stagetimewrcp_t gA_StageWRCP[STYLE_LIMIT][TRACKS_SIZE];
|
|
ArrayList gA_StagePB[MAXPLAYERS+1][STYLE_LIMIT][TRACKS_SIZE]; // player's best WRCP times or something
|
|
float gA_StageTimes[MAXPLAYERS+1][MAX_STAGES]; // player's current run stage times
|
|
|
|
public Plugin myinfo =
|
|
{
|
|
name = "[shavit] World Records",
|
|
author = "shavit",
|
|
description = "World records for shavit's bhop timer.",
|
|
version = SHAVIT_VERSION,
|
|
url = "https://github.com/shavitush/bhoptimer"
|
|
}
|
|
|
|
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
|
|
{
|
|
// natives
|
|
CreateNative("Shavit_GetClientPB", Native_GetClientPB);
|
|
CreateNative("Shavit_SetClientPB", Native_SetClientPB);
|
|
CreateNative("Shavit_GetClientCompletions", Native_GetClientCompletions);
|
|
CreateNative("Shavit_GetRankForTime", Native_GetRankForTime);
|
|
CreateNative("Shavit_GetRecordAmount", Native_GetRecordAmount);
|
|
CreateNative("Shavit_GetTimeForRank", Native_GetTimeForRank);
|
|
CreateNative("Shavit_GetWorldRecord", Native_GetWorldRecord);
|
|
CreateNative("Shavit_GetWRName", Native_GetWRName);
|
|
CreateNative("Shavit_GetWRRecordID", Native_GetWRRecordID);
|
|
CreateNative("Shavit_ReloadLeaderboards", Native_ReloadLeaderboards);
|
|
CreateNative("Shavit_WR_DeleteMap", Native_WR_DeleteMap);
|
|
CreateNative("Shavit_DeleteWR", Native_DeleteWR);
|
|
CreateNative("Shavit_GetStageWR", Native_GetStageWR);
|
|
CreateNative("Shavit_GetStagePB", Native_GetStagePB);
|
|
|
|
// registers library, check "bool LibraryExists(const char[] name)" in order to use with other plugins
|
|
RegPluginLibrary("shavit-wr");
|
|
|
|
gB_Late = late;
|
|
|
|
return APLRes_Success;
|
|
}
|
|
|
|
public void OnPluginStart()
|
|
{
|
|
LoadTranslations("common.phrases");
|
|
LoadTranslations("shavit-common.phrases");
|
|
LoadTranslations("shavit-wr.phrases");
|
|
|
|
#if defined DEBUG
|
|
RegConsoleCmd("sm_junk", Command_Junk);
|
|
RegConsoleCmd("sm_printleaderboards", Command_PrintLeaderboards);
|
|
#endif
|
|
|
|
gSM_WRNames = new StringMap();
|
|
gSM_StyleCommands = new StringMap();
|
|
|
|
// forwards
|
|
gH_OnWorldRecord = CreateGlobalForward("Shavit_OnWorldRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
|
|
gH_OnFinish_Post = CreateGlobalForward("Shavit_OnFinish_Post", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
|
|
gH_OnWRDeleted = CreateGlobalForward("Shavit_OnWRDeleted", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_String);
|
|
gH_OnWorstRecord = CreateGlobalForward("Shavit_OnWorstRecord", ET_Event, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
|
|
gH_OnFinishMessage = CreateGlobalForward("Shavit_OnFinishMessage", ET_Event, Param_Cell, Param_CellByRef, Param_Array, Param_Cell, Param_Cell, Param_String, Param_Cell, Param_String, Param_Cell);
|
|
gH_OnWorldRecordsCached = CreateGlobalForward("Shavit_OnWorldRecordsCached", ET_Event);
|
|
|
|
// player commands
|
|
RegConsoleCmd("sm_wr", Command_WorldRecord, "View the leaderboard of a map. Usage: sm_wr [map]");
|
|
RegConsoleCmd("sm_worldrecord", Command_WorldRecord, "View the leaderboard of a map. Usage: sm_worldrecord [map]");
|
|
|
|
RegConsoleCmd("sm_bwr", Command_WorldRecord, "View the leaderboard of a map. Usage: sm_bwr [map] [bonus number]");
|
|
RegConsoleCmd("sm_bworldrecord", Command_WorldRecord, "View the leaderboard of a map. Usage: sm_bworldrecord [map] [bonus number]");
|
|
RegConsoleCmd("sm_bonusworldrecord", Command_WorldRecord, "View the leaderboard of a map. Usage: sm_bonusworldrecord [map] [bonus number]");
|
|
|
|
RegConsoleCmd("sm_recent", Command_RecentRecords, "View the recent #1 times set.");
|
|
RegConsoleCmd("sm_recentrecords", Command_RecentRecords, "View the recent #1 times set.");
|
|
RegConsoleCmd("sm_rr", Command_RecentRecords, "View the recent #1 times set.");
|
|
|
|
// delete records
|
|
RegAdminCmd("sm_delete", Command_Delete, ADMFLAG_RCON, "Opens a record deletion menu interface.");
|
|
RegAdminCmd("sm_deleterecord", Command_Delete, ADMFLAG_RCON, "Opens a record deletion menu interface.");
|
|
RegAdminCmd("sm_deleterecords", Command_Delete, ADMFLAG_RCON, "Opens a record deletion menu interface.");
|
|
RegAdminCmd("sm_deleteall", Command_DeleteAll, ADMFLAG_RCON, "Deletes all the records for this map.");
|
|
|
|
// cvars
|
|
gCV_RecordsLimit = new Convar("shavit_wr_recordlimit", "50", "Limit of records shown in the WR menu.\nAdvised to not set above 1,000 because scrolling through so many pages is useless.\n(And can also cause the command to take long time to run)", 0, true, 1.0);
|
|
gCV_RecentLimit = new Convar("shavit_wr_recentlimit", "50", "Limit of records shown in the RR menu.", 0, true, 1.0);
|
|
gCV_NewDBConnection = new Convar("shavit_wr_new_db_connection", "0", "Use a new DB connection for rankings. This should help with point-recalculation blocking other queries from running.\nYou probably don't need to use this unless you have a DB with hundreds of thousands of player times.\n0 - Reuses shavit-core DB connection.\n1 - Creates a new DB connection.", 0, true, 0.0, true, 1.0);
|
|
|
|
Convar.AutoExecConfig();
|
|
|
|
// modules
|
|
gB_Rankings = LibraryExists("shavit-rankings");
|
|
gB_Stats = LibraryExists("shavit-stats");
|
|
|
|
// cache
|
|
gA_ValidMaps = new ArrayList(ByteCountToCells(PLATFORM_MAX_PATH));
|
|
|
|
if(gB_Late)
|
|
{
|
|
Shavit_OnStyleConfigLoaded(Shavit_GetStyleCount());
|
|
Shavit_OnChatConfigLoaded();
|
|
Shavit_OnDatabaseLoaded();
|
|
}
|
|
|
|
CreateTimer(2.5, Timer_Dominating, 0, TIMER_REPEAT);
|
|
}
|
|
|
|
public void OnAllPluginsLoaded()
|
|
{
|
|
// admin menu
|
|
if(LibraryExists("adminmenu") && ((gH_AdminMenu = GetAdminTopMenu()) != null))
|
|
{
|
|
OnAdminMenuReady(gH_AdminMenu);
|
|
}
|
|
}
|
|
|
|
public void OnAdminMenuCreated(Handle topmenu)
|
|
{
|
|
if(gH_AdminMenu == null || (topmenu == gH_AdminMenu && gH_TimerCommands != INVALID_TOPMENUOBJECT))
|
|
{
|
|
return;
|
|
}
|
|
|
|
gH_TimerCommands = gH_AdminMenu.AddCategory("Timer Commands", CategoryHandler, "shavit_admin", ADMFLAG_RCON);
|
|
}
|
|
|
|
public void CategoryHandler(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength)
|
|
{
|
|
if(action == TopMenuAction_DisplayTitle)
|
|
{
|
|
FormatEx(buffer, maxlength, "%T:", "TimerCommands", param);
|
|
}
|
|
|
|
else if(action == TopMenuAction_DisplayOption)
|
|
{
|
|
FormatEx(buffer, maxlength, "%T", "TimerCommands", param);
|
|
}
|
|
}
|
|
|
|
public void OnAdminMenuReady(Handle topmenu)
|
|
{
|
|
if((gH_AdminMenu = GetAdminTopMenu()) != null)
|
|
{
|
|
if(gH_TimerCommands == INVALID_TOPMENUOBJECT)
|
|
{
|
|
gH_TimerCommands = gH_AdminMenu.FindCategory("Timer Commands");
|
|
|
|
if(gH_TimerCommands == INVALID_TOPMENUOBJECT)
|
|
{
|
|
OnAdminMenuCreated(topmenu);
|
|
}
|
|
}
|
|
|
|
gH_AdminMenu.AddItem("sm_deleteall", AdminMenu_DeleteAll, gH_TimerCommands, "sm_deleteall", ADMFLAG_RCON);
|
|
gH_AdminMenu.AddItem("sm_delete", AdminMenu_Delete, gH_TimerCommands, "sm_delete", ADMFLAG_RCON);
|
|
}
|
|
}
|
|
|
|
public void AdminMenu_Delete(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength)
|
|
{
|
|
if(action == TopMenuAction_DisplayOption)
|
|
{
|
|
FormatEx(buffer, maxlength, "%t", "DeleteSingleRecord");
|
|
}
|
|
|
|
else if(action == TopMenuAction_SelectOption)
|
|
{
|
|
Command_Delete(param, 0);
|
|
}
|
|
}
|
|
|
|
public void AdminMenu_DeleteAll(Handle topmenu, TopMenuAction action, TopMenuObject object_id, int param, char[] buffer, int maxlength)
|
|
{
|
|
if(action == TopMenuAction_DisplayOption)
|
|
{
|
|
FormatEx(buffer, maxlength, "%t", "DeleteAllRecords");
|
|
}
|
|
|
|
else if(action == TopMenuAction_SelectOption)
|
|
{
|
|
Command_DeleteAll(param, 0);
|
|
}
|
|
}
|
|
|
|
public void OnLibraryAdded(const char[] name)
|
|
{
|
|
if(StrEqual(name, "shavit-rankings"))
|
|
{
|
|
gB_Rankings = true;
|
|
}
|
|
|
|
else if(StrEqual(name, "shavit-stats"))
|
|
{
|
|
gB_Stats = true;
|
|
}
|
|
|
|
else if (StrEqual(name, "adminmenu"))
|
|
{
|
|
if ((gH_AdminMenu = GetAdminTopMenu()) != null)
|
|
{
|
|
OnAdminMenuReady(gH_AdminMenu);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnLibraryRemoved(const char[] name)
|
|
{
|
|
if(StrEqual(name, "shavit-rankings"))
|
|
{
|
|
gB_Rankings = false;
|
|
}
|
|
|
|
else if(StrEqual(name, "shavit-stats"))
|
|
{
|
|
gB_Stats = false;
|
|
}
|
|
|
|
else if (StrEqual(name, "adminmenu"))
|
|
{
|
|
gH_AdminMenu = null;
|
|
gH_TimerCommands = INVALID_TOPMENUOBJECT;
|
|
}
|
|
}
|
|
|
|
public Action Timer_Dominating(Handle timer)
|
|
{
|
|
bool bHasWR[MAXPLAYERS+1];
|
|
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (IsValidClient(i))
|
|
{
|
|
char sSteamID[20];
|
|
IntToString(GetSteamAccountID(i), sSteamID, sizeof(sSteamID));
|
|
bHasWR[i] = gSM_WRNames.GetString(sSteamID, sSteamID, sizeof(sSteamID));
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (!IsValidClient(i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (int x = 1; x <= MaxClients; x++)
|
|
{
|
|
SetEntProp(i, Prop_Send, "m_bPlayerDominatingMe", bHasWR[x], 1, x);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ResetWRs()
|
|
{
|
|
gSM_WRNames.Clear();
|
|
|
|
any empty_cells[TRACKS_SIZE];
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
gF_WRTime[i] = empty_cells;
|
|
gI_WRRecordID[i] = empty_cells;
|
|
gI_WRSteamID[i] = empty_cells;
|
|
}
|
|
}
|
|
|
|
void ResetLeaderboards()
|
|
{
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
for(int j = 0; j < TRACKS_SIZE; j++)
|
|
{
|
|
gA_Leaderboard[i][j].Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnMapStart()
|
|
{
|
|
if(!gB_Connected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetLowercaseMapName(gS_Map);
|
|
|
|
UpdateWRCache();
|
|
|
|
gA_ValidMaps.Clear();
|
|
gA_ValidMaps.PushString(gS_Map);
|
|
|
|
char sQuery[128];
|
|
FormatEx(sQuery, 128, "SELECT map FROM %smapzones GROUP BY map;", gS_MySQLPrefix);
|
|
gH_SQL.Query(SQL_UpdateMaps_Callback, sQuery, 0, DBPrio_Low);
|
|
|
|
for(int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if(IsValidClient(i) && IsClientAuthorized(i))
|
|
{
|
|
OnClientAuthorized(i, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnMapEnd()
|
|
{
|
|
ResetWRs();
|
|
}
|
|
|
|
public void SQL_UpdateMaps_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR maps cache update) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
results.FetchString(0, sMap, sizeof(sMap));
|
|
LowercaseString(sMap);
|
|
|
|
if(gA_ValidMaps.FindString(sMap) == -1)
|
|
{
|
|
gA_ValidMaps.PushString(sMap);
|
|
}
|
|
}
|
|
|
|
SortADTArray(gA_ValidMaps, Sort_Ascending, Sort_String);
|
|
}
|
|
|
|
void RegisterWRCommands(int style)
|
|
{
|
|
char sStyleCommands[32][32];
|
|
int iCommands = ExplodeString(gS_StyleStrings[style].sChangeCommand, ";", sStyleCommands, 32, 32, false);
|
|
|
|
char sDescription[128];
|
|
FormatEx(sDescription, 128, "View the leaderboard of a map on style %s.", gS_StyleStrings[style].sStyleName);
|
|
|
|
for (int x = 0; x < iCommands; x++)
|
|
{
|
|
TrimString(sStyleCommands[x]);
|
|
StripQuotes(sStyleCommands[x]);
|
|
|
|
if (strlen(sStyleCommands[x]) < 1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
char sCommand[40];
|
|
FormatEx(sCommand, sizeof(sCommand), "sm_wr%s", sStyleCommands[x]);
|
|
gSM_StyleCommands.SetValue(sCommand, style);
|
|
RegConsoleCmd(sCommand, Command_WorldRecord_Style, sDescription);
|
|
|
|
FormatEx(sCommand, sizeof(sCommand), "sm_bwr%s", sStyleCommands[x]);
|
|
gSM_StyleCommands.SetValue(sCommand, style);
|
|
RegConsoleCmd(sCommand, Command_WorldRecord_Style, sDescription);
|
|
}
|
|
}
|
|
|
|
public void Shavit_OnStyleConfigLoaded(int styles)
|
|
{
|
|
for(int i = 0; i < STYLE_LIMIT; i++)
|
|
{
|
|
if (i < styles)
|
|
{
|
|
Shavit_GetStyleStringsStruct(i, gS_StyleStrings[i]);
|
|
RegisterWRCommands(i);
|
|
}
|
|
|
|
for (int j = 0; j < TRACKS_SIZE; j++)
|
|
{
|
|
if (i < styles)
|
|
{
|
|
if (gA_Leaderboard[i][j] == null)
|
|
{
|
|
gA_Leaderboard[i][j] = new ArrayList();
|
|
}
|
|
|
|
gA_Leaderboard[i][j].Clear();
|
|
}
|
|
else
|
|
{
|
|
delete gA_Leaderboard[i][j];
|
|
}
|
|
}
|
|
}
|
|
|
|
gI_Styles = styles;
|
|
}
|
|
|
|
public void Shavit_OnChatConfigLoaded()
|
|
{
|
|
Shavit_GetChatStringsStruct(gS_ChatStrings);
|
|
}
|
|
|
|
public void OnClientConnected(int client)
|
|
{
|
|
wrcache_t empty_cache;
|
|
gA_WRCache[client] = empty_cache;
|
|
|
|
gB_LoadedCache[client] = false;
|
|
|
|
any empty_cells[TRACKS_SIZE];
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
gF_PlayerRecord[client][i] = empty_cells;
|
|
gI_PlayerCompletion[client][i] = empty_cells;
|
|
}
|
|
}
|
|
|
|
public void OnClientAuthorized(int client)
|
|
{
|
|
if (gB_Connected && !IsFakeClient(client))
|
|
{
|
|
UpdateClientCache(client);
|
|
}
|
|
}
|
|
|
|
void UpdateClientCache(int client)
|
|
{
|
|
int iSteamID = GetSteamAccountID(client);
|
|
|
|
if(iSteamID == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery), "SELECT time, style, track, completions, exact_time_int FROM %splayertimes WHERE map = '%s' AND auth = %d;", gS_MySQLPrefix, gS_Map, iSteamID);
|
|
gH_SQL.Query(SQL_UpdateCache_Callback, sQuery, GetClientSerial(client), DBPrio_High);
|
|
}
|
|
|
|
public void SQL_UpdateCache_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (PB cache update) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
int client = GetClientFromSerial(data);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OnClientConnected(client);
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
int style = results.FetchInt(1);
|
|
int track = results.FetchInt(2);
|
|
|
|
if(style >= gI_Styles || style < 0 || track >= TRACKS_SIZE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
gF_PlayerRecord[client][style][track] = ExactTimeMaybe(results.FetchFloat(0), results.FetchInt(4));
|
|
gI_PlayerCompletion[client][style][track] = results.FetchInt(3);
|
|
|
|
}
|
|
|
|
gB_LoadedCache[client] = true;
|
|
}
|
|
|
|
void UpdateWRCache(int client = -1)
|
|
{
|
|
if (client == -1)
|
|
{
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (IsValidClient(i) && !IsFakeClient(i))
|
|
{
|
|
UpdateClientCache(i);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateClientCache(client);
|
|
}
|
|
|
|
char sQuery[512];
|
|
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"SELECT p.id, p.auth, p.style, p.track, p.time, u.name, p.exact_time_int FROM %swrs p JOIN %susers u ON p.auth = u.auth WHERE p.map = '%s';",
|
|
gS_MySQLPrefix, gS_MySQLPrefix, gS_Map);
|
|
|
|
gH_SQL.Query(SQL_UpdateWRCache_Callback, sQuery, client);
|
|
|
|
UpdateLeaderboards();
|
|
|
|
if (client != -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"SELECT style, track, auth, stage, time FROM `%sstagetimeswr` WHERE map = '%s';",
|
|
gS_MySQLPrefix, gS_Map);
|
|
|
|
gH_SQL.Query(SQL_UpdateWRStageTimes_Callback, sQuery);
|
|
}
|
|
|
|
public void SQL_UpdateWRCache_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR cache update) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
ResetWRs();
|
|
|
|
// setup cache again, dynamically and not hardcoded
|
|
while(results.FetchRow())
|
|
{
|
|
int iStyle = results.FetchInt(2);
|
|
int iTrack = results.FetchInt(3);
|
|
|
|
if(iStyle >= gI_Styles || iStyle < 0 || Shavit_GetStyleSettingInt(iStyle, "unranked"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
gI_WRRecordID[iStyle][iTrack] = results.FetchInt(0);
|
|
gF_WRTime[iStyle][iTrack] = ExactTimeMaybe(results.FetchFloat(4), results.FetchInt(6));
|
|
gI_WRSteamID[iStyle][iTrack] = results.FetchInt(1);
|
|
|
|
char sSteamID[20];
|
|
IntToString(gI_WRSteamID[iStyle][iTrack], sSteamID, sizeof(sSteamID));
|
|
|
|
char sName[MAX_NAME_LENGTH];
|
|
results.FetchString(5, sName, MAX_NAME_LENGTH);
|
|
ReplaceString(sName, MAX_NAME_LENGTH, "#", "?");
|
|
gSM_WRNames.SetString(sSteamID, sName, false);
|
|
}
|
|
|
|
Call_StartForward(gH_OnWorldRecordsCached);
|
|
Call_Finish();
|
|
}
|
|
|
|
public void SQL_UpdateWRStageTimes_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(!db || !results || error[0])
|
|
{
|
|
LogError("Timer (WR stage times cache) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
float empty_times[MAX_STAGES];
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
for(int j = 0; j < TRACKS_SIZE; j++)
|
|
{
|
|
gA_StageWR[i][j] = empty_times;
|
|
}
|
|
}
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
int style = results.FetchInt(0);
|
|
int track = results.FetchInt(1);
|
|
int stage = results.FetchInt(3);
|
|
|
|
gA_StageWR[style][track][stage] = results.FetchFloat(4);
|
|
}
|
|
}
|
|
|
|
public int Native_GetWorldRecord(Handle handler, int numParams)
|
|
{
|
|
return view_as<int>(gF_WRTime[GetNativeCell(1)][GetNativeCell(2)]);
|
|
}
|
|
|
|
public int Native_ReloadLeaderboards(Handle handler, int numParams)
|
|
{
|
|
UpdateWRCache();
|
|
}
|
|
|
|
public int Native_GetWRRecordID(Handle handler, int numParams)
|
|
{
|
|
SetNativeCellRef(2, gI_WRRecordID[GetNativeCell(1)][GetNativeCell(3)]);
|
|
}
|
|
|
|
public int Native_GetWRName(Handle handler, int numParams)
|
|
{
|
|
int iSteamID = gI_WRSteamID[GetNativeCell(1)][GetNativeCell(4)];
|
|
char sName[MAX_NAME_LENGTH];
|
|
|
|
if (iSteamID != 0)
|
|
{
|
|
char sSteamID[20];
|
|
IntToString(iSteamID, sSteamID, sizeof(sSteamID));
|
|
|
|
if (gSM_WRNames.GetString(sSteamID, sName, sizeof(sName)))
|
|
{
|
|
SetNativeString(2, sName, GetNativeCell(3));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SetNativeString(2, "invalid", GetNativeCell(3));
|
|
return 0;
|
|
}
|
|
|
|
public int Native_GetClientPB(Handle handler, int numParams)
|
|
{
|
|
return view_as<int>(gF_PlayerRecord[GetNativeCell(1)][GetNativeCell(2)][GetNativeCell(3)]);
|
|
}
|
|
|
|
public int Native_SetClientPB(Handle handler, int numParams)
|
|
{
|
|
int client = GetNativeCell(1);
|
|
int style = GetNativeCell(2);
|
|
int track = GetNativeCell(3);
|
|
float time = GetNativeCell(4);
|
|
|
|
gF_PlayerRecord[client][style][track] = time;
|
|
}
|
|
|
|
public int Native_GetPlayerPB(Handle handler, int numParams)
|
|
{
|
|
SetNativeCellRef(3, gF_PlayerRecord[GetNativeCell(1)][GetNativeCell(2)][GetNativeCell(4)]);
|
|
}
|
|
|
|
public int Native_GetClientCompletions(Handle handler, int numParams)
|
|
{
|
|
return gI_PlayerCompletion[GetNativeCell(1)][GetNativeCell(2)][GetNativeCell(3)];
|
|
}
|
|
|
|
public int Native_GetRankForTime(Handle handler, int numParams)
|
|
{
|
|
int style = GetNativeCell(1);
|
|
int track = GetNativeCell(3);
|
|
|
|
if(gA_Leaderboard[style][track] == null || gA_Leaderboard[style][track].Length == 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return GetRankForTime(style, GetNativeCell(2), track);
|
|
}
|
|
|
|
public int Native_GetRecordAmount(Handle handler, int numParams)
|
|
{
|
|
return GetRecordAmount(GetNativeCell(1), GetNativeCell(2));
|
|
}
|
|
|
|
public int Native_GetTimeForRank(Handle handler, int numParams)
|
|
{
|
|
int style = GetNativeCell(1);
|
|
int rank = GetNativeCell(2);
|
|
int track = GetNativeCell(3);
|
|
|
|
#if defined DEBUG
|
|
Shavit_PrintToChatAll("style %d | rank %d | track %d | amount %d", style, rank, track, GetRecordAmount(style, track));
|
|
#endif
|
|
|
|
if(rank > GetRecordAmount(style, track))
|
|
{
|
|
return view_as<int>(0.0);
|
|
}
|
|
|
|
return view_as<int>(gA_Leaderboard[style][track].Get(rank - 1));
|
|
}
|
|
|
|
public int Native_WR_DeleteMap(Handle handler, int numParams)
|
|
{
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
GetNativeString(1, sMap, sizeof(sMap));
|
|
LowercaseString(sMap);
|
|
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery), "DELETE FROM %splayertimes WHERE map = '%s';", gS_MySQLPrefix, sMap);
|
|
gH_SQL.Query(SQL_DeleteMap_Callback, sQuery, StrEqual(gS_Map, sMap, false), DBPrio_High);
|
|
}
|
|
|
|
void DeleteWRFinal(int style, int track, const char[] map, int steamid, int recordid, bool update_cache)
|
|
{
|
|
Call_StartForward(gH_OnWRDeleted);
|
|
Call_PushCell(style);
|
|
Call_PushCell(recordid);
|
|
Call_PushCell(track);
|
|
Call_PushCell(steamid);
|
|
Call_PushString(map);
|
|
Call_Finish();
|
|
|
|
if (update_cache)
|
|
{
|
|
UpdateWRCache();
|
|
}
|
|
}
|
|
|
|
public void DeleteWR_Callback(Database db, DBResultSet results, const char[] error, DataPack hPack)
|
|
{
|
|
hPack.Reset();
|
|
|
|
int style = hPack.ReadCell();
|
|
int track = hPack.ReadCell();
|
|
char map[PLATFORM_MAX_PATH];
|
|
hPack.ReadString(map, sizeof(map));
|
|
bool update_cache = view_as<bool>(hPack.ReadCell());
|
|
int steamid = hPack.ReadCell();
|
|
int recordid = hPack.ReadCell();
|
|
|
|
delete hPack;
|
|
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR DeleteWR) SQL query failed. Reason: %s", error);
|
|
return;
|
|
}
|
|
|
|
DeleteWRFinal(style, track, map, steamid, recordid, update_cache);
|
|
}
|
|
|
|
void DeleteWRInner(int recordid, int steamid, DataPack hPack)
|
|
{
|
|
hPack.WriteCell(steamid);
|
|
hPack.WriteCell(recordid);
|
|
|
|
char sQuery[169];
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"DELETE FROM %splayertimes WHERE id = %d;",
|
|
gS_MySQLPrefix, recordid);
|
|
gH_SQL.Query(DeleteWR_Callback, sQuery, hPack, DBPrio_High);
|
|
}
|
|
|
|
public void DeleteWRGetID_Callback(Database db, DBResultSet results, const char[] error, DataPack hPack)
|
|
{
|
|
if(results == null || !results.FetchRow())
|
|
{
|
|
LogError("Timer (WR DeleteWRGetID) SQL query failed. Reason: %s", error);
|
|
return;
|
|
}
|
|
|
|
DeleteWRInner(results.FetchInt(0), results.FetchInt(1), hPack);
|
|
}
|
|
|
|
void DeleteWR(int style, int track, const char[] map, int steamid, int recordid, bool delete_sql, bool update_cache)
|
|
{
|
|
if (delete_sql)
|
|
{
|
|
DataPack hPack = new DataPack();
|
|
hPack.WriteCell(style);
|
|
hPack.WriteCell(track);
|
|
hPack.WriteString(map);
|
|
hPack.WriteCell(update_cache);
|
|
|
|
char sQuery[512];
|
|
|
|
if (recordid == -1) // missing WR recordid thing...
|
|
{
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"SELECT id, auth FROM %swrs WHERE map = '%s' AND style = %d AND track = %d;",
|
|
gS_MySQLPrefix, map, style, track, gS_MySQLPrefix, map, style, track);
|
|
gH_SQL.Query(DeleteWRGetID_Callback, sQuery, hPack, DBPrio_High);
|
|
}
|
|
else
|
|
{
|
|
DeleteWRInner(recordid, steamid, hPack);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DeleteWRFinal(style, track, map, steamid, recordid, update_cache);
|
|
}
|
|
}
|
|
|
|
public int Native_DeleteWR(Handle handle, int numParams)
|
|
{
|
|
int style = GetNativeCell(1);
|
|
int track = GetNativeCell(2);
|
|
char map[PLATFORM_MAX_PATH];
|
|
GetNativeString(3, map, sizeof(map));
|
|
LowercaseString(map);
|
|
int steamid = GetNativeCell(4);
|
|
int recordid = GetNativeCell(5);
|
|
bool delete_sql = view_as<bool>(GetNativeCell(6));
|
|
bool update_cache = view_as<bool>(GetNativeCell(7));
|
|
|
|
DeleteWR(style, track, map, steamid, recordid, delete_sql, update_cache);
|
|
}
|
|
|
|
public int Native_GetStageWR(Handle plugin, int numParams)
|
|
{
|
|
int track = GetNativeCell(1);
|
|
int style = GetNativeCell(2);
|
|
int stage = GetNativeCell(3);
|
|
return view_as<int>(gA_StageWR[style][track][stage]);
|
|
}
|
|
|
|
public int Native_GetStagePB(Handle plugin, int numParams)
|
|
{
|
|
int client = GetNativeCell(1);
|
|
int track = GetNativeCell(2);
|
|
int style = GetNativeCell(3);
|
|
int stage = GetNativeCell(4);
|
|
float pb;
|
|
|
|
if (gA_StagePB[client][style][track] != null)
|
|
{
|
|
pb = gA_StagePB[client][style][track].Get(stage);
|
|
}
|
|
|
|
return view_as<int>(pb);
|
|
}
|
|
|
|
public void SQL_DeleteMap_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR deletemap) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
if(view_as<bool>(data))
|
|
{
|
|
OnMapStart();
|
|
}
|
|
}
|
|
|
|
#if defined DEBUG
|
|
// debug
|
|
public Action Command_Junk(int client, int args)
|
|
{
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"INSERT INTO %splayertimes (auth, map, time, jumps, date, style, strafes, sync) VALUES (%d, '%s', %f, %d, %d, 0, %d, %.02f);",
|
|
gS_MySQLPrefix, GetSteamAccountID(client), gS_Map, GetRandomFloat(10.0, 20.0), GetRandomInt(5, 15), GetTime(), GetRandomInt(5, 15), GetRandomFloat(50.0, 99.99));
|
|
|
|
SQL_LockDatabase(gH_SQL);
|
|
SQL_FastQuery(gH_SQL, sQuery);
|
|
SQL_UnlockDatabase(gH_SQL);
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_PrintLeaderboards(int client, int args)
|
|
{
|
|
char sArg[8];
|
|
GetCmdArg(1, sArg, 8);
|
|
|
|
int iStyle = StringToInt(sArg);
|
|
int iRecords = GetRecordAmount(iStyle, Track_Main);
|
|
|
|
ReplyToCommand(client, "Track: Main - Style: %d", iStyle);
|
|
ReplyToCommand(client, "Current PB: %f", gF_PlayerRecord[client][iStyle][0]);
|
|
ReplyToCommand(client, "Count: %d", iRecords);
|
|
ReplyToCommand(client, "Rank: %d", Shavit_GetRankForTime(iStyle, gF_PlayerRecord[client][iStyle][0], iStyle));
|
|
|
|
for(int i = 0; i < iRecords; i++)
|
|
{
|
|
ReplyToCommand(client, "#%d: %f", i, gA_Leaderboard[iStyle][0].Get(i));
|
|
}
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
#endif
|
|
|
|
int GetTrackRecordCount(int track)
|
|
{
|
|
int count = 0;
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
count += GetRecordAmount(i, track);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
public Action Command_Delete(int client, int args)
|
|
{
|
|
if(!IsValidClient(client))
|
|
{
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
Menu menu = new Menu(MenuHandler_Delete_First);
|
|
menu.SetTitle("%T\n ", "DeleteTrackSingle", client);
|
|
|
|
for(int i = 0; i < TRACKS_SIZE; i++)
|
|
{
|
|
char sInfo[8];
|
|
IntToString(i, sInfo, 8);
|
|
|
|
int records = GetTrackRecordCount(i);
|
|
|
|
char sTrack[64];
|
|
GetTrackName(client, i, sTrack, 64);
|
|
|
|
if(records > 0)
|
|
{
|
|
Format(sTrack, 64, "%s (%T: %d)", sTrack, "WRRecord", client, records);
|
|
}
|
|
|
|
menu.AddItem(sInfo, sTrack, (records > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public int MenuHandler_Delete_First(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char info[16];
|
|
menu.GetItem(param2, info, 16);
|
|
gA_WRCache[param1].iLastTrack = StringToInt(info);
|
|
|
|
DeleteSubmenu(param1);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DeleteSubmenu(int client)
|
|
{
|
|
Menu menu = new Menu(MenuHandler_Delete);
|
|
menu.SetTitle("%T\n ", "DeleteMenuTitle", client);
|
|
|
|
int[] styles = new int[gI_Styles];
|
|
Shavit_GetOrderedStyles(styles, gI_Styles);
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
int iStyle = styles[i];
|
|
|
|
if(Shavit_GetStyleSettingInt(iStyle, "enabled") == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char sInfo[8];
|
|
IntToString(iStyle, sInfo, 8);
|
|
|
|
char sDisplay[64];
|
|
FormatEx(sDisplay, 64, "%s (%T: %d)", gS_StyleStrings[iStyle].sStyleName, "WRRecord", client, GetRecordAmount(iStyle, gA_WRCache[client].iLastTrack));
|
|
|
|
menu.AddItem(sInfo, sDisplay, (GetRecordAmount(iStyle, gA_WRCache[client].iLastTrack) > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
}
|
|
|
|
public Action Command_DeleteAll(int client, int args)
|
|
{
|
|
if(!IsValidClient(client))
|
|
{
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
Menu menu = new Menu(MenuHandler_DeleteAll_First);
|
|
menu.SetTitle("%T\n ", "DeleteTrackAll", client);
|
|
|
|
for(int i = 0; i < TRACKS_SIZE; i++)
|
|
{
|
|
char sInfo[8];
|
|
IntToString(i, sInfo, 8);
|
|
|
|
int iRecords = GetTrackRecordCount(i);
|
|
|
|
char sTrack[64];
|
|
GetTrackName(client, i, sTrack, 64);
|
|
|
|
if(iRecords > 0)
|
|
{
|
|
Format(sTrack, 64, "%s (%T: %d)", sTrack, "WRRecord", client, iRecords);
|
|
}
|
|
|
|
menu.AddItem(sInfo, sTrack, (iRecords > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public int MenuHandler_DeleteAll_First(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[8];
|
|
menu.GetItem(param2, sInfo, 8);
|
|
int iTrack = gA_WRCache[param1].iLastTrack = StringToInt(sInfo);
|
|
|
|
char sTrack[64];
|
|
GetTrackName(param1, iTrack, sTrack, 64);
|
|
|
|
Menu subMenu = new Menu(MenuHandler_DeleteAll_Second);
|
|
subMenu.SetTitle("%T\n ", "DeleteTrackAllStyle", param1, sTrack);
|
|
|
|
int[] styles = new int[gI_Styles];
|
|
Shavit_GetOrderedStyles(styles, gI_Styles);
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
int iStyle = styles[i];
|
|
|
|
if(Shavit_GetStyleSettingInt(iStyle, "enabled") == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char sStyle[64];
|
|
strcopy(sStyle, 64, gS_StyleStrings[iStyle].sStyleName);
|
|
|
|
IntToString(iStyle, sInfo, 8);
|
|
|
|
int iRecords = GetRecordAmount(iStyle, iTrack);
|
|
|
|
if(iRecords > 0)
|
|
{
|
|
Format(sStyle, 64, "%s (%T: %d)", sStyle, "WRRecord", param1, iRecords);
|
|
}
|
|
|
|
subMenu.AddItem(sInfo, sStyle, (iRecords > 0)? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
subMenu.ExitButton = true;
|
|
subMenu.Display(param1, 300);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public int MenuHandler_DeleteAll_Second(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[8];
|
|
menu.GetItem(param2, sInfo, 8);
|
|
gA_WRCache[param1].iLastStyle = StringToInt(sInfo);
|
|
|
|
DeleteAllSubmenu(param1);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DeleteAllSubmenu(int client)
|
|
{
|
|
char sTrack[32];
|
|
GetTrackName(client, gA_WRCache[client].iLastTrack, sTrack, 32);
|
|
|
|
Menu menu = new Menu(MenuHandler_DeleteAll);
|
|
menu.SetTitle("%T\n ", "DeleteAllRecordsMenuTitle", client, gS_Map, sTrack, gS_StyleStrings[gA_WRCache[client].iLastStyle].sStyleName);
|
|
|
|
char sMenuItem[64];
|
|
|
|
for(int i = 1; i <= GetRandomInt(1, 4); i++)
|
|
{
|
|
FormatEx(sMenuItem, 64, "%T", "MenuResponseNo", client);
|
|
menu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
FormatEx(sMenuItem, 64, "%T", "MenuResponseYes", client);
|
|
menu.AddItem("yes", sMenuItem);
|
|
|
|
for(int i = 1; i <= GetRandomInt(1, 3); i++)
|
|
{
|
|
FormatEx(sMenuItem, 64, "%T", "MenuResponseNo", client);
|
|
menu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
}
|
|
|
|
public int MenuHandler_DeleteAll(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char info[16];
|
|
menu.GetItem(param2, info, 16);
|
|
|
|
if(StringToInt(info) == -1)
|
|
{
|
|
Shavit_PrintToChat(param1, "%T", "DeletionAborted", param1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
char sTrack[32];
|
|
GetTrackName(LANG_SERVER, gA_WRCache[param1].iLastTrack, sTrack, 32);
|
|
|
|
Shavit_LogMessage("%L - deleted all %s track and %s style records from map `%s`.",
|
|
param1, sTrack, gS_StyleStrings[gA_WRCache[param1].iLastStyle].sStyleName, gS_Map);
|
|
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery), "DELETE FROM %splayertimes WHERE map = '%s' AND style = %d AND track = %d;",
|
|
gS_MySQLPrefix, gS_Map, gA_WRCache[param1].iLastStyle, gA_WRCache[param1].iLastTrack);
|
|
|
|
DataPack hPack = new DataPack();
|
|
hPack.WriteCell(GetClientSerial(param1));
|
|
hPack.WriteCell(gA_WRCache[param1].iLastStyle);
|
|
hPack.WriteCell(gA_WRCache[param1].iLastTrack);
|
|
|
|
gH_SQL.Query(DeleteAll_Callback, sQuery, hPack, DBPrio_High);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public int MenuHandler_Delete(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char info[16];
|
|
menu.GetItem(param2, info, 16);
|
|
gA_WRCache[param1].iLastStyle = StringToInt(info);
|
|
|
|
OpenDelete(param1);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void OpenDelete(int client)
|
|
{
|
|
char sQuery[512];
|
|
FormatEx(sQuery, 512, "SELECT p.id, u.name, p.time, p.jumps FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE map = '%s' AND style = %d AND track = %d ORDER BY time ASC, date ASC LIMIT 1000;",
|
|
gS_MySQLPrefix, gS_MySQLPrefix, gS_Map, gA_WRCache[client].iLastStyle, gA_WRCache[client].iLastTrack);
|
|
|
|
gH_SQL.Query(SQL_OpenDelete_Callback, sQuery, GetClientSerial(client), DBPrio_High);
|
|
}
|
|
|
|
public void SQL_OpenDelete_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR OpenDelete) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
int client = GetClientFromSerial(data);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int iStyle = gA_WRCache[client].iLastStyle;
|
|
|
|
Menu menu = new Menu(OpenDelete_Handler);
|
|
menu.SetTitle("%t", "ListClientRecords", gS_Map, gS_StyleStrings[iStyle].sStyleName);
|
|
|
|
int iCount = 0;
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
iCount++;
|
|
|
|
// 0 - record id, for statistic purposes.
|
|
int id = results.FetchInt(0);
|
|
char sID[8];
|
|
IntToString(id, sID, 8);
|
|
|
|
// 1 - player name
|
|
char sName[MAX_NAME_LENGTH];
|
|
results.FetchString(1, sName, MAX_NAME_LENGTH);
|
|
ReplaceString(sName, MAX_NAME_LENGTH, "#", "?");
|
|
|
|
// 2 - time
|
|
float time = results.FetchFloat(2);
|
|
char sTime[16];
|
|
FormatSeconds(time, sTime, 16);
|
|
|
|
// 3 - jumps
|
|
int jumps = results.FetchInt(3);
|
|
|
|
char sDisplay[128];
|
|
FormatEx(sDisplay, 128, "#%d - %s - %s (%d jump%s)", iCount, sName, sTime, jumps, (jumps != 1)? "s":"");
|
|
menu.AddItem(sID, sDisplay);
|
|
}
|
|
|
|
if(iCount == 0)
|
|
{
|
|
char sNoRecords[64];
|
|
FormatEx(sNoRecords, 64, "%T", "WRMapNoRecords", client);
|
|
menu.AddItem("-1", sNoRecords);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
}
|
|
|
|
public int OpenDelete_Handler(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[16];
|
|
menu.GetItem(param2, sInfo, 16);
|
|
|
|
int id = StringToInt(sInfo);
|
|
|
|
if(id != -1)
|
|
{
|
|
OpenDeleteMenu(param1, id);
|
|
}
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void OpenDeleteMenu(int client, int id)
|
|
{
|
|
char sMenuItem[64];
|
|
|
|
Menu menu = new Menu(DeleteConfirm_Handler);
|
|
menu.SetTitle("%T\n ", "DeleteConfirm", client);
|
|
|
|
for(int i = 1; i <= GetRandomInt(1, 4); i++)
|
|
{
|
|
FormatEx(sMenuItem, 64, "%T", "MenuResponseNo", client);
|
|
menu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
FormatEx(sMenuItem, 64, "%T", "MenuResponseYesSingle", client);
|
|
|
|
char sInfo[16];
|
|
IntToString(id, sInfo, 16);
|
|
menu.AddItem(sInfo, sMenuItem);
|
|
|
|
for(int i = 1; i <= GetRandomInt(1, 3); i++)
|
|
{
|
|
FormatEx(sMenuItem, 64, "%T", "MenuResponseNo", client);
|
|
menu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
}
|
|
|
|
public int DeleteConfirm_Handler(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[16];
|
|
menu.GetItem(param2, sInfo, 16);
|
|
|
|
int iRecordID = StringToInt(sInfo);
|
|
|
|
if(iRecordID == -1)
|
|
{
|
|
Shavit_PrintToChat(param1, "%T", "DeletionAborted", param1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"SELECT u.auth, u.name, p.map, p.time, p.sync, p.perfs, p.jumps, p.strafes, p.id, p.date, "...
|
|
"(SELECT id FROM %splayertimes WHERE style = %d AND track = %d AND map = p.map ORDER BY time, date ASC LIMIT 1) "...
|
|
"FROM %susers u LEFT JOIN %splayertimes p ON u.auth = p.auth WHERE p.id = %d;",
|
|
gS_MySQLPrefix, gA_WRCache[param1].iLastStyle, gA_WRCache[param1].iLastTrack, gS_MySQLPrefix, gS_MySQLPrefix, iRecordID);
|
|
|
|
gH_SQL.Query(GetRecordDetails_Callback, sQuery, GetClientSerial(param1), DBPrio_High);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public void GetRecordDetails_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR GetRecordDetails) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
int client = GetClientFromSerial(data);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(results.FetchRow())
|
|
{
|
|
int iSteamID = results.FetchInt(0);
|
|
|
|
char sName[MAX_NAME_LENGTH];
|
|
results.FetchString(1, sName, MAX_NAME_LENGTH);
|
|
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
results.FetchString(2, sMap, sizeof(sMap));
|
|
|
|
float fTime = results.FetchFloat(3);
|
|
float fSync = results.FetchFloat(4);
|
|
float fPerfectJumps = results.FetchFloat(5);
|
|
|
|
int iJumps = results.FetchInt(6);
|
|
int iStrafes = results.FetchInt(7);
|
|
int iRecordID = results.FetchInt(8);
|
|
int iTimestamp = results.FetchInt(9);
|
|
int iWRRecordID = results.FetchInt(10);
|
|
|
|
int iStyle = gA_WRCache[client].iLastStyle;
|
|
int iTrack = gA_WRCache[client].iLastTrack;
|
|
|
|
// that's a big datapack ya yeet
|
|
DataPack hPack = new DataPack();
|
|
hPack.WriteCell(GetClientSerial(client));
|
|
hPack.WriteCell(iSteamID);
|
|
hPack.WriteString(sName);
|
|
hPack.WriteString(sMap);
|
|
hPack.WriteCell(fTime);
|
|
hPack.WriteCell(fSync);
|
|
hPack.WriteCell(fPerfectJumps);
|
|
hPack.WriteCell(iJumps);
|
|
hPack.WriteCell(iStrafes);
|
|
hPack.WriteCell(iRecordID);
|
|
hPack.WriteCell(iTimestamp);
|
|
hPack.WriteCell(iStyle);
|
|
hPack.WriteCell(iTrack);
|
|
|
|
bool bWRDeleted = iWRRecordID == iRecordID;
|
|
hPack.WriteCell(bWRDeleted);
|
|
|
|
char sQuery[256];
|
|
FormatEx(sQuery, 256, "DELETE FROM %splayertimes WHERE id = %d;",
|
|
gS_MySQLPrefix, iRecordID);
|
|
|
|
gH_SQL.Query(DeleteConfirm_Callback, sQuery, hPack, DBPrio_High);
|
|
}
|
|
}
|
|
|
|
public void DeleteConfirm_Callback(Database db, DBResultSet results, const char[] error, DataPack hPack)
|
|
{
|
|
hPack.Reset();
|
|
|
|
int iSerial = hPack.ReadCell();
|
|
int iSteamID = hPack.ReadCell();
|
|
|
|
char sName[MAX_NAME_LENGTH];
|
|
hPack.ReadString(sName, MAX_NAME_LENGTH);
|
|
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
hPack.ReadString(sMap, sizeof(sMap));
|
|
|
|
float fTime = view_as<float>(hPack.ReadCell());
|
|
float fSync = view_as<float>(hPack.ReadCell());
|
|
float fPerfectJumps = view_as<float>(hPack.ReadCell());
|
|
|
|
int iJumps = hPack.ReadCell();
|
|
int iStrafes = hPack.ReadCell();
|
|
int iRecordID = hPack.ReadCell();
|
|
int iTimestamp = hPack.ReadCell();
|
|
int iStyle = hPack.ReadCell();
|
|
int iTrack = hPack.ReadCell();
|
|
|
|
bool bWRDeleted = view_as<bool>(hPack.ReadCell());
|
|
delete hPack;
|
|
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR DeleteConfirm) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
if(bWRDeleted)
|
|
{
|
|
DeleteWR(iStyle, iTrack, sMap, iSteamID, iRecordID, false, true);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (IsValidClient(i) && GetSteamAccountID(i) == iSteamID)
|
|
{
|
|
UpdateClientCache(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int client = GetClientFromSerial(iSerial);
|
|
|
|
char sTrack[32];
|
|
GetTrackName(LANG_SERVER, iTrack, sTrack, 32);
|
|
|
|
char sDate[32];
|
|
FormatTime(sDate, 32, "%Y-%m-%d %H:%M:%S", iTimestamp);
|
|
|
|
// above the client == 0 so log doesn't get lost if admin disconnects between deleting record and query execution
|
|
Shavit_LogMessage("%L - deleted record. Runner: %s ([U:1:%d]) | Map: %s | Style: %s | Track: %s | Time: %.2f (%s) | Strafes: %d (%.1f%%) | Jumps: %d (%.1f%%) | Run date: %s | Record ID: %d",
|
|
client, sName, iSteamID, sMap, gS_StyleStrings[iStyle].sStyleName, sTrack, fTime, (bWRDeleted)? "WR":"not WR", iStrafes, fSync, iJumps, fPerfectJumps, sDate, iRecordID);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Shavit_PrintToChat(client, "%T", "DeletedRecord", client);
|
|
}
|
|
|
|
public void DeleteAll_Callback(Database db, DBResultSet results, const char[] error, DataPack hPack)
|
|
{
|
|
hPack.Reset();
|
|
int client = GetClientFromSerial(hPack.ReadCell());
|
|
int style = hPack.ReadCell();
|
|
int track = hPack.ReadCell();
|
|
delete hPack;
|
|
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR DeleteAll) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
DeleteWR(style, track, gS_Map, 0, -1, false, true);
|
|
|
|
Shavit_PrintToChat(client, "%T", "DeletedRecordsMap", client, gS_ChatStrings.sVariable, gS_Map, gS_ChatStrings.sText);
|
|
}
|
|
|
|
public Action Command_WorldRecord_Style(int client, int args)
|
|
{
|
|
char sCommand[128];
|
|
GetCmdArg(0, sCommand, sizeof(sCommand));
|
|
|
|
int style = 0;
|
|
|
|
if (gSM_StyleCommands.GetValue(sCommand, style))
|
|
{
|
|
gA_WRCache[client].bForceStyle = true;
|
|
gA_WRCache[client].iLastStyle = style;
|
|
Command_WorldRecord(client, args);
|
|
}
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_WorldRecord(int client, int args)
|
|
{
|
|
if(!IsValidClient(client))
|
|
{
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
char sCommand[16];
|
|
GetCmdArg(0, sCommand, 16);
|
|
|
|
int track = Track_Main;
|
|
bool havemap = false;
|
|
|
|
if(StrContains(sCommand, "sm_b", false) == 0)
|
|
{
|
|
if (args >= 1)
|
|
{
|
|
char arg[6];
|
|
GetCmdArg((args > 1) ? 2 : 1, arg, sizeof(arg));
|
|
track = StringToInt(arg);
|
|
|
|
// if the track doesn't fit in the bonus track range then assume it's a map name
|
|
if (args > 1 || (track < Track_Bonus || track > Track_Bonus_Last))
|
|
{
|
|
havemap = true;
|
|
}
|
|
}
|
|
|
|
if (track < Track_Bonus || track > Track_Bonus_Last)
|
|
{
|
|
track = Track_Bonus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
havemap = (args >= 1);
|
|
}
|
|
|
|
if(!havemap)
|
|
{
|
|
gA_WRCache[client].sClientMap = gS_Map;
|
|
}
|
|
else
|
|
{
|
|
GetCmdArg(1, gA_WRCache[client].sClientMap, sizeof(wrcache_t::sClientMap));
|
|
LowercaseString(gA_WRCache[client].sClientMap);
|
|
|
|
if (!GuessBestMapName(gA_ValidMaps, gA_WRCache[client].sClientMap, gA_WRCache[client].sClientMap))
|
|
{
|
|
Shavit_PrintToChat(client, "%t", "Map was not found", gA_WRCache[client].sClientMap);
|
|
return Plugin_Handled;
|
|
}
|
|
}
|
|
|
|
gA_WRCache[client].iLastTrack = track;
|
|
|
|
RetrieveWRMenu(client, track);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
void RetrieveWRMenu(int client, int track)
|
|
{
|
|
if (gA_WRCache[client].bPendingMenu)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (StrEqual(gA_WRCache[client].sClientMap, gS_Map))
|
|
{
|
|
for (int i = 0; i < gI_Styles; i++)
|
|
{
|
|
gA_WRCache[client].fWRs[i] = gF_WRTime[i][track];
|
|
}
|
|
|
|
if (gA_WRCache[client].bForceStyle)
|
|
{
|
|
StartWRMenu(client);
|
|
}
|
|
else
|
|
{
|
|
ShowWRStyleMenu(client);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gA_WRCache[client].bPendingMenu = true;
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"SELECT style, time FROM %swrs WHERE map = '%s' AND track = %d AND style < %d ORDER BY style;",
|
|
gS_MySQLPrefix, gA_WRCache[client].sClientMap, track, gI_Styles);
|
|
gH_SQL.Query(SQL_RetrieveWRMenu_Callback, sQuery, GetClientSerial(client));
|
|
}
|
|
}
|
|
|
|
public void SQL_RetrieveWRMenu_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR RetrieveWRMenu) SQL query failed. Reason: %s", error);
|
|
return;
|
|
}
|
|
|
|
int client = GetClientFromSerial(data);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gA_WRCache[client].bPendingMenu = false;
|
|
|
|
for (int i = 0; i < gI_Styles; i++)
|
|
{
|
|
gA_WRCache[client].fWRs[i] = 0.0;
|
|
}
|
|
|
|
while (results.FetchRow())
|
|
{
|
|
int style = results.FetchInt(0);
|
|
float time = results.FetchFloat(1);
|
|
gA_WRCache[client].fWRs[style] = time;
|
|
}
|
|
|
|
if (gA_WRCache[client].bForceStyle)
|
|
{
|
|
StartWRMenu(client);
|
|
}
|
|
else
|
|
{
|
|
ShowWRStyleMenu(client);
|
|
}
|
|
}
|
|
|
|
void ShowWRStyleMenu(int client, int first_item=0)
|
|
{
|
|
Menu menu = new Menu(MenuHandler_StyleChooser);
|
|
menu.SetTitle("%T", "WRMenuTitle", client);
|
|
|
|
int[] styles = new int[gI_Styles];
|
|
Shavit_GetOrderedStyles(styles, gI_Styles);
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
int iStyle = styles[i];
|
|
|
|
if(Shavit_GetStyleSettingInt(iStyle, "unranked") || Shavit_GetStyleSettingInt(iStyle, "enabled") == -1)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char sInfo[8];
|
|
IntToString(iStyle, sInfo, 8);
|
|
|
|
char sDisplay[64];
|
|
|
|
if (gA_WRCache[client].fWRs[iStyle] > 0.0)
|
|
{
|
|
char sTime[32];
|
|
FormatSeconds(gA_WRCache[client].fWRs[iStyle], sTime, 32, false);
|
|
|
|
FormatEx(sDisplay, 64, "%s - WR: %s", gS_StyleStrings[iStyle].sStyleName, sTime);
|
|
}
|
|
else
|
|
{
|
|
strcopy(sDisplay, 64, gS_StyleStrings[iStyle].sStyleName);
|
|
}
|
|
|
|
menu.AddItem(sInfo, sDisplay, (gA_WRCache[client].fWRs[iStyle] > 0.0) ? ITEMDRAW_DEFAULT:ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
// should NEVER happen
|
|
if(menu.ItemCount == 0)
|
|
{
|
|
char sMenuItem[64];
|
|
FormatEx(sMenuItem, 64, "%T", "WRStyleNothing", client);
|
|
menu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.DisplayAt(client, first_item, MENU_TIME_FOREVER);
|
|
}
|
|
|
|
public int MenuHandler_StyleChooser(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
if(!IsValidClient(param1))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
char sInfo[8];
|
|
menu.GetItem(param2, sInfo, 8);
|
|
|
|
int iStyle = StringToInt(sInfo);
|
|
|
|
if(iStyle == -1)
|
|
{
|
|
Shavit_PrintToChat(param1, "%T", "NoStyles", param1, gS_ChatStrings.sWarning, gS_ChatStrings.sText);
|
|
|
|
return 0;
|
|
}
|
|
|
|
gA_WRCache[param1].iLastStyle = iStyle;
|
|
gA_WRCache[param1].iPagePosition = GetMenuSelectionPosition();
|
|
|
|
StartWRMenu(param1);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void StartWRMenu(int client)
|
|
{
|
|
gA_WRCache[client].bForceStyle = false;
|
|
|
|
DataPack dp = new DataPack();
|
|
dp.WriteCell(GetClientSerial(client));
|
|
dp.WriteCell(gA_WRCache[client].iLastTrack);
|
|
dp.WriteString(gA_WRCache[client].sClientMap);
|
|
|
|
int iLength = ((strlen(gA_WRCache[client].sClientMap) * 2) + 1);
|
|
char[] sEscapedMap = new char[iLength];
|
|
gH_SQL.Escape(gA_WRCache[client].sClientMap, sEscapedMap, iLength);
|
|
|
|
char sQuery[512];
|
|
FormatEx(sQuery, 512, "SELECT p.id, u.name, p.time, p.jumps, p.auth FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE map = '%s' AND style = %d AND track = %d ORDER BY time ASC, date ASC;", gS_MySQLPrefix, gS_MySQLPrefix, sEscapedMap, gA_WRCache[client].iLastStyle, gA_WRCache[client].iLastTrack);
|
|
gH_SQL.Query(SQL_WR_Callback, sQuery, dp);
|
|
}
|
|
|
|
public void SQL_WR_Callback(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
data.Reset();
|
|
int serial = data.ReadCell();
|
|
int track = data.ReadCell();
|
|
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
data.ReadString(sMap, sizeof(sMap));
|
|
|
|
delete data;
|
|
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR SELECT) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
int client = GetClientFromSerial(serial);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int iSteamID = GetSteamAccountID(client);
|
|
|
|
Menu hMenu = new Menu(WRMenu_Handler);
|
|
|
|
int iCount = 0;
|
|
int iMyRank = 0;
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
if(++iCount <= gCV_RecordsLimit.IntValue)
|
|
{
|
|
// 0 - record id, for statistic purposes.
|
|
int id = results.FetchInt(0);
|
|
char sID[8];
|
|
IntToString(id, sID, 8);
|
|
|
|
// 1 - player name
|
|
char sName[MAX_NAME_LENGTH];
|
|
results.FetchString(1, sName, MAX_NAME_LENGTH);
|
|
|
|
// 2 - time
|
|
float time = results.FetchFloat(2);
|
|
char sTime[16];
|
|
FormatSeconds(time, sTime, 16);
|
|
|
|
// 3 - jumps
|
|
int jumps = results.FetchInt(3);
|
|
|
|
char sDisplay[128];
|
|
FormatEx(sDisplay, 128, "#%d - %s - %s (%d %T)", iCount, sName, sTime, jumps, "WRJumps", client);
|
|
hMenu.AddItem(sID, sDisplay);
|
|
}
|
|
|
|
// check if record exists in the map's top X
|
|
int iQuerySteamID = results.FetchInt(4);
|
|
|
|
if(iQuerySteamID == iSteamID)
|
|
{
|
|
iMyRank = iCount;
|
|
}
|
|
}
|
|
|
|
char sFormattedTitle[256];
|
|
|
|
if(hMenu.ItemCount == 0)
|
|
{
|
|
hMenu.SetTitle("%T", "WRMap", client, sMap);
|
|
char sNoRecords[64];
|
|
FormatEx(sNoRecords, 64, "%T", "WRMapNoRecords", client);
|
|
|
|
hMenu.AddItem("-1", sNoRecords);
|
|
}
|
|
|
|
else
|
|
{
|
|
int iStyle = gA_WRCache[client].iLastStyle;
|
|
int iRecords = results.RowCount;
|
|
|
|
// [32] just in case there are 150k records on a map and you're ranked 100k or something
|
|
char sRanks[32];
|
|
|
|
if(gF_PlayerRecord[client][iStyle][track] == 0.0 || iMyRank == 0)
|
|
{
|
|
FormatEx(sRanks, 32, "(%d %T)", iRecords, "WRRecord", client);
|
|
}
|
|
|
|
else
|
|
{
|
|
FormatEx(sRanks, 32, "(#%d/%d)", iMyRank, iRecords);
|
|
}
|
|
|
|
char sTrack[32];
|
|
GetTrackName(client, track, sTrack, 32);
|
|
|
|
FormatEx(sFormattedTitle, 192, "%T %s: [%s]\n%s", "WRRecordFor", client, sMap, sTrack, sRanks);
|
|
hMenu.SetTitle(sFormattedTitle);
|
|
}
|
|
|
|
hMenu.ExitBackButton = true;
|
|
hMenu.Display(client, 300);
|
|
}
|
|
|
|
public int WRMenu_Handler(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[16];
|
|
menu.GetItem(param2, sInfo, 16);
|
|
int id = StringToInt(sInfo);
|
|
|
|
if(id != -1)
|
|
{
|
|
OpenSubMenu(param1, id);
|
|
}
|
|
|
|
else
|
|
{
|
|
ShowWRStyleMenu(param1);
|
|
}
|
|
}
|
|
|
|
else if(action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
|
|
{
|
|
ShowWRStyleMenu(param1, gA_WRCache[param1].iPagePosition);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public Action Command_RecentRecords(int client, int args)
|
|
{
|
|
if(gA_WRCache[client].bPendingMenu || !IsValidClient(client))
|
|
{
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
char sQuery[512];
|
|
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"SELECT a.id, a.map, u.name, a.time, a.style, a.track FROM %swrs a JOIN %susers u on a.auth = u.auth ORDER BY a.date DESC LIMIT 100;",
|
|
gS_MySQLPrefix, gS_MySQLPrefix);
|
|
|
|
gH_SQL.Query(SQL_RR_Callback, sQuery, GetClientSerial(client), DBPrio_Low);
|
|
|
|
gA_WRCache[client].bPendingMenu = true;
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public void SQL_RR_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
int client = GetClientFromSerial(data);
|
|
|
|
gA_WRCache[client].bPendingMenu = false;
|
|
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (RR SELECT) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Menu menu = new Menu(RRMenu_Handler);
|
|
menu.SetTitle("%T:", "RecentRecords", client, gCV_RecentLimit.IntValue);
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
results.FetchString(1, sMap, sizeof(sMap));
|
|
|
|
char sName[MAX_NAME_LENGTH];
|
|
results.FetchString(2, sName, sizeof(sName));
|
|
TrimDisplayString(sName, sName, sizeof(sName), 9);
|
|
|
|
char sTime[16];
|
|
float fTime = results.FetchFloat(3);
|
|
FormatSeconds(fTime, sTime, 16);
|
|
|
|
int iStyle = results.FetchInt(4);
|
|
if(iStyle >= gI_Styles || iStyle < 0 || Shavit_GetStyleSettingInt(iStyle, "unranked"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
char sTrack[32];
|
|
GetTrackName(client, results.FetchInt(5), sTrack, 32);
|
|
|
|
char sDisplay[192];
|
|
FormatEx(sDisplay, 192, "[%s/%c] %s - %s @ %s", gS_StyleStrings[iStyle].sShortName, sTrack[0], sMap, sName, sTime);
|
|
|
|
char sInfo[192];
|
|
FormatEx(sInfo, 192, "%d;%s", results.FetchInt(0), sMap);
|
|
|
|
menu.AddItem(sInfo, sDisplay);
|
|
}
|
|
|
|
if(menu.ItemCount == 0)
|
|
{
|
|
char sMenuItem[64];
|
|
FormatEx(sMenuItem, 64, "%T", "WRMapNoRecords", client);
|
|
menu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, 300);
|
|
}
|
|
|
|
public int RRMenu_Handler(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[128];
|
|
menu.GetItem(param2, sInfo, 128);
|
|
|
|
if(StringToInt(sInfo) != -1)
|
|
{
|
|
char sExploded[2][128];
|
|
ExplodeString(sInfo, ";", sExploded, 2, 128, true);
|
|
|
|
strcopy(gA_WRCache[param1].sClientMap, 128, sExploded[1]);
|
|
|
|
OpenSubMenu(param1, StringToInt(sExploded[0]));
|
|
}
|
|
|
|
else
|
|
{
|
|
RetrieveWRMenu(param1, gA_WRCache[param1].iLastTrack);
|
|
}
|
|
}
|
|
|
|
else if(action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
|
|
{
|
|
RetrieveWRMenu(param1, gA_WRCache[param1].iLastTrack);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void OpenSubMenu(int client, int id)
|
|
{
|
|
char sQuery[512];
|
|
FormatEx(sQuery, 512,
|
|
"SELECT u.name, p.time, p.jumps, p.style, u.auth, p.date, p.map, p.strafes, p.sync, p.perfs, p.points, p.track, p.completions FROM %splayertimes p JOIN %susers u ON p.auth = u.auth WHERE p.id = %d LIMIT 1;",
|
|
gS_MySQLPrefix, gS_MySQLPrefix, id);
|
|
|
|
DataPack datapack = new DataPack();
|
|
datapack.WriteCell(GetClientSerial(client));
|
|
datapack.WriteCell(id);
|
|
|
|
gH_SQL.Query(SQL_SubMenu_Callback, sQuery, datapack, DBPrio_High);
|
|
}
|
|
|
|
public void SQL_SubMenu_Callback(Database db, DBResultSet results, const char[] error, DataPack data)
|
|
{
|
|
data.Reset();
|
|
int client = GetClientFromSerial(data.ReadCell());
|
|
int id = data.ReadCell();
|
|
delete data;
|
|
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR SUBMENU) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Menu hMenu = new Menu(SubMenu_Handler);
|
|
|
|
char sFormattedTitle[256];
|
|
char sName[MAX_NAME_LENGTH];
|
|
int iSteamID = 0;
|
|
char sTrack[32];
|
|
char sMap[PLATFORM_MAX_PATH];
|
|
|
|
if(results.FetchRow())
|
|
{
|
|
results.FetchString(0, sName, MAX_NAME_LENGTH);
|
|
|
|
float fTime = results.FetchFloat(1);
|
|
char sTime[16];
|
|
FormatSeconds(fTime, sTime, 16);
|
|
|
|
char sDisplay[128];
|
|
FormatEx(sDisplay, 128, "%T: %s", "WRTime", client, sTime);
|
|
hMenu.AddItem("-1", sDisplay);
|
|
|
|
int iStyle = results.FetchInt(3);
|
|
int iJumps = results.FetchInt(2);
|
|
float fPerfs = results.FetchFloat(9);
|
|
|
|
if(Shavit_GetStyleSettingInt(iStyle, "autobhop"))
|
|
{
|
|
FormatEx(sDisplay, 128, "%T: %d", "WRJumps", client, iJumps);
|
|
}
|
|
|
|
else
|
|
{
|
|
FormatEx(sDisplay, 128, "%T: %d (%.2f%%)", "WRJumps", client, iJumps, fPerfs);
|
|
}
|
|
|
|
hMenu.AddItem("-1", sDisplay);
|
|
|
|
FormatEx(sDisplay, 128, "%T: %d", "WRCompletions", client, results.FetchInt(12));
|
|
hMenu.AddItem("-1", sDisplay);
|
|
|
|
FormatEx(sDisplay, 128, "%T: %s", "WRStyle", client, gS_StyleStrings[iStyle].sStyleName);
|
|
hMenu.AddItem("-1", sDisplay);
|
|
|
|
results.FetchString(6, sMap, sizeof(sMap));
|
|
|
|
float fPoints = results.FetchFloat(10);
|
|
|
|
if(gB_Rankings && fPoints > 0.0)
|
|
{
|
|
FormatEx(sDisplay, 128, "%T: %.03f", "WRPointsCap", client, fPoints);
|
|
hMenu.AddItem("-1", sDisplay);
|
|
}
|
|
|
|
iSteamID = results.FetchInt(4);
|
|
|
|
char sDate[32];
|
|
results.FetchString(5, sDate, 32);
|
|
|
|
if(sDate[4] != '-')
|
|
{
|
|
FormatTime(sDate, 32, "%Y-%m-%d %H:%M:%S", StringToInt(sDate));
|
|
}
|
|
|
|
FormatEx(sDisplay, 128, "%T: %s", "WRDate", client, sDate);
|
|
hMenu.AddItem("-1", sDisplay);
|
|
|
|
int strafes = results.FetchInt(7);
|
|
float sync = results.FetchFloat(8);
|
|
|
|
if(iJumps > 0 || strafes > 0)
|
|
{
|
|
FormatEx(sDisplay, 128, (sync != -1.0)? "%T: %d (%.02f%%)":"%T: %d", "WRStrafes", client, strafes, sync);
|
|
hMenu.AddItem("-1", sDisplay);
|
|
}
|
|
|
|
char sMenuItem[64];
|
|
FormatEx(sMenuItem, 64, "%T", "WRPlayerStats", client);
|
|
|
|
char sInfo[32];
|
|
FormatEx(sInfo, 32, "0;%d", iSteamID);
|
|
|
|
if(gB_Stats)
|
|
{
|
|
hMenu.AddItem(sInfo, sMenuItem);
|
|
}
|
|
|
|
if(CheckCommandAccess(client, "sm_delete", ADMFLAG_RCON))
|
|
{
|
|
FormatEx(sMenuItem, 64, "%T", "WRDeleteRecord", client);
|
|
FormatEx(sInfo, 32, "1;%d", id);
|
|
hMenu.AddItem(sInfo, sMenuItem);
|
|
}
|
|
|
|
GetTrackName(client, results.FetchInt(11), sTrack, 32);
|
|
}
|
|
|
|
else
|
|
{
|
|
char sMenuItem[64];
|
|
FormatEx(sMenuItem, 64, "%T", "DatabaseError", client);
|
|
hMenu.AddItem("-1", sMenuItem);
|
|
}
|
|
|
|
if(strlen(sName) > 0)
|
|
{
|
|
FormatEx(sFormattedTitle, 256, "%s [U:1:%d]\n--- %s: [%s]", sName, iSteamID, sMap, sTrack);
|
|
}
|
|
|
|
else
|
|
{
|
|
FormatEx(sFormattedTitle, 256, "%T", "Error", client);
|
|
}
|
|
|
|
hMenu.SetTitle(sFormattedTitle);
|
|
hMenu.ExitBackButton = true;
|
|
hMenu.Display(client, 300);
|
|
}
|
|
|
|
public int SubMenu_Handler(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
if(action == MenuAction_Select)
|
|
{
|
|
char sInfo[32];
|
|
menu.GetItem(param2, sInfo, 32);
|
|
|
|
if(gB_Stats && StringToInt(sInfo) != -1)
|
|
{
|
|
char sExploded[2][32];
|
|
ExplodeString(sInfo, ";", sExploded, 2, 32, true);
|
|
|
|
int first = StringToInt(sExploded[0]);
|
|
|
|
switch(first)
|
|
{
|
|
case 0:
|
|
{
|
|
Shavit_OpenStatsMenu(param1, StringToInt(sExploded[1]));
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
OpenDeleteMenu(param1, StringToInt(sExploded[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
StartWRMenu(param1);
|
|
}
|
|
}
|
|
|
|
else if(action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
|
|
{
|
|
StartWRMenu(param1);
|
|
}
|
|
|
|
else if(action == MenuAction_End)
|
|
{
|
|
delete menu;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public void Shavit_OnDatabaseLoaded()
|
|
{
|
|
GetTimerSQLPrefix(gS_MySQLPrefix, 32);
|
|
gH_SQL = gCV_NewDBConnection.BoolValue ? GetTimerDatabaseHandle2(false) : view_as<Database2>(Shavit_GetDatabase());
|
|
|
|
gB_Connected = true;
|
|
OnMapStart();
|
|
}
|
|
|
|
public void Shavit_OnFinish(int client, int style, float time, int jumps, int strafes, float sync, int track, float oldtime, float perfs, float avgvel, float maxvel, int timestamp)
|
|
{
|
|
// do not risk overwriting the player's data if their PB isn't loaded to cache yet
|
|
if (!gB_LoadedCache[client])
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
time = view_as<float>(0x43611FB3); // 225.123825; // this value loses accuracy and becomes 0x43611FBE \ 225.123992 once it's returned from mysql
|
|
PrintToServer("time = %f %X record = %f %X", time, time, gF_WRTime[style][track], gF_WRTime[style][track]);
|
|
#endif
|
|
|
|
int iSteamID = GetSteamAccountID(client);
|
|
|
|
char sTime[32];
|
|
FormatSeconds(time, sTime, 32);
|
|
|
|
char sTrack[32];
|
|
GetTrackName(LANG_SERVER, track, sTrack, 32);
|
|
|
|
// 0 - no query
|
|
// 1 - insert
|
|
// 2 - update
|
|
bool bIncrementCompletions = true;
|
|
int iOverwrite = 0;
|
|
|
|
if(Shavit_GetStyleSettingInt(style, "unranked") || Shavit_IsPracticeMode(client))
|
|
{
|
|
iOverwrite = 0; // ugly way of not writing to database
|
|
bIncrementCompletions = false;
|
|
}
|
|
|
|
else if(gF_PlayerRecord[client][style][track] == 0.0)
|
|
{
|
|
iOverwrite = 1;
|
|
}
|
|
|
|
else if(time < gF_PlayerRecord[client][style][track])
|
|
{
|
|
iOverwrite = 2;
|
|
}
|
|
|
|
bool bEveryone = (iOverwrite > 0);
|
|
char sMessage[255];
|
|
char sMessage2[255];
|
|
|
|
if(iOverwrite > 0 && (time < gF_WRTime[style][track] || gF_WRTime[style][track] == 0.0)) // WR?
|
|
{
|
|
float fOldWR = gF_WRTime[style][track];
|
|
gF_WRTime[style][track] = time;
|
|
|
|
gI_WRSteamID[style][track] = iSteamID;
|
|
|
|
Call_StartForward(gH_OnWorldRecord);
|
|
Call_PushCell(client);
|
|
Call_PushCell(style);
|
|
Call_PushCell(time);
|
|
Call_PushCell(jumps);
|
|
Call_PushCell(strafes);
|
|
Call_PushCell(sync);
|
|
Call_PushCell(track);
|
|
Call_PushCell(fOldWR);
|
|
Call_PushCell(oldtime);
|
|
Call_PushCell(perfs);
|
|
Call_PushCell(avgvel);
|
|
Call_PushCell(maxvel);
|
|
Call_PushCell(timestamp);
|
|
Call_Finish();
|
|
|
|
#if defined DEBUG
|
|
Shavit_PrintToChat(client, "old: %.01f new: %.01f", fOldWR, time);
|
|
#endif
|
|
|
|
Transaction2 hTransaction = new Transaction2();
|
|
char query[512];
|
|
|
|
FormatEx(query, sizeof(query),
|
|
"DELETE FROM `%sstagetimeswr` WHERE style = %d AND track = %d AND map = '%s';",
|
|
gS_MySQLPrefix, style, track, gS_Map
|
|
);
|
|
|
|
hTransaction.AddQuery(query);
|
|
|
|
for (int i = 0; i < MAX_STAGES; i++)
|
|
{
|
|
float fTime = gA_StageTimes[client][i];
|
|
gA_StageWR[style][track][i] = fTime;
|
|
|
|
if (fTime == 0.0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FormatEx(query, sizeof(query),
|
|
"INSERT INTO `%sstagetimeswr` (`style`, `track`, `map`, `auth`, `time`, `stage`) VALUES (%d, %d, '%s', %d, %f, %d);",
|
|
gS_MySQLPrefix, style, track, gS_Map, iSteamID, fTime, i
|
|
);
|
|
|
|
hTransaction.AddQuery(query);
|
|
}
|
|
|
|
gH_SQL.Execute(hTransaction, Trans_ReplaceStageTimes_Success, Trans_ReplaceStageTimes_Error, 0, DBPrio_High);
|
|
}
|
|
|
|
int iRank = GetRankForTime(style, time, track);
|
|
|
|
if(iRank >= GetRecordAmount(style, track))
|
|
{
|
|
Call_StartForward(gH_OnWorstRecord);
|
|
Call_PushCell(client);
|
|
Call_PushCell(style);
|
|
Call_PushCell(time);
|
|
Call_PushCell(jumps);
|
|
Call_PushCell(strafes);
|
|
Call_PushCell(sync);
|
|
Call_PushCell(track);
|
|
Call_PushCell(oldtime);
|
|
Call_PushCell(perfs);
|
|
Call_PushCell(avgvel);
|
|
Call_PushCell(maxvel);
|
|
Call_PushCell(timestamp);
|
|
Call_Finish();
|
|
}
|
|
|
|
float fDifference = (gF_PlayerRecord[client][style][track] - time);
|
|
|
|
if(fDifference < 0.0)
|
|
{
|
|
fDifference = -fDifference;
|
|
}
|
|
|
|
char sDifference[16];
|
|
FormatSeconds(fDifference, sDifference, 16, true);
|
|
|
|
char sSync[32]; // 32 because colors
|
|
FormatEx(sSync, 32, (sync != -1.0)? " @ %s%.02f%%":"", gS_ChatStrings.sVariable, sync);
|
|
|
|
if(iOverwrite > 0)
|
|
{
|
|
float fPoints = gB_Rankings ? Shavit_GuessPointsForTime(track, style, -1, time, gF_WRTime[style][track]) : 0.0;
|
|
|
|
char sQuery[1024];
|
|
|
|
if(iOverwrite == 1) // insert
|
|
{
|
|
FormatEx(sMessage, 255, "%s[%s]%s %T",
|
|
gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText, "FirstCompletion", LANG_SERVER, gS_ChatStrings.sVariable2, client, gS_ChatStrings.sText, gS_ChatStrings.sStyle, gS_StyleStrings[style].sStyleName, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText, gS_ChatStrings.sVariable, iRank, gS_ChatStrings.sText, jumps, strafes, sSync, gS_ChatStrings.sText);
|
|
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"INSERT INTO %splayertimes (auth, map, time, jumps, date, style, strafes, sync, points, track, perfs, exact_time_int) VALUES (%d, '%s', %f, %d, %d, %d, %d, %.2f, %f, %d, %.2f, %d);",
|
|
gS_MySQLPrefix, iSteamID, gS_Map, time, jumps, timestamp, style, strafes, sync, fPoints, track, perfs, view_as<int>(time));
|
|
}
|
|
else // update
|
|
{
|
|
FormatEx(sMessage, 255, "%s[%s]%s %T",
|
|
gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText, "NotFirstCompletion", LANG_SERVER, gS_ChatStrings.sVariable2, client, gS_ChatStrings.sText, gS_ChatStrings.sStyle, gS_StyleStrings[style].sStyleName, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText, gS_ChatStrings.sVariable, iRank, gS_ChatStrings.sText, jumps, strafes, sSync, gS_ChatStrings.sText, gS_ChatStrings.sWarning, sDifference);
|
|
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"UPDATE %splayertimes SET time = %f, jumps = %d, date = %d, strafes = %d, sync = %.02f, points = %f, perfs = %.2f, exact_time_int = %d, completions = completions + 1 WHERE map = '%s' AND auth = %d AND style = %d AND track = %d;",
|
|
gS_MySQLPrefix, time, jumps, timestamp, strafes, sync, fPoints, perfs, view_as<int>(time), gS_Map, iSteamID, style, track);
|
|
}
|
|
|
|
gH_SQL.Query(SQL_OnFinish_Callback, sQuery, GetClientSerial(client), DBPrio_High);
|
|
|
|
Call_StartForward(gH_OnFinish_Post);
|
|
Call_PushCell(client);
|
|
Call_PushCell(style);
|
|
Call_PushCell(time);
|
|
Call_PushCell(jumps);
|
|
Call_PushCell(strafes);
|
|
Call_PushCell(sync);
|
|
Call_PushCell(iRank);
|
|
Call_PushCell(iOverwrite);
|
|
Call_PushCell(track);
|
|
Call_PushCell(oldtime);
|
|
Call_PushCell(perfs);
|
|
Call_PushCell(avgvel);
|
|
Call_PushCell(maxvel);
|
|
Call_PushCell(timestamp);
|
|
Call_Finish();
|
|
}
|
|
|
|
if(bIncrementCompletions)
|
|
{
|
|
if (iOverwrite == 0)
|
|
{
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery),
|
|
"UPDATE %splayertimes SET completions = completions + 1 WHERE map = '%s' AND auth = %d AND style = %d AND track = %d;",
|
|
gS_MySQLPrefix, gS_Map, iSteamID, style, track);
|
|
|
|
gH_SQL.Query(SQL_OnIncrementCompletions_Callback, sQuery, 0, DBPrio_Low);
|
|
}
|
|
|
|
gI_PlayerCompletion[client][style][track]++;
|
|
|
|
if(iOverwrite == 0 && !Shavit_GetStyleSettingInt(style, "unranked"))
|
|
{
|
|
FormatEx(sMessage, 255, "%s[%s]%s %T",
|
|
gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText, "WorseTime", client, gS_ChatStrings.sStyle, gS_StyleStrings[style].sStyleName, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText, jumps, strafes, sSync, gS_ChatStrings.sText, sDifference);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FormatEx(sMessage, 255, "%s[%s]%s %T",
|
|
gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText, "UnrankedTime", client, gS_ChatStrings.sStyle, gS_StyleStrings[style].sStyleName, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText, jumps, strafes, sSync, gS_ChatStrings.sText);
|
|
}
|
|
|
|
timer_snapshot_t aSnapshot;
|
|
Shavit_SaveSnapshot(client, aSnapshot);
|
|
|
|
if (!Shavit_GetStyleSettingBool(style, "autobhop"))
|
|
{
|
|
FormatEx(sMessage2, sizeof(sMessage2), "%s[%s]%s %T", gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText, "CompletionExtraInfo", LANG_SERVER, gS_ChatStrings.sVariable, avgvel, gS_ChatStrings.sText, gS_ChatStrings.sVariable, maxvel, gS_ChatStrings.sText, gS_ChatStrings.sVariable, perfs, gS_ChatStrings.sText);
|
|
}
|
|
|
|
Action aResult = Plugin_Continue;
|
|
Call_StartForward(gH_OnFinishMessage);
|
|
Call_PushCell(client);
|
|
Call_PushCellRef(bEveryone);
|
|
Call_PushArrayEx(aSnapshot, sizeof(timer_snapshot_t), SM_PARAM_COPYBACK);
|
|
Call_PushCell(iOverwrite);
|
|
Call_PushCell(iRank);
|
|
Call_PushStringEx(sMessage, 255, SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
|
|
Call_PushCell(255);
|
|
Call_PushStringEx(sMessage2, sizeof(sMessage2), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
|
|
Call_PushCell(sizeof(sMessage2));
|
|
Call_Finish(aResult);
|
|
|
|
if(aResult < Plugin_Handled)
|
|
{
|
|
if(bEveryone)
|
|
{
|
|
Shavit_PrintToChatAll("%s", sMessage);
|
|
}
|
|
else
|
|
{
|
|
Shavit_PrintToChat(client, "%s", sMessage);
|
|
|
|
for(int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if(client != i && IsValidClient(i) && GetSpectatorTarget(i) == client)
|
|
{
|
|
FormatEx(sMessage, sizeof(sMessage), "%s[%s]%s %T",
|
|
gS_ChatStrings.sVariable, sTrack, gS_ChatStrings.sText, "NotFirstCompletionWorse", i, gS_ChatStrings.sVariable2, client, gS_ChatStrings.sText, gS_ChatStrings.sStyle, gS_StyleStrings[style].sStyleName, gS_ChatStrings.sText, gS_ChatStrings.sVariable2, sTime, gS_ChatStrings.sText, gS_ChatStrings.sVariable, iRank, gS_ChatStrings.sText, jumps, strafes, sSync, gS_ChatStrings.sText, sDifference);
|
|
Shavit_PrintToChat(i, "%s", sMessage);
|
|
|
|
if (sMessage2[0] != 0)
|
|
{
|
|
Shavit_PrintToChat(i, "%s", sMessage2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sMessage2[0] != 0)
|
|
{
|
|
Shavit_PrintToChat(client, "%s", sMessage2);
|
|
}
|
|
}
|
|
|
|
// update pb cache only after sending the message so we can grab the old one inside the Shavit_OnFinishMessage forward
|
|
if(iOverwrite > 0)
|
|
{
|
|
gF_PlayerRecord[client][style][track] = time;
|
|
}
|
|
}
|
|
|
|
public void SQL_OnIncrementCompletions_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR OnIncrementCompletions) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
public void SQL_OnFinish_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR OnFinish) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
int client = GetClientFromSerial(data);
|
|
|
|
if(client == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateWRCache(client);
|
|
}
|
|
|
|
public void Trans_ReplaceStageTimes_Success(Database db, any data, int numQueries, DBResultSet[] results, any[] queryData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
public void Trans_ReplaceStageTimes_Error(Database db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
|
|
{
|
|
LogError("Timer (ReplaceStageTimes) SQL query failed %d/%d. Reason: %s", failIndex, numQueries, error);
|
|
}
|
|
|
|
void UpdateLeaderboards()
|
|
{
|
|
char sQuery[512];
|
|
FormatEx(sQuery, sizeof(sQuery), "SELECT style, track, time, exact_time_int FROM %splayertimes WHERE map = '%s' ORDER BY time ASC, date ASC;", gS_MySQLPrefix, gS_Map);
|
|
gH_SQL.Query(SQL_UpdateLeaderboards_Callback, sQuery);
|
|
}
|
|
|
|
public void SQL_UpdateLeaderboards_Callback(Database db, DBResultSet results, const char[] error, any data)
|
|
{
|
|
if(results == null)
|
|
{
|
|
LogError("Timer (WR UpdateLeaderboards) SQL query failed. Reason: %s", error);
|
|
|
|
return;
|
|
}
|
|
|
|
ResetLeaderboards();
|
|
|
|
while(results.FetchRow())
|
|
{
|
|
int style = results.FetchInt(0);
|
|
int track = results.FetchInt(1);
|
|
|
|
if(style >= gI_Styles || Shavit_GetStyleSettingInt(style, "unranked") || track >= TRACKS_SIZE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
gA_Leaderboard[style][track].Push(ExactTimeMaybe(results.FetchFloat(2), results.FetchInt(3)));
|
|
}
|
|
|
|
for(int i = 0; i < gI_Styles; i++)
|
|
{
|
|
if (Shavit_GetStyleSettingInt(i, "unranked"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for(int j = 0; j < TRACKS_SIZE; j++)
|
|
{
|
|
SortADTArray(gA_Leaderboard[i][j], Sort_Ascending, Sort_Float);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Action Shavit_OnStageMessage(int client, int stageNumber, char[] message, int maxlen)
|
|
{
|
|
int style = Shavit_GetBhopStyle(client);
|
|
int track = Shavit_GetClientTrack(client);
|
|
float stageTime = Shavit_GetClientTime(client);
|
|
float stageTimeWR = gA_StageWR[style][track][stageNumber];
|
|
|
|
gA_StageTimes[client][stageNumber] = stageTime;
|
|
|
|
if (stageTimeWR == 0.0)
|
|
{
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
float fDifference = (stageTime - stageTimeWR);
|
|
|
|
char sStageTime[16];
|
|
FormatSeconds(stageTime, sStageTime, 16);
|
|
|
|
char sDifference[16];
|
|
FormatSeconds(fDifference, sDifference, 16);
|
|
|
|
if(fDifference >= 0.0)
|
|
{
|
|
Format(sDifference, sizeof(sDifference), "+%s", sDifference);
|
|
}
|
|
|
|
Shavit_PrintToChat(client, "%T", "WRStageTime", client, gS_ChatStrings.sText, gS_ChatStrings.sVariable, stageNumber, gS_ChatStrings.sText, gS_ChatStrings.sVariable, sStageTime, gS_ChatStrings.sText, (fDifference <= 0.0) ? gS_ChatStrings.sVariable : gS_ChatStrings.sWarning, sDifference, gS_ChatStrings.sText);
|
|
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Shavit_OnStart(int client, int track)
|
|
{
|
|
float empty_times[MAX_STAGES];
|
|
gA_StageTimes[client] = empty_times;
|
|
|
|
return Plugin_Continue;
|
|
}
|
|
|
|
int GetRecordAmount(int style, int track)
|
|
{
|
|
if(gA_Leaderboard[style][track] == null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return gA_Leaderboard[style][track].Length;
|
|
}
|
|
|
|
int GetRankForTime(int style, float time, int track)
|
|
{
|
|
int iRecords = GetRecordAmount(style, track);
|
|
|
|
if(time <= gF_WRTime[style][track] || iRecords <= 0)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if(gA_Leaderboard[style][track] != null && gA_Leaderboard[style][track].Length > 0)
|
|
{
|
|
for(int i = 0; i < iRecords; i++)
|
|
{
|
|
if(time <= gA_Leaderboard[style][track].Get(i))
|
|
{
|
|
return ++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (iRecords + 1);
|
|
}
|
|
|
|
float ExactTimeMaybe(float time, int exact_time)
|
|
{
|
|
return (exact_time != 0) ? view_as<float>(exact_time) : time;
|
|
}
|