summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-localranks
diff options
context:
space:
mode:
authornavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
committernavewindre <nw@moneybot.cc>2023-12-04 18:06:10 +0100
commitaef0d1c1268ab7d4bc18996c9c6b4da16a40aadc (patch)
tree43e766b51704f4ab8b383583bdc1871eeeb9c698 /sourcemod/scripting/gokz-localranks
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-localranks')
-rw-r--r--sourcemod/scripting/gokz-localranks/api.sp120
-rw-r--r--sourcemod/scripting/gokz-localranks/commands.sp506
-rw-r--r--sourcemod/scripting/gokz-localranks/db/cache_pbs.sp62
-rw-r--r--sourcemod/scripting/gokz-localranks/db/cache_records.sp54
-rw-r--r--sourcemod/scripting/gokz-localranks/db/create_tables.sp27
-rw-r--r--sourcemod/scripting/gokz-localranks/db/display_js.sp325
-rw-r--r--sourcemod/scripting/gokz-localranks/db/get_completion.sp155
-rw-r--r--sourcemod/scripting/gokz-localranks/db/helpers.sp91
-rw-r--r--sourcemod/scripting/gokz-localranks/db/js_top.sp286
-rw-r--r--sourcemod/scripting/gokz-localranks/db/map_top.sp388
-rw-r--r--sourcemod/scripting/gokz-localranks/db/player_top.sp165
-rw-r--r--sourcemod/scripting/gokz-localranks/db/print_average.sp152
-rw-r--r--sourcemod/scripting/gokz-localranks/db/print_js.sp108
-rw-r--r--sourcemod/scripting/gokz-localranks/db/print_pbs.sp266
-rw-r--r--sourcemod/scripting/gokz-localranks/db/print_records.sp173
-rw-r--r--sourcemod/scripting/gokz-localranks/db/process_new_time.sp157
-rw-r--r--sourcemod/scripting/gokz-localranks/db/recent_records.sp171
-rw-r--r--sourcemod/scripting/gokz-localranks/db/sql.sp411
-rw-r--r--sourcemod/scripting/gokz-localranks/db/update_ranked_map_pool.sp104
-rw-r--r--sourcemod/scripting/gokz-localranks/misc.sp319
20 files changed, 4040 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-localranks/api.sp b/sourcemod/scripting/gokz-localranks/api.sp
new file mode 100644
index 0000000..34b3ece
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/api.sp
@@ -0,0 +1,120 @@
+static GlobalForward H_OnTimeProcessed;
+static GlobalForward H_OnNewRecord;
+static GlobalForward H_OnRecordMissed;
+static GlobalForward H_OnPBMissed;
+
+
+
+// =====[ FORWARDS ]=====
+
+void CreateGlobalForwards()
+{
+ H_OnTimeProcessed = new GlobalForward("GOKZ_LR_OnTimeProcessed", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell, Param_Cell, Param_Float, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell, Param_Cell);
+ H_OnNewRecord = new GlobalForward("GOKZ_LR_OnNewRecord", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Cell, Param_Float, Param_Cell);
+ H_OnRecordMissed = new GlobalForward("GOKZ_LR_OnRecordMissed", ET_Ignore, Param_Cell, Param_Float, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
+ H_OnPBMissed = new GlobalForward("GOKZ_LR_OnPBMissed", ET_Ignore, Param_Cell, Param_Float, Param_Cell, Param_Cell, Param_Cell, Param_Cell);
+}
+
+void Call_OnTimeProcessed(
+ int client,
+ int steamID,
+ int mapID,
+ int course,
+ int mode,
+ int style,
+ float runTime,
+ int teleports,
+ bool firstTime,
+ float pbDiff,
+ int rank,
+ int maxRank,
+ bool firstTimePro,
+ float pbDiffPro,
+ int rankPro,
+ int maxRankPro)
+{
+ Call_StartForward(H_OnTimeProcessed);
+ Call_PushCell(client);
+ Call_PushCell(steamID);
+ Call_PushCell(mapID);
+ Call_PushCell(course);
+ Call_PushCell(mode);
+ Call_PushCell(style);
+ Call_PushFloat(runTime);
+ Call_PushCell(teleports);
+ Call_PushCell(firstTime);
+ Call_PushFloat(pbDiff);
+ Call_PushCell(rank);
+ Call_PushCell(maxRank);
+ Call_PushCell(firstTimePro);
+ Call_PushFloat(pbDiffPro);
+ Call_PushCell(rankPro);
+ Call_PushCell(maxRankPro);
+ Call_Finish();
+}
+
+void Call_OnNewRecord(int client, int steamID, int mapID, int course, int mode, int style, int recordType, float pbDiff, int teleportsUsed)
+{
+ Call_StartForward(H_OnNewRecord);
+ Call_PushCell(client);
+ Call_PushCell(steamID);
+ Call_PushCell(mapID);
+ Call_PushCell(course);
+ Call_PushCell(mode);
+ Call_PushCell(style);
+ Call_PushCell(recordType);
+ Call_PushFloat(pbDiff);
+ Call_PushCell(teleportsUsed);
+ Call_Finish();
+}
+
+void Call_OnRecordMissed(int client, float recordTime, int course, int mode, int style, int recordType)
+{
+ Call_StartForward(H_OnRecordMissed);
+ Call_PushCell(client);
+ Call_PushFloat(recordTime);
+ Call_PushCell(course);
+ Call_PushCell(mode);
+ Call_PushCell(style);
+ Call_PushCell(recordType);
+ Call_Finish();
+}
+
+void Call_OnPBMissed(int client, float pbTime, int course, int mode, int style, int recordType)
+{
+ Call_StartForward(H_OnPBMissed);
+ Call_PushCell(client);
+ Call_PushFloat(pbTime);
+ Call_PushCell(course);
+ Call_PushCell(mode);
+ Call_PushCell(style);
+ Call_PushCell(recordType);
+ Call_Finish();
+}
+
+
+
+// =====[ NATIVES ]=====
+
+void CreateNatives()
+{
+ CreateNative("GOKZ_LR_GetRecordMissed", Native_GetRecordMissed);
+ CreateNative("GOKZ_LR_GetPBMissed", Native_GetPBMissed);
+ CreateNative("GOKZ_LR_ReopenMapTopMenu", Native_ReopenMapTopMenu);
+}
+
+public int Native_GetRecordMissed(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_RecordMissed[GetNativeCell(1)][GetNativeCell(2)]);
+}
+
+public int Native_GetPBMissed(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_PBMissed[GetNativeCell(1)][GetNativeCell(2)]);
+}
+
+public int Native_ReopenMapTopMenu(Handle plugin, int numParams)
+{
+ ReopenMapTopMenu(GetNativeCell(1));
+ return 0;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/commands.sp b/sourcemod/scripting/gokz-localranks/commands.sp
new file mode 100644
index 0000000..44063af
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/commands.sp
@@ -0,0 +1,506 @@
+static float lastCommandTime[MAXPLAYERS + 1];
+
+
+
+void RegisterCommands()
+{
+ RegConsoleCmd("sm_top", CommandTop, "[KZ] Open a menu showing the top record holders.");
+ RegConsoleCmd("sm_maptop", CommandMapTop, "[KZ] Open a menu showing the top main course times of a map. Usage: !maptop <map>");
+ RegConsoleCmd("sm_bmaptop", CommandBMapTop, "[KZ] Open a menu showing the top bonus times of a map. Usage: !bmaptop <#bonus> <map>");
+ RegConsoleCmd("sm_bonustop", CommandBMapTop, "[KZ] Open a menu showing the top bonus times of a map. Usage: !bonustop <#bonus> <map>");
+ RegConsoleCmd("sm_btop", CommandBMapTop, "[KZ] Open a menu showing the top bonus times of a map. Usage: !btop <#bonus> <map>");
+ RegConsoleCmd("sm_pb", CommandPB, "[KZ] Show PB main course times and ranks in chat. Usage: !pb <map> <player>");
+ RegConsoleCmd("sm_bpb", CommandBPB, "[KZ] Show PB bonus times and ranks in chat. Usage: !bpb <#bonus> <map> <player>");
+ RegConsoleCmd("sm_wr", CommandWR, "[KZ] Show main course record times in chat. Usage: !wr <map>");
+ RegConsoleCmd("sm_bwr", CommandBWR, "[KZ] Show bonus record times in chat. Usage: !bwr <#bonus> <map>");
+ RegConsoleCmd("sm_avg", CommandAVG, "[KZ] Show the average main course run time in chat. Usage !avg <map>");
+ RegConsoleCmd("sm_bavg", CommandBAVG, "[KZ] Show the average bonus run time in chat. Usage !bavg <#bonus> <map>");
+ RegConsoleCmd("sm_pc", CommandPC, "[KZ] Show course completion in chat. Usage: !pc <player>");
+ RegConsoleCmd("sm_rr", CommandRecentRecords, "[KZ] Open a menu showing recently broken records.");
+ RegConsoleCmd("sm_latest", CommandRecentRecords, "[KZ] Open a menu showing recently broken records.");
+
+ RegConsoleCmd("sm_ljpb", CommandLJPB, "[KZ] Show PB Long Jump in chat. Usage: !ljpb <jumper>");
+ RegConsoleCmd("sm_bhpb", CommandBHPB, "[KZ] Show PB Bunnyhop in chat. Usage: !bhpb <jumper>");
+ RegConsoleCmd("sm_lbhpb", CommandLBHPB, "[KZ] Show PB Lowpre Bunnyhop in chat. Usage: !lbhpb <jumper>");
+ RegConsoleCmd("sm_mbhpb", CommandMBHPB, "[KZ] Show PB Multi Bunnyhop in chat. Usage: !mbhpb <jumper>");
+ RegConsoleCmd("sm_wjpb", CommandWJPB, "[KZ] Show PB Weird Jump in chat. Usage: !wjpb <jumper>");
+ RegConsoleCmd("sm_lwjpb", CommandLWJPB, "[KZ] Show PB Lowpre Weird Jump in chat. Usage: !lwjpb <jumper>");
+ RegConsoleCmd("sm_lajpb", CommandLAJPB, "[KZ] Show PB Ladder Jump in chat. Usage: !lajpb <jumper>");
+ RegConsoleCmd("sm_lahpb", CommandLAHPB, "[KZ] Show PB Ladderhop in chat. Usage: !lahpb <jumper>");
+ RegConsoleCmd("sm_jbpb", CommandJBPB, "[KZ] Show PB Jumpbug in chat. Usage: !jbpb <jumper>");
+ RegConsoleCmd("sm_js", CommandJS, "[KZ] Open a menu showing jumpstat PBs. Usage: !js <jumper>");
+ RegConsoleCmd("sm_jumpstats", CommandJS, "[KZ] Open a menu showing jumpstat PBs. Usage: !jumpstats <jumper>");
+ RegConsoleCmd("sm_jstop", CommandJSTop, "[KZ] Open a menu showing the top jumpstats.");
+ RegConsoleCmd("sm_jumptop", CommandJSTop, "[KZ] Open a menu showing the top jumpstats.");
+
+ RegAdminCmd("sm_updatemappool", CommandUpdateMapPool, ADMFLAG_ROOT, "[KZ] Update the ranked map pool with the list of maps in cfg/sourcemod/gokz/gokz-localranks-mappool.cfg.");
+}
+
+public Action CommandTop(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ DisplayPlayerTopModeMenu(client);
+ return Plugin_Handled;
+}
+
+public Action CommandMapTop(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Open map top for current map
+ DB_OpenMapTopModeMenu(client, GOKZ_DB_GetCurrentMapID(), 0);
+ }
+ else if (args >= 1)
+ { // Open map top for specified map
+ char specifiedMap[33];
+ GetCmdArg(1, specifiedMap, sizeof(specifiedMap));
+ DB_OpenMapTopModeMenu_FindMap(client, specifiedMap, 0);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandBMapTop(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Open Bonus 1 top for current map
+ DB_OpenMapTopModeMenu(client, GOKZ_DB_GetCurrentMapID(), 1);
+ }
+ else if (args == 1)
+ { // Open specified Bonus # top for current map
+ char argBonus[4];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_OpenMapTopModeMenu(client, GOKZ_DB_GetCurrentMapID(), bonus);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ else if (args >= 2)
+ { // Open specified bonus top for specified map
+ char argBonus[4], argMap[33];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ GetCmdArg(2, argMap, sizeof(argMap));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_OpenMapTopModeMenu_FindMap(client, argMap, bonus);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandPB(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Print their PBs for current map and their current mode
+ DB_PrintPBs(client, GetSteamAccountID(client), GOKZ_DB_GetCurrentMapID(), 0, GOKZ_GetCoreOption(client, Option_Mode));
+ if (gB_GOKZGlobal)
+ {
+ char steamid[32];
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ GOKZ_GL_PrintRecords(client, "", 0, GOKZ_GetCoreOption(client, Option_Mode), steamid);
+ }
+ }
+ else if (args == 1)
+ { // Print their PBs for specified map and their current mode
+ char argMap[33];
+ GetCmdArg(1, argMap, sizeof(argMap));
+ DB_PrintPBs_FindMap(client, GetSteamAccountID(client), argMap, 0, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else if (args >= 2)
+ { // Print specified player's PBs for specified map and their current mode
+ char argMap[33], argPlayer[MAX_NAME_LENGTH];
+ GetCmdArg(1, argMap, sizeof(argMap));
+ GetCmdArg(2, argPlayer, sizeof(argPlayer));
+ DB_PrintPBs_FindPlayerAndMap(client, argPlayer, argMap, 0, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandBPB(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Print their Bonus 1 PBs for current map and their current mode
+ DB_PrintPBs(client, GetSteamAccountID(client), GOKZ_DB_GetCurrentMapID(), 1, GOKZ_GetCoreOption(client, Option_Mode));
+ if (gB_GOKZGlobal)
+ {
+ char steamid[32];
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ GOKZ_GL_PrintRecords(client, "", 1, GOKZ_GetCoreOption(client, Option_Mode), steamid);
+ }
+ }
+ else if (args == 1)
+ { // Print their specified Bonus # PBs for current map and their current mode
+ char argBonus[4];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintPBs(client, GetSteamAccountID(client), GOKZ_DB_GetCurrentMapID(), bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ if (gB_GOKZGlobal)
+ {
+ char steamid[32];
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ GOKZ_GL_PrintRecords(client, "", bonus, GOKZ_GetCoreOption(client, Option_Mode), steamid);
+ }
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ else if (args == 2)
+ { // Print their specified Bonus # PBs for specified map and their current mode
+ char argBonus[4], argMap[33];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ GetCmdArg(2, argMap, sizeof(argMap));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintPBs_FindMap(client, GetSteamAccountID(client), argMap, bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ else if (args >= 3)
+ { // Print specified player's specified Bonus # PBs for specified map and their current mode
+ char argBonus[4], argMap[33], argPlayer[MAX_NAME_LENGTH];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ GetCmdArg(2, argMap, sizeof(argMap));
+ GetCmdArg(3, argPlayer, sizeof(argPlayer));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintPBs_FindPlayerAndMap(client, argPlayer, argMap, bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandWR(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Print record times for current map and their current mode
+ DB_PrintRecords(client, GOKZ_DB_GetCurrentMapID(), 0, GOKZ_GetCoreOption(client, Option_Mode));
+ if (gB_GOKZGlobal)
+ {
+ GOKZ_GL_PrintRecords(client, "", 0, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ }
+ else if (args >= 1)
+ { // Print record times for specified map and their current mode
+ char argMap[33];
+ GetCmdArg(1, argMap, sizeof(argMap));
+ DB_PrintRecords_FindMap(client, argMap, 0, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandBWR(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Print Bonus 1 record times for current map and their current mode
+ DB_PrintRecords(client, GOKZ_DB_GetCurrentMapID(), 1, GOKZ_GetCoreOption(client, Option_Mode));
+ if (gB_GOKZGlobal)
+ {
+ GOKZ_GL_PrintRecords(client, "", 1, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ }
+ else if (args == 1)
+ { // Print specified Bonus # record times for current map and their current mode
+ char argBonus[4];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintRecords(client, GOKZ_DB_GetCurrentMapID(), bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ if (gB_GOKZGlobal)
+ {
+ GOKZ_GL_PrintRecords(client, "", bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ else if (args >= 2)
+ { // Print specified Bonus # record times for specified map and their current mode
+ char argBonus[4], argMap[33];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ GetCmdArg(2, argMap, sizeof(argMap));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintRecords_FindMap(client, argMap, bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandAVG(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Print average times for current map and their current mode
+ DB_PrintAverage(client, GOKZ_DB_GetCurrentMapID(), 0, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else if (args >= 1)
+ { // Print average times for specified map and their current mode
+ char argMap[33];
+ GetCmdArg(1, argMap, sizeof(argMap));
+ DB_PrintAverage_FindMap(client, argMap, 0, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandBAVG(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args == 0)
+ { // Print Bonus 1 average times for current map and their current mode
+ DB_PrintAverage(client, GOKZ_DB_GetCurrentMapID(), 1, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else if (args == 1)
+ { // Print specified Bonus # average times for current map and their current mode
+ char argBonus[4];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintAverage(client, GOKZ_DB_GetCurrentMapID(), bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ else if (args >= 2)
+ { // Print specified Bonus # average times for specified map and their current mode
+ char argBonus[4], argMap[33];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ GetCmdArg(2, argMap, sizeof(argMap));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DB_PrintAverage_FindMap(client, argMap, bonus, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandPC(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args < 1)
+ {
+ DB_GetCompletion(client, GetSteamAccountID(client), GOKZ_GetCoreOption(client, Option_Mode), true);
+ }
+ else if (args >= 1)
+ { // Print record times for specified map and their current mode
+ char argPlayer[MAX_NAME_LENGTH];
+ GetCmdArg(1, argPlayer, sizeof(argPlayer));
+ DB_GetCompletion_FindPlayer(client, argPlayer, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandRecentRecords(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ DisplayRecentRecordsModeMenu(client);
+ return Plugin_Handled;
+}
+
+public Action CommandUpdateMapPool(int client, int args)
+{
+ DB_UpdateRankedMapPool(client);
+ return Plugin_Handled;
+}
+
+public Action CommandLJPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_LongJump);
+ return Plugin_Handled;
+}
+
+public Action CommandBHPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_Bhop);
+ return Plugin_Handled;
+}
+
+public Action CommandLBHPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_LowpreBhop);
+ return Plugin_Handled;
+}
+
+public Action CommandMBHPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_MultiBhop);
+ return Plugin_Handled;
+}
+
+public Action CommandWJPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_WeirdJump);
+ return Plugin_Handled;
+}
+
+public Action CommandLWJPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_LowpreWeirdJump);
+ return Plugin_Handled;
+}
+
+public Action CommandLAJPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_LadderJump);
+ return Plugin_Handled;
+}
+
+public Action CommandLAHPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_Ladderhop);
+ return Plugin_Handled;
+}
+
+public Action CommandJBPB(int client, int args)
+{
+ DisplayJumpstatRecordCommand(client, args, JumpType_Jumpbug);
+ return Plugin_Handled;
+}
+
+public Action CommandJS(int client, int args)
+{
+ if (IsSpammingCommands(client))
+ {
+ return Plugin_Handled;
+ }
+
+ if (args < 1)
+ {
+ DB_OpenJumpStatsModeMenu(client, GetSteamAccountID(client));
+ }
+ else if (args >= 1)
+ {
+ char argPlayer[MAX_NAME_LENGTH];
+ GetCmdArg(1, argPlayer, sizeof(argPlayer));
+ DB_OpenJumpStatsModeMenu_FindPlayer(client, argPlayer);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandJSTop(int client, int args)
+{
+ DisplayJumpTopModeMenu(client);
+ return Plugin_Handled;
+}
+
+void DisplayJumpstatRecordCommand(int client, int args, int jumpType)
+{
+ if (args >= 1)
+ {
+ char argJumper[33];
+ GetCmdArg(1, argJumper, sizeof(argJumper));
+ DisplayJumpstatRecord(client, jumpType, argJumper);
+ }
+ else
+ {
+ DisplayJumpstatRecord(client, jumpType);
+ }
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+bool IsSpammingCommands(int client, bool printMessage = true)
+{
+ float currentTime = GetEngineTime();
+ float timeSinceLastCommand = currentTime - lastCommandTime[client];
+ if (timeSinceLastCommand < LR_COMMAND_COOLDOWN)
+ {
+ if (printMessage)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Please Wait Before Using Command", LR_COMMAND_COOLDOWN - timeSinceLastCommand + 0.1);
+ }
+ return true;
+ }
+
+ // Not spamming commands - all good!
+ lastCommandTime[client] = currentTime;
+ return false;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/cache_pbs.sp b/sourcemod/scripting/gokz-localranks/db/cache_pbs.sp
new file mode 100644
index 0000000..12c3ed2
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/cache_pbs.sp
@@ -0,0 +1,62 @@
+/*
+ Caches the player's personal best times on the map.
+*/
+
+
+
+void DB_CachePBs(int client, int steamID)
+{
+ char query[1024];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Reset PB exists array
+ for (int course = 0; course < GOKZ_MAX_COURSES; course++)
+ {
+ for (int mode = 0; mode < MODE_COUNT; mode++)
+ {
+ for (int timeType = 0; timeType < TIMETYPE_COUNT; timeType++)
+ {
+ gB_PBExistsCache[client][course][mode][timeType] = false;
+ }
+ }
+ }
+
+ int mapID = GOKZ_DB_GetCurrentMapID();
+
+ // Get Map PBs
+ FormatEx(query, sizeof(query), sql_getpbs, steamID, mapID);
+ txn.AddQuery(query);
+ // Get PRO PBs
+ FormatEx(query, sizeof(query), sql_getpbspro, steamID, mapID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_CachePBs, DB_TxnFailure_Generic, GetClientUserId(client), DBPrio_High);
+}
+
+public void DB_TxnSuccess_CachePBs(Handle db, int userID, int numQueries, Handle[] results, any[] queryData)
+{
+ int client = GetClientOfUserId(userID);
+ if (client < 1 || client > MaxClients || !IsClientAuthorized(client) || IsFakeClient(client))
+ {
+ return;
+ }
+
+ int course, mode;
+
+ while (SQL_FetchRow(results[0]))
+ {
+ course = SQL_FetchInt(results[0], 1);
+ mode = SQL_FetchInt(results[0], 2);
+ gB_PBExistsCache[client][course][mode][TimeType_Nub] = true;
+ gF_PBTimesCache[client][course][mode][TimeType_Nub] = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[0], 0));
+ }
+
+ while (SQL_FetchRow(results[1]))
+ {
+ course = SQL_FetchInt(results[1], 1);
+ mode = SQL_FetchInt(results[1], 2);
+ gB_PBExistsCache[client][course][mode][TimeType_Pro] = true;
+ gF_PBTimesCache[client][course][mode][TimeType_Pro] = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[1], 0));
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/cache_records.sp b/sourcemod/scripting/gokz-localranks/db/cache_records.sp
new file mode 100644
index 0000000..611b13c
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/cache_records.sp
@@ -0,0 +1,54 @@
+/*
+ Caches the record times on the map.
+*/
+
+
+
+void DB_CacheRecords(int mapID)
+{
+ char query[1024];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Reset record exists array
+ for (int course = 0; course < GOKZ_MAX_COURSES; course++)
+ {
+ for (int mode = 0; mode < MODE_COUNT; mode++)
+ {
+ for (int timeType = 0; timeType < TIMETYPE_COUNT; timeType++)
+ {
+ gB_RecordExistsCache[course][mode][timeType] = false;
+ }
+ }
+ }
+
+ // Get Map WRs
+ FormatEx(query, sizeof(query), sql_getwrs, mapID);
+ txn.AddQuery(query);
+ // Get PRO WRs
+ FormatEx(query, sizeof(query), sql_getwrspro, mapID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_CacheRecords, DB_TxnFailure_Generic, _, DBPrio_High);
+}
+
+public void DB_TxnSuccess_CacheRecords(Handle db, any data, int numQueries, Handle[] results, any[] queryData)
+{
+ int course, mode;
+
+ while (SQL_FetchRow(results[0]))
+ {
+ course = SQL_FetchInt(results[0], 1);
+ mode = SQL_FetchInt(results[0], 2);
+ gB_RecordExistsCache[course][mode][TimeType_Nub] = true;
+ gF_RecordTimesCache[course][mode][TimeType_Nub] = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[0], 0));
+ }
+
+ while (SQL_FetchRow(results[1]))
+ {
+ course = SQL_FetchInt(results[1], 1);
+ mode = SQL_FetchInt(results[1], 2);
+ gB_RecordExistsCache[course][mode][TimeType_Pro] = true;
+ gF_RecordTimesCache[course][mode][TimeType_Pro] = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[1], 0));
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/create_tables.sp b/sourcemod/scripting/gokz-localranks/db/create_tables.sp
new file mode 100644
index 0000000..a15f67c
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/create_tables.sp
@@ -0,0 +1,27 @@
+/*
+ Table creation and alteration.
+*/
+
+
+
+void DB_CreateTables()
+{
+ Transaction txn = SQL_CreateTransaction();
+
+ // Create/alter database tables
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ txn.AddQuery(sqlite_maps_alter1);
+ }
+ case DatabaseType_MySQL:
+ {
+ txn.AddQuery(mysql_maps_alter1);
+ }
+ }
+
+ // No error logs for this transaction as it will always throw an error
+ // if the column already exists, which is more annoying than helpful.
+ SQL_ExecuteTransaction(gH_DB, txn, _, _, _, DBPrio_High);
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/display_js.sp b/sourcemod/scripting/gokz-localranks/db/display_js.sp
new file mode 100644
index 0000000..779148f
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/display_js.sp
@@ -0,0 +1,325 @@
+/*
+ Displays player's best jumpstats in a menu.
+*/
+
+static int jumpStatsTargetSteamID[MAXPLAYERS + 1];
+static char jumpStatsTargetAlias[MAXPLAYERS + 1][MAX_NAME_LENGTH];
+static int jumpStatsMode[MAXPLAYERS + 1];
+
+
+
+// =====[ JUMPSTATS MODE ]=====
+
+void DB_OpenJumpStatsModeMenu(int client, int targetSteamID)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(targetSteamID);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Retrieve name of target
+ FormatEx(query, sizeof(query), sql_players_getalias, targetSteamID);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_OpenJumpStatsModeMenu, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenJumpStatsModeMenu(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int targetSteamID = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ // Get name of target
+ if (!SQL_FetchRow(results[0]))
+ {
+ return;
+ }
+ SQL_FetchString(results[0], 0, jumpStatsTargetAlias[client], sizeof(jumpStatsTargetAlias[]));
+
+ jumpStatsTargetSteamID[client] = targetSteamID;
+ DisplayJumpStatsModeMenu(client);
+}
+
+void DB_OpenJumpStatsModeMenu_FindPlayer(int client, const char[] target)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteString(target);
+
+ DB_FindPlayer(target, DB_TxnSuccess_OpenJumpStatsModeMenu_FindPlayer, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenJumpStatsModeMenu_FindPlayer(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ char playerSearch[33];
+ data.ReadString(playerSearch, sizeof(playerSearch));
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ else if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Player Not Found", playerSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]))
+ {
+ DB_OpenJumpStatsModeMenu(client, SQL_FetchInt(results[0], 0));
+ }
+}
+
+
+
+// =====[ MENUS ]=====
+
+static void DisplayJumpStatsModeMenu(int client)
+{
+ Menu menu = new Menu(MenuHandler_JumpStatsMode);
+ menu.SetTitle("%T", "Jump Stats Mode Menu - Title", client, jumpStatsTargetAlias[client]);
+ GOKZ_MenuAddModeItems(client, menu, false);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void DisplayJumpStatsBlockTypeMenu(int client, int mode)
+{
+ jumpStatsMode[client] = mode;
+
+ Menu menu = new Menu(MenuHandler_JumpStatsBlockType);
+ menu.SetTitle("%T", "Jump Stats Block Type Menu - Title", client, jumpStatsTargetAlias[client], gC_ModeNames[jumpStatsMode[client]]);
+ JumpStatsBlockTypeMenuAddItems(client, menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void JumpStatsBlockTypeMenuAddItems(int client, Menu menu)
+{
+ char str[64];
+ FormatEx(str, sizeof(str), "%T", "Jump Records", client);
+ menu.AddItem("jump", str);
+ FormatEx(str, sizeof(str), "%T %T", "Block", client, "Jump Records", client);
+ menu.AddItem("blockjump", str);
+}
+
+
+
+// =====[ JUMPSTATS ]=====
+
+void DB_OpenJumpStats(int client, int targetSteamID, int mode, int blockType)
+{
+ char query[1024];
+ Transaction txn = SQL_CreateTransaction();
+
+ // Get alias
+ FormatEx(query, sizeof(query), sql_players_getalias, targetSteamID);
+ txn.AddQuery(query);
+
+ // Get jumpstat pbs
+ if (blockType == 0)
+ {
+ FormatEx(query, sizeof(query), sql_jumpstats_getpbs, targetSteamID, mode);
+ }
+ else
+ {
+ FormatEx(query, sizeof(query), sql_jumpstats_getblockpbs, targetSteamID, mode);
+ }
+ txn.AddQuery(query);
+
+ DataPack datapack = new DataPack();
+ datapack.WriteCell(GetClientUserId(client));
+ datapack.WriteCell(targetSteamID);
+ datapack.WriteCell(mode);
+ datapack.WriteCell(blockType);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_OpenJumpStats, DB_TxnFailure_Generic_DataPack, datapack, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenJumpStats(Handle db, DataPack datapack, int numQueries, Handle[] results, any[] queryData)
+{
+ datapack.Reset();
+ int client = GetClientOfUserId(datapack.ReadCell());
+ int targetSteamID = datapack.ReadCell();
+ int mode = datapack.ReadCell();
+ int blockType = datapack.ReadCell();
+ delete datapack;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ // Get target name
+ if (!SQL_FetchRow(results[0]))
+ {
+ return;
+ }
+ char alias[MAX_NAME_LENGTH];
+ SQL_FetchString(results[0], 0, alias, sizeof(alias));
+
+ if (SQL_GetRowCount(results[1]) == 0)
+ {
+ if (blockType == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%T", "Jump Stats Menu - No Jump Stats", client, alias);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%T", "Jump Stats Menu - No Block Jump Stats", client, alias);
+ }
+
+ DisplayJumpStatsBlockTypeMenu(client, mode);
+ return;
+ }
+
+ Menu menu = new Menu(MenuHandler_JumpStatsSubmenu);
+ if (blockType == 0)
+ {
+ menu.SetTitle("%T", "Jump Stats Submenu - Title (Jump)", client, alias, gC_ModeNames[mode]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Jump Stats Submenu - Title (Block Jump)", client, alias, gC_ModeNames[mode]);
+ }
+
+ char buffer[128], admin[64];
+ bool clientIsAdmin = CheckCommandAccess(client, "sm_deletejump", ADMFLAG_ROOT, false);
+
+ if (blockType == 0)
+ {
+ FormatEx(buffer, sizeof(buffer), "%T", "Jump Stats - Jump Console Header",
+ client, gC_ModeNames[mode], alias, targetSteamID & 1, targetSteamID >> 1);
+ PrintToConsole(client, "%s", buffer);
+ int titleLength = strlen(buffer);
+ strcopy(buffer, sizeof(buffer), "----------------------------------------------------------------");
+ buffer[titleLength] = '\0';
+ PrintToConsole(client, "%s", buffer);
+
+ while (SQL_FetchRow(results[1]))
+ {
+ int jumpid = SQL_FetchInt(results[1], JumpstatDB_PBMenu_JumpID);
+ int jumpType = SQL_FetchInt(results[1], JumpstatDB_PBMenu_JumpType);
+ float distance = SQL_FetchFloat(results[1], JumpstatDB_PBMenu_Distance) / GOKZ_DB_JS_DISTANCE_PRECISION;
+ int strafes = SQL_FetchInt(results[1], JumpstatDB_PBMenu_Strafes);
+ float sync = SQL_FetchFloat(results[1], JumpstatDB_PBMenu_Sync) / GOKZ_DB_JS_SYNC_PRECISION;
+ float pre = SQL_FetchFloat(results[1], JumpstatDB_PBMenu_Pre) / GOKZ_DB_JS_PRE_PRECISION;
+ float max = SQL_FetchFloat(results[1], JumpstatDB_PBMenu_Max) / GOKZ_DB_JS_MAX_PRECISION;
+ float airtime = SQL_FetchFloat(results[1], JumpstatDB_PBMenu_Air) / GOKZ_DB_JS_AIRTIME_PRECISION;
+
+ FormatEx(buffer, sizeof(buffer), "%0.4f %s", distance, gC_JumpTypes[jumpType]);
+ menu.AddItem("", buffer, ITEMDRAW_DISABLED);
+
+ FormatEx(buffer, sizeof(buffer), "%8s", gC_JumpTypesShort[jumpType]);
+ buffer[3] = '\0';
+
+ if (clientIsAdmin)
+ {
+ FormatEx(admin, sizeof(admin), "<id: %d>", jumpid);
+ }
+
+ PrintToConsole(client, "%s %0.4f [%d %t | %.2f%% %t | %.2f %t | %.2f %t | %.4f %t] %s",
+ buffer, distance, strafes, "Strafes", sync, "Sync", pre, "Pre", max, "Max", airtime, "Air", admin);
+ }
+ }
+ else
+ {
+ FormatEx(buffer, sizeof(buffer), "%T", "Jump Stats - Block Jump Console Header",
+ client, gC_ModeNames[mode], alias, targetSteamID & 1, targetSteamID >> 1);
+ PrintToConsole(client, "%s", buffer);
+ int titleLength = strlen(buffer);
+ strcopy(buffer, sizeof(buffer), "----------------------------------------------------------------");
+ buffer[titleLength] = '\0';
+ PrintToConsole(client, "%s", buffer);
+
+ while (SQL_FetchRow(results[1]))
+ {
+ int jumpid = SQL_FetchInt(results[1], JumpstatDB_BlockPBMenu_JumpID);
+ int jumpType = SQL_FetchInt(results[1], JumpstatDB_BlockPBMenu_JumpType);
+ int block = SQL_FetchInt(results[1], JumpstatDB_BlockPBMenu_Block);
+ float distance = SQL_FetchFloat(results[1], JumpstatDB_BlockPBMenu_Distance) / GOKZ_DB_JS_DISTANCE_PRECISION;
+ int strafes = SQL_FetchInt(results[1], JumpstatDB_BlockPBMenu_Strafes);
+ float sync = SQL_FetchFloat(results[1], JumpstatDB_BlockPBMenu_Sync) / GOKZ_DB_JS_SYNC_PRECISION;
+ float pre = SQL_FetchFloat(results[1], JumpstatDB_BlockPBMenu_Pre) / GOKZ_DB_JS_PRE_PRECISION;
+ float max = SQL_FetchFloat(results[1], JumpstatDB_BlockPBMenu_Max) / GOKZ_DB_JS_MAX_PRECISION;
+ float airtime = SQL_FetchFloat(results[1], JumpstatDB_BlockPBMenu_Air) / GOKZ_DB_JS_AIRTIME_PRECISION;
+
+ FormatEx(buffer, sizeof(buffer), "%d %T (%0.4f) %s", block, "Block", client, distance, gC_JumpTypes[jumpType]);
+ menu.AddItem("", buffer, ITEMDRAW_DISABLED);
+
+ FormatEx(buffer, sizeof(buffer), "%8s", gC_JumpTypesShort[jumpType]);
+ buffer[3] = '\0';
+
+ if (clientIsAdmin)
+ {
+ FormatEx(admin, sizeof(admin), "<id: %d>", jumpid);
+ }
+
+ PrintToConsole(client, "%s %d %t (%0.4f) [%d %t | %.2f%% %t | %.2f %t | %.2f %t | %.4f %t] %s",
+ buffer, block, "Block", distance, strafes, "Strafes", sync, "Sync", pre, "Pre", max, "Max", airtime, "Air", admin);
+ }
+ }
+
+ PrintToConsole(client, "");
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+
+
+// =====[ MENU HANDLERS ]=====
+
+public int MenuHandler_JumpStatsMode(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = mode
+ DisplayJumpStatsBlockTypeMenu(param1, param2);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_JumpStatsBlockType(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = blockType
+ DB_OpenJumpStats(param1, jumpStatsTargetSteamID[param1], jumpStatsMode[param1], param2);
+ }
+ else if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayJumpStatsModeMenu(param1);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_JumpStatsSubmenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayJumpStatsBlockTypeMenu(param1, jumpStatsMode[param1]);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/get_completion.sp b/sourcemod/scripting/gokz-localranks/db/get_completion.sp
new file mode 100644
index 0000000..fbc76e7
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/get_completion.sp
@@ -0,0 +1,155 @@
+/*
+ Gets the number and percentage of maps completed.
+*/
+
+
+
+void DB_GetCompletion(int client, int targetSteamID, int mode, bool print)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(targetSteamID);
+ data.WriteCell(mode);
+ data.WriteCell(print);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Retrieve Alias of SteamID
+ FormatEx(query, sizeof(query), sql_players_getalias, targetSteamID);
+ txn.AddQuery(query);
+ // Get total number of ranked main courses
+ txn.AddQuery(sql_getcount_maincourses);
+ // Get number of main course completions
+ FormatEx(query, sizeof(query), sql_getcount_maincoursescompleted, targetSteamID, mode);
+ txn.AddQuery(query);
+ // Get number of main course completions (PRO)
+ FormatEx(query, sizeof(query), sql_getcount_maincoursescompletedpro, targetSteamID, mode);
+ txn.AddQuery(query);
+
+ // Get total number of ranked bonuses
+ txn.AddQuery(sql_getcount_bonuses);
+ // Get number of bonus completions
+ FormatEx(query, sizeof(query), sql_getcount_bonusescompleted, targetSteamID, mode);
+ txn.AddQuery(query);
+ // Get number of bonus completions (PRO)
+ FormatEx(query, sizeof(query), sql_getcount_bonusescompletedpro, targetSteamID, mode);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_GetCompletion, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_GetCompletion(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int targetSteamID = data.ReadCell();
+ int mode = data.ReadCell();
+ bool print = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ char playerName[MAX_NAME_LENGTH];
+ int totalMainCourses, completions, completionsPro;
+ int totalBonuses, bonusCompletions, bonusCompletionsPro;
+
+ // Get Player Name from results
+ if (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, playerName, sizeof(playerName));
+ }
+
+ // Get total number of main courses
+ if (SQL_FetchRow(results[1]))
+ {
+ totalMainCourses = SQL_FetchInt(results[1], 0);
+ }
+ // Get completed main courses
+ if (SQL_FetchRow(results[2]))
+ {
+ completions = SQL_FetchInt(results[2], 0);
+ }
+ // Get completed main courses (PRO)
+ if (SQL_FetchRow(results[3]))
+ {
+ completionsPro = SQL_FetchInt(results[3], 0);
+ }
+
+ // Get total number of bonuses
+ if (SQL_FetchRow(results[4]))
+ {
+ totalBonuses = SQL_FetchInt(results[4], 0);
+ }
+ // Get completed bonuses
+ if (SQL_FetchRow(results[5])) {
+ bonusCompletions = SQL_FetchInt(results[5], 0);
+ }
+ // Get completed bonuses (PRO)
+ if (SQL_FetchRow(results[6]))
+ {
+ bonusCompletionsPro = SQL_FetchInt(results[6], 0);
+ }
+
+ // Print completion message to chat if specified
+ if (print)
+ {
+ if (totalMainCourses + totalBonuses == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "No Ranked Maps");
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Completion",
+ playerName,
+ completions, totalMainCourses, completionsPro, totalMainCourses,
+ bonusCompletions, totalBonuses, bonusCompletionsPro, totalBonuses,
+ gC_ModeNamesShort[mode]);
+ }
+ }
+
+ // Set scoreboard MVP stars to percentage PRO completion of server's default mode
+ if (totalMainCourses + totalBonuses != 0 && targetSteamID == GetSteamAccountID(client) && mode == GOKZ_GetDefaultMode())
+ {
+ CS_SetMVPCount(client, RoundToFloor(float(completionsPro + bonusCompletionsPro) / float(totalMainCourses + totalBonuses) * 100.0));
+ }
+}
+
+void DB_GetCompletion_FindPlayer(int client, const char[] target, int mode)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteString(target);
+ data.WriteCell(mode);
+
+ DB_FindPlayer(target, DB_TxnSuccess_GetCompletion_FindPlayer, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_GetCompletion_FindPlayer(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ char playerSearch[33];
+ data.ReadString(playerSearch, sizeof(playerSearch));
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ else if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Player Not Found", playerSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]))
+ {
+ DB_GetCompletion(client, SQL_FetchInt(results[0], 0), mode, true);
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/helpers.sp b/sourcemod/scripting/gokz-localranks/db/helpers.sp
new file mode 100644
index 0000000..670a420
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/helpers.sp
@@ -0,0 +1,91 @@
+/*
+ Database helper functions and callbacks.
+*/
+
+
+
+/* Error report callback for failed transactions */
+public void DB_TxnFailure_Generic(Handle db, any data, int numQueries, const char[] error, int failIndex, any[] queryData)
+{
+ LogError("Database transaction error: %s", error);
+}
+
+/* Error report callback for failed transactions which deletes the DataPack */
+public void DB_TxnFailure_Generic_DataPack(Handle db, DataPack data, int numQueries, const char[] error, int failIndex, any[] queryData)
+{
+ delete data;
+ LogError("Database transaction error: %s", error);
+}
+
+/* Used to search the database for a player name and return their PlayerID and alias
+
+ For SQLTxnSuccess onSuccess:
+ results[0] - 0:PlayerID, 1:Alias
+*/
+void DB_FindPlayer(const char[] playerSearch, SQLTxnSuccess onSuccess, any data = 0, DBPriority priority = DBPrio_Normal)
+{
+ char query[1024], playerEscaped[MAX_NAME_LENGTH * 2 + 1];
+ SQL_EscapeString(gH_DB, playerSearch, playerEscaped, sizeof(playerEscaped));
+
+ String_ToLower(playerEscaped, playerEscaped, sizeof(playerEscaped));
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Look for player name and retrieve their PlayerID
+ FormatEx(query, sizeof(query), sql_players_searchbyalias, playerEscaped, playerEscaped);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, onSuccess, DB_TxnFailure_Generic, data, priority);
+}
+
+/* Used to search the database for a map name and return its MapID and name
+
+ For SQLTxnSuccess onSuccess:
+ results[0] - 0:MapID, 1:Name
+*/
+void DB_FindMap(const char[] mapSearch, SQLTxnSuccess onSuccess, any data = 0, DBPriority priority = DBPrio_Normal)
+{
+ char query[1024], mapEscaped[129];
+ SQL_EscapeString(gH_DB, mapSearch, mapEscaped, sizeof(mapEscaped));
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Look for map name and retrieve it's MapID
+ FormatEx(query, sizeof(query), sql_maps_searchbyname, mapEscaped, mapEscaped);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, onSuccess, DB_TxnFailure_Generic, data, priority);
+}
+
+/* Used to search the database for a player name and return their PlayerID and alias,
+ and search the database for a map name and return its MapID and name
+
+ For SQLTxnSuccess onSuccess:
+ results[0] - 0:PlayerID, 1:Alias
+ results[1] - 0:MapID, 1:Name
+*/
+void DB_FindPlayerAndMap(const char[] playerSearch, const char[] mapSearch, SQLTxnSuccess onSuccess, any data = 0, DBPriority priority = DBPrio_Normal)
+{
+ char query[1024], mapEscaped[129], playerEscaped[MAX_NAME_LENGTH * 2 + 1];
+ SQL_EscapeString(gH_DB, playerSearch, playerEscaped, sizeof(playerEscaped));
+ SQL_EscapeString(gH_DB, mapSearch, mapEscaped, sizeof(mapEscaped));
+
+ String_ToLower(playerEscaped, playerEscaped, sizeof(playerEscaped));
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Look for player name and retrieve their PlayerID
+ FormatEx(query, sizeof(query), sql_players_searchbyalias, playerEscaped, playerEscaped);
+ txn.AddQuery(query);
+ // Look for map name and retrieve it's MapID
+ FormatEx(query, sizeof(query), sql_maps_searchbyname, mapEscaped, mapEscaped);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, onSuccess, DB_TxnFailure_Generic, data, priority);
+}
+
+// Used to convert the Account ID to the SteamID we can use for a Global API query
+int GetSteam2FromAccountId(char[] result, int maxlen, int account_id)
+{
+ return Format(result, maxlen, "STEAM_1:%d:%d", view_as<bool>(account_id % 2), account_id / 2);
+}
diff --git a/sourcemod/scripting/gokz-localranks/db/js_top.sp b/sourcemod/scripting/gokz-localranks/db/js_top.sp
new file mode 100644
index 0000000..a336a90
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/js_top.sp
@@ -0,0 +1,286 @@
+
+static int jumpTopMode[MAXPLAYERS + 1];
+static int jumpTopType[MAXPLAYERS + 1];
+static int blockNums[MAXPLAYERS + 1][JS_TOP_RECORD_COUNT];
+static int jumpInfo[MAXPLAYERS + 1][JS_TOP_RECORD_COUNT][3];
+
+
+
+void DB_OpenJumpTop(int client, int mode, int jumpType, int blockType)
+{
+ char query[1024];
+
+ Transaction txn = SQL_CreateTransaction();
+
+ FormatEx(query, sizeof(query), sql_jumpstats_gettop, jumpType, mode, blockType, jumpType, mode, blockType, JS_TOP_RECORD_COUNT);
+ txn.AddQuery(query);
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(mode);
+ data.WriteCell(jumpType);
+ data.WriteCell(blockType);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_GetJumpTop, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+void DB_TxnSuccess_GetJumpTop(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int mode = data.ReadCell();
+ int type = data.ReadCell();
+ int blockType = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ jumpTopMode[client] = mode;
+ jumpTopType[client] = type;
+
+ int rows = SQL_GetRowCount(results[0]);
+ if (rows == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "No Jumpstats Found");
+ DisplayJumpTopBlockTypeMenu(client, mode, type);
+ return;
+ }
+
+ char display[128], alias[33], title[65], admin[65];
+ int jumpid, steamid, block, strafes;
+ float distance, sync, pre, max, airtime;
+
+ bool clientIsAdmin = CheckCommandAccess(client, "sm_deletejump", ADMFLAG_ROOT, false);
+
+ Menu menu = new Menu(MenuHandler_JumpTopList);
+ menu.Pagination = 5;
+
+ if (blockType == 0)
+ {
+ menu.SetTitle("%T", "Jump Top Submenu - Title (Jump)", client, gC_ModeNames[mode], gC_JumpTypes[type]);
+
+ FormatEx(title, sizeof(title), "%s %s %T", gC_ModeNames[mode], gC_JumpTypes[type], "Top", client);
+ strcopy(display, sizeof(display), "----------------------------------------------------------------");
+ display[strlen(title)] = '\0';
+
+ PrintToConsole(client, title);
+ PrintToConsole(client, display);
+
+ for (int i = 0; i < rows; i++)
+ {
+ SQL_FetchRow(results[0]);
+ jumpid = SQL_FetchInt(results[0], JumpstatDB_Top20_JumpID);
+ steamid = SQL_FetchInt(results[0], JumpstatDB_Top20_SteamID);
+ SQL_FetchString(results[0], JumpstatDB_Top20_Alias, alias, sizeof(alias));
+ distance = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Distance)) / GOKZ_DB_JS_DISTANCE_PRECISION;
+ strafes = SQL_FetchInt(results[0], JumpstatDB_Top20_Strafes);
+ sync = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Sync)) / GOKZ_DB_JS_SYNC_PRECISION;
+ pre = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Pre)) / GOKZ_DB_JS_PRE_PRECISION;
+ max = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Max)) / GOKZ_DB_JS_MAX_PRECISION;
+ airtime = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Air)) / GOKZ_DB_JS_AIRTIME_PRECISION;
+
+ FormatEx(display, sizeof(display), "#%-2d %.4f %s", i + 1, distance, alias);
+
+ menu.AddItem(IntToStringEx(i), display);
+
+ if (clientIsAdmin)
+ {
+ FormatEx(admin, sizeof(admin), "<id: %d>", jumpid);
+ }
+
+ PrintToConsole(client, "#%-2d %.4f %s <STEAM_1:%d:%d> [%d %t | %.2f%% %t | %.2f %t | %.2f %t | %.4f %t] %s",
+ i + 1, distance, alias, steamid & 1, steamid >> 1,
+ strafes, "Strafes", sync, "Sync", pre, "Pre", max, "Max", airtime, "Air",
+ admin);
+
+ jumpInfo[client][i][0] = steamid;
+ jumpInfo[client][i][1] = type;
+ jumpInfo[client][i][2] = mode;
+ blockNums[client][i] = 0;
+ }
+ }
+ else
+ {
+ menu.SetTitle("%T", "Jump Top Submenu - Title (Block Jump)", client, gC_ModeNames[mode], gC_JumpTypes[type]);
+
+ FormatEx(title, sizeof(title), "%s %T %s %T", gC_ModeNames[mode], "Block", client, gC_JumpTypes[type], "Top", client);
+ strcopy(display, sizeof(display), "----------------------------------------------------------------");
+ display[strlen(title)] = '\0';
+
+ PrintToConsole(client, title);
+ PrintToConsole(client, display);
+
+ for (int i = 0; i < rows; i++)
+ {
+ SQL_FetchRow(results[0]);
+ jumpid = SQL_FetchInt(results[0], JumpstatDB_Top20_JumpID);
+ steamid = SQL_FetchInt(results[0], JumpstatDB_Top20_SteamID);
+ SQL_FetchString(results[0], JumpstatDB_Top20_Alias, alias, sizeof(alias));
+ block = SQL_FetchInt(results[0], JumpstatDB_Top20_Block);
+ distance = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Distance)) / GOKZ_DB_JS_DISTANCE_PRECISION;
+ strafes = SQL_FetchInt(results[0], JumpstatDB_Top20_Strafes);
+ sync = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Sync)) / GOKZ_DB_JS_SYNC_PRECISION;
+ pre = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Pre)) / GOKZ_DB_JS_PRE_PRECISION;
+ max = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Max)) / GOKZ_DB_JS_MAX_PRECISION;
+ airtime = float(SQL_FetchInt(results[0], JumpstatDB_Top20_Air)) / GOKZ_DB_JS_AIRTIME_PRECISION;
+
+ FormatEx(display, sizeof(display), "#%-2d %d %T (%.4f) %s", i + 1, block, "Block", client, distance, alias);
+ menu.AddItem(IntToStringEx(i), display);
+
+ if (clientIsAdmin)
+ {
+ FormatEx(admin, sizeof(admin), "<id: %d>", jumpid);
+ }
+
+ PrintToConsole(client, "#%-2d %d %t (%.4f) %s <STEAM_1:%d:%d> [%d %t | %.2f%% %t | %.2f %t | %.2f %t | %.4f %t] %s",
+ i + 1, block, "Block", distance, alias, steamid & 1, steamid >> 1,
+ strafes, "Strafes", sync, "Sync", pre, "Pre", max, "Max", airtime, "Air",
+ admin);
+
+ jumpInfo[client][i][0] = steamid;
+ jumpInfo[client][i][1] = type;
+ jumpInfo[client][i][2] = mode;
+ blockNums[client][i] = block;
+ }
+ }
+ menu.Display(client, MENU_TIME_FOREVER);
+ PrintToConsole(client, "");
+}
+
+// =====[ MENUS ]=====
+
+void DisplayJumpTopModeMenu(int client)
+{
+ Menu menu = new Menu(MenuHandler_JumpTopMode);
+ menu.SetTitle("%T", "Jump Top Mode Menu - Title", client);
+ GOKZ_MenuAddModeItems(client, menu, false);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+void DisplayJumpTopTypeMenu(int client, int mode)
+{
+ jumpTopMode[client] = mode;
+
+ Menu menu = new Menu(MenuHandler_JumpTopType);
+ menu.SetTitle("%T", "Jump Top Type Menu - Title", client, gC_ModeNames[jumpTopMode[client]]);
+ JumpTopTypeMenuAddItems(menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void JumpTopTypeMenuAddItems(Menu menu)
+{
+ char display[32];
+ for (int i = 0; i < JUMPTYPE_COUNT - 3; i++)
+ {
+ FormatEx(display, sizeof(display), "%s", gC_JumpTypes[i]);
+ menu.AddItem(IntToStringEx(i), display);
+ }
+}
+
+void DisplayJumpTopBlockTypeMenu(int client, int mode, int type)
+{
+ jumpTopMode[client] = mode;
+ jumpTopType[client] = type;
+
+ Menu menu = new Menu(MenuHandler_JumpTopBlockType);
+ menu.SetTitle("%T", "Jump Top Block Type Menu - Title", client, gC_ModeNames[jumpTopMode[client]], gC_JumpTypes[jumpTopType[client]]);
+ JumpTopBlockTypeMenuAddItems(client, menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void JumpTopBlockTypeMenuAddItems(int client, Menu menu)
+{
+ char str[64];
+ FormatEx(str, sizeof(str), "%T", "Jump Records", client);
+ menu.AddItem("jump", str);
+ FormatEx(str, sizeof(str), "%T %T", "Block", client, "Jump Records", client);
+ menu.AddItem("blockjump", str);
+}
+
+
+
+// =====[ MENU HANDLERS ]=====
+
+public int MenuHandler_JumpTopMode(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = mode
+ DisplayJumpTopTypeMenu(param1, param2);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_JumpTopType(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = type
+ DisplayJumpTopBlockTypeMenu(param1, jumpTopMode[param1], param2);
+ }
+ else if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayJumpTopModeMenu(param1);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_JumpTopBlockType(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = block type
+ DB_OpenJumpTop(param1, jumpTopMode[param1], jumpTopType[param1], param2);
+ }
+ else if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayJumpTopTypeMenu(param1, jumpTopMode[param1]);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_JumpTopList(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ char path[PLATFORM_MAX_PATH];
+ if (blockNums[param1][param2] == 0)
+ {
+ BuildPath(Path_SM, path, sizeof(path),
+ "%s/%d/%d_%s_%s.%s",
+ RP_DIRECTORY_JUMPS, jumpInfo[param1][param2][0], jumpTopType[param1], gC_ModeNamesShort[jumpInfo[param1][param2][2]], gC_StyleNamesShort[0], RP_FILE_EXTENSION);
+ }
+ else
+ {
+ BuildPath(Path_SM, path, sizeof(path),
+ "%s/%d/%s/%d_%d_%s_%s.%s",
+ RP_DIRECTORY_JUMPS, jumpInfo[param1][param2][0], RP_DIRECTORY_BLOCKJUMPS, jumpTopType[param1], blockNums[param1][param2], gC_ModeNamesShort[jumpInfo[param1][param2][2]], gC_StyleNamesShort[0], RP_FILE_EXTENSION);
+ }
+ GOKZ_RP_LoadJumpReplay(param1, path);
+ }
+
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayJumpTopBlockTypeMenu(param1, jumpTopMode[param1], jumpTopType[param1]);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
diff --git a/sourcemod/scripting/gokz-localranks/db/map_top.sp b/sourcemod/scripting/gokz-localranks/db/map_top.sp
new file mode 100644
index 0000000..5b296e9
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/map_top.sp
@@ -0,0 +1,388 @@
+/*
+ Opens a menu with the top times for a map course and mode.
+*/
+
+
+
+#define ITEM_INFO_GLOBAL_TOP_NUB "gn"
+#define ITEM_INFO_GLOBAL_TOP_PRO "gp"
+
+static char mapTopMap[MAXPLAYERS + 1][64];
+static int mapTopMapID[MAXPLAYERS + 1];
+static int mapTopCourse[MAXPLAYERS + 1];
+static int mapTopMode[MAXPLAYERS + 1];
+
+
+
+// =====[ MAP TOP MODE ]=====
+
+void DB_OpenMapTopModeMenu(int client, int mapID, int course)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(mapID);
+ data.WriteCell(course);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Retrieve Map Name of MapID
+ FormatEx(query, sizeof(query), sql_maps_getname, mapID);
+ txn.AddQuery(query);
+ // Check for existence of map course with that MapID and Course
+ FormatEx(query, sizeof(query), sql_mapcourses_findid, mapID, course);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_OpenMapTopModeMenu, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenMapTopModeMenu(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int mapID = data.ReadCell();
+ int course = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ // Get name of map
+ if (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, mapTopMap[client], sizeof(mapTopMap[]));
+ }
+ // Check if the map course exists in the database
+ if (SQL_GetRowCount(results[1]) == 0)
+ {
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Main Course Not Found", mapTopMap[client]);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Bonus Not Found", mapTopMap[client], course);
+ }
+ return;
+ }
+
+ mapTopMapID[client] = mapID;
+ mapTopCourse[client] = course;
+ DisplayMapTopModeMenu(client);
+}
+
+void DB_OpenMapTopModeMenu_FindMap(int client, const char[] mapSearch, int course)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteString(mapSearch);
+ data.WriteCell(course);
+
+ DB_FindMap(mapSearch, DB_TxnSuccess_OpenMapTopModeMenu_FindMap, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenMapTopModeMenu_FindMap(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ char mapSearch[33];
+ data.ReadString(mapSearch, sizeof(mapSearch));
+ int course = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Not Found", mapSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]))
+ { // Result is the MapID
+ DB_OpenMapTopModeMenu(client, SQL_FetchInt(results[0], 0), course);
+ }
+}
+
+
+
+// =====[ MAP TOP ]=====
+
+void DB_OpenMapTop(int client, int mapID, int course, int mode, int timeType)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(course);
+ data.WriteCell(mode);
+ data.WriteCell(timeType);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Get map name
+ FormatEx(query, sizeof(query), sql_maps_getname, mapID);
+ txn.AddQuery(query);
+ // Check for existence of map course with that MapID and Course
+ FormatEx(query, sizeof(query), sql_mapcourses_findid, mapID, course);
+ txn.AddQuery(query);
+
+ // Get top times for each time type
+ switch (timeType)
+ {
+ case TimeType_Nub:FormatEx(query, sizeof(query), sql_getmaptop, mapID, course, mode, LR_MAP_TOP_CUTOFF);
+ case TimeType_Pro:FormatEx(query, sizeof(query), sql_getmaptoppro, mapID, course, mode, LR_MAP_TOP_CUTOFF);
+ }
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_OpenMapTop, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenMapTop(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ int timeType = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ // Get map name from results
+ char mapName[64];
+ if (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, mapName, sizeof(mapName));
+ }
+ // Check if the map course exists in the database
+ if (SQL_GetRowCount(results[1]) == 0)
+ {
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Main Course Not Found", mapName);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Bonus Not Found", mapName, course);
+ }
+ return;
+ }
+
+ // Check if there are any times
+ if (SQL_GetRowCount(results[2]) == 0)
+ {
+ switch (timeType)
+ {
+ case TimeType_Nub:GOKZ_PrintToChat(client, true, "%t", "No Times Found");
+ case TimeType_Pro:GOKZ_PrintToChat(client, true, "%t", "No Times Found (PRO)");
+ }
+ DisplayMapTopMenu(client, mode);
+ return;
+ }
+
+ Menu menu = new Menu(MenuHandler_MapTopSubmenu);
+ menu.Pagination = 5;
+
+ // Set submenu title
+ if (course == 0)
+ {
+ menu.SetTitle("%T", "Map Top Submenu - Title", client,
+ LR_MAP_TOP_CUTOFF, gC_TimeTypeNames[timeType], mapName, gC_ModeNames[mode]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Map Top Submenu - Title (Bonus)", client,
+ LR_MAP_TOP_CUTOFF, gC_TimeTypeNames[timeType], mapName, course, gC_ModeNames[mode]);
+ }
+
+ // Add submenu items
+ char display[128], title[65], admin[65];
+ char playerName[MAX_NAME_LENGTH];
+ float runTime;
+ int timeid, steamid, teleports, rank = 0;
+
+ bool clientIsAdmin = CheckCommandAccess(client, "sm_deletetime", ADMFLAG_ROOT, false);
+
+ FormatEx(title, sizeof(title), "%s %s %s %T", gC_ModeNames[mode], mapName, gC_TimeTypeNames[timeType], "Top", client);
+ strcopy(display, sizeof(display), "----------------------------------------------------------------");
+ display[strlen(title)] = '\0';
+ PrintToConsole(client, title);
+ PrintToConsole(client, display);
+
+ while (SQL_FetchRow(results[2]))
+ {
+ rank++;
+ timeid = SQL_FetchInt(results[2], 0);
+ steamid = SQL_FetchInt(results[2], 1);
+ SQL_FetchString(results[2], 2, playerName, sizeof(playerName));
+ runTime = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[2], 3));
+
+ if (clientIsAdmin)
+ {
+ FormatEx(admin, sizeof(admin), "<id: %d>", timeid);
+ }
+
+ switch (timeType)
+ {
+ case TimeType_Nub:
+ {
+ teleports = SQL_FetchInt(results[2], 4);
+ FormatEx(display, sizeof(display), "#%-2d %11s %3d TP %s",
+ rank, GOKZ_FormatTime(runTime), teleports, playerName);
+
+ PrintToConsole(client, "#%-2d %11s %3d TP %s <STEAM_1:%d:%d> %s",
+ rank, GOKZ_FormatTime(runTime), teleports, playerName, steamid & 1, steamid >> 1, admin);
+ }
+ case TimeType_Pro:
+ {
+ FormatEx(display, sizeof(display), "#%-2d %11s %s",
+ rank, GOKZ_FormatTime(runTime), playerName);
+
+ PrintToConsole(client, "#%-2d %11s %s <STEAM_1:%d:%d> %s",
+ rank, GOKZ_FormatTime(runTime), playerName, steamid & 1, steamid >> 1, admin);
+ }
+ }
+ menu.AddItem("", display, ITEMDRAW_DISABLED);
+ }
+
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+
+
+// =====[ MENUS ]=====
+
+void DisplayMapTopModeMenu(int client)
+{
+ Menu menu = new Menu(MenuHandler_MapTopMode);
+ MapTopModeMenuSetTitle(client, menu);
+ GOKZ_MenuAddModeItems(client, menu, false);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void MapTopModeMenuSetTitle(int client, Menu menu)
+{
+ if (mapTopCourse[client] == 0)
+ {
+ menu.SetTitle("%T", "Map Top Mode Menu - Title", client, mapTopMap[client]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Map Top Mode Menu - Title (Bonus)", client, mapTopMap[client], mapTopCourse[client]);
+ }
+}
+
+void DisplayMapTopMenu(int client, int mode)
+{
+ mapTopMode[client] = mode;
+
+ Menu menu = new Menu(MenuHandler_MapTop);
+ if (mapTopCourse[client] == 0)
+ {
+ menu.SetTitle("%T", "Map Top Menu - Title", client,
+ mapTopMap[client], gC_ModeNames[mapTopMode[client]]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Map Top Menu - Title (Bonus)", client,
+ mapTopMap[client], mapTopCourse[client], gC_ModeNames[mapTopMode[client]]);
+ }
+ MapTopMenuAddItems(client, menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void MapTopMenuAddItems(int client, Menu menu)
+{
+ char display[32];
+ for (int i = 0; i < TIMETYPE_COUNT; i++)
+ {
+ FormatEx(display, sizeof(display), "%T", "Map Top Menu - Top", client, LR_MAP_TOP_CUTOFF, gC_TimeTypeNames[i]);
+ menu.AddItem(IntToStringEx(i), display);
+ }
+ if (gB_GOKZGlobal)
+ {
+ FormatEx(display, sizeof(display), "%T", "Map Top Menu - Global Top", client, gC_TimeTypeNames[TimeType_Nub]);
+ menu.AddItem(ITEM_INFO_GLOBAL_TOP_NUB, display);
+
+ FormatEx(display, sizeof(display), "%T", "Map Top Menu - Global Top", client, gC_TimeTypeNames[TimeType_Pro]);
+ menu.AddItem(ITEM_INFO_GLOBAL_TOP_PRO, display);
+ }
+}
+
+void ReopenMapTopMenu(int client)
+{
+ DisplayMapTopMenu(client, mapTopMode[client]);
+}
+
+
+
+// =====[ MENU HANDLERS ]=====
+
+public int MenuHandler_MapTopMode(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = mode
+ DisplayMapTopMenu(param1, param2);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_MapTop(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ char info[8];
+ menu.GetItem(param2, info, sizeof(info));
+
+ if (gB_GOKZGlobal && StrEqual(info, ITEM_INFO_GLOBAL_TOP_NUB))
+ {
+ GOKZ_GL_DisplayMapTopMenu(param1, mapTopMap[param1], mapTopCourse[param1], mapTopMode[param1], TimeType_Nub);
+ }
+ else if (gB_GOKZGlobal && StrEqual(info, ITEM_INFO_GLOBAL_TOP_PRO))
+ {
+ GOKZ_GL_DisplayMapTopMenu(param1, mapTopMap[param1], mapTopCourse[param1], mapTopMode[param1], TimeType_Pro);
+ }
+ else
+ {
+ int timeType = StringToInt(info);
+ DB_OpenMapTop(param1, mapTopMapID[param1], mapTopCourse[param1], mapTopMode[param1], timeType);
+ }
+ }
+ else if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayMapTopModeMenu(param1);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_MapTopSubmenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ // TODO Menu item info is player's SteamID32, but is currently not used
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ ReopenMapTopMenu(param1);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/player_top.sp b/sourcemod/scripting/gokz-localranks/db/player_top.sp
new file mode 100644
index 0000000..0348eec
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/player_top.sp
@@ -0,0 +1,165 @@
+/*
+ Opens a menu with top record holders of a time type and mode.
+*/
+
+
+
+static int playerTopMode[MAXPLAYERS + 1];
+
+
+
+void DB_OpenPlayerTop(int client, int timeType, int mode)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(timeType);
+ data.WriteCell(mode);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Get top players
+ switch (timeType)
+ {
+ case TimeType_Nub:
+ {
+ FormatEx(query, sizeof(query), sql_gettopplayers, mode, LR_PLAYER_TOP_CUTOFF);
+ txn.AddQuery(query);
+ }
+ case TimeType_Pro:
+ {
+ FormatEx(query, sizeof(query), sql_gettopplayerspro, mode, LR_PLAYER_TOP_CUTOFF);
+ txn.AddQuery(query);
+ }
+ }
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_OpenPlayerTop, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenPlayerTop(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int timeType = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ switch (timeType)
+ {
+ case TimeType_Nub:GOKZ_PrintToChat(client, true, "%t", "Player Top - No Times");
+ case TimeType_Pro:GOKZ_PrintToChat(client, true, "%t", "Player Top - No Times (PRO)");
+ }
+ DisplayPlayerTopMenu(client, playerTopMode[client]);
+ return;
+ }
+
+ Menu menu = new Menu(MenuHandler_PlayerTopSubmenu);
+ menu.Pagination = 5;
+
+ // Set submenu title
+ menu.SetTitle("%T", "Player Top Submenu - Title", client,
+ LR_PLAYER_TOP_CUTOFF, gC_TimeTypeNames[timeType], gC_ModeNames[mode]);
+
+ // Add submenu items
+ char display[256];
+ int rank = 0;
+ while (SQL_FetchRow(results[0]))
+ {
+ rank++;
+ char playerString[33];
+ SQL_FetchString(results[0], 1, playerString, sizeof(playerString));
+ FormatEx(display, sizeof(display), "#%-2d %s (%d)", rank, playerString, SQL_FetchInt(results[0], 2));
+ menu.AddItem(IntToStringEx(SQL_FetchInt(results[0], 0)), display, ITEMDRAW_DISABLED);
+ }
+
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+
+
+// =====[ MENUS ]=====
+
+void DisplayPlayerTopModeMenu(int client)
+{
+ Menu menu = new Menu(MenuHandler_PlayerTopMode);
+ menu.SetTitle("%T", "Player Top Mode Menu - Title", client);
+ GOKZ_MenuAddModeItems(client, menu, false);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+void DisplayPlayerTopMenu(int client, int mode)
+{
+ playerTopMode[client] = mode;
+
+ Menu menu = new Menu(MenuHandler_PlayerTop);
+ menu.SetTitle("%T", "Player Top Menu - Title", client, gC_ModeNames[mode]);
+ PlayerTopMenuAddItems(client, menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void PlayerTopMenuAddItems(int client, Menu menu)
+{
+ char display[32];
+ for (int timeType = 0; timeType < TIMETYPE_COUNT; timeType++)
+ {
+ FormatEx(display, sizeof(display), "%T", "Player Top Menu - Top", client,
+ LR_PLAYER_TOP_CUTOFF, gC_TimeTypeNames[timeType]);
+ menu.AddItem("", display, ITEMDRAW_DEFAULT);
+ }
+}
+
+
+
+// =====[ MENU HANLDERS ]=====
+
+public int MenuHandler_PlayerTopMode(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ DisplayPlayerTopMenu(param1, param2);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_PlayerTop(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ DB_OpenPlayerTop(param1, param2, playerTopMode[param1]);
+ }
+ else if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayPlayerTopModeMenu(param1);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_PlayerTopSubmenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ // Menu item info is player's SteamID32, but is currently not used
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayPlayerTopMenu(param1, playerTopMode[param1]);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/print_average.sp b/sourcemod/scripting/gokz-localranks/db/print_average.sp
new file mode 100644
index 0000000..7f7c4e4
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/print_average.sp
@@ -0,0 +1,152 @@
+/*
+ Gets the average personal best time of a course.
+*/
+
+
+
+void DB_PrintAverage(int client, int mapID, int course, int mode)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Retrieve Map Name of MapID
+ FormatEx(query, sizeof(query), sql_maps_getname, mapID);
+ txn.AddQuery(query);
+ // Check for existence of map course with that MapID and Course
+ FormatEx(query, sizeof(query), sql_mapcourses_findid, mapID, course);
+ txn.AddQuery(query);
+ // Get Average PB Time
+ FormatEx(query, sizeof(query), sql_getaverage, mapID, course, mode);
+ txn.AddQuery(query);
+ // Get Average PRO PB Time
+ FormatEx(query, sizeof(query), sql_getaverage_pro, mapID, course, mode);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_PrintAverage, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintAverage(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ char mapName[33];
+ int mapCompletions, mapCompletionsPro;
+ float averageTime, averageTimePro;
+
+ // Get Map Name from results
+ if (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, mapName, sizeof(mapName));
+ }
+ if (SQL_GetRowCount(results[1]) == 0)
+ {
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Main Course Not Found", mapName);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Bonus Not Found", mapName, course);
+ }
+ return;
+ }
+
+ // Get number of completions and average time
+ if (SQL_FetchRow(results[2]))
+ {
+ mapCompletions = SQL_FetchInt(results[2], 1);
+ if (mapCompletions > 0)
+ {
+ averageTime = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[2], 0));
+ }
+ }
+
+ // Get number of completions and average time (PRO)
+ if (SQL_FetchRow(results[3]))
+ {
+ mapCompletionsPro = SQL_FetchInt(results[3], 1);
+ if (mapCompletions > 0)
+ {
+ averageTimePro = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[3], 0));
+ }
+ }
+
+ // Print average time header to chat
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Average Time Header", mapName, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Average Time Header (Bonus)", mapName, course, gC_ModeNamesShort[mode]);
+ }
+
+ if (mapCompletions == 0)
+ {
+ CPrintToChat(client, "%t", "No Times Found");
+ }
+ else if (mapCompletionsPro == 0)
+ {
+ CPrintToChat(client, "%t, %t",
+ "Average Time - NUB", GOKZ_FormatTime(averageTime), mapCompletions,
+ "Average Time - No PRO Time");
+ }
+ else
+ {
+ CPrintToChat(client, "%t, %t",
+ "Average Time - NUB", GOKZ_FormatTime(averageTime), mapCompletions,
+ "Average Time - PRO", GOKZ_FormatTime(averageTimePro), mapCompletionsPro);
+ }
+}
+
+void DB_PrintAverage_FindMap(int client, const char[] mapSearch, int course, int mode)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteString(mapSearch);
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ DB_FindMap(mapSearch, DB_TxnSuccess_PrintAverage_FindMap, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintAverage_FindMap(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ char mapSearch[33];
+ data.ReadString(mapSearch, sizeof(mapSearch));
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Not Found", mapSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]))
+ { // Result is the MapID
+ DB_PrintAverage(client, SQL_FetchInt(results[0], 0), course, mode);
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/print_js.sp b/sourcemod/scripting/gokz-localranks/db/print_js.sp
new file mode 100644
index 0000000..e694a95
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/print_js.sp
@@ -0,0 +1,108 @@
+/*
+ Prints the player's personal best jumps.
+*/
+
+
+
+void DisplayJumpstatRecord(int client, int jumpType, char[] jumper = "")
+{
+ int mode = GOKZ_GetCoreOption(client, Option_Mode);
+
+ int steamid;
+ char alias[33];
+ if (StrEqual(jumper, ""))
+ {
+ steamid = GetSteamAccountID(client);
+ FormatEx(alias, sizeof(alias), "%N", client);
+
+ DB_JS_OpenPlayerRecord(client, steamid, alias, jumpType, mode, 0);
+ DB_JS_OpenPlayerRecord(client, steamid, alias, jumpType, mode, 1);
+ }
+ else
+ {
+ DataPack data = new DataPack();
+ data.WriteCell(client);
+ data.WriteCell(jumpType);
+ data.WriteCell(mode);
+ data.WriteString(jumper);
+
+ DB_FindPlayer(jumper, DB_JS_TxnSuccess_LookupPlayer, data);
+ }
+}
+
+public void DB_JS_TxnSuccess_LookupPlayer(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ char jumper[MAX_NAME_LENGTH];
+
+ data.Reset();
+ int client = data.ReadCell();
+ int jumpType = data.ReadCell();
+ int mode = data.ReadCell();
+ data.ReadString(jumper, sizeof(jumper));
+ delete data;
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Player Not Found", jumper);
+ return;
+ }
+
+ char alias[33];
+ SQL_FetchRow(results[0]);
+ int steamid = SQL_FetchInt(results[0], JumpstatDB_FindPlayer_SteamID32);
+ SQL_FetchString(results[0], JumpstatDB_FindPlayer_Alias, alias, sizeof(alias));
+
+ DB_JS_OpenPlayerRecord(client, steamid, alias, jumpType, mode, 0);
+ DB_JS_OpenPlayerRecord(client, steamid, alias, jumpType, mode, 1);
+}
+
+void DB_JS_OpenPlayerRecord(int client, int steamid, char[] alias, int jumpType, int mode, int block)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(client);
+ data.WriteString(alias);
+ data.WriteCell(jumpType);
+ data.WriteCell(mode);
+
+ Transaction txn = SQL_CreateTransaction();
+ FormatEx(query, sizeof(query), sql_jumpstats_getrecord, steamid, jumpType, mode, block);
+ txn.AddQuery(query);
+ SQL_ExecuteTransaction(gH_DB, txn, DB_JS_TxnSuccess_OpenPlayerRecord, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_JS_TxnSuccess_OpenPlayerRecord(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ char alias[33];
+ data.Reset();
+ int client = data.ReadCell();
+ data.ReadString(alias, sizeof(alias));
+ int jumpType = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "No Jumpstats Found");
+ return;
+ }
+
+ SQL_FetchRow(results[0]);
+ float distance = float(SQL_FetchInt(results[0], JumpstatDB_Lookup_Distance)) / 10000;
+ int block = SQL_FetchInt(results[0], JumpstatDB_Lookup_Block);
+
+ if (block == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Jump Record", gC_ModeNamesShort[mode], gC_JumpTypes[jumpType], alias, distance);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Block Jump Record", gC_ModeNamesShort[mode], gC_JumpTypes[jumpType], alias, block, distance);
+ }
+}
diff --git a/sourcemod/scripting/gokz-localranks/db/print_pbs.sp b/sourcemod/scripting/gokz-localranks/db/print_pbs.sp
new file mode 100644
index 0000000..0d541d4
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/print_pbs.sp
@@ -0,0 +1,266 @@
+/*
+ Prints the player's personal times on a map course and given mode.
+*/
+
+
+
+void DB_PrintPBs(int client, int targetSteamID, int mapID, int course, int mode)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Retrieve Alias of SteamID
+ FormatEx(query, sizeof(query), sql_players_getalias, targetSteamID);
+ txn.AddQuery(query);
+ // Retrieve Map Name of MapID
+ FormatEx(query, sizeof(query), sql_maps_getname, mapID);
+ txn.AddQuery(query);
+ // Check for existence of map course with that MapID and Course
+ FormatEx(query, sizeof(query), sql_mapcourses_findid, mapID, course);
+ txn.AddQuery(query);
+
+ // Get PB
+ FormatEx(query, sizeof(query), sql_getpb, targetSteamID, mapID, course, mode, 1);
+ txn.AddQuery(query);
+ // Get Rank
+ FormatEx(query, sizeof(query), sql_getmaprank, mapID, course, mode, targetSteamID, mapID, course, mode);
+ txn.AddQuery(query);
+ // Get Number of Players with Times
+ FormatEx(query, sizeof(query), sql_getlowestmaprank, mapID, course, mode);
+ txn.AddQuery(query);
+
+ // Get PRO PB
+ FormatEx(query, sizeof(query), sql_getpbpro, targetSteamID, mapID, course, mode, 1);
+ txn.AddQuery(query);
+ // Get PRO Rank
+ FormatEx(query, sizeof(query), sql_getmaprankpro, mapID, course, mode, targetSteamID, mapID, course, mode);
+ txn.AddQuery(query);
+ // Get Number of Players with PRO Times
+ FormatEx(query, sizeof(query), sql_getlowestmaprankpro, mapID, course, mode);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_PrintPBs, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintPBs(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ char playerName[MAX_NAME_LENGTH], mapName[33];
+
+ bool hasPB = false;
+ bool hasPBPro = false;
+
+ float runTime;
+ int teleportsUsed;
+ int rank;
+ int maxRank;
+
+ float runTimePro;
+ int rankPro;
+ int maxRankPro;
+
+ // Get Player Name from results
+ if (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, playerName, sizeof(playerName));
+ }
+ // Get Map Name from results
+ if (SQL_FetchRow(results[1]))
+ {
+ SQL_FetchString(results[1], 0, mapName, sizeof(mapName));
+ }
+ if (SQL_GetRowCount(results[2]) == 0)
+ {
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Main Course Not Found", mapName);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Bonus Not Found", mapName, course);
+ }
+ return;
+ }
+
+ // Get PB info from results
+ if (SQL_GetRowCount(results[3]) > 0)
+ {
+ hasPB = true;
+ if (SQL_FetchRow(results[3]))
+ {
+ runTime = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[3], 0));
+ teleportsUsed = SQL_FetchInt(results[3], 1);
+ }
+ if (SQL_FetchRow(results[4]))
+ {
+ rank = SQL_FetchInt(results[4], 0);
+ }
+ if (SQL_FetchRow(results[5]))
+ {
+ maxRank = SQL_FetchInt(results[5], 0);
+ }
+ }
+ // Get PB info (Pro) from results
+ if (SQL_GetRowCount(results[6]) > 0)
+ {
+ hasPBPro = true;
+ if (SQL_FetchRow(results[6]))
+ {
+ runTimePro = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[6], 0));
+ }
+ if (SQL_FetchRow(results[7]))
+ {
+ rankPro = SQL_FetchInt(results[7], 0);
+ }
+ if (SQL_FetchRow(results[8]))
+ {
+ maxRankPro = SQL_FetchInt(results[8], 0);
+ }
+ }
+
+ // Print PB header to chat
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "PB Header", playerName, mapName, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "PB Header (Bonus)", playerName, mapName, course, gC_ModeNamesShort[mode]);
+ }
+
+ // Print PB times to chat
+ if (!hasPB)
+ {
+ CPrintToChat(client, "%t", "PB Time - No Times");
+ }
+ else if (!hasPBPro)
+ {
+ CPrintToChat(client, "%t", "PB Time - NUB", GOKZ_FormatTime(runTime), teleportsUsed, rank, maxRank);
+ CPrintToChat(client, "%t", "PB Time - No PRO Time");
+ }
+ else if (teleportsUsed == 0)
+ { // Their MAP PB has 0 teleports, and is therefore also their PRO PB
+ CPrintToChat(client, "%t", "PB Time - NUB and PRO", GOKZ_FormatTime(runTime), rank, maxRank, rankPro, maxRankPro);
+ }
+ else
+ {
+ CPrintToChat(client, "%t", "PB Time - NUB", GOKZ_FormatTime(runTime), teleportsUsed, rank, maxRank);
+ CPrintToChat(client, "%t", "PB Time - PRO", GOKZ_FormatTime(runTimePro), rankPro, maxRankPro);
+ }
+}
+
+void DB_PrintPBs_FindMap(int client, int targetSteamID, const char[] mapSearch, int course, int mode)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(targetSteamID);
+ data.WriteString(mapSearch);
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ DB_FindMap(mapSearch, DB_TxnSuccess_PrintPBs_FindMap, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintPBs_FindMap(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int targetSteamID = data.ReadCell();
+ char mapSearch[33];
+ data.ReadString(mapSearch, sizeof(mapSearch));
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ // Check if the map course exists in the database
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Not Found", mapSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]))
+ { // Result is the MapID
+ DB_PrintPBs(client, targetSteamID, SQL_FetchInt(results[0], 0), course, mode);
+ if (gB_GOKZGlobal)
+ {
+ char map[33], steamid[32];
+ SQL_FetchString(results[0], 1, map, sizeof(map));
+ GetSteam2FromAccountId(steamid, sizeof(steamid), targetSteamID);
+ GOKZ_GL_PrintRecords(client, map, course, GOKZ_GetCoreOption(client, Option_Mode), steamid);
+ }
+ }
+}
+
+void DB_PrintPBs_FindPlayerAndMap(int client, const char[] playerSearch, const char[] mapSearch, int course, int mode)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteString(playerSearch);
+ data.WriteString(mapSearch);
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ DB_FindPlayerAndMap(playerSearch, mapSearch, DB_TxnSuccess_PrintPBs_FindPlayerAndMap, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintPBs_FindPlayerAndMap(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ char playerSearch[MAX_NAME_LENGTH];
+ data.ReadString(playerSearch, sizeof(playerSearch));
+ char mapSearch[33];
+ data.ReadString(mapSearch, sizeof(mapSearch));
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Player Not Found", playerSearch);
+ return;
+ }
+ else if (SQL_GetRowCount(results[1]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Not Found", mapSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]) && SQL_FetchRow(results[1]))
+ {
+ int accountid = SQL_FetchInt(results[0], 0);
+ DB_PrintPBs(client, accountid, SQL_FetchInt(results[1], 0), course, mode);
+ if (gB_GOKZGlobal)
+ {
+ char map[33], steamid[32];
+ SQL_FetchString(results[1], 1, map, sizeof(map));
+ GetSteam2FromAccountId(steamid, sizeof(steamid), accountid);
+ GOKZ_GL_PrintRecords(client, map, course, GOKZ_GetCoreOption(client, Option_Mode), steamid);
+ }
+ }
+}
diff --git a/sourcemod/scripting/gokz-localranks/db/print_records.sp b/sourcemod/scripting/gokz-localranks/db/print_records.sp
new file mode 100644
index 0000000..b5de03b
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/print_records.sp
@@ -0,0 +1,173 @@
+/*
+ Prints the record times on a map course and given mode.
+*/
+
+
+
+void DB_PrintRecords(int client, int mapID, int course, int mode)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Retrieve Map Name of MapID
+ FormatEx(query, sizeof(query), sql_maps_getname, mapID);
+ txn.AddQuery(query);
+ // Check for existence of map course with that MapID and Course
+ FormatEx(query, sizeof(query), sql_mapcourses_findid, mapID, course);
+ txn.AddQuery(query);
+
+ // Get Map WR
+ FormatEx(query, sizeof(query), sql_getmaptop, mapID, course, mode, 1);
+ txn.AddQuery(query);
+ // Get PRO WR
+ FormatEx(query, sizeof(query), sql_getmaptoppro, mapID, course, mode, 1);
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_PrintRecords, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintRecords(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ char mapName[33];
+
+ bool mapHasRecord = false;
+ bool mapHasRecordPro = false;
+
+ char recordHolder[33];
+ float runTime;
+ int teleportsUsed;
+
+ char recordHolderPro[33];
+ float runTimePro;
+
+ // Get Map Name from results
+ if (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, mapName, sizeof(mapName));
+ }
+ // Check if the map course exists in the database
+ if (SQL_GetRowCount(results[1]) == 0)
+ {
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Main Course Not Found", mapName);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Bonus Not Found", mapName, course);
+ }
+ return;
+ }
+
+ // Get WR info from results
+ if (SQL_GetRowCount(results[2]) > 0)
+ {
+ mapHasRecord = true;
+ if (SQL_FetchRow(results[2]))
+ {
+ SQL_FetchString(results[2], 2, recordHolder, sizeof(recordHolder));
+ runTime = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[2], 3));
+ teleportsUsed = SQL_FetchInt(results[2], 4);
+ }
+ }
+ // Get Pro WR info from results
+ if (SQL_GetRowCount(results[3]) > 0)
+ {
+ mapHasRecordPro = true;
+ if (SQL_FetchRow(results[3]))
+ {
+ SQL_FetchString(results[3], 2, recordHolderPro, sizeof(recordHolderPro));
+ runTimePro = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[3], 3));
+ }
+ }
+
+ // Print WR header to chat
+ if (course == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "WR Header", mapName, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "WR Header (Bonus)", mapName, course, gC_ModeNamesShort[mode]);
+ }
+
+ // Print WR times to chat
+ if (!mapHasRecord)
+ {
+ CPrintToChat(client, "%t", "No Times Found");
+ }
+ else if (!mapHasRecordPro)
+ {
+ CPrintToChat(client, "%t", "WR Time - NUB", GOKZ_FormatTime(runTime), teleportsUsed, recordHolder);
+ CPrintToChat(client, "%t", "WR Time - No PRO Time");
+ }
+ else if (teleportsUsed == 0)
+ {
+ CPrintToChat(client, "%t", "WR Time - NUB and PRO", GOKZ_FormatTime(runTimePro), recordHolderPro);
+ }
+ else
+ {
+ CPrintToChat(client, "%t", "WR Time - NUB", GOKZ_FormatTime(runTime), teleportsUsed, recordHolder);
+ CPrintToChat(client, "%t", "WR Time - PRO", GOKZ_FormatTime(runTimePro), recordHolderPro);
+ }
+}
+
+void DB_PrintRecords_FindMap(int client, const char[] mapSearch, int course, int mode)
+{
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteString(mapSearch);
+ data.WriteCell(course);
+ data.WriteCell(mode);
+
+ DB_FindMap(mapSearch, DB_TxnSuccess_PrintRecords_FindMap, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_PrintRecords_FindMap(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ char mapSearch[33];
+ data.ReadString(mapSearch, sizeof(mapSearch));
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Not Found", mapSearch);
+ return;
+ }
+ else if (SQL_FetchRow(results[0]))
+ { // Result is the MapID
+ DB_PrintRecords(client, SQL_FetchInt(results[0], 0), course, mode);
+ if (gB_GOKZGlobal)
+ {
+ char map[33];
+ SQL_FetchString(results[0], 1, map, sizeof(map));
+ GOKZ_GL_PrintRecords(client, map, course, GOKZ_GetCoreOption(client, Option_Mode));
+ }
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/process_new_time.sp b/sourcemod/scripting/gokz-localranks/db/process_new_time.sp
new file mode 100644
index 0000000..f9aaf73
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/process_new_time.sp
@@ -0,0 +1,157 @@
+/*
+ Processes a newly submitted time, determining if the player beat their
+ personal best and if they beat the map course and mode's record time.
+*/
+
+
+
+void DB_ProcessNewTime(int client, int steamID, int mapID, int course, int mode, int style, int runTimeMS, int teleportsUsed)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(steamID);
+ data.WriteCell(mapID);
+ data.WriteCell(course);
+ data.WriteCell(mode);
+ data.WriteCell(style);
+ data.WriteCell(runTimeMS);
+ data.WriteCell(teleportsUsed);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ // Get Top 2 PBs
+ FormatEx(query, sizeof(query), sql_getpb, steamID, mapID, course, mode, 2);
+ txn.AddQuery(query);
+ // Get Rank
+ FormatEx(query, sizeof(query), sql_getmaprank, mapID, course, mode, steamID, mapID, course, mode);
+ txn.AddQuery(query);
+ // Get Number of Players with Times
+ FormatEx(query, sizeof(query), sql_getlowestmaprank, mapID, course, mode);
+ txn.AddQuery(query);
+
+ if (teleportsUsed == 0)
+ {
+ // Get Top 2 PRO PBs
+ FormatEx(query, sizeof(query), sql_getpbpro, steamID, mapID, course, mode, 2);
+ txn.AddQuery(query);
+ // Get PRO Rank
+ FormatEx(query, sizeof(query), sql_getmaprankpro, mapID, course, mode, steamID, mapID, course, mode);
+ txn.AddQuery(query);
+ // Get Number of Players with PRO Times
+ FormatEx(query, sizeof(query), sql_getlowestmaprankpro, mapID, course, mode);
+ txn.AddQuery(query);
+ }
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_ProcessTimerEnd, DB_TxnFailure_Generic_DataPack, data, DBPrio_Normal);
+}
+
+public void DB_TxnSuccess_ProcessTimerEnd(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int steamID = data.ReadCell();
+ int mapID = data.ReadCell();
+ int course = data.ReadCell();
+ int mode = data.ReadCell();
+ int style = data.ReadCell();
+ int runTimeMS = data.ReadCell();
+ int teleportsUsed = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ bool firstTime = SQL_GetRowCount(results[0]) == 1;
+ int pbDiff = 0;
+ int rank = -1;
+ int maxRank = -1;
+ if (!firstTime)
+ {
+ SQL_FetchRow(results[0]);
+ int pb = SQL_FetchInt(results[0], 0);
+ if (runTimeMS == pb) // New time is new PB
+ {
+ SQL_FetchRow(results[0]);
+ int oldPB = SQL_FetchInt(results[0], 0);
+ pbDiff = runTimeMS - oldPB;
+ }
+ else // Didn't beat PB
+ {
+ pbDiff = runTimeMS - pb;
+ }
+ }
+ // Get NUB Rank
+ SQL_FetchRow(results[1]);
+ rank = SQL_FetchInt(results[1], 0);
+ SQL_FetchRow(results[2]);
+ maxRank = SQL_FetchInt(results[2], 0);
+
+ // Repeat for PRO Runs
+ bool firstTimePro = false;
+ int pbDiffPro = 0;
+ int rankPro = -1;
+ int maxRankPro = -1;
+ if (teleportsUsed == 0)
+ {
+ firstTimePro = SQL_GetRowCount(results[3]) == 1;
+ if (!firstTimePro)
+ {
+ SQL_FetchRow(results[3]);
+ int pb = SQL_FetchInt(results[3], 0);
+ if (runTimeMS == pb) // New time is new PB
+ {
+ SQL_FetchRow(results[3]);
+ int oldPB = SQL_FetchInt(results[3], 0);
+ pbDiffPro = runTimeMS - oldPB;
+ }
+ else // Didn't beat PB
+ {
+ pbDiffPro = runTimeMS - pb;
+ }
+ }
+ // Get PRO Rank
+ SQL_FetchRow(results[4]);
+ rankPro = SQL_FetchInt(results[4], 0);
+ SQL_FetchRow(results[5]);
+ maxRankPro = SQL_FetchInt(results[5], 0);
+ }
+
+ // Call OnTimeProcessed forward
+ Call_OnTimeProcessed(
+ client,
+ steamID,
+ mapID,
+ course,
+ mode,
+ style,
+ GOKZ_DB_TimeIntToFloat(runTimeMS),
+ teleportsUsed,
+ firstTime,
+ GOKZ_DB_TimeIntToFloat(pbDiff),
+ rank,
+ maxRank,
+ firstTimePro,
+ GOKZ_DB_TimeIntToFloat(pbDiffPro),
+ rankPro,
+ maxRankPro);
+
+ // Call OnNewRecord forward
+ bool newWR = (firstTime || pbDiff < 0) && rank == 1;
+ bool newWRPro = (firstTimePro || pbDiffPro < 0) && rankPro == 1;
+ if (newWR && newWRPro)
+ {
+ Call_OnNewRecord(client, steamID, mapID, course, mode, style, RecordType_NubAndPro, GOKZ_DB_TimeIntToFloat(pbDiffPro), teleportsUsed);
+ }
+ else if (newWR)
+ {
+ Call_OnNewRecord(client, steamID, mapID, course, mode, style, RecordType_Nub, GOKZ_DB_TimeIntToFloat(pbDiff), teleportsUsed);
+ }
+ else if (newWRPro)
+ {
+ Call_OnNewRecord(client, steamID, mapID, course, mode, style, RecordType_Pro, GOKZ_DB_TimeIntToFloat(pbDiffPro), teleportsUsed);
+ }
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-localranks/db/recent_records.sp b/sourcemod/scripting/gokz-localranks/db/recent_records.sp
new file mode 100644
index 0000000..939b132
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/recent_records.sp
@@ -0,0 +1,171 @@
+/*
+ Opens the menu with a list of recently broken records for the given mode
+ and time type.
+*/
+
+
+
+static int recentRecordsMode[MAXPLAYERS + 1];
+
+
+
+void DB_OpenRecentRecords(int client, int mode, int timeType)
+{
+ char query[1024];
+
+ DataPack data = new DataPack();
+ data.WriteCell(GetClientUserId(client));
+ data.WriteCell(mode);
+ data.WriteCell(timeType);
+
+ Transaction txn = SQL_CreateTransaction();
+
+ switch (timeType)
+ {
+ case TimeType_Nub:FormatEx(query, sizeof(query), sql_getrecentrecords, mode, LR_PLAYER_TOP_CUTOFF);
+ case TimeType_Pro:FormatEx(query, sizeof(query), sql_getrecentrecords_pro, mode, LR_PLAYER_TOP_CUTOFF);
+ }
+ txn.AddQuery(query);
+
+ SQL_ExecuteTransaction(gH_DB, txn, DB_TxnSuccess_OpenRecentRecords, DB_TxnFailure_Generic_DataPack, data, DBPrio_Low);
+}
+
+public void DB_TxnSuccess_OpenRecentRecords(Handle db, DataPack data, int numQueries, Handle[] results, any[] queryData)
+{
+ data.Reset();
+ int client = GetClientOfUserId(data.ReadCell());
+ int mode = data.ReadCell();
+ int timeType = data.ReadCell();
+ delete data;
+
+ if (!IsValidClient(client))
+ {
+ return;
+ }
+
+ // Check if there are any times
+ if (SQL_GetRowCount(results[0]) == 0)
+ {
+ switch (timeType)
+ {
+ case TimeType_Nub:GOKZ_PrintToChat(client, true, "%t", "No Times Found");
+ case TimeType_Pro:GOKZ_PrintToChat(client, true, "%t", "No Times Found (PRO)");
+ }
+ return;
+ }
+
+ Menu menu = new Menu(MenuHandler_RecentRecordsSubmenu);
+ menu.Pagination = 5;
+
+ // Set submenu title
+ menu.SetTitle("%T", "Recent Records Submenu - Title", client,
+ gC_TimeTypeNames[timeType], gC_ModeNames[mode]);
+
+ // Add submenu items
+ char display[256], mapName[64], playerName[33];
+ int course;
+ float runTime;
+
+ while (SQL_FetchRow(results[0]))
+ {
+ SQL_FetchString(results[0], 0, mapName, sizeof(mapName));
+ course = SQL_FetchInt(results[0], 1);
+ SQL_FetchString(results[0], 3, playerName, sizeof(playerName));
+ runTime = GOKZ_DB_TimeIntToFloat(SQL_FetchInt(results[0], 4));
+
+ if (course == 0)
+ {
+ FormatEx(display, sizeof(display), "%s - %s (%s)",
+ mapName, playerName, GOKZ_FormatTime(runTime));
+ }
+ else
+ {
+ FormatEx(display, sizeof(display), "%s B%d - %s (%s)",
+ mapName, course, playerName, GOKZ_FormatTime(runTime));
+ }
+
+ menu.AddItem(IntToStringEx(SQL_FetchInt(results[0], 2)), display, ITEMDRAW_DISABLED);
+ }
+
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+
+
+// =====[ MENUS ]=====
+
+void DisplayRecentRecordsModeMenu(int client)
+{
+ Menu menu = new Menu(MenuHandler_RecentRecordsMode);
+ menu.SetTitle("%T", "Recent Records Mode Menu - Title", client);
+ GOKZ_MenuAddModeItems(client, menu, false);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+void DisplayRecentRecordsTimeTypeMenu(int client, int mode)
+{
+ recentRecordsMode[client] = mode;
+
+ Menu menu = new Menu(MenuHandler_RecentRecordsTimeType);
+ menu.SetTitle("%T", "Recent Records Menu - Title", client, gC_ModeNames[recentRecordsMode[client]]);
+ RecentRecordsTimeTypeAddItems(client, menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+static void RecentRecordsTimeTypeAddItems(int client, Menu menu)
+{
+ char display[32];
+ for (int timeType = 0; timeType < TIMETYPE_COUNT; timeType++)
+ {
+ FormatEx(display, sizeof(display), "%T", "Recent Records Menu - Record Type", client, gC_TimeTypeNames[timeType]);
+ menu.AddItem("", display, ITEMDRAW_DEFAULT);
+ }
+}
+
+
+
+// =====[ MENU HANDLERS ]=====
+
+public int MenuHandler_RecentRecordsMode(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ DisplayRecentRecordsTimeTypeMenu(param1, param2);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_RecentRecordsTimeType(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ DB_OpenRecentRecords(param1, recentRecordsMode[param1], param2);
+ }
+ else if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayRecentRecordsModeMenu(param1);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_RecentRecordsSubmenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ // TODO Menu item info is course's MapCourseID, but is currently not used
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayRecentRecordsTimeTypeMenu(param1, recentRecordsMode[param1]);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
diff --git a/sourcemod/scripting/gokz-localranks/db/sql.sp b/sourcemod/scripting/gokz-localranks/db/sql.sp
new file mode 100644
index 0000000..f768e9a
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/sql.sp
@@ -0,0 +1,411 @@
+/*
+ SQL query templates.
+*/
+
+
+
+// =====[ MAPS ]=====
+
+char sqlite_maps_alter1[] = "\
+ALTER TABLE Maps \
+ ADD InRankedPool INTEGER NOT NULL DEFAULT '0'";
+
+char mysql_maps_alter1[] = "\
+ALTER TABLE Maps \
+ ADD InRankedPool TINYINT NOT NULL DEFAULT '0'";
+
+char sqlite_maps_insertranked[] = "\
+INSERT OR IGNORE INTO Maps \
+ (InRankedPool, Name) \
+ VALUES (%d, '%s')";
+
+char sqlite_maps_updateranked[] = "\
+UPDATE OR IGNORE Maps \
+ SET InRankedPool=%d \
+ WHERE Name = '%s'";
+
+char mysql_maps_upsertranked[] = "\
+INSERT INTO Maps (InRankedPool, Name) \
+ VALUES (%d, '%s') \
+ ON DUPLICATE KEY UPDATE \
+ InRankedPool=VALUES(InRankedPool)";
+
+char sql_maps_reset_mappool[] = "\
+UPDATE Maps \
+ SET InRankedPool=0";
+
+char sql_maps_getname[] = "\
+SELECT Name \
+ FROM Maps \
+ WHERE MapID=%d";
+
+char sql_maps_searchbyname[] = "\
+SELECT MapID, Name \
+ FROM Maps \
+ WHERE Name LIKE '%%%s%%' \
+ ORDER BY (Name='%s') DESC, LENGTH(Name) \
+ LIMIT 1";
+
+
+
+// =====[ PLAYERS ]=====
+
+char sql_players_getalias[] = "\
+SELECT Alias \
+ FROM Players \
+ WHERE SteamID32=%d";
+
+char sql_players_searchbyalias[] = "\
+SELECT SteamID32, Alias \
+ FROM Players \
+ WHERE Players.Cheater=0 AND LOWER(Alias) LIKE '%%%s%%' \
+ ORDER BY (LOWER(Alias)='%s') DESC, LastPlayed DESC \
+ LIMIT 1";
+
+
+
+// =====[ MAPCOURSES ]=====
+
+char sql_mapcourses_findid[] = "\
+SELECT MapCourseID \
+ FROM MapCourses \
+ WHERE MapID=%d AND Course=%d";
+
+
+
+// =====[ GENERAL ]=====
+
+char sql_getpb[] = "\
+SELECT Times.RunTime, Times.Teleports \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ WHERE Times.SteamID32=%d AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d \
+ ORDER BY Times.RunTime \
+ LIMIT %d";
+
+char sql_getpbpro[] = "\
+SELECT Times.RunTime \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ WHERE Times.SteamID32=%d AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d AND Times.Teleports=0 \
+ ORDER BY Times.RunTime \
+ LIMIT %d";
+
+char sql_getmaptop[] = "\
+SELECT t.TimeID, t.SteamID32, p.Alias, t.RunTime AS PBTime, t.Teleports \
+ FROM Times t \
+ INNER JOIN MapCourses mc ON mc.MapCourseID=t.MapCourseID \
+ INNER JOIN Players p ON p.SteamID32=t.SteamID32 \
+ LEFT OUTER JOIN Times t2 ON t2.SteamID32=t.SteamID32 \
+ AND t2.MapCourseID=t.MapCourseID AND t2.Mode=t.Mode AND t2.RunTime<t.RunTime \
+ WHERE t2.TimeID IS NULL AND p.Cheater=0 AND mc.MapID=%d AND mc.Course=%d AND t.Mode=%d \
+ ORDER BY PBTime \
+ LIMIT %d";
+
+char sql_getmaptoppro[] = "\
+SELECT t.TimeID, t.SteamID32, p.Alias, t.RunTime AS PBTime, t.Teleports \
+ FROM Times t \
+ INNER JOIN MapCourses mc ON mc.MapCourseID=t.MapCourseID \
+ INNER JOIN Players p ON p.SteamID32=t.SteamID32 \
+ LEFT OUTER JOIN Times t2 ON t2.SteamID32=t.SteamID32 AND t2.MapCourseID=t.MapCourseID \
+ AND t2.Mode=t.Mode AND t2.RunTime<t.RunTime AND t.Teleports=0 AND t2.Teleports=0 \
+ WHERE t2.TimeID IS NULL AND p.Cheater=0 AND mc.MapID=%d \
+ AND mc.Course=%d AND t.Mode=%d AND t.Teleports=0 \
+ ORDER BY PBTime \
+ LIMIT %d";
+
+char sql_getwrs[] = "\
+SELECT MIN(Times.RunTime), MapCourses.Course, Times.Mode \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d \
+ GROUP BY MapCourses.Course, Times.Mode";
+
+char sql_getwrspro[] = "\
+SELECT MIN(Times.RunTime), MapCourses.Course, Times.Mode \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d AND Times.Teleports=0 \
+ GROUP BY MapCourses.Course, Times.Mode";
+
+char sql_getpbs[] = "\
+SELECT MIN(Times.RunTime), MapCourses.Course, Times.Mode \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ WHERE Times.SteamID32=%d AND MapCourses.MapID=%d \
+ GROUP BY MapCourses.Course, Times.Mode";
+
+char sql_getpbspro[] = "\
+SELECT MIN(Times.RunTime), MapCourses.Course, Times.Mode \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ WHERE Times.SteamID32=%d AND MapCourses.MapID=%d AND Times.Teleports=0 \
+ GROUP BY MapCourses.Course, Times.Mode";
+
+char sql_getmaprank[] = "\
+SELECT COUNT(DISTINCT Times.SteamID32) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d AND MapCourses.Course=%d \
+ AND Times.Mode=%d AND Times.RunTime < \
+ (SELECT MIN(Times.RunTime) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND Times.SteamID32=%d AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d) \
+ + 1";
+
+char sql_getmaprankpro[] = "\
+SELECT COUNT(DISTINCT Times.SteamID32) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d AND MapCourses.Course=%d \
+ AND Times.Mode=%d AND Times.Teleports=0 \
+ AND Times.RunTime < \
+ (SELECT MIN(Times.RunTime) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND Times.SteamID32=%d AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d AND Times.Teleports=0) \
+ + 1";
+
+char sql_getlowestmaprank[] = "\
+SELECT COUNT(DISTINCT Times.SteamID32) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d";
+
+char sql_getlowestmaprankpro[] = "\
+SELECT COUNT(DISTINCT Times.SteamID32) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d AND Times.Teleports=0";
+
+char sql_getcount_maincourses[] = "\
+SELECT COUNT(*) \
+ FROM MapCourses \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ WHERE Maps.InRankedPool=1 AND MapCourses.Course=0";
+
+char sql_getcount_maincoursescompleted[] = "\
+SELECT COUNT(DISTINCT Times.MapCourseID) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ WHERE Maps.InRankedPool=1 AND MapCourses.Course=0 \
+ AND Times.SteamID32=%d AND Times.Mode=%d";
+
+char sql_getcount_maincoursescompletedpro[] = "\
+SELECT COUNT(DISTINCT Times.MapCourseID) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ WHERE Maps.InRankedPool=1 AND MapCourses.Course=0 \
+ AND Times.SteamID32=%d AND Times.Mode=%d AND Times.Teleports=0";
+
+char sql_getcount_bonuses[] = "\
+SELECT COUNT(*) \
+ FROM MapCourses \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ WHERE Maps.InRankedPool=1 AND MapCourses.Course>0";
+
+char sql_getcount_bonusescompleted[] = "\
+SELECT COUNT(DISTINCT Times.MapCourseID) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ WHERE Maps.InRankedPool=1 AND MapCourses.Course>0 \
+ AND Times.SteamID32=%d AND Times.Mode=%d";
+
+char sql_getcount_bonusescompletedpro[] = "\
+SELECT COUNT(DISTINCT Times.MapCourseID) \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ WHERE Maps.InRankedPool=1 AND MapCourses.Course>0 \
+ AND Times.SteamID32=%d AND Times.Mode=%d AND Times.Teleports=0";
+
+char sql_gettopplayers[] = "\
+SELECT Players.SteamID32, Players.Alias, COUNT(*) AS RecordCount \
+ FROM Times \
+ INNER JOIN \
+ (SELECT Times.MapCourseID, Times.Mode, MIN(Times.RunTime) AS RecordTime \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND Maps.InRankedPool=1 AND MapCourses.Course=0 \
+ AND Times.Mode=%d \
+ GROUP BY Times.MapCourseID) Records \
+ ON Times.MapCourseID=Records.MapCourseID AND Times.Mode=Records.Mode AND Times.RunTime=Records.RecordTime \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ GROUP BY Players.SteamID32, Players.Alias \
+ ORDER BY RecordCount DESC \
+ LIMIT %d"; // Doesn't include bonuses
+
+char sql_gettopplayerspro[] = "\
+SELECT Players.SteamID32, Players.Alias, COUNT(*) AS RecordCount \
+ FROM Times \
+ INNER JOIN \
+ (SELECT Times.MapCourseID, Times.Mode, MIN(Times.RunTime) AS RecordTime \
+ FROM Times \
+ INNER JOIN MapCourses ON MapCourses.MapCourseID=Times.MapCourseID \
+ INNER JOIN Maps ON Maps.MapID=MapCourses.MapID \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ WHERE Players.Cheater=0 AND Maps.InRankedPool=1 AND MapCourses.Course=0 \
+ AND Times.Mode=%d AND Times.Teleports=0 \
+ GROUP BY Times.MapCourseID) Records \
+ ON Times.MapCourseID=Records.MapCourseID AND Times.Mode=Records.Mode AND Times.RunTime=Records.RecordTime AND Times.Teleports=0 \
+ INNER JOIN Players ON Players.SteamID32=Times.SteamID32 \
+ GROUP BY Players.SteamID32, Players.Alias \
+ ORDER BY RecordCount DESC \
+ LIMIT %d"; // Doesn't include bonuses
+
+char sql_getaverage[] = "\
+SELECT AVG(PBTime), COUNT(*) \
+ FROM \
+ (SELECT MIN(Times.RunTime) AS PBTime \
+ FROM Times \
+ INNER JOIN MapCourses ON Times.MapCourseID=MapCourses.MapCourseID \
+ INNER JOIN Players ON Times.SteamID32=Players.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d \
+ GROUP BY Times.SteamID32) AS PBTimes";
+
+char sql_getaverage_pro[] = "\
+SELECT AVG(PBTime), COUNT(*) \
+ FROM \
+ (SELECT MIN(Times.RunTime) AS PBTime \
+ FROM Times \
+ INNER JOIN MapCourses ON Times.MapCourseID=MapCourses.MapCourseID \
+ INNER JOIN Players ON Times.SteamID32=Players.SteamID32 \
+ WHERE Players.Cheater=0 AND MapCourses.MapID=%d \
+ AND MapCourses.Course=%d AND Times.Mode=%d AND Times.Teleports=0 \
+ GROUP BY Times.SteamID32) AS PBTimes";
+
+char sql_getrecentrecords[] = "\
+SELECT Maps.Name, MapCourses.Course, MapCourses.MapCourseID, Players.Alias, a.RunTime \
+ FROM Times AS a \
+ INNER JOIN MapCourses ON a.MapCourseID=MapCourses.MapCourseID \
+ INNER JOIN Maps ON MapCourses.MapID=Maps.MapID \
+ INNER JOIN Players ON a.SteamID32=Players.SteamID32 \
+ WHERE Players.Cheater=0 AND Maps.InRankedPool AND a.Mode=%d \
+ AND NOT EXISTS \
+ (SELECT * \
+ FROM Times AS b \
+ WHERE a.MapCourseID=b.MapCourseID AND a.Mode=b.Mode \
+ AND a.Created>b.Created AND a.RunTime>b.RunTime) \
+ ORDER BY a.TimeID DESC \
+ LIMIT %d";
+
+char sql_getrecentrecords_pro[] = "\
+SELECT Maps.Name, MapCourses.Course, MapCourses.MapCourseID, Players.Alias, a.RunTime \
+ FROM Times AS a \
+ INNER JOIN MapCourses ON a.MapCourseID=MapCourses.MapCourseID \
+ INNER JOIN Maps ON MapCourses.MapID=Maps.MapID \
+ INNER JOIN Players ON a.SteamID32=Players.SteamID32 \
+ WHERE Players.Cheater=0 AND Maps.InRankedPool AND a.Mode=%d AND a.Teleports=0 \
+ AND NOT EXISTS \
+ (SELECT * \
+ FROM Times AS b \
+ WHERE b.Teleports=0 AND a.MapCourseID=b.MapCourseID AND a.Mode=b.Mode \
+ AND a.Created>b.Created AND a.RunTime>b.RunTime) \
+ ORDER BY a.TimeID DESC \
+ LIMIT %d";
+
+
+
+// =====[ JUMPSTATS ]=====
+
+char sql_jumpstats_gettop[] = "\
+SELECT j.JumpID, p.SteamID32, p.Alias, j.Block, j.Distance, j.Strafes, j.Sync, j.Pre, j.Max, j.Airtime \
+ FROM \
+ Jumpstats j \
+ INNER JOIN \
+ Players p ON \
+ p.SteamID32=j.SteamID32 AND \
+ p.Cheater = 0 \
+ INNER JOIN \
+ ( \
+ SELECT j.SteamID32, j.JumpType, j.Mode, j.IsBlockJump, MAX(j.Distance) BestDistance \
+ FROM \
+ Jumpstats j \
+ INNER JOIN \
+ ( \
+ SELECT SteamID32, MAX(Block) AS MaxBlockDist \
+ FROM \
+ Jumpstats \
+ WHERE \
+ JumpType = %d AND \
+ Mode = %d AND \
+ IsBlockJump = %d \
+ GROUP BY SteamID32 \
+ ) MaxBlock ON \
+ j.SteamID32 = MaxBlock.SteamID32 AND \
+ j.Block = MaxBlock.MaxBlockDist \
+ WHERE \
+ j.JumpType = %d AND \
+ j.Mode = %d AND \
+ j.IsBlockJump = %d \
+ GROUP BY j.SteamID32, j.JumpType, j.Mode, j.IsBlockJump \
+ ) MaxDist ON \
+ j.SteamID32 = MaxDist.SteamID32 AND \
+ j.JumpType = MaxDist.JumpType AND \
+ j.Mode = MaxDist.Mode AND \
+ j.IsBlockJump = MaxDist.IsBlockJump AND \
+ j.Distance = MaxDist.BestDistance \
+ ORDER BY j.Block DESC, j.Distance DESC \
+ LIMIT %d";
+
+char sql_jumpstats_getrecord[] = "\
+SELECT JumpID, Distance, Block \
+ FROM \
+ Jumpstats rec \
+ WHERE \
+ SteamID32 = %d AND \
+ JumpType = %d AND \
+ Mode = %d AND \
+ IsBlockJump = %d \
+ ORDER BY Block DESC, Distance DESC";
+
+char sql_jumpstats_getpbs[] = "\
+SELECT b.JumpID, b.JumpType, b.Distance, b.Strafes, b.Sync, b.Pre, b.Max, b.Airtime \
+ FROM Jumpstats b \
+ INNER JOIN ( \
+ SELECT a.SteamID32, a.Mode, a.JumpType, MAX(a.Distance) Distance \
+ FROM Jumpstats a \
+ WHERE a.SteamID32=%d AND a.Mode=%d AND NOT a.IsBlockJump \
+ GROUP BY a.JumpType, a.Mode, a.SteamID32 \
+ ) a ON a.JumpType=b.JumpType AND a.Distance=b.Distance \
+ WHERE a.SteamID32=b.SteamID32 AND a.Mode=b.Mode AND NOT b.IsBlockJump \
+ ORDER BY b.JumpType";
+
+char sql_jumpstats_getblockpbs[] = "\
+SELECT c.JumpID, c.JumpType, c.Block, c.Distance, c.Strafes, c.Sync, c.Pre, c.Max, c.Airtime \
+ FROM Jumpstats c \
+ INNER JOIN ( \
+ SELECT a.SteamID32, a.Mode, a.JumpType, a.Block, MAX(b.Distance) Distance \
+ FROM Jumpstats b \
+ INNER JOIN ( \
+ SELECT a.SteamID32, a.Mode, a.JumpType, MAX(a.Block) Block \
+ FROM Jumpstats a \
+ WHERE a.SteamID32=%d AND a.Mode=%d AND a.IsBlockJump \
+ GROUP BY a.JumpType, a.Mode, a.SteamID32 \
+ ) a ON a.JumpType=b.JumpType AND a.Block=b.Block \
+ WHERE a.SteamID32=b.SteamID32 AND a.Mode=b.Mode AND b.IsBlockJump \
+ GROUP BY a.JumpType, a.Mode, a.SteamID32, a.Block \
+ ) b ON b.JumpType=c.JumpType AND b.Block=c.Block AND b.Distance=c.Distance \
+ WHERE b.SteamID32=c.SteamID32 AND b.Mode=c.Mode AND c.IsBlockJump \
+ ORDER BY c.JumpType";
diff --git a/sourcemod/scripting/gokz-localranks/db/update_ranked_map_pool.sp b/sourcemod/scripting/gokz-localranks/db/update_ranked_map_pool.sp
new file mode 100644
index 0000000..ee9bd6d
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/db/update_ranked_map_pool.sp
@@ -0,0 +1,104 @@
+/*
+ Inserts a list of maps read from a file into the Maps table,
+ and updates them to be part of the ranked map pool.
+*/
+
+
+
+void DB_UpdateRankedMapPool(int client)
+{
+ File file = OpenFile(LR_CFG_MAP_POOL, "r");
+ if (file == null)
+ {
+ LogError("Failed to load file: '%s'.", LR_CFG_MAP_POOL);
+ if (IsValidClient(client))
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Ranked Map Pool - Error");
+ }
+ return;
+ }
+
+ char map[256];
+ int mapsCount = 0;
+
+ Transaction txn = new Transaction();
+
+ // Reset all maps to be unranked
+ txn.AddQuery(sql_maps_reset_mappool);
+
+ // Insert/Update maps in gokz-localranks-mappool.cfg to be ranked
+ while (file.ReadLine(map, sizeof(map)))
+ {
+ TrimString(map);
+ String_ToLower(map, map, sizeof(map));
+
+ // Ignore blank lines and comments
+ if (map[0] == '\0' || map[0] == ';' || (map[0] == '/' && map[1] == '/'))
+ {
+ continue;
+ }
+
+ mapsCount++;
+
+ switch (g_DBType)
+ {
+ case DatabaseType_SQLite:
+ {
+ char updateQuery[512];
+ gH_DB.Format(updateQuery, sizeof(updateQuery), sqlite_maps_updateranked, 1, map);
+
+ char insertQuery[512];
+ gH_DB.Format(insertQuery, sizeof(insertQuery), sqlite_maps_insertranked, 1, map);
+
+ txn.AddQuery(updateQuery);
+ txn.AddQuery(insertQuery);
+ }
+ case DatabaseType_MySQL:
+ {
+ char query[512];
+ gH_DB.Format(query, sizeof(query), mysql_maps_upsertranked, 1, map);
+
+ txn.AddQuery(query);
+ }
+ }
+ }
+
+ delete file;
+
+ if (mapsCount == 0)
+ {
+ LogError("No maps found in file: '%s'.", LR_CFG_MAP_POOL);
+
+ if (IsValidClient(client))
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Ranked Map Pool - No Maps In File");
+ GOKZ_PlayErrorSound(client);
+ }
+
+ delete txn;
+ return;
+ }
+
+ // Pass client user ID (or -1) as data
+ int data = -1;
+ if (IsValidClient(client))
+ {
+ data = GetClientUserId(client);
+ }
+
+ gH_DB.Execute(txn, DB_TxnSuccess_UpdateRankedMapPool, DB_TxnFailure_Generic, data);
+}
+
+public void DB_TxnSuccess_UpdateRankedMapPool(Handle db, int userid, int numQueries, Handle[] results, any[] queryData)
+{
+ int client = GetClientOfUserId(userid);
+ if (IsValidClient(client))
+ {
+ LogMessage("The ranked map pool was updated by %L.", client);
+ GOKZ_PrintToChat(client, true, "%t", "Ranked Map Pool - Success");
+ }
+ else
+ {
+ LogMessage("The ranked map pool was updated.");
+ }
+}
diff --git a/sourcemod/scripting/gokz-localranks/misc.sp b/sourcemod/scripting/gokz-localranks/misc.sp
new file mode 100644
index 0000000..0c6d96c
--- /dev/null
+++ b/sourcemod/scripting/gokz-localranks/misc.sp
@@ -0,0 +1,319 @@
+/*
+ Miscellaneous functions.
+*/
+
+
+
+// =====[ COMPLETION MVP STARS ]=====
+
+void CompletionMVPStarsUpdate(int client)
+{
+ DB_GetCompletion(client, GetSteamAccountID(client), GOKZ_GetDefaultMode(), false);
+}
+
+void CompletionMVPStarsUpdateAll()
+{
+ for (int client = 1; client <= MaxClients; client++)
+ {
+ if (IsClientInGame(client) && !IsFakeClient(client))
+ {
+ CompletionMVPStarsUpdate(client);
+ }
+ }
+}
+
+
+
+// =====[ ANNOUNCEMENTS ]=====
+
+void PrecacheAnnouncementSounds()
+{
+ if (!LoadSounds())
+ {
+ SetFailState("Failed to load file: \"%s\".", LR_CFG_SOUNDS);
+ }
+}
+
+static bool LoadSounds()
+{
+ KeyValues kv = new KeyValues("sounds");
+ if (!kv.ImportFromFile(LR_CFG_SOUNDS))
+ {
+ return false;
+ }
+
+ char downloadPath[256];
+
+ kv.GetString("beatrecord", gC_BeatRecordSound, sizeof(gC_BeatRecordSound));
+ FormatEx(downloadPath, sizeof(downloadPath), "sound/%s", gC_BeatRecordSound);
+ AddFileToDownloadsTable(downloadPath);
+ PrecacheSound(gC_BeatRecordSound, true);
+
+ delete kv;
+ return true;
+}
+
+static void PlayBeatRecordSound()
+{
+ GOKZ_EmitSoundToAll(gC_BeatRecordSound, _, "Server Record");
+}
+
+void AnnounceNewTime(
+ int client,
+ int course,
+ int mode,
+ float runTime,
+ int teleportsUsed,
+ bool firstTime,
+ float pbDiff,
+ int rank,
+ int maxRank,
+ bool firstTimePro,
+ float pbDiffPro,
+ int rankPro,
+ int maxRankPro)
+{
+ // Main Course
+ if (course == 0)
+ {
+ // Main Course PRO Times
+ if (teleportsUsed == 0)
+ {
+ if (firstTimePro)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Time - First Time (PRO)",
+ client, GOKZ_FormatTime(runTime), rankPro, maxRankPro, gC_ModeNamesShort[mode]);
+ }
+ else if (pbDiffPro < 0)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Time - Beat PB (PRO)",
+ client, GOKZ_FormatTime(runTime), GOKZ_FormatTime(FloatAbs(pbDiffPro)), rankPro, maxRankPro, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Time - Miss PB (PRO)",
+ client, GOKZ_FormatTime(runTime), GOKZ_FormatTime(pbDiffPro), rankPro, maxRankPro, gC_ModeNamesShort[mode]);
+ }
+ }
+ // Main Course NUB Times
+ else
+ {
+ if (firstTime)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Time - First Time",
+ client, GOKZ_FormatTime(runTime), rank, maxRank, gC_ModeNamesShort[mode]);
+ }
+ else if (pbDiff < 0)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Time - Beat PB",
+ client, GOKZ_FormatTime(runTime), GOKZ_FormatTime(FloatAbs(pbDiff)), rank, maxRank, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Time - Miss PB",
+ client, GOKZ_FormatTime(runTime), GOKZ_FormatTime(pbDiff), rank, maxRank, gC_ModeNamesShort[mode]);
+ }
+ }
+ }
+ // Bonus Course
+ else
+ {
+ // Bonus Course PRO Times
+ if (teleportsUsed == 0)
+ {
+ if (firstTimePro)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Time - First Time (PRO)",
+ client, course, GOKZ_FormatTime(runTime), rankPro, maxRankPro, gC_ModeNamesShort[mode]);
+ }
+ else if (pbDiffPro < 0)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Time - Beat PB (PRO)",
+ client, course, GOKZ_FormatTime(runTime), GOKZ_FormatTime(FloatAbs(pbDiffPro)), rankPro, maxRankPro, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Time - Miss PB (PRO)",
+ client, course, GOKZ_FormatTime(runTime), GOKZ_FormatTime(pbDiffPro), rankPro, maxRankPro, gC_ModeNamesShort[mode]);
+ }
+ }
+ // Bonus Course NUB Times
+ else
+ {
+ if (firstTime)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Time - First Time",
+ client, course, GOKZ_FormatTime(runTime), rank, maxRank, gC_ModeNamesShort[mode]);
+ }
+ else if (pbDiff < 0)
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Time - Beat PB",
+ client, course, GOKZ_FormatTime(runTime), GOKZ_FormatTime(FloatAbs(pbDiff)), rank, maxRank, gC_ModeNamesShort[mode]);
+ }
+ else
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Time - Miss PB",
+ client, course, GOKZ_FormatTime(runTime), GOKZ_FormatTime(pbDiff), rank, maxRank, gC_ModeNamesShort[mode]);
+ }
+ }
+ }
+}
+
+void AnnounceNewRecord(int client, int course, int mode, int recordType)
+{
+ if (course == 0)
+ {
+ switch (recordType)
+ {
+ case RecordType_Nub:
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Record (NUB)", client, gC_ModeNamesShort[mode]);
+ }
+ case RecordType_Pro:
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Record (PRO)", client, gC_ModeNamesShort[mode]);
+ }
+ case RecordType_NubAndPro:
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Record (NUB and PRO)", client, gC_ModeNamesShort[mode]);
+ }
+ }
+ }
+ else
+ {
+ switch (recordType)
+ {
+ case RecordType_Nub:
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Record (NUB)", client, course, gC_ModeNamesShort[mode]);
+ }
+ case RecordType_Pro:
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Record (PRO)", client, course, gC_ModeNamesShort[mode]);
+ }
+ case RecordType_NubAndPro:
+ {
+ GOKZ_PrintToChatAll(true, "%t", "New Bonus Record (NUB and PRO)", client, course, course, gC_ModeNamesShort[mode]);
+ }
+ }
+ }
+
+ PlayBeatRecordSound(); // Play sound!
+}
+
+
+
+// =====[ MISSED RECORD TRACKING ]=====
+
+void ResetRecordMissed(int client)
+{
+ for (int timeType = 0; timeType < TIMETYPE_COUNT; timeType++)
+ {
+ gB_RecordMissed[client][timeType] = false;
+ }
+}
+
+void UpdateRecordMissed(int client)
+{
+ if (!GOKZ_GetTimerRunning(client) || gB_RecordMissed[client][TimeType_Nub] && gB_RecordMissed[client][TimeType_Pro])
+ {
+ return;
+ }
+
+ int course = GOKZ_GetCourse(client);
+ int mode = GOKZ_GetCoreOption(client, Option_Mode);
+ float currentTime = GOKZ_GetTime(client);
+
+ bool nubRecordExists = gB_RecordExistsCache[course][mode][TimeType_Nub];
+ float nubRecordTime = gF_RecordTimesCache[course][mode][TimeType_Nub];
+ bool nubRecordMissed = gB_RecordMissed[client][TimeType_Nub];
+ bool proRecordExists = gB_RecordExistsCache[course][mode][TimeType_Pro];
+ float proRecordTime = gF_RecordTimesCache[course][mode][TimeType_Pro];
+ bool proRecordMissed = gB_RecordMissed[client][TimeType_Pro];
+
+ if (nubRecordExists && !nubRecordMissed && currentTime >= nubRecordTime)
+ {
+ gB_RecordMissed[client][TimeType_Nub] = true;
+
+ // Check if nub record is also the pro record, and call the forward appropriately
+ if (proRecordExists && FloatAbs(nubRecordTime - proRecordTime) < EPSILON)
+ {
+ gB_RecordMissed[client][TimeType_Pro] = true;
+ Call_OnRecordMissed(client, nubRecordTime, course, mode, Style_Normal, RecordType_NubAndPro);
+ }
+ else
+ {
+ Call_OnRecordMissed(client, nubRecordTime, course, mode, Style_Normal, RecordType_Nub);
+ }
+ }
+ else if (proRecordExists && !proRecordMissed && currentTime >= proRecordTime)
+ {
+ gB_RecordMissed[client][TimeType_Pro] = true;
+ Call_OnRecordMissed(client, proRecordTime, course, mode, Style_Normal, RecordType_Pro);
+ }
+}
+
+
+
+// =====[ MISSED PB TRACKING ]=====
+
+#define MISSED_PB_SOUND "buttons/button18.wav"
+
+void ResetPBMissed(int client)
+{
+ for (int timeType = 0; timeType < TIMETYPE_COUNT; timeType++)
+ {
+ gB_PBMissed[client][timeType] = false;
+ }
+}
+
+void UpdatePBMissed(int client)
+{
+ if (!GOKZ_GetTimerRunning(client) || gB_PBMissed[client][TimeType_Nub] && gB_PBMissed[client][TimeType_Pro])
+ {
+ return;
+ }
+
+ int course = GOKZ_GetCourse(client);
+ int mode = GOKZ_GetCoreOption(client, Option_Mode);
+ float currentTime = GOKZ_GetTime(client);
+
+ bool nubPBExists = gB_PBExistsCache[client][course][mode][TimeType_Nub];
+ float nubPBTime = gF_PBTimesCache[client][course][mode][TimeType_Nub];
+ bool nubPBMissed = gB_PBMissed[client][TimeType_Nub];
+ bool proPBExists = gB_PBExistsCache[client][course][mode][TimeType_Pro];
+ float proPBTime = gF_PBTimesCache[client][course][mode][TimeType_Pro];
+ bool proPBMissed = gB_PBMissed[client][TimeType_Pro];
+
+ if (nubPBExists && !nubPBMissed && currentTime >= nubPBTime)
+ {
+ gB_PBMissed[client][TimeType_Nub] = true;
+
+ // Check if nub PB is also the pro PB, and call the forward appropriately
+ if (proPBExists && FloatAbs(nubPBTime - proPBTime) < EPSILON)
+ {
+ gB_PBMissed[client][TimeType_Pro] = true;
+ Call_OnPBMissed(client, nubPBTime, course, mode, Style_Normal, RecordType_NubAndPro);
+ }
+ else
+ {
+ Call_OnPBMissed(client, nubPBTime, course, mode, Style_Normal, RecordType_Nub);
+ }
+ }
+ else if (proPBExists && !proPBMissed && currentTime >= proPBTime)
+ {
+ gB_PBMissed[client][TimeType_Pro] = true;
+ Call_OnPBMissed(client, proPBTime, course, mode, Style_Normal, RecordType_Pro);
+ }
+}
+
+void DoPBMissedReport(int client, float pbTime, int recordType)
+{
+ switch (recordType)
+ {
+ case RecordType_Nub:GOKZ_PrintToChat(client, true, "%t", "Missed PB (NUB)", GOKZ_FormatTime(pbTime));
+ case RecordType_Pro:GOKZ_PrintToChat(client, true, "%t", "Missed PB (PRO)", GOKZ_FormatTime(pbTime));
+ case RecordType_NubAndPro:GOKZ_PrintToChat(client, true, "%t", "Missed PB (NUB and PRO)", GOKZ_FormatTime(pbTime));
+ }
+ GOKZ_EmitSoundToClient(client, MISSED_PB_SOUND, _, "Missed PB");
+} \ No newline at end of file