From db5aca99c88e8e2b1422116d45ce82c27d7f2d61 Mon Sep 17 00:00:00 2001 From: "zer0.k" Date: Tue, 4 Feb 2025 01:56:45 +0700 Subject: [PATCH] Add global stuff to record queries --- metamod-source | 2 +- src/kz/global/api/api.h | 6 +- src/kz/global/api/events.h | 108 +++++++++++- src/kz/global/api/players.h | 6 +- src/kz/global/api/records.h | 10 ++ src/kz/global/kz_global.cpp | 52 ++++-- src/kz/global/kz_global.h | 33 +++- src/kz/timer/announce_queue.cpp | 6 +- src/kz/timer/kz_timer.cpp | 5 + src/kz/timer/queries/base_request.cpp | 6 +- src/kz/timer/queries/base_request.h | 3 +- src/kz/timer/queries/course_top.cpp | 133 +++++++++++--- src/kz/timer/queries/personal_best.cpp | 166 +++++++++++++++--- src/kz/timer/queries/top_record.cpp | 100 +++++++++-- src/utils/json.h | 11 ++ translations/cs2kz-common.phrases.txt | 16 +- .../cs2kz-timer-personalbest.phrases.txt | 36 ++++ ....txt => cs2kz-timer-toprecord.phrases.txt} | 14 +- 18 files changed, 613 insertions(+), 100 deletions(-) rename translations/{cs2kz-timer-getrecords.phrases.txt => cs2kz-timer-toprecord.phrases.txt} (97%) diff --git a/metamod-source b/metamod-source index d1866d08..ccce734e 160000 --- a/metamod-source +++ b/metamod-source @@ -1 +1 @@ -Subproject commit d1866d08e7e04898e6e5d56ff15dfd5de2e28087 +Subproject commit ccce734e121a6086cb45773d6d3d754e81a81dbb diff --git a/src/kz/global/api/api.h b/src/kz/global/api/api.h index 3e499ede..580839a7 100644 --- a/src/kz/global/api/api.h +++ b/src/kz/global/api/api.h @@ -8,14 +8,14 @@ namespace KZ::API Classic = 2, }; - static bool DecodeModeString(const std::string &modeString, Mode &mode) + static bool DecodeModeString(std::string_view modeString, Mode &mode) { - if (modeString == "vanilla") + if (KZ_STREQI(modeString.data(), "vanilla") || KZ_STREQI(modeString.data(), "vnl")) { mode = Mode::Vanilla; return true; } - else if (modeString == "classic") + else if (KZ_STREQI(modeString.data(), "classic") || KZ_STREQI(modeString.data(), "ckz")) { mode = Mode::Classic; return true; diff --git a/src/kz/global/api/events.h b/src/kz/global/api/events.h index 8ca8c7fd..94607e74 100644 --- a/src/kz/global/api/events.h +++ b/src/kz/global/api/events.h @@ -176,7 +176,7 @@ namespace KZ::API::events } }; - struct WantPlayerRecords + struct WantPlayerRecordsForCache { u16 mapId; u64 playerId; @@ -190,7 +190,7 @@ namespace KZ::API::events } }; - struct PlayerRecords + struct PlayerRecordsForCache { std::vector records {}; @@ -200,11 +200,11 @@ namespace KZ::API::events } }; - struct WantWorldRecords + struct WantWorldRecordsForCache { u16 mapId; - WantWorldRecords(u16 mapId) : mapId(mapId) {} + WantWorldRecordsForCache(u16 mapId) : mapId(mapId) {} bool ToJson(Json &json) const { @@ -212,7 +212,7 @@ namespace KZ::API::events } }; - struct WorldRecords + struct WorldRecordsForCache { std::vector records {}; @@ -221,4 +221,102 @@ namespace KZ::API::events return json.Get("records", this->records); } }; + + struct WantCourseTop + { + std::string mapName; + std::string courseNameOrNumber; + Mode mode; + u32 limit; + u32 offset; + + bool ToJson(Json &json) const + { + bool success = true; + success &= json.Set("map", mapName); + success &= json.Set("course", courseNameOrNumber); + success &= json.Set("mode", (u8)mode); + success &= json.Set("limit", limit); + success &= json.Set("offset", offset); + return success; + } + }; + + struct CourseTop + { + std::vector overall; + std::vector pro; + + bool FromJson(const Json &json) + { + return json.Get("overall", this->overall) && json.Get("pro", this->pro); + } + }; + + struct WantPersonalBest + { + u64 steamid64; + std::string playerName; + std::string mapName; + std::string courseNameOrNumber; + Mode mode; + std::vector styles; + + bool ToJson(Json &json) const + { + bool success = true; + if (steamid64) + { + success &= json.Set("player", steamid64); + } + else + { + success &= json.Set("player", playerName); + } + success &= json.Set("map", mapName); + success &= json.Set("course", courseNameOrNumber); + success &= json.Set("mode", (u8)mode); + success &= json.Set("styles", styles); + return success; + } + }; + + struct PersonalBest + { + std::optional player; + std::optional overall; + std::optional pro; + + bool FromJson(const Json &json) + { + return json.Get("player", this->player) && json.Get("overall", this->overall) && json.Get("pro", this->pro); + } + }; + + struct WantWorldRecords + { + std::string mapName; + std::string courseNameOrNumber; + Mode mode; + + bool ToJson(Json &json) const + { + bool success = true; + success &= json.Set("map", mapName); + success &= json.Set("course", courseNameOrNumber); + success &= json.Set("mode", (u8)mode); + return success; + } + }; + + struct WorldRecords + { + std::optional overall; + std::optional pro; + + bool FromJson(const Json &json) + { + return json.Get("overall", this->overall) && json.Get("pro", this->pro); + } + }; } // namespace KZ::API::events diff --git a/src/kz/global/api/players.h b/src/kz/global/api/players.h index f2903144..af21b62d 100644 --- a/src/kz/global/api/players.h +++ b/src/kz/global/api/players.h @@ -33,11 +33,15 @@ namespace KZ::API return false; } + if (!json.Get("is_banned", this->isCheater)) + { + return false; + } return true; } - private: std::string id {}; std::string name {}; + bool isCheater; }; } // namespace KZ::API diff --git a/src/kz/global/api/records.h b/src/kz/global/api/records.h index a3940558..09f65425 100644 --- a/src/kz/global/api/records.h +++ b/src/kz/global/api/records.h @@ -19,8 +19,10 @@ namespace KZ::API f64 time; u32 nubRank = 0; f64 nubPoints = -1; + u32 nubMaxRank = 0; u32 proRank = 0; f64 proPoints = -1; + u32 proMaxRank = 0; bool FromJson(const Json &json) { @@ -72,6 +74,10 @@ namespace KZ::API { return false; } + if (!json.Get("nub_max_rank", this->nubMaxRank)) + { + return false; + } } if (json.Get("pro_rank", this->proRank)) @@ -80,6 +86,10 @@ namespace KZ::API { return false; } + if (!json.Get("pro_max_rank", this->proMaxRank)) + { + return false; + } } return true; diff --git a/src/kz/global/kz_global.cpp b/src/kz/global/kz_global.cpp index e8e5097e..0cbfa19f 100644 --- a/src/kz/global/kz_global.cpp +++ b/src/kz/global/kz_global.cpp @@ -11,6 +11,7 @@ #include "common.h" #include "kz/kz.h" +#include "kz/language/kz_language.h" #include "kz/mode/kz_mode.h" #include "kz/timer/kz_timer.h" #include "kz/option/kz_option.h" @@ -139,9 +140,9 @@ void KZGlobalService::OnActivateServer() KZ::course::UpdateCourseGlobalID(course.name.c_str(), course.id); } - KZ::API::events::WantWorldRecords data(mapInfo.map->id); + KZ::API::events::WantWorldRecordsForCache data(mapInfo.map->id); - auto callback = [](const KZ::API::events::WorldRecords &records) + auto callback = [](const KZ::API::events::WorldRecordsForCache &records) { for (const KZ::API::Record &record : records.records) { @@ -186,11 +187,11 @@ void KZGlobalService::OnPlayerAuthorized() if (KZGlobalService::currentMap.has_value()) { - KZ::API::events::WantPlayerRecords data; + KZ::API::events::WantPlayerRecordsForCache data; data.mapId = KZGlobalService::currentMap->id; data.playerId = this->player->GetSteamId64(); - auto callback = [player = this->player](const KZ::API::events::PlayerRecords &pbs) + auto callback = [player = this->player](const KZ::API::events::PlayerRecordsForCache &pbs) { for (const KZ::API::Record &record : pbs.records) { @@ -239,17 +240,15 @@ void KZGlobalService::OnClientDisconnect() KZGlobalService::SendMessage("player-leave", data); } -bool KZGlobalService::SubmitRecord(u32 localId, const char *courseName, const char *modeName, f64 time, u32 teleports, const char *metadata) +bool KZGlobalService::SubmitRecord(u32 localId, const char *courseName, KZ::API::Mode mode, f64 time, u32 teleports, const char *metadata) { - if (!KZGlobalService::currentMap.has_value()) + if (!this->player->IsAuthenticated() && !this->player->hasPrime) { - META_CONPRINTF("[KZ::Global] Cannot submit record on non-global map.\n"); return false; } - - if (!(KZ_STREQI(modeName, "vnl") || KZ_STREQI(modeName, "ckz"))) + if (!KZGlobalService::currentMap.has_value()) { - META_CONPRINTF("[KZ::Global] Cannot submit record on non-global mode.\n"); + META_CONPRINTF("[KZ::Global] Cannot submit record on non-global map.\n"); return false; } @@ -272,7 +271,7 @@ bool KZGlobalService::SubmitRecord(u32 localId, const char *courseName, const ch KZ::API::events::NewRecord data; data.playerId = this->player->GetSteamId64(); - data.filterId = KZ_STREQI(modeName, "vnl") ? course->filters.vanilla.id : course->filters.classic.id; + data.filterId = (mode == KZ::API::Mode::Vanilla) ? course->filters.vanilla.id : course->filters.classic.id; data.styles = {}; // TODO data.teleports = teleports; data.time = time; @@ -302,6 +301,37 @@ bool KZGlobalService::SubmitRecord(u32 localId, const char *courseName, const ch return true; } +bool KZGlobalService::QueryPB(u64 steamid64, CUtlString targetPlayerName, CUtlString mapName, CUtlString courseNameOrNumber, KZ::API::Mode mode, + CUtlVector &styleNames, Callback cb) +{ + KZ::API::events::WantPersonalBest pbRequest = {steamid64, targetPlayerName.Get(), mapName.Get(), courseNameOrNumber, mode}; + FOR_EACH_VEC(styleNames, i) + { + pbRequest.styles.emplace_back(styleNames[i].Get()); + } + + KZGlobalService::SendMessage("query-pb", pbRequest, cb); + return true; +} + +bool KZGlobalService::QueryCourseTop(CUtlString mapName, CUtlString courseNameOrNumber, KZ::API::Mode mode, u32 limit, u32 offset, + Callback cb) +{ + KZ::API::events::WantCourseTop ctopRequest = {mapName.Get(), courseNameOrNumber.Get(), mode, limit, offset}; + + KZGlobalService::SendMessage("query-course-top", ctopRequest, cb); + return true; +} + +bool KZGlobalService::QueryWorldRecords(CUtlString mapName, CUtlString courseNameOrNumber, KZ::API::Mode mode, + Callback cb) +{ + KZ::API::events::WantWorldRecords ctopRequest = {mapName.Get(), courseNameOrNumber.Get(), mode}; + + KZGlobalService::SendMessage("query-wr", ctopRequest, cb); + return true; +} + void KZGlobalService::OnWebSocketMessage(const ix::WebSocketMessagePtr &message) { switch (message->type) diff --git a/src/kz/global/kz_global.h b/src/kz/global/kz_global.h index 882c5b08..968bc0db 100644 --- a/src/kz/global/kz_global.h +++ b/src/kz/global/kz_global.h @@ -5,6 +5,8 @@ #include "utils/json.h" #include "kz/kz.h" +#include "kz/global/api/api.h" +#include "kz/global/api/events.h" #include "kz/global/api/maps.h" class KZGlobalService : public KZBaseService @@ -12,6 +14,9 @@ class KZGlobalService : public KZBaseService using KZBaseService::KZBaseService; public: + template + using Callback = std::function; + static void Init(); static void Cleanup(); static void RegisterCommands(); @@ -47,12 +52,32 @@ class KZGlobalService : public KZBaseService * * Returns whether the record was submitted. This could be false if the filter is not ranked. */ - bool SubmitRecord(u32 localId, const char *courseName, const char *modeName, f64 time, u32 teleports, const char *metadata); + bool SubmitRecord(u32 localId, const char *courseName, KZ::API::Mode mode, f64 time, u32 teleports, const char *metadata); -private: - template - using Callback = std::function; + /** + * Query the personal best of a player on a certain map, course, mode, style. + * + * Always return true. + */ + static bool QueryPB(u64 steamid64, CUtlString targetPlayerName, CUtlString mapName, CUtlString courseNameOrNumber, KZ::API::Mode mode, + CUtlVector &styleNames, Callback cb); + /** + * Query the course top on a certain map, mode with a certain limit and offset. + * + * Always return true. + */ + static bool QueryCourseTop(CUtlString mapName, CUtlString courseNameOrNumber, KZ::API::Mode mode, u32 limit, u32 offset, + Callback cb); + + /** + * Query the world record of a course on a certain mode. + * + * Always return true. + */ + static bool QueryWorldRecords(CUtlString mapName, CUtlString courseNameOrNumber, KZ::API::Mode mode, Callback cb); + +private: /** * URL to make HTTP requests to. * diff --git a/src/kz/timer/announce_queue.cpp b/src/kz/timer/announce_queue.cpp index 09cbed71..b472ffd8 100644 --- a/src/kz/timer/announce_queue.cpp +++ b/src/kz/timer/announce_queue.cpp @@ -28,7 +28,11 @@ struct AnnounceData } if (KZGlobalService::IsConnected() && player->IsAuthenticated()) { - player->globalService->SubmitRecord(id, courseName.Get(), modeName, time, teleportsUsed, metadata); + KZ::API::Mode mode; + if (KZ::API::DecodeModeString(modeName.Get(), mode)) + { + player->globalService->SubmitRecord(id, courseName.Get(), mode, time, teleportsUsed, metadata); + } } } diff --git a/src/kz/timer/kz_timer.cpp b/src/kz/timer/kz_timer.cpp index 9c2c3cf1..e337274a 100644 --- a/src/kz/timer/kz_timer.cpp +++ b/src/kz/timer/kz_timer.cpp @@ -1,5 +1,6 @@ #include "kz_timer.h" #include "kz/db/kz_db.h" +#include "kz/global/kz_global.h" #include "kz/language/kz_language.h" #include "kz/mode/kz_mode.h" #include "kz/style/kz_style.h" @@ -229,6 +230,10 @@ bool KZTimerService::TimerStart(const KZCourseDescriptor *courseDesc, bool playS { this->player->languageService->PrintChat(true, false, "No Steam Authentication Warning"); } + if (KZGlobalService::IsConnected() && !this->player->hasPrime) + { + this->player->languageService->PrintChat(true, false, "No Prime Warning"); + } FOR_EACH_VEC(eventListeners, i) { diff --git a/src/kz/timer/queries/base_request.cpp b/src/kz/timer/queries/base_request.cpp index 9a02e52c..70f1b7aa 100644 --- a/src/kz/timer/queries/base_request.cpp +++ b/src/kz/timer/queries/base_request.cpp @@ -1,6 +1,7 @@ #include "base_request.h" #include "kz/db/kz_db.h" #include "kz/course/kz_course.h" +#include "kz/global/kz_global.h" #include "kz/timer/kz_timer.h" #include "kz/mode/kz_mode.h" #include "kz/style/kz_style.h" @@ -38,8 +39,7 @@ void BaseRequest::Init(u64 features, const CCommand *args, bool queryLocal, bool } this->localStatus = (queryLocal && KZDatabaseService::IsReady()) ? ResponseStatus::ENABLED : ResponseStatus::DISABLED; - // TODO: check global status here - this->globalStatus = ResponseStatus::DISABLED; + this->globalStatus = (queryGlobal && KZGlobalService::IsConnected() ? ResponseStatus::ENABLED : ResponseStatus::DISABLED); KeyValues3 *kv = NULL; @@ -292,6 +292,8 @@ void BaseRequest::SetupPlayer(CUtlString playerName) } else { + // TODO: Re-query the local database if we query from the global API and got a steamid through its response + req->requestingGlobalPlayer = true; req->localStatus = ResponseStatus::DISABLED; } req->requestingLocalPlayer = false; diff --git a/src/kz/timer/queries/base_request.h b/src/kz/timer/queries/base_request.h index 5d602479..75cb7f52 100644 --- a/src/kz/timer/queries/base_request.h +++ b/src/kz/timer/queries/base_request.h @@ -123,7 +123,8 @@ struct BaseRequest CUtlString targetPlayerName {}; // If this is true, the queries should be delayed until it's true. bool requestingLocalPlayer = false; - + // If this is true, the *local* query should be delayed. + bool requestingGlobalPlayer = false; u64 localModeID; CUtlString modeName; diff --git a/src/kz/timer/queries/course_top.cpp b/src/kz/timer/queries/course_top.cpp index bd755642..641c9840 100644 --- a/src/kz/timer/queries/course_top.cpp +++ b/src/kz/timer/queries/course_top.cpp @@ -1,35 +1,55 @@ #include "base_request.h" #include "kz/timer/kz_timer.h" #include "kz/db/kz_db.h" +#include "kz/global/kz_global.h" +#include "kz/global/api/api.h" +#include "kz/global/api/events.h" #include "utils/simplecmds.h" #include "vendor/sql_mm/src/public/sql_mm.h" - +// clang-format off #define COURSE_TOP_TABLE_KEY "Course Top - Table Name (Server Overall)" -static_global const char *columnKeysLocal[] = {"#", - "Course Top Header - Player Alias", - "Course Top Header - Time", - "Course Top Header - Teleports", - "Course Top Header - SteamID64", - "Course Top Header - Run ID"}; +static_global const char *columnKeysLocal[] = { + "#", + "Course Top Header - Player Alias", + "Course Top Header - Time", + "Course Top Header - Teleports", + "Course Top Header - SteamID64", + "Course Top Header - Run ID" +}; #define COURSE_TOP_PRO_TABLE_KEY "Course Top - Table Name (Server Pro)" -static_global const char *columnKeysLocalPro[] = {"#", "Course Top Header - Player Alias", "Course Top Header - Time", - "Course Top Header - SteamID64", "Course Top Header - Run ID"}; +static_global const char *columnKeysLocalPro[] = { + "#", + "Course Top Header - Player Alias", + "Course Top Header - Time", + "Course Top Header - SteamID64", + "Course Top Header - Run ID" +}; #define COURSE_TOP_TABLE_KEY_GLOBAL "Course Top - Table Name (Global Overall)" -static_global const char *columnKeysGlobal[] = {"#", - "Course Top Header - Player Alias", - "Course Top Header - Time", - "Course Top Header - Teleports", - "Course Top Header - SteamID64", - "Course Top Header - Points"}; +static_global const char *columnKeysGlobal[] = { + "#", + "Course Top Header - Player Alias", + "Course Top Header - Time", + "Course Top Header - Teleports", + "Course Top Header - SteamID64", + "Course Top Header - Points", + "Course Top Header - Run ID" +}; #define COURSE_TOP_PRO_TABLE_KEY_GLOBAL "Course Top - Table Name (Global Pro)" -static_global const char *columnKeysGlobalPro[] = {"#", "Course Top Header - Player Alias", "Course Top Header - Time", - "Course Top Header - SteamID64", "Course Top Header - Points"}; +static_global const char *columnKeysGlobalPro[] = { + "#", + "Course Top Header - Player Alias", + "Course Top Header - Time", + "Course Top Header - SteamID64", + "Course Top Header - Points", + "Course Top Header - Run ID", +}; +//clang-format on struct CourseTopRequest : public BaseRequest { using BaseRequest::BaseRequest; @@ -71,7 +91,13 @@ struct CourseTopRequest : public BaseRequest fmt.Format("%llu", steamid64); return fmt; } - }; + + CUtlString GetPoints() + { + CUtlString fmt; + fmt.Format("%llu", points); + return fmt; + }}; struct CourseTopData { @@ -162,7 +188,37 @@ struct CourseTopRequest : public BaseRequest { return; } - // TODO + + if (this->globalStatus == ResponseStatus::ENABLED) + { + KZ::API::Mode mode; + if (!KZ::API::DecodeModeString(this->modeName.Get(), mode)) + { + this->globalStatus = ResponseStatus::DISABLED; + return; + } + auto callback = [uid = this->uid](const KZ::API::events::CourseTop &ctops) + { + CourseTopRequest *req = (CourseTopRequest *)CourseTopRequest::Find(uid); + if (!req) + { + return; + } + req->globalStatus = ResponseStatus::RECEIVED; + for (const auto &record : ctops.overall) + { + req->wrData.overallData.AddToTail({record.id, record.player.name.c_str(), record.teleports, record.time, + (u64)atoll(record.player.id.c_str()), (u64)floor(record.nubPoints)}); + } + for (const auto &record : ctops.pro) + { + req->wrData.proData.AddToTail( + {record.id, record.player.name.c_str(), 0, record.time, (u64)atoll(record.player.id.c_str()), (u64)floor(record.proPoints)}); + } + }; + this->globalStatus = ResponseStatus::PENDING; + KZGlobalService::QueryCourseTop(this->mapName, this->courseName, mode, this->limit, this->offset, callback); + } } virtual void Reply() @@ -192,7 +248,40 @@ struct CourseTopRequest : public BaseRequest void ReplyGlobal() { - // TODO + KZPlayer *player = g_pKZPlayerManager->ToPlayer(userID); + CUtlString headers[Q_ARRAYSIZE(columnKeysGlobal)]; + for (u32 i = 0; i < Q_ARRAYSIZE(columnKeysGlobal); i++) + { + headers[i] = player->languageService->PrepareMessage(columnKeysGlobal[i]).c_str(); + } + CUtlString headersPro[Q_ARRAYSIZE(columnKeysGlobalPro)]; + for (u32 i = 0; i < Q_ARRAYSIZE(columnKeysGlobalPro); i++) + { + headersPro[i] = player->languageService->PrepareMessage(columnKeysGlobalPro[i]).c_str(); + } + utils::DualTable dualTable( + player->languageService->PrepareMessage(COURSE_TOP_TABLE_KEY, mapName.Get(), courseName.Get(), modeName.Get()).c_str(), headers, + player->languageService->PrepareMessage(COURSE_TOP_PRO_TABLE_KEY, mapName.Get(), courseName.Get(), modeName.Get()).c_str(), headersPro); + CUtlString rank; + FOR_EACH_VEC(wrData.overallData, i) + { + rank.Format("%i", this->offset + i + 1); + RunStats stats = wrData.overallData[i]; + dualTable.left.SetRow(i, rank, stats.name, stats.GetTime(), stats.GetTeleportCount(), stats.GetSteamID64(), stats.GetPoints(), stats.GetRunID()); + } + FOR_EACH_VEC(wrData.proData, i) + { + rank.Format("%i", this->offset + i + 1); + RunStats stats = wrData.proData[i]; + dualTable.right.SetRow(i, rank, stats.name, stats.GetTime(), stats.GetSteamID64(), stats.GetPoints(), stats.GetRunID()); + } + player->PrintConsole(false, false, dualTable.GetTitle()); + player->PrintConsole(false, false, dualTable.GetHeader()); + player->PrintConsole(false, false, dualTable.GetSeparator()); + for (u32 i = 0; i < dualTable.GetNumEntries(); i++) + { + player->PrintConsole(false, false, dualTable.GetLine(i)); + } } void ReplyLocal() @@ -214,13 +303,13 @@ struct CourseTopRequest : public BaseRequest CUtlString rank; FOR_EACH_VEC(srData.overallData, i) { - rank.Format("%i", i + 1); + rank.Format("%i", this->offset + i + 1); RunStats stats = srData.overallData[i]; dualTable.left.SetRow(i, rank, stats.name, stats.GetTime(), stats.GetTeleportCount(), stats.GetSteamID64(), stats.GetRunID()); } FOR_EACH_VEC(srData.proData, i) { - rank.Format("%i", i + 1); + rank.Format("%i", this->offset + i + 1); RunStats stats = srData.proData[i]; dualTable.right.SetRow(i, rank, stats.name, stats.GetTime(), stats.GetSteamID64(), stats.GetRunID()); } diff --git a/src/kz/timer/queries/personal_best.cpp b/src/kz/timer/queries/personal_best.cpp index 6bb18a41..43bbd78b 100644 --- a/src/kz/timer/queries/personal_best.cpp +++ b/src/kz/timer/queries/personal_best.cpp @@ -1,5 +1,8 @@ #include "base_request.h" #include "kz/db/kz_db.h" +#include "kz/global/kz_global.h" +#include "kz/global/api/api.h" +#include "kz/global/api/events.h" #include "utils/simplecmds.h" @@ -18,13 +21,16 @@ struct PBRequest : public BaseRequest u32 teleportsUsed {}; u32 rank {}; u32 maxRank {}; + u32 points {}; // Global only. bool hasPBPro {}; f32 runTimePro {}; u32 rankPro {}; u32 maxRankPro {}; + u32 pointsPro {}; // Global only. } pbData, gpbData; + bool globallyBanned = false; bool queryLocalRanking = true; virtual void Init(u64 features, const CCommand *args, bool queryLocal, bool queryGlobal) override @@ -64,7 +70,7 @@ struct PBRequest : public BaseRequest virtual void QueryLocal() { - if (this->requestingLocalPlayer || this->requestingFirstCourse) + if (this->requestingLocalPlayer || this->requestingFirstCourse || this->requestingGlobalPlayer) { return; } @@ -89,7 +95,52 @@ struct PBRequest : public BaseRequest { return; } - // TODO + + KZ::API::Mode mode; + if (!KZ::API::DecodeModeString(this->modeName.Get(), mode)) + { + // If the local query is waiting for a response from the global service... + if (this->requestingGlobalPlayer && this->localStatus == ResponseStatus::ENABLED) + { + this->localStatus = ResponseStatus::DISABLED; + this->requestingGlobalPlayer = false; + } + this->globalStatus = ResponseStatus::DISABLED; + return; + } + auto callback = [uid = this->uid](const KZ::API::events::PersonalBest &pb) + { + PBRequest *req = (PBRequest *)PBRequest::Find(uid); + if (!req) + { + return; + } + if (req->requestingGlobalPlayer && req->localStatus == ResponseStatus::ENABLED && pb.player.has_value()) + { + req->requestingGlobalPlayer = false; + req->targetSteamID64 = atoll(pb.player->id.c_str()); + req->queryLocalRanking = !pb.player->isCheater; + } + req->globalStatus = ResponseStatus::RECEIVED; + req->gpbData.hasPB = pb.overall.has_value(); + if (req->gpbData.hasPB) + { + req->gpbData.runTime = pb.overall->time; + req->gpbData.rank = pb.overall->nubRank; + req->gpbData.maxRank = pb.overall->nubMaxRank; + req->gpbData.teleportsUsed = pb.overall->teleports; + req->gpbData.points = pb.overall->nubPoints; + } + req->gpbData.hasPBPro = pb.pro.has_value(); + if (req->gpbData.hasPBPro) + { + req->gpbData.runTimePro = pb.pro->time; + req->gpbData.rankPro = pb.pro->proRank; + req->gpbData.maxRankPro = pb.pro->proMaxRank; + req->gpbData.pointsPro = pb.pro->proPoints; + } + }; + KZGlobalService::QueryPB(this->targetSteamID64, this->targetPlayerName, this->mapName, this->courseName, mode, this->styleList, callback); } virtual void Reply() @@ -118,32 +169,98 @@ struct PBRequest : public BaseRequest player->languageService->PrintChat(true, false, "PB Header", targetPlayerName.Get(), mapName.Get(), courseName.Get(), combinedModeStyleText.Get()); - if (this->globalStatus == ResponseStatus::RECEIVED) + if (!this->pbData.hasPB && !this->gpbData.hasPB) { - this->ReplyGlobal(); + player->languageService->PrintChat(true, false, "PB Time - No Times"); } - if (this->localStatus == ResponseStatus::RECEIVED) + else { - this->ReplyLocal(); + if (this->globalStatus == ResponseStatus::RECEIVED) + { + this->ReplyGlobal(); + } + if (this->localStatus == ResponseStatus::RECEIVED) + { + this->ReplyLocal(); + } + } + } + + void ReplyGlobal() + { + KZPlayer *player = g_pKZPlayerManager->ToPlayer(userID); + std::string tpText; + if (this->gpbData.teleportsUsed > 0) + { + tpText = this->gpbData.teleportsUsed == 1 ? player->languageService->PrepareMessage("1 Teleport Text") + : player->languageService->PrepareMessage("2+ Teleports Text", this->gpbData.teleportsUsed); + } + + char overallTime[32]; + KZTimerService::FormatTime(this->gpbData.runTime, overallTime, sizeof(overallTime)); + char proTime[32]; + KZTimerService::FormatTime(this->gpbData.runTimePro, proTime, sizeof(proTime)); + + if (!globallyBanned) + { + if (!this->gpbData.hasPBPro) + { + // KZ | Global: 12.34 (5 TPs) [Overall / 10000 pts] + player->languageService->PrintChat(true, false, "PB Time - Overall (Global)", overallTime, tpText, this->gpbData.rank, + this->gpbData.maxRank, this->gpbData.points); + } + // Their MAP PB has 0 teleports, and is therefore also their PRO PB + else if (this->gpbData.teleportsUsed == 0) + { + // KZ | Global: 12.34 [#1/24 Overall / 10000 pts] [#1/2 PRO / 10000 pts] + player->languageService->PrintChat(true, false, "PB Time - Combined (Global)", overallTime, this->gpbData.rank, this->gpbData.maxRank, + this->gpbData.rankPro, this->gpbData.maxRankPro, this->gpbData.points, this->gpbData.pointsPro); + } + else + { + // KZ | Global: 12.34 (5 TPs) [#1/24 Overall / 10000 pts] | 23.45 [#1/2 PRO / 10000 pts] + player->languageService->PrintChat(true, false, "PB Time - Split (Global)", overallTime, tpText, this->gpbData.rank, + this->gpbData.maxRank, proTime, this->gpbData.rankPro, this->gpbData.maxRankPro, + this->gpbData.points, this->gpbData.pointsPro); + } + } + else + { + if (!this->gpbData.hasPBPro) + { + // KZ | Global: 12.34 (5 TPs) [Overall] + player->languageService->PrintChat(true, false, "PB Time - Overall Rankless (Global)", overallTime, tpText); + } + // Their MAP PB has 0 teleports, and is therefore also their PRO PB + else if (this->gpbData.teleportsUsed == 0) + { + // KZ | Global: 12.34 [Overall/PRO] + player->languageService->PrintChat(true, false, "PB Time - Combined Rankless (Global)", overallTime); + } + else + { + // KZ | Global: 12.34 (5 TPs) [Overall] | 23.45 [PRO] + player->languageService->PrintChat(true, false, "PB Time - Split Rankless (Global)", overallTime, tpText, proTime); + } } } void ReplyLocal() { KZPlayer *player = g_pKZPlayerManager->ToPlayer(userID); - std::string localTPText; + std::string tpText; if (this->pbData.teleportsUsed > 0) { - localTPText = this->pbData.teleportsUsed == 1 ? player->languageService->PrepareMessage("1 Teleport Text") - : player->languageService->PrepareMessage("2+ Teleports Text", this->pbData.teleportsUsed); + tpText = this->pbData.teleportsUsed == 1 ? player->languageService->PrepareMessage("1 Teleport Text") + : player->languageService->PrepareMessage("2+ Teleports Text", this->pbData.teleportsUsed); } - char localOverallTime[32]; - KZTimerService::FormatTime(this->pbData.runTime, localOverallTime, sizeof(localOverallTime)); - char localProTime[32]; - KZTimerService::FormatTime(this->pbData.runTimePro, localProTime, sizeof(localProTime)); + char overallTime[32]; + KZTimerService::FormatTime(this->pbData.runTime, overallTime, sizeof(overallTime)); + char proTime[32]; + KZTimerService::FormatTime(this->pbData.runTimePro, proTime, sizeof(proTime)); - if (queryLocalRanking) + if (!globallyBanned) { if (!this->pbData.hasPB) { @@ -152,21 +269,21 @@ struct PBRequest : public BaseRequest else if (!this->pbData.hasPBPro) { // KZ | Server: 12.34 (5 TPs) [Overall] - player->languageService->PrintChat(true, false, "PB Time - Overall (Server)", localOverallTime, localTPText, this->pbData.rank, + player->languageService->PrintChat(true, false, "PB Time - Overall (Server)", overallTime, tpText, this->pbData.rank, this->pbData.maxRank); } // Their MAP PB has 0 teleports, and is therefore also their PRO PB else if (this->pbData.teleportsUsed == 0) { // KZ | Server: 12.34 [#1/24 Overall] [#1/2 PRO] - player->languageService->PrintChat(true, false, "PB Time - Combined (Server)", localOverallTime, this->pbData.rank, - this->pbData.maxRank, this->pbData.rankPro, this->pbData.maxRankPro); + player->languageService->PrintChat(true, false, "PB Time - Combined (Server)", overallTime, this->pbData.rank, this->pbData.maxRank, + this->pbData.rankPro, this->pbData.maxRankPro); } else { // KZ | Server: 12.34 (5 TPs) [#1/24 Overall] | 23.45 [#1/2 PRO] - player->languageService->PrintChat(true, false, "PB Time - Split (Server)", localOverallTime, localTPText, this->pbData.rank, - this->pbData.maxRank, localProTime, this->pbData.rankPro, this->pbData.maxRankPro); + player->languageService->PrintChat(true, false, "PB Time - Split (Server)", overallTime, tpText, this->pbData.rank, + this->pbData.maxRank, proTime, this->pbData.rankPro, this->pbData.maxRankPro); } } else @@ -178,27 +295,22 @@ struct PBRequest : public BaseRequest else if (!this->pbData.hasPBPro) { // KZ | Server: 12.34 (5 TPs) [Overall] - player->languageService->PrintChat(true, false, "PB Time - Overall Rankless (Server)", localOverallTime, localTPText); + player->languageService->PrintChat(true, false, "PB Time - Overall Rankless (Server)", overallTime, tpText); } // Their MAP PB has 0 teleports, and is therefore also their PRO PB else if (this->pbData.teleportsUsed == 0) { // KZ | Server: 12.34 [Overall/PRO] - player->languageService->PrintChat(true, false, "PB Time - Combined Rankless (Server)", localOverallTime); + player->languageService->PrintChat(true, false, "PB Time - Combined Rankless (Server)", overallTime); } else { // KZ | Server: 12.34 (5 TPs) [Overall] | 23.45 [PRO] - player->languageService->PrintChat(true, false, "PB Time - Split Rankless (Server)", localOverallTime, localTPText, localProTime); + player->languageService->PrintChat(true, false, "PB Time - Split Rankless (Server)", overallTime, tpText, proTime); } } } - void ReplyGlobal() - { - // TODO - } - void ExecuteStandardLocalQuery() { u64 uid = this->uid; diff --git a/src/kz/timer/queries/top_record.cpp b/src/kz/timer/queries/top_record.cpp index e9fe60e7..767dca83 100644 --- a/src/kz/timer/queries/top_record.cpp +++ b/src/kz/timer/queries/top_record.cpp @@ -1,6 +1,9 @@ #include "base_request.h" #include "kz/timer/kz_timer.h" #include "kz/db/kz_db.h" +#include "kz/global/kz_global.h" +#include "kz/global/api/api.h" +#include "kz/global/api/events.h" #include "utils/simplecmds.h" @@ -36,7 +39,43 @@ struct TopRecordRequest : public BaseRequest virtual void QueryGlobal() { - // TODO + if (this->requestingFirstCourse) + { + return; + } + if (this->globalStatus == ResponseStatus::ENABLED) + { + KZ::API::Mode mode; + if (!KZ::API::DecodeModeString(this->modeName.Get(), mode)) + { + this->globalStatus = ResponseStatus::DISABLED; + return; + } + auto callback = [uid = this->uid](const KZ::API::events::WorldRecords &wrs) + { + TopRecordRequest *req = (TopRecordRequest *)TopRecordRequest::Find(uid); + if (!req) + { + return; + } + req->globalStatus = ResponseStatus::RECEIVED; + req->wrData.hasRecord = wrs.overall.has_value(); + if (req->wrData.hasRecord) + { + req->wrData.holder = wrs.overall->player.name.c_str(); + req->wrData.runTime = wrs.overall->time; + req->wrData.teleportsUsed = wrs.overall->teleports; + } + req->wrData.hasRecordPro = wrs.pro.has_value(); + if (req->wrData.hasRecordPro) + { + req->wrData.holderPro = wrs.pro->player.name.c_str(); + req->wrData.runTimePro = wrs.pro->time; + } + }; + this->globalStatus = ResponseStatus::PENDING; + KZGlobalService::QueryWorldRecords(this->mapName, this->courseName, mode, callback); + } } virtual void QueryLocal() @@ -125,7 +164,44 @@ struct TopRecordRequest : public BaseRequest void ReplyGlobal() { - // TODO + KZPlayer *player = g_pKZPlayerManager->ToPlayer(userID); + // Local stuff + std::string tpText; + if (wrData.teleportsUsed > 0) + { + tpText = wrData.teleportsUsed == 1 ? player->languageService->PrepareMessage("1 Teleport Text") + : player->languageService->PrepareMessage("2+ Teleports Text", wrData.teleportsUsed); + } + + char standardTime[32]; + KZTimerService::FormatTime(wrData.runTime, standardTime, sizeof(standardTime)); + char proTime[32]; + KZTimerService::FormatTime(wrData.runTimePro, proTime, sizeof(proTime)); + + // Global Records on kz_map (Main) [VNL] + player->languageService->PrintChat(true, false, "WR Header", mapName.Get(), courseName.Get(), modeName.Get()); + if (!wrData.hasRecord) + { + player->languageService->PrintChat(true, false, "No Times"); + } + else if (!wrData.hasRecordPro) + { + // KZ | Overall Record: 01:23.45 (5 TP) by Bill + player->languageService->PrintChat(true, false, "Top Record - Overall", standardTime, wrData.holder.Get(), tpText); + } + // Their MAP PB has 0 teleports, and is therefore also their PRO PB + else if (wrData.teleportsUsed == 0) + { + // KZ | Overall/PRO Record: 01:23.45 by Bill + player->languageService->PrintChat(true, false, "Top Record - Combined", standardTime, wrData.holder.Get()); + } + else + { + // KZ | Overall Record: 01:23.45 (5 TP) by Bill + player->languageService->PrintChat(true, false, "Top Record - Overall", standardTime, wrData.holder.Get(), tpText); + // KZ | PRO Record: 23.45 by Player + player->languageService->PrintChat(true, false, "Top Record - PRO", proTime, wrData.holderPro.Get()); + } } void ReplyLocal() @@ -139,34 +215,34 @@ struct TopRecordRequest : public BaseRequest : player->languageService->PrepareMessage("2+ Teleports Text", srData.teleportsUsed); } - char localStandardTime[32]; - KZTimerService::FormatTime(srData.runTime, localStandardTime, sizeof(localStandardTime)); - char localProTime[32]; - KZTimerService::FormatTime(srData.runTimePro, localProTime, sizeof(localProTime)); + char standardTime[32]; + KZTimerService::FormatTime(srData.runTime, standardTime, sizeof(standardTime)); + char proTime[32]; + KZTimerService::FormatTime(srData.runTimePro, proTime, sizeof(proTime)); - // Records on kz_map (Main) [VNL] + // Server Records on kz_map (Main) [VNL] player->languageService->PrintChat(true, false, "SR Header", mapName.Get(), courseName.Get(), modeName.Get()); if (!srData.hasRecord) { - player->languageService->PrintChat(true, false, "SR - No Times"); + player->languageService->PrintChat(true, false, "No Times"); } else if (!srData.hasRecordPro) { // KZ | Overall Record: 01:23.45 (5 TP) by Bill - player->languageService->PrintChat(true, false, "SR - Overall", localStandardTime, srData.holder.Get(), localTPText); + player->languageService->PrintChat(true, false, "Top Record - Overall", standardTime, srData.holder.Get(), localTPText); } // Their MAP PB has 0 teleports, and is therefore also their PRO PB else if (srData.teleportsUsed == 0) { // KZ | Overall/PRO Record: 01:23.45 by Bill - player->languageService->PrintChat(true, false, "SR - Combined", localStandardTime, srData.holder.Get()); + player->languageService->PrintChat(true, false, "Top Record - Combined", standardTime, srData.holder.Get()); } else { // KZ | Overall Record: 01:23.45 (5 TP) by Bill - player->languageService->PrintChat(true, false, "SR - Overall", localStandardTime, srData.holder.Get(), localTPText); + player->languageService->PrintChat(true, false, "Top Record - Overall", standardTime, srData.holder.Get(), localTPText); // KZ | PRO Record: 23.45 by Player - player->languageService->PrintChat(true, false, "SR - PRO", localProTime, srData.holderPro.Get()); + player->languageService->PrintChat(true, false, "Top Record - PRO", proTime, srData.holderPro.Get()); } } }; diff --git a/src/utils/json.h b/src/utils/json.h index ea7df92f..e6b985f7 100644 --- a/src/utils/json.h +++ b/src/utils/json.h @@ -67,6 +67,17 @@ class Json return true; } + bool Set(const std::string &key, const u8 &value) + { + if (!this->inner.is_object()) + { + return false; + } + + this->inner[key] = value; + return true; + } + bool Set(const std::string &key, const u16 &value) { if (!this->inner.is_object()) diff --git a/translations/cs2kz-common.phrases.txt b/translations/cs2kz-common.phrases.txt index a83d366c..26f01191 100644 --- a/translations/cs2kz-common.phrases.txt +++ b/translations/cs2kz-common.phrases.txt @@ -2,12 +2,16 @@ { "No Steam Authentication Warning" { - "en" "{darkred}Warning{grey}: You are not currently authenticated with Steam. Your run might not be saved. Type {default}!pc{grey} to check your current authentication status." - "chi" "{darkred}注意{grey}: 你当前未通过Steam身份验证. 你的跳跃可能不会被记录. 输入 {default}!pc{grey} 以检查你的身份认证状态." - "sv" "{darkred}Varning{grey}: Du är inte autentiserad med Steam. Din tid sparas kanske inte. Skriv {default}!pc{grey} för att kolla din autentiseringsstatus." - "ua" "{darkred}Увага{grey}: Ви не автентифіковані у Steam. Можливо, ваше проходження не буде зараховано. Напишіть {default}!pc{grey} в чаті, щоб перевірити поточний стан автентифікації." - "tr" "{darkred}Dikkat{grey}: Steam kimliğin doğrulanamadı.{default}!pc{grey} yazarak kimlik doğrulama statünü öğrenebilirsin." - "ko" "{darkred}경고{grey}: 현재 Steam에 인증되지 않았습니다. 주행 기록이 저장되지 않을 수 있습니다. {default}!pc{grey}를 입력하여 현재 인증 상태를 확인하세요." + "en" "{darkred}Warning{grey}: You are not currently authenticated with Steam. Your run might not be saved. Type {default}!pc{grey} to check your current authentication status." + "chi" "{darkred}注意{grey}: 你当前未通过Steam身份验证. 你的跳跃可能不会被记录. 输入 {default}!pc{grey} 以检查你的身份认证状态." + "sv" "{darkred}Varning{grey}: Du är inte autentiserad med Steam. Din tid sparas kanske inte. Skriv {default}!pc{grey} för att kolla din autentiseringsstatus." + "ua" "{darkred}Увага{grey}: Ви не автентифіковані у Steam. Можливо, ваше проходження не буде зараховано. Напишіть {default}!pc{grey} в чаті, щоб перевірити поточний стан автентифікації." + "tr" "{darkred}Dikkat{grey}: Steam kimliğin doğrulanamadı.{default}!pc{grey} yazarak kimlik doğrulama statünü öğrenebilirsin." + "ko" "{darkred}경고{grey}: 현재 Steam에 인증되지 않았습니다. 주행 기록이 저장되지 않을 수 있습니다. {default}!pc{grey}를 입력하여 현재 인증 상태를 확인하세요." + } + "No Prime Warning" + { + "en" "{darkred}Warning{grey}: You do not have Prime Status upgrade. Your run will not be saved globally." } "Player Authenticated (Steam)" { diff --git a/translations/cs2kz-timer-personalbest.phrases.txt b/translations/cs2kz-timer-personalbest.phrases.txt index 9bb6b0ba..073b73e0 100644 --- a/translations/cs2kz-timer-personalbest.phrases.txt +++ b/translations/cs2kz-timer-personalbest.phrases.txt @@ -126,6 +126,42 @@ "pl" "{grey}Serwer: {default}{time}{grey} ({yellow}{tp_count_text}{grey}) [{yellow}Ogółem{grey}] | {default}{time_pro}{grey} [{blue}PRO{grey}]" "ko" "{grey}Server: {default}{time}{grey} ({yellow}{tp_count_text}{grey}) [{yellow}Overall{grey}] | {default}{time_pro}{grey} [{blue}PRO{grey}]" } + "PB Time - Overall (Global)" + { + // Global: 12.34 (5 TPs) [#1/24 Overall / 10000 pts] + "#format" "time:s,tp_count_text:s,rank:d,max_rank:d,points:d" + "en" "{grey}Global: {default}{time}{grey} ({yellow}{tp_count_text}{grey}) [#{default}{rank}{grey}/{default}{max_rank}{grey} {yellow}Overall{grey} / {purple}{points}{grey} pts]" + } + "PB Time - Combined (Global)" + { + // Global: 12.34 [#1/24 Overall / 10000 pts] [#1/2 PRO / 10000 pts] + "#format" "time:s,rank:d,max_rank:d,rank_pro:d,max_rank_pro:d" + "en" "{grey}Global: {default}{time}{grey} [#{default}{rank}{grey}/{default}{max_rank}{grey} {yellow}Overall{grey} / {purple}{points}{grey} pts] [#{default}{rank_pro}{grey}/{default}{max_rank_pro}{grey} {blue}PRO{grey} / {purple}{points_pro}{grey} pts]" + } + "PB Time - Split (Global)" + { + // Global: 12.34 (5 TPs) [#1/24 Overall / 10000 pts] | 23.45 [#1/2 PRO / 10000 pts] + "#format" "time:s,tp_count_text:s,rank:d,max_rank:d,time_pro:s,rank_pro:d,max_rank_pro:d" + "en" "{grey}Global: {default}{time}{grey} ({yellow}{tp_count_text}{grey}) [#{default}{rank}{grey}/{default}{max_rank}{grey} {yellow}Overall{grey} / {purple}{points}{grey} pts] | {default}{time_pro}{grey} [#{default}{rank_pro}{grey}/{default}{max_rank_pro}{grey} {blue}PRO{grey} / {purple}{points_pro}{grey} pts]" + } + "PB Time - Overall Rankless (Global)" + { + // Global: 12.34 (5 TPs) [Overall] + "#format" "time:s,tp_count_text:s" + "en" "{grey}Global: {default}{time}{grey} ({yellow}{tp_count_text}{grey}) [{yellow}Overall{grey}]" + } + "PB Time - Combined Rankless (Global)" + { + // Global: 12.34 [Overall/PRO] + "#format" "time:s" + "en" "{grey}Global: {default}{time}{grey} [{yellow}Overall{grey}/{blue}PRO{grey}]" + } + "PB Time - Split Rankless (Global)" + { + // Global: 12.34 (5 TPs) [Overall] | 23.45 [PRO] + "#format" "time:s,tp_count_text:s,time_pro:s" + "en" "{grey}Global: {default}{time}{grey} ({yellow}{tp_count_text}{grey}) [{yellow}Overall{grey}] | {default}{time_pro}{grey} [{blue}PRO{grey}]" + } "PB Request - Failed (Generic)" { "en" "{darkred}Failed to retrieve personal best stats." diff --git a/translations/cs2kz-timer-getrecords.phrases.txt b/translations/cs2kz-timer-toprecord.phrases.txt similarity index 97% rename from translations/cs2kz-timer-getrecords.phrases.txt rename to translations/cs2kz-timer-toprecord.phrases.txt index 3910f24a..d29f7844 100644 --- a/translations/cs2kz-timer-getrecords.phrases.txt +++ b/translations/cs2kz-timer-toprecord.phrases.txt @@ -17,7 +17,13 @@ "de" "{grey}Serverrekorde auf {default}{map_name}{grey} ({default}{course_name}{grey}) [{purple}{mode_name}{grey}]" "ko" "{grey}서버 기록: {default}{map_name}{grey} ({default}{course_name}{grey}) [{purple}{mode_name}{grey}]" } - "SR - No Times" + "WR Header" + { + // Global Records on kz_map (Main) [VNL] + "#format" "map_name:s,course_name:s,mode_name:s" + "en" "{grey}Global Records on {default}{map_name}{grey} ({default}{course_name}{grey}) [{purple}{mode_name}{grey}]" + } + "No Times" { "en" "{grey}No times were found!" "chi" "{grey}未找到任何记录!" @@ -32,7 +38,7 @@ "de" "{grey}Es wurden keine Zeiten gefunden!" "ko" "{grey}기록이 없습니다!" } - "SR - Overall" + "Top Record - Overall" { // KZ | Overall Record: 01:23.45 (5 TP) by Bill "#format" "time:s,player_name:s,tp_count_text:s" @@ -49,7 +55,7 @@ "de" "{yellow}Gesamt{grey}: {default}{time}{grey} von {lime}{player_name}{grey} ({yellow}{tp_count_text}{grey})" "ko" "{yellow}전체{grey}: {default}{time}{grey} {lime}{player_name}{grey}님 ({yellow}{tp_count_text}{grey})" } - "SR - Combined" + "Top Record - Combined" { // KZ | Overall/PRO Record: 01:23.45 by Bill "#format" "time:s,player_name:s" @@ -66,7 +72,7 @@ "de" "{yellow}Gesamt/{blue}PRO{grey}: {default}{time}{grey} von {lime}{player_name}{grey}" "ko" "{yellow}전체/{blue}PRO{grey}: {default}{time}{grey} {lime}{player_name}{grey}님" } - "SR - PRO" + "Top Record - PRO" { // KZ | PRO Record: 23.45 by Player "#format" "time_pro:s,player_name_pro:s"