summaryrefslogtreecommitdiff
path: root/sourcemod/scripting/gokz-global
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-global
parent38f1140c11724da05a23a10385061200b907cf6e (diff)
bbbbbbbbwaaaaaaaaaaa
Diffstat (limited to 'sourcemod/scripting/gokz-global')
-rw-r--r--sourcemod/scripting/gokz-global/api.sp142
-rw-r--r--sourcemod/scripting/gokz-global/ban_player.sp42
-rw-r--r--sourcemod/scripting/gokz-global/commands.sp169
-rw-r--r--sourcemod/scripting/gokz-global/maptop_menu.sp249
-rw-r--r--sourcemod/scripting/gokz-global/points.sp147
-rw-r--r--sourcemod/scripting/gokz-global/print_records.sp190
-rw-r--r--sourcemod/scripting/gokz-global/send_run.sp143
7 files changed, 1082 insertions, 0 deletions
diff --git a/sourcemod/scripting/gokz-global/api.sp b/sourcemod/scripting/gokz-global/api.sp
new file mode 100644
index 0000000..23caa15
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/api.sp
@@ -0,0 +1,142 @@
+static GlobalForward H_OnNewTopTime;
+static GlobalForward H_OnPointsUpdated;
+
+
+
+// =====[ FORWARDS ]=====
+
+void CreateGlobalForwards()
+{
+ H_OnNewTopTime = new GlobalForward("GOKZ_GL_OnNewTopTime", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Float);
+ H_OnPointsUpdated = new GlobalForward("GOKZ_GL_OnPointsUpdated", ET_Ignore, Param_Cell, Param_Cell);
+}
+
+void Call_OnNewTopTime(int client, int course, int mode, int timeType, int rank, int rankOverall, float time)
+{
+ Call_StartForward(H_OnNewTopTime);
+ Call_PushCell(client);
+ Call_PushCell(course);
+ Call_PushCell(mode);
+ Call_PushCell(timeType);
+ Call_PushCell(rank);
+ Call_PushCell(rankOverall);
+ Call_PushFloat(time);
+ Call_Finish();
+}
+
+void Call_OnPointsUpdated(int client, int mode)
+{
+ Call_StartForward(H_OnPointsUpdated);
+ Call_PushCell(client);
+ Call_PushCell(mode);
+ Call_Finish();
+}
+
+
+
+// =====[ NATIVES ]=====
+
+void CreateNatives()
+{
+ CreateNative("GOKZ_GL_PrintRecords", Native_PrintRecords);
+ CreateNative("GOKZ_GL_DisplayMapTopMenu", Native_DisplayMapTopMenu);
+ CreateNative("GOKZ_GL_GetPoints", Native_GetPoints);
+ CreateNative("GOKZ_GL_GetMapPoints", Native_GetMapPoints);
+ CreateNative("GOKZ_GL_GetRankPoints", Native_GetRankPoints);
+ CreateNative("GOKZ_GL_GetFinishes", Native_GetFinishes);
+ CreateNative("GOKZ_GL_UpdatePoints", Native_UpdatePoints);
+ CreateNative("GOKZ_GL_GetAPIKeyValid", Native_GetAPIKeyValid);
+ CreateNative("GOKZ_GL_GetPluginsValid", Native_GetPluginsValid);
+ CreateNative("GOKZ_GL_GetSettingsEnforcerValid", Native_GetSettingsEnforcerValid);
+ CreateNative("GOKZ_GL_GetMapValid", Native_GetMapValid);
+ CreateNative("GOKZ_GL_GetPlayerValid", Native_GetPlayerValid);
+}
+
+public int Native_PrintRecords(Handle plugin, int numParams)
+{
+ char map[33], steamid[32];
+ GetNativeString(2, map, sizeof(map));
+ GetNativeString(5, steamid, sizeof(steamid));
+
+ if (StrEqual(map, ""))
+ {
+ PrintRecords(GetNativeCell(1), gC_CurrentMap, GetNativeCell(3), GetNativeCell(4), steamid);
+ }
+ else
+ {
+ PrintRecords(GetNativeCell(1), map, GetNativeCell(3), GetNativeCell(4), steamid);
+ }
+ return 0;
+}
+
+public int Native_DisplayMapTopMenu(Handle plugin, int numParams)
+{
+ char pluginName[32];
+ GetPluginFilename(plugin, pluginName, sizeof(pluginName));
+ bool localRanksCall = StrEqual(pluginName, "gokz-localranks.smx", false);
+
+ char map[33];
+ GetNativeString(2, map, sizeof(map));
+
+ if (StrEqual(map, ""))
+ {
+ DisplayMapTopSubmenu(GetNativeCell(1), gC_CurrentMap, GetNativeCell(3), GetNativeCell(4), GetNativeCell(5), localRanksCall);
+ }
+ else
+ {
+ DisplayMapTopSubmenu(GetNativeCell(1), map, GetNativeCell(3), GetNativeCell(4), GetNativeCell(5), localRanksCall);
+ }
+ return 0;
+}
+
+public int Native_GetPoints(Handle plugin, int numParams)
+{
+ return GetPoints(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3));
+}
+
+public int Native_GetMapPoints(Handle plugin, int numParams)
+{
+ return GetMapPoints(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3));
+}
+
+public int Native_GetRankPoints(Handle plugin, int numParams)
+{
+ return GetRankPoints(GetNativeCell(1), GetNativeCell(2));
+}
+
+public int Native_GetFinishes(Handle plugin, int numParams)
+{
+ return GetFinishes(GetNativeCell(1), GetNativeCell(2), GetNativeCell(3));
+}
+
+public int Native_UpdatePoints(Handle plugin, int numParams)
+{
+ // We're gonna always force an update here, cause otherwise the call doesn't really make sense
+ UpdatePoints(GetNativeCell(1), true, GetNativeCell(2));
+ return 0;
+}
+
+public int Native_GetAPIKeyValid(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_APIKeyCheck);
+}
+
+public int Native_GetPluginsValid(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_BannedCommandsCheck);
+}
+
+public int Native_GetSettingsEnforcerValid(Handle plugin, int numParams)
+{
+ return view_as<int>(gCV_gokz_settings_enforcer.BoolValue && gB_EnforcerOnFreshMap);
+}
+
+public int Native_GetMapValid(Handle plugin, int numParams)
+{
+ return view_as<int>(MapCheck());
+}
+
+public int Native_GetPlayerValid(Handle plugin, int numParams)
+{
+ return view_as<int>(gB_GloballyVerified[GetNativeCell(1)]);
+}
diff --git a/sourcemod/scripting/gokz-global/ban_player.sp b/sourcemod/scripting/gokz-global/ban_player.sp
new file mode 100644
index 0000000..835d9e5
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/ban_player.sp
@@ -0,0 +1,42 @@
+/*
+ Globally ban players when they are suspected by gokz-anticheat.
+*/
+
+
+
+// =====[ PUBLIC ]=====
+
+void GlobalBanPlayer(int client, ACReason reason, const char[] notes, const char[] stats)
+{
+ char playerName[MAX_NAME_LENGTH], steamid[32], ip[32];
+
+ GetClientName(client, playerName, sizeof(playerName));
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ GetClientIP(client, ip, sizeof(ip));
+
+ DataPack dp = new DataPack();
+ dp.WriteString(playerName);
+ dp.WriteString(steamid);
+
+ switch (reason)
+ {
+ case ACReason_BhopHack:GlobalAPI_CreateBan(BanPlayerCallback, dp, steamid, "bhop_hack", stats, notes, ip);
+ case ACReason_BhopMacro:GlobalAPI_CreateBan(BanPlayerCallback, dp, steamid, "bhop_macro", stats, notes, ip);
+ }
+}
+
+public int BanPlayerCallback(JSON_Object response, GlobalAPIRequestData request, DataPack dp)
+{
+ char playerName[MAX_NAME_LENGTH], steamid[32];
+
+ dp.Reset();
+ dp.ReadString(playerName, sizeof(playerName));
+ dp.ReadString(steamid, sizeof(steamid));
+ delete dp;
+
+ if (request.Failure)
+ {
+ LogError("Failed to globally ban %s (%s).", playerName, steamid);
+ }
+ return 0;
+}
diff --git a/sourcemod/scripting/gokz-global/commands.sp b/sourcemod/scripting/gokz-global/commands.sp
new file mode 100644
index 0000000..8ee7c32
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/commands.sp
@@ -0,0 +1,169 @@
+void RegisterCommands()
+{
+ RegConsoleCmd("sm_globalcheck", CommandGlobalCheck, "[KZ] Show whether global records are currently enabled in chat.");
+ RegConsoleCmd("sm_gc", CommandGlobalCheck, "[KZ] Show whether global records are currently enabled in chat.");
+ RegConsoleCmd("sm_tier", CommandTier, "[KZ] Show the map's tier in chat.");
+ RegConsoleCmd("sm_gpb", CommandPrintPBs, "[KZ] Show main course global personal best in chat. Usage: !gpb <map>");
+ RegConsoleCmd("sm_gr", CommandPrintRecords, "[KZ] Show main course global record times in chat. Usage: !gr <map>");
+ RegConsoleCmd("sm_gwr", CommandPrintRecords, "[KZ] Show main course global record times in chat. Usage: !gwr <map>");
+ RegConsoleCmd("sm_gbpb", CommandPrintBonusPBs, "[KZ] Show bonus global personal best in chat. Usage: !gbpb <#bonus> <map>");
+ RegConsoleCmd("sm_gbr", CommandPrintBonusRecords, "[KZ] Show bonus global record times in chat. Usage: !bgr <#bonus> <map>");
+ RegConsoleCmd("sm_gbwr", CommandPrintBonusRecords, "[KZ] Show bonus global record times in chat. Usage: !bgwr <#bonus> <map>");
+ RegConsoleCmd("sm_gmaptop", CommandMapTop, "[KZ] Open a menu showing the top global main course times of a map. Usage: !gmaptop <map>");
+ RegConsoleCmd("sm_gbmaptop", CommandBonusMapTop, "[KZ] Open a menu showing the top global bonus times of a map. Usage: !gbmaptop <#bonus> <map>");
+}
+
+public Action CommandGlobalCheck(int client, int args)
+{
+ PrintGlobalCheckToChat(client);
+ return Plugin_Handled;
+}
+
+public Action CommandTier(int client, int args)
+{
+ if (gI_MapTier != -1)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Tier", gC_CurrentMap, gI_MapTier);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Map Tier (Unknown)", gC_CurrentMap);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandPrintPBs(int client, int args)
+{
+ char steamid[32];
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ return CommandPrintRecordsHelper(client, args, steamid);
+}
+
+public Action CommandPrintRecords(int client, int args)
+{
+ return CommandPrintRecordsHelper(client, args);
+}
+
+static Action CommandPrintRecordsHelper(int client, int args, const char[] steamid = DEFAULT_STRING)
+{
+ KZPlayer player = KZPlayer(client);
+ int mode = player.Mode;
+
+ if (args == 0)
+ { // Print record times for current map and their current mode
+ PrintRecords(client, gC_CurrentMap, 0, mode, steamid);
+ }
+ else if (args >= 1)
+ { // Print record times for specified map and their current mode
+ char argMap[33];
+ GetCmdArg(1, argMap, sizeof(argMap));
+ PrintRecords(client, argMap, 0, mode, steamid);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandPrintBonusPBs(int client, int args)
+{
+ char steamid[32];
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ return CommandPrintBonusRecordsHelper(client, args, steamid);
+}
+
+public Action CommandPrintBonusRecords(int client, int args)
+{
+ return CommandPrintBonusRecordsHelper(client, args);
+}
+
+static Action CommandPrintBonusRecordsHelper(int client, int args, const char[] steamid = DEFAULT_STRING)
+{
+ KZPlayer player = KZPlayer(client);
+ int mode = player.Mode;
+
+ if (args == 0)
+ { // Print Bonus 1 record times for current map and their current mode
+ PrintRecords(client, gC_CurrentMap, 1, mode, steamid);
+ }
+ 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))
+ {
+ PrintRecords(client, gC_CurrentMap, bonus, mode, steamid);
+ }
+ 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))
+ {
+ PrintRecords(client, argMap, bonus, mode, steamid);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandMapTop(int client, int args)
+{
+ if (args <= 0)
+ { // Open global map top for current map
+ DisplayMapTopModeMenu(client, gC_CurrentMap, 0);
+ }
+ else if (args >= 1)
+ { // Open global map top for specified map
+ char argMap[64];
+ GetCmdArg(1, argMap, sizeof(argMap));
+ DisplayMapTopModeMenu(client, argMap, 0);
+ }
+ return Plugin_Handled;
+}
+
+public Action CommandBonusMapTop(int client, int args)
+{
+ if (args == 0)
+ { // Open global Bonus 1 top for current map
+ DisplayMapTopModeMenu(client, gC_CurrentMap, 1);
+ }
+ else if (args == 1)
+ { // Open specified global Bonus # top for current map
+ char argBonus[4];
+ GetCmdArg(1, argBonus, sizeof(argBonus));
+ int bonus = StringToInt(argBonus);
+ if (GOKZ_IsValidCourse(bonus, true))
+ {
+ DisplayMapTopModeMenu(client, gC_CurrentMap, bonus);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ else if (args >= 2)
+ { // Open specified global 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))
+ {
+ DisplayMapTopModeMenu(client, argMap, bonus);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Invalid Bonus Number", argBonus);
+ }
+ }
+ return Plugin_Handled;
+}
diff --git a/sourcemod/scripting/gokz-global/maptop_menu.sp b/sourcemod/scripting/gokz-global/maptop_menu.sp
new file mode 100644
index 0000000..0c71346
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/maptop_menu.sp
@@ -0,0 +1,249 @@
+/*
+ Menu with the top global times for a map course and mode.
+*/
+
+static bool cameFromLocalRanks[MAXPLAYERS + 1];
+static char mapTopMap[MAXPLAYERS + 1][64];
+static int mapTopCourse[MAXPLAYERS + 1];
+static int mapTopMode[MAXPLAYERS + 1];
+
+
+
+// =====[ PUBLIC ]=====
+
+void DisplayMapTopModeMenu(int client, const char[] map, int course)
+{
+ FormatEx(mapTopMap[client], sizeof(mapTopMap[]), map);
+ mapTopCourse[client] = course;
+
+ Menu menu = new Menu(MenuHandler_MapTopModeMenu);
+ MapTopModeMenuSetTitle(client, menu);
+ GOKZ_MenuAddModeItems(client, menu, false);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+void DisplayMapTopMenu(int client, const char[] map, int course, int mode)
+{
+ FormatEx(mapTopMap[client], sizeof(mapTopMap[]), map);
+ mapTopCourse[client] = course;
+ mapTopMode[client] = mode;
+
+ Menu menu = new Menu(MenuHandler_MapTopMenu);
+ MapTopMenuSetTitle(client, menu);
+ MapTopMenuAddItems(client, menu);
+ menu.Display(client, MENU_TIME_FOREVER);
+}
+
+void DisplayMapTopSubmenu(int client, const char[] map, int course, int mode, int timeType, bool fromLocalRanks = false)
+{
+ char modeStr[32];
+
+ cameFromLocalRanks[client] = fromLocalRanks;
+
+ DataPack dp = new DataPack();
+ dp.WriteCell(GetClientUserId(client));
+ dp.WriteCell(timeType);
+
+ FormatEx(mapTopMap[client], sizeof(mapTopMap[]), map);
+ mapTopCourse[client] = course;
+ mapTopMode[client] = mode;
+ GOKZ_GL_GetModeString(mode, modeStr, sizeof(modeStr));
+
+ // TODO Hard coded 128 tick
+ // TODO Hard coded cap at top 20
+ // TODO Not true NUB yet
+ GlobalAPI_GetRecordsTop(DisplayMapTopSubmenuCallback, dp, _, _, _, map, 128, course, modeStr,
+ timeType == TimeType_Nub ? DEFAULT_BOOL : false, _, 0, 20);
+}
+
+
+
+// =====[ EVENTS ]=====
+
+public int MenuHandler_MapTopModeMenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ // param1 = client, param2 = mode
+ DisplayMapTopMenu(param1, mapTopMap[param1], mapTopCourse[param1], param2);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_MapTopMenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Select)
+ {
+ char info[8];
+ menu.GetItem(param2, info, sizeof(info));
+ int timeType = StringToInt(info);
+ DisplayMapTopSubmenu(param1, mapTopMap[param1], mapTopCourse[param1], mapTopMode[param1], timeType);
+ }
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ DisplayMapTopModeMenu(param1, mapTopMap[param1], mapTopCourse[param1]);
+ }
+ else if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+public int MenuHandler_MapTopSubmenu(Menu menu, MenuAction action, int param1, int param2)
+{
+ if (action == MenuAction_Cancel && param2 == MenuCancel_Exit)
+ {
+ if (cameFromLocalRanks[param1])
+ {
+ GOKZ_LR_ReopenMapTopMenu(param1);
+ }
+ else
+ {
+ DisplayMapTopMenu(param1, mapTopMap[param1], mapTopCourse[param1], mapTopMode[param1]);
+ }
+ }
+ if (action == MenuAction_End)
+ {
+ delete menu;
+ }
+ return 0;
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void MapTopModeMenuSetTitle(int client, Menu menu)
+{
+ if (mapTopCourse[client] == 0)
+ {
+ menu.SetTitle("%T", "Global Map Top Mode Menu - Title", client, mapTopMap[client]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Global Map Top Mode Menu - Title (Bonus)", client, mapTopMap[client], mapTopCourse[client]);
+ }
+}
+
+static void MapTopMenuSetTitle(int client, Menu menu)
+{
+ if (mapTopCourse[client] == 0)
+ {
+ menu.SetTitle("%T", "Global Map Top Menu - Title", client, mapTopMap[client], gC_ModeNames[mapTopMode[client]]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Global Map Top Menu - Title (Bonus)", client, mapTopMap[client], mapTopCourse[client], gC_ModeNames[mapTopMode[client]]);
+ }
+}
+
+static void MapTopMenuAddItems(int client, Menu menu)
+{
+ char display[32];
+ for (int i = 0; i < TIMETYPE_COUNT; i++)
+ {
+ FormatEx(display, sizeof(display), "%T", "Global Map Top Menu - Top", client, gC_TimeTypeNames[i]);
+ menu.AddItem(IntToStringEx(i), display);
+ }
+}
+
+public int DisplayMapTopSubmenuCallback(JSON_Object top, GlobalAPIRequestData request, DataPack dp)
+{
+ dp.Reset();
+ int client = GetClientOfUserId(dp.ReadCell());
+ int timeType = dp.ReadCell();
+ delete dp;
+
+ if (request.Failure)
+ {
+ LogError("Failed to get top records with Global API.");
+ return 0;
+ }
+
+ if (!top.IsArray)
+ {
+ LogError("GlobalAPI returned a malformed response while looking up the top records.");
+ return 0;
+ }
+
+ if (!IsValidClient(client))
+ {
+ return 0;
+ }
+
+ Menu menu = new Menu(MenuHandler_MapTopSubmenu);
+ if (mapTopCourse[client] == 0)
+ {
+ menu.SetTitle("%T", "Global Map Top Submenu - Title", client,
+ gC_TimeTypeNames[timeType], mapTopMap[client], gC_ModeNames[mapTopMode[client]]);
+ }
+ else
+ {
+ menu.SetTitle("%T", "Global Map Top Submenu - Title (Bonus)", client,
+ gC_TimeTypeNames[timeType], mapTopMap[client], mapTopCourse[client], gC_ModeNames[mapTopMode[client]]);
+ }
+
+ if (MapTopSubmenuAddItems(menu, top, timeType) == 0)
+ { // If no records found
+ if (timeType == TimeType_Pro)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "No Global Times Found (PRO)");
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "No Global Times Found");
+ }
+
+ if (cameFromLocalRanks[client])
+ {
+ GOKZ_LR_ReopenMapTopMenu(client);
+ }
+ else
+ {
+ DisplayMapTopMenu(client, mapTopMap[client], mapTopCourse[client], mapTopMode[client]);
+ }
+ }
+ else
+ {
+ menu.Pagination = 5;
+ menu.Display(client, MENU_TIME_FOREVER);
+ }
+ return 0;
+}
+
+// Returns number of record times added to the menu
+static int MapTopSubmenuAddItems(Menu menu, JSON_Object records, int timeType)
+{
+ char playerName[MAX_NAME_LENGTH];
+ char display[128];
+
+ for (int i = 0; i < records.Length; i++)
+ {
+ APIRecord record = view_as<APIRecord>(records.GetObjectIndexed(i));
+
+ record.GetPlayerName(playerName, sizeof(playerName));
+
+ switch (timeType)
+ {
+ case TimeType_Nub:
+ {
+ FormatEx(display, sizeof(display), "#%-2d %11s %3d TP %s",
+ i + 1, GOKZ_FormatTime(record.Time), record.Teleports, playerName);
+ }
+ case TimeType_Pro:
+ {
+ FormatEx(display, sizeof(display), "#%-2d %11s %s",
+ i + 1, GOKZ_FormatTime(record.Time), playerName);
+ }
+ }
+
+ menu.AddItem("", display, ITEMDRAW_DISABLED);
+ }
+
+ return records.Length;
+} \ No newline at end of file
diff --git a/sourcemod/scripting/gokz-global/points.sp b/sourcemod/scripting/gokz-global/points.sp
new file mode 100644
index 0000000..7758318
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/points.sp
@@ -0,0 +1,147 @@
+
+int pointsTotal[MAXPLAYERS + 1][MODE_COUNT][TIMETYPE_COUNT];
+int finishes[MAXPLAYERS + 1][MODE_COUNT][TIMETYPE_COUNT];
+int pointsMap[MAXPLAYERS + 1][MODE_COUNT][TIMETYPE_COUNT];
+int requestsInProgress[MAXPLAYERS + 1];
+
+
+
+void ResetPoints(int client)
+{
+ for (int mode = 0; mode < MODE_COUNT; mode++)
+ {
+ for (int type = 0; type < TIMETYPE_COUNT; type++)
+ {
+ pointsTotal[client][mode][type] = -1;
+ finishes[client][mode][type] = -1;
+ pointsMap[client][mode][type] = -1;
+ }
+ }
+ requestsInProgress[client] = 0;
+}
+
+void ResetMapPoints(int client)
+{
+ for (int mode = 0; mode < MODE_COUNT; mode++)
+ {
+ for (int type = 0; type < TIMETYPE_COUNT; type++)
+ {
+ pointsMap[client][mode][type] = -1;
+ }
+ }
+ requestsInProgress[client] = 0;
+}
+
+int GetRankPoints(int client, int mode)
+{
+ return pointsTotal[client][mode][TimeType_Nub];
+}
+
+int GetPoints(int client, int mode, int timeType)
+{
+ return pointsTotal[client][mode][timeType];
+}
+
+int GetMapPoints(int client, int mode, int timeType)
+{
+ return pointsMap[client][mode][timeType];
+}
+
+int GetFinishes(int client, int mode, int timeType)
+{
+ return finishes[client][mode][timeType];
+}
+
+// Note: This only gets 128 tick records
+void UpdatePoints(int client, bool force = false, int mode = -1)
+{
+ if (requestsInProgress[client] != 0)
+ {
+ return;
+ }
+
+ if (mode == -1)
+ {
+ mode = GOKZ_GetCoreOption(client, Option_Mode);
+ }
+
+ if (!force || pointsTotal[client][mode][TimeType_Nub] == -1)
+ {
+ GetPlayerRanks(client, mode, TimeType_Nub);
+ GetPlayerRanks(client, mode, TimeType_Pro);
+ requestsInProgress[client] += 2;
+ }
+
+ if (gI_MapID != -1 && (!force || pointsMap[client][mode][TimeType_Nub] == -1))
+ {
+ GetPlayerRanks(client, mode, TimeType_Nub, gI_MapID);
+ GetPlayerRanks(client, mode, TimeType_Pro, gI_MapID);
+ requestsInProgress[client] += 2;
+ }
+}
+
+static void GetPlayerRanks(int client, int mode, int timeType, int mapID = DEFAULT_INT)
+{
+ char steamid[21];
+ int modes[1], mapIDs[1];
+
+ modes[0] = view_as<int>(GOKZ_GL_GetGlobalMode(mode));
+ mapIDs[0] = mapID;
+ GetClientAuthId(client, AuthId_SteamID64, steamid, sizeof(steamid));
+
+ DataPack dp = new DataPack();
+ dp.WriteCell(GetClientUserId(client));
+ dp.WriteCell(mode);
+ dp.WriteCell(timeType);
+ dp.WriteCell(mapID == DEFAULT_INT);
+ GlobalAPI_GetPlayerRanks(UpdatePointsCallback, dp, _, _, _, _, steamid, _, _,
+ mapIDs, mapID == DEFAULT_INT ? DEFAULT_INT : 1, { 0 }, 1,
+ modes, 1, { 128 }, 1, timeType == TimeType_Nub ? DEFAULT_BOOL : false, _, _);
+}
+
+static void UpdatePointsCallback(JSON_Object ranks, GlobalAPIRequestData request, DataPack dp)
+{
+ dp.Reset();
+ int client = GetClientOfUserId(dp.ReadCell());
+ int mode = dp.ReadCell();
+ int timeType = dp.ReadCell();
+ bool isTotal = dp.ReadCell();
+ delete dp;
+
+ requestsInProgress[client]--;
+
+ if (client == 0)
+ {
+ return;
+ }
+
+ int points, totalFinishes;
+ if (request.Failure || !ranks.IsArray || ranks.Length == 0)
+ {
+ points = 0;
+ totalFinishes = 0;
+ }
+ else
+ {
+ APIPlayerRank rank = view_as<APIPlayerRank>(ranks.GetObjectIndexed(0));
+ // points = timeType == TimeType_Nub ? rank.PointsOverall : rank.Points;
+ points = points == -1 ? 0 : rank.Points;
+ totalFinishes = rank.Finishes == -1 ? 0 : rank.Finishes;
+ }
+
+ if (isTotal)
+ {
+ pointsTotal[client][mode][timeType] = points;
+ finishes[client][mode][timeType] = totalFinishes;
+ }
+ else
+ {
+ pointsMap[client][mode][timeType] = points;
+ }
+
+ // We always do that cause not all of the requests might have failed
+ if (requestsInProgress[client] == 0)
+ {
+ Call_OnPointsUpdated(client, mode);
+ }
+}
diff --git a/sourcemod/scripting/gokz-global/print_records.sp b/sourcemod/scripting/gokz-global/print_records.sp
new file mode 100644
index 0000000..3966ed5
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/print_records.sp
@@ -0,0 +1,190 @@
+/*
+ Prints the global record times for a map course and mode.
+*/
+
+
+static bool inProgress[MAXPLAYERS + 1];
+static bool waitingForOtherCallback[MAXPLAYERS + 1];
+static bool isPBQuery[MAXPLAYERS + 1];
+static char printRecordsMap[MAXPLAYERS + 1][64];
+static int printRecordsCourse[MAXPLAYERS + 1];
+static int printRecordsMode[MAXPLAYERS + 1];
+static bool printRecordsTimeExists[MAXPLAYERS + 1][TIMETYPE_COUNT];
+static float printRecordsTimes[MAXPLAYERS + 1][TIMETYPE_COUNT];
+static char printRecordsPlayerNames[MAXPLAYERS + 1][TIMETYPE_COUNT][MAX_NAME_LENGTH];
+
+
+
+// =====[ PUBLIC ]=====
+
+void PrintRecords(int client, const char[] map, int course, int mode, const char[] steamid = DEFAULT_STRING)
+{
+ char mode_str[32];
+
+ if (inProgress[client])
+ {
+ GOKZ_PrintToChat(client, true, "%t", "Please Wait Before Using Command Again");
+ return;
+ }
+
+ GOKZ_GL_GetModeString(mode, mode_str, sizeof(mode_str));
+
+ DataPack dpNUB = CreateDataPack();
+ dpNUB.WriteCell(GetClientUserId(client));
+ dpNUB.WriteCell(TimeType_Nub);
+ GlobalAPI_GetRecordsTop(PrintRecordsCallback, dpNUB, steamid, _, _, map, 128, course, mode_str, _, _, 0, 1);
+
+ DataPack dpPRO = CreateDataPack();
+ dpPRO.WriteCell(GetClientUserId(client));
+ dpPRO.WriteCell(TimeType_Pro);
+ GlobalAPI_GetRecordsTop(PrintRecordsCallback, dpPRO, steamid, _, _, map, 128, course, mode_str, false, _, 0, 1);
+
+ inProgress[client] = true;
+ waitingForOtherCallback[client] = true;
+ isPBQuery[client] = !StrEqual(steamid, DEFAULT_STRING);
+ FormatEx(printRecordsMap[client], sizeof(printRecordsMap[]), map);
+ printRecordsCourse[client] = course;
+ printRecordsMode[client] = mode;
+}
+
+public int PrintRecordsCallback(JSON_Object records, GlobalAPIRequestData request, DataPack dp)
+{
+ dp.Reset();
+ int client = GetClientOfUserId(dp.ReadCell());
+ int timeType = dp.ReadCell();
+ delete dp;
+
+ if (request.Failure)
+ {
+ LogError("Failed to retrieve record from the Global API for printing.");
+ return 0;
+ }
+
+ if (!IsValidClient(client))
+ {
+ return 0;
+ }
+
+ if (records.Length <= 0)
+ {
+ printRecordsTimeExists[client][timeType] = false;
+ }
+ else
+ {
+ APIRecord record = view_as<APIRecord>(records.GetObjectIndexed(0));
+ printRecordsTimeExists[client][timeType] = true;
+ printRecordsTimes[client][timeType] = record.Time;
+ record.GetPlayerName(printRecordsPlayerNames[client][timeType], sizeof(printRecordsPlayerNames[][]));
+ }
+
+ if (!waitingForOtherCallback[client])
+ {
+ if (isPBQuery[client])
+ {
+ PrintPBsFinally(client);
+ }
+ else
+ {
+ PrintRecordsFinally(client);
+ }
+ inProgress[client] = false;
+ }
+ else
+ {
+ waitingForOtherCallback[client] = false;
+ }
+ return 0;
+}
+
+
+
+// =====[ EVENTS ]=====
+
+void OnClientPutInServer_PrintRecords(int client)
+{
+ inProgress[client] = false;
+}
+
+
+
+// =====[ PRIVATE ]=====
+
+static void PrintPBsFinally(int client)
+{
+ // Print GPB header to chat
+ if (printRecordsCourse[client] == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "GPB Header",
+ printRecordsMap[client],
+ gC_ModeNamesShort[printRecordsMode[client]]);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "GPB Header (Bonus)",
+ printRecordsMap[client],
+ printRecordsCourse[client],
+ gC_ModeNamesShort[printRecordsMode[client]]);
+ }
+
+ // Print GPB times to chat
+ if (!printRecordsTimeExists[client][TimeType_Nub])
+ {
+ GOKZ_PrintToChat(client, false, "%t", "No Global Times Found");
+ }
+ else if (!printRecordsTimeExists[client][TimeType_Pro])
+ {
+ GOKZ_PrintToChat(client, false, "%t", "GPB Time - NUB",
+ GOKZ_FormatTime(printRecordsTimes[client][TimeType_Nub]),
+ printRecordsPlayerNames[client][TimeType_Nub]);
+ GOKZ_PrintToChat(client, false, "%t", "GPB Time - No PRO Time");
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, false, "%t", "GPB Time - NUB",
+ GOKZ_FormatTime(printRecordsTimes[client][TimeType_Nub]),
+ printRecordsPlayerNames[client][TimeType_Nub]);
+ GOKZ_PrintToChat(client, false, "%t", "GPB Time - PRO",
+ GOKZ_FormatTime(printRecordsTimes[client][TimeType_Pro]),
+ printRecordsPlayerNames[client][TimeType_Pro]);
+ }
+}
+
+static void PrintRecordsFinally(int client)
+{
+ // Print GWR header to chat
+ if (printRecordsCourse[client] == 0)
+ {
+ GOKZ_PrintToChat(client, true, "%t", "GWR Header",
+ printRecordsMap[client],
+ gC_ModeNamesShort[printRecordsMode[client]]);
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, true, "%t", "GWR Header (Bonus)",
+ printRecordsMap[client],
+ printRecordsCourse[client],
+ gC_ModeNamesShort[printRecordsMode[client]]);
+ }
+
+ // Print GWR times to chat
+ if (!printRecordsTimeExists[client][TimeType_Nub])
+ {
+ GOKZ_PrintToChat(client, false, "%t", "No Global Times Found");
+ }
+ else if (!printRecordsTimeExists[client][TimeType_Pro])
+ {
+ GOKZ_PrintToChat(client, false, "%t", "GWR Time - NUB",
+ GOKZ_FormatTime(printRecordsTimes[client][TimeType_Nub]),
+ printRecordsPlayerNames[client][TimeType_Nub]);
+ GOKZ_PrintToChat(client, false, "%t", "GWR Time - No PRO Time");
+ }
+ else
+ {
+ GOKZ_PrintToChat(client, false, "%t", "GWR Time - NUB",
+ GOKZ_FormatTime(printRecordsTimes[client][TimeType_Nub]),
+ printRecordsPlayerNames[client][TimeType_Nub]);
+ GOKZ_PrintToChat(client, false, "%t", "GWR Time - PRO",
+ GOKZ_FormatTime(printRecordsTimes[client][TimeType_Pro]),
+ printRecordsPlayerNames[client][TimeType_Pro]);
+ }
+}
diff --git a/sourcemod/scripting/gokz-global/send_run.sp b/sourcemod/scripting/gokz-global/send_run.sp
new file mode 100644
index 0000000..f560958
--- /dev/null
+++ b/sourcemod/scripting/gokz-global/send_run.sp
@@ -0,0 +1,143 @@
+/*
+ Sends a run to the global API and delete the replay if it is a temporary replay.
+*/
+
+
+
+char storedReplayPath[MAXPLAYERS + 1][512];
+int lastRecordId[MAXPLAYERS + 1], storedCourse[MAXPLAYERS + 1], storedTimeType[MAXPLAYERS + 1], storedUserId[MAXPLAYERS + 1];
+float storedTime[MAXPLAYERS + 1];
+bool deleteRecord[MAXPLAYERS + 1];
+
+// =====[ PUBLIC ]=====
+
+void SendTime(int client, int course, float time, int teleportsUsed)
+{
+ char steamid[32], modeStr[32];
+ KZPlayer player = KZPlayer(client);
+ int mode = player.Mode;
+
+ if (GlobalsEnabled(mode))
+ {
+ DataPack dp = CreateDataPack();
+ dp.WriteCell(GetClientUserId(client));
+ dp.WriteCell(course);
+ dp.WriteCell(mode);
+ dp.WriteCell(GOKZ_GetTimeTypeEx(teleportsUsed));
+ dp.WriteFloat(time);
+
+ GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
+ GOKZ_GL_GetModeString(mode, modeStr, sizeof(modeStr));
+ GlobalAPI_CreateRecord(SendTimeCallback, dp, steamid, gI_MapID, modeStr, course, 128, teleportsUsed, time);
+ }
+}
+
+public int SendTimeCallback(JSON_Object response, GlobalAPIRequestData request, DataPack dp)
+{
+ dp.Reset();
+ int userID = dp.ReadCell();
+ int client = GetClientOfUserId(userID);
+ int course = dp.ReadCell();
+ int mode = dp.ReadCell();
+ int timeType = dp.ReadCell();
+ float time = dp.ReadFloat();
+ delete dp;
+
+ if (!IsValidClient(client))
+ {
+ return 0;
+ }
+
+ if (request.Failure)
+ {
+ LogError("Failed to send a time to the global API.");
+ return 0;
+ }
+
+ int top_place = response.GetInt("top_100");
+ int top_overall_place = response.GetInt("top_100_overall");
+
+ if (top_place > 0)
+ {
+ Call_OnNewTopTime(client, course, mode, timeType, top_place, top_overall_place, time);
+ }
+
+ // Don't like doing this here, but seems to be the most efficient place
+ UpdatePoints(client, true);
+
+ // Check if we can send the replay
+ lastRecordId[client] = response.GetInt("record_id");
+ if (IsReplayReadyToSend(client, course, timeType, time))
+ {
+ SendReplay(client);
+ }
+ else
+ {
+ storedUserId[client] = userID;
+ storedCourse[client] = course;
+ storedTimeType[client] = timeType;
+ storedTime[client] = time;
+ }
+ return 0;
+}
+
+public void OnReplaySaved_SendReplay(int client, int replayType, const char[] map, int course, int timeType, float time, const char[] filePath, bool tempReplay)
+{
+ strcopy(storedReplayPath[client], sizeof(storedReplayPath[]), filePath);
+ if (IsReplayReadyToSend(client, course, timeType, time))
+ {
+ SendReplay(client);
+ }
+ else
+ {
+ lastRecordId[client] = -1;
+ storedUserId[client] = GetClientUserId(client);
+ storedCourse[client] = course;
+ storedTimeType[client] = timeType;
+ storedTime[client] = time;
+ deleteRecord[client] = tempReplay;
+ }
+}
+
+bool IsReplayReadyToSend(int client, int course, int timeType, float time)
+{
+ // Not an error, just not ready yet
+ if (lastRecordId[client] == -1 || storedReplayPath[client][0] == '\0')
+ {
+ return false;
+ }
+
+ if (storedUserId[client] == GetClientUserId(client) && storedCourse[client] == course
+ && storedTimeType[client] == timeType && storedTime[client] == time)
+ {
+ return true;
+ }
+ else
+ {
+ LogError("Failed to upload replay to the global API. Record mismatch.");
+ return false;
+ }
+}
+
+public void SendReplay(int client)
+{
+ DataPack dp = new DataPack();
+ dp.WriteString(deleteRecord[client] ? storedReplayPath[client] : "");
+ GlobalAPI_CreateReplayForRecordId(SendReplayCallback, dp, lastRecordId[client], storedReplayPath[client]);
+ lastRecordId[client] = -1;
+ storedReplayPath[client] = "";
+}
+
+public int SendReplayCallback(JSON_Object response, GlobalAPIRequestData request, DataPack dp)
+{
+ // Delete the temporary replay file if needed.
+ dp.Reset();
+ char replayPath[PLATFORM_MAX_PATH];
+ dp.ReadString(replayPath, sizeof(replayPath));
+ if (replayPath[0] != '\0')
+ {
+ DeleteFile(replayPath);
+ }
+ delete dp;
+ return 0;
+}