From 56c27b2b4c51297becc825a268a501f3227303c7 Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Wed, 11 May 2022 12:31:11 +1000 Subject: [PATCH 1/7] Add extra div cutoffs and refactor UpdateFromAPI into a coroutine + add API and GameInfo classes --- info.toml | 3 +- src/COTDStats.as | 253 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 199 insertions(+), 57 deletions(-) diff --git a/info.toml b/info.toml index 69e5a14..2b69310 100644 --- a/info.toml +++ b/info.toml @@ -8,4 +8,5 @@ siteid = 162 [script] dependencies = [ "NadeoServices" ] -imports = [ "Permissions.as", "Time.as" ] \ No newline at end of file +imports = [ "Permissions.as", "Time.as" ] +# defines = [ "DEV" ] diff --git a/src/COTDStats.as b/src/COTDStats.as index b1e5607..deff258 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -7,6 +7,13 @@ bool showDivDelta = false; [Setting category="Display Settings" name="Show lower bound" description="Disabled by default"] bool showLowerBound = false; +[Setting category="Display Settings" name="Show additional division cutoffs" description="Disabled by default"] +bool showExtraDivs = false; + +[Setting category="Display Settings" name="Locator mode" description="Shows the window outside COTD so you can drag it around"] +bool locatorMode = false; + + class DivTime { string div; int time; @@ -34,8 +41,8 @@ class DivTime { } } -// Global variables -string cotdName = ""; +// Global variables +string cotdName = ""; int totalPlayers = 0; int curdiv = 0; @@ -44,23 +51,29 @@ DivTime@ nextdiv = DivTime("--", 9999999, "\\$fff"); DivTime@ lowerbounddiv = DivTime("--", 9999999, "\\$fff", !showLowerBound); DivTime@ pb = DivTime("--", 9999999, "\\$0ff", false); -array divs = { pb, div1, nextdiv, lowerbounddiv }; +DivTime@ nextnextdiv = DivTime("--", 9999999, "\\$fff"); +DivTime@ belowdiv = DivTime("--", 9999999, "\\$fff"); + +// to track when pb changes +DivTime@ lastPb = DivTime("--", 9999999, "\\$0ff", false); +bool flag_api_haveNewPb = false; + +array divs = { pb, div1, nextnextdiv, nextdiv, lowerbounddiv, belowdiv }; + +GameInfo@ gameInfo; void Render() { #if TMNEXT - auto app = cast(GetApp()); - auto network = cast(app.Network); - auto server_info = cast(network.ServerInfo); + bool showWindow = windowVisible && gameInfo !is null && gameInfo.IsCotd(); + if (showWindow || locatorMode) { - if (windowVisible && app.CurrentPlayground !is null && server_info.CurGameModeStr == "TM_TimeAttackDaily_Online") { - int windowFlags = UI::WindowFlags::NoTitleBar | UI::WindowFlags::NoCollapse | UI::WindowFlags::AlwaysAutoResize | UI::WindowFlags::NoDocking; if (!UI::IsOverlayShown()) { windowFlags |= UI::WindowFlags::NoInputs; } - UI::Begin("COTD Qualifying", windowFlags); + UI::Begin("COTD Qualifying 2", windowFlags); UI::PushStyleVar(UI::StyleVar::ItemSpacing, vec2(0, 0)); UI::Dummy(vec2(0, 0)); @@ -114,9 +127,9 @@ void Render() { } UI::EndTable(); - + UI::EndGroup(); - + UI::End(); } @@ -131,22 +144,13 @@ void RenderMenu() { #endif } -Json::Value FetchEndpoint(const string &in route) { - auto req = NadeoServices::Get("NadeoClubServices", route); - req.Start(); - while(!req.Finished()) { - yield(); - } - return Json::Parse(req.String()); -} - void ReadHUD() { auto app = cast(GetApp()); auto network = cast(app.Network); auto server_info = cast(network.ServerInfo); while (true) { - if (network.ClientManiaAppPlayground !is null && network.ClientManiaAppPlayground.Playground !is null && server_info.CurGameModeStr == "TM_TimeAttackDaily_Online") { + if (gameInfo.IsCotd()) { auto uilayers = network.ClientManiaAppPlayground.UILayers; for (uint i = 0; i < uilayers.Length; i++) { if (uilayers[i].LocalPage.MainFrame !is null) { @@ -166,6 +170,10 @@ void ReadHUD() { pb.div = "" + curdiv; pb.time = Time::ParseRelativeTime(dtime); + // check if we have a new pb + flag_api_haveNewPb = pb.time != lastPb.time; + lastPb.time = pb.time; + if (curdiv > Text::ParseInt(lowerbounddiv.div) || pb.time > lowerbounddiv.time) { lowerbounddiv.hidden = true; } @@ -174,29 +182,82 @@ void ReadHUD() { } } divs.SortAsc(); - sleep(500); + sleep(100); } } void Main() { #if TMNEXT - auto app = cast(GetApp()); - auto network = cast(app.Network); - auto server_info = cast(network.ServerInfo); - - NadeoServices::AddAudience("NadeoClubServices"); - string compUrl = NadeoServices::BaseURLCompetition(); + @gameInfo = GameInfo(); // Use Co-routine to read HUD faster than API calls startnew(ReadHUD); + startnew(UpdateFromAPI); +#endif +} + + +CTrackMania@ GetTmApp() { + return cast(GetApp()); +} + +// todo: check if the div is full or not -- will get wrong times otherwise +void SetDivCutoff(CotdApi@ api, DivTime@&in divObj, int cid, string mid, int div) { + // Only do this if div > 1 b/c we're already fetching div1 separately, + if (div > 1) { + // and don't request div cutoff times if the div isn't full + if (div * 64 <= totalPlayers) { + auto res = api.GetCutoffForDiv(cid, mid, div); + divObj.time = (res.Length > 0) ? res[0]["time"] : 0; + } else { // when the division isn't full + divObj.time = 0; + } + divObj.div = "" + div; + divObj.hidden = false; + } else { + divObj.hidden = true; + } +} + +void UpdateFromAPI() { + auto api = CotdApi(); + + auto app = GetTmApp(); + auto network = cast(app.Network); + auto server_info = cast(network.ServerInfo); + int challengeid = 0; - while(true) { + // loop and sleep params/vars + int msBetweenCalls = 15000; + int sleepPerLoop = 100; + + // use loopCounter to only check API once every 15s + int loopCounter = 0; + int maxLoopCounter = msBetweenCalls / sleepPerLoop; + + // vars used every loop + bool canCallAPI; + bool checkThisLoop; + bool hasPlayerId; + string mapid; + + while (true) { + canCallAPI = Permissions::PlayOnlineCompetition() && gameInfo.IsCotd(); + + if (flag_api_haveNewPb) { + loopCounter = 0; // reset the counter to call the API now and also prevent us excessively calling the API + flag_api_haveNewPb = false; + } + + mapid = gameInfo.MapId(); - if (Permissions::PlayOnlineCompetition() && network.ClientManiaAppPlayground !is null && network.ClientManiaAppPlayground.Playground !is null && network.ClientManiaAppPlayground.Playground.Map !is null && server_info.CurGameModeStr == "TM_TimeAttackDaily_Online") { - - string mapid = network.ClientManiaAppPlayground.Playground.Map.MapInfo.MapUid; + checkThisLoop = canCallAPI && mapid != "" && loopCounter == 0; + + if (checkThisLoop) { + + // trace("mapid:" + mapid); while (!NadeoServices::IsAuthenticated("NadeoClubServices")) { yield(); @@ -204,52 +265,132 @@ void Main() { // We only need this info once at the beginning of the COTD if (challengeid == 0) { - auto matchstatus = FetchEndpoint(compUrl + "/api/daily-cup/current"); + auto matchstatus = api.GetCotdStatus(); string challengeName = matchstatus["challenge"]["name"]; cotdName = "COTD " + challengeName.SubStr(15, 13); challengeid = matchstatus["challenge"]["id"]; } - // Use this to obtain "real-time" number of players registered in the COTD + // Use this to obtain "real-time" number of players registered in the COTD // (could've also used this to determine player rank and score, but for better experience we get those from HUD instead) - auto rank = FetchEndpoint(compUrl + "/api/challenges/" + challengeid + "/records/maps/" + mapid + "/players?players[]=" + network.PlayerInfo.WebServicesUserId); + auto rank = api.GetPlayersRank(challengeid, mapid, network.PlayerInfo.WebServicesUserId); totalPlayers = rank["cardinal"]; // Fetch Div 1 cutoff record - auto leadDiv1 = FetchEndpoint(compUrl + "/api/challenges/" + challengeid + "/records/maps/" + mapid + "?length=1&offset=63"); + auto leadDiv1 = api.GetCutoffForDiv(challengeid, mapid, 1); if (leadDiv1.Length > 0) { div1.time = leadDiv1[0]["time"]; } - if (showLowerBound && curdiv > 1) { - auto lowerBound = FetchEndpoint(compUrl + "/api/challenges/" + challengeid + "/records/maps/" + mapid + "?length=1&offset=" + (64 * (curdiv) - 1)); - if (lowerBound.Length > 0) { - lowerbounddiv.time = lowerBound[0]["time"]; - } - lowerbounddiv.div = "" + curdiv; - lowerbounddiv.hidden = false; + if (showLowerBound) { + SetDivCutoff(api, lowerbounddiv, challengeid, mapid, curdiv); } else { lowerbounddiv.hidden = true; } - // Fetch next best Div cutoff record only if we are higher than Div 2 - if (curdiv > 2) { - auto leadNextBest = FetchEndpoint(compUrl + "/api/challenges/" + challengeid + "/records/maps/" + mapid + "?length=1&offset=" + (64 * (curdiv - 1) - 1)); - if (leadNextBest.Length > 0) { - nextdiv.time = leadNextBest[0]["time"]; - } - nextdiv.div = "" + (curdiv-1); - nextdiv.hidden = false; + SetDivCutoff(api, nextdiv, challengeid, mapid, curdiv - 1); + + if (showExtraDivs) { + SetDivCutoff(api, nextnextdiv, challengeid, mapid, curdiv - 2); + SetDivCutoff(api, belowdiv, challengeid, mapid, curdiv + 1); } else { - nextdiv.hidden = true; + nextnextdiv.hidden = true; + belowdiv.hidden = true; } } else { - // Reset challenge id once COTD ends - challengeid = 0; + if (!canCallAPI) { + // Reset challenge id once COTD ends + challengeid = 0; + } } - sleep(15000); + + loopCounter = (loopCounter + 1) % maxLoopCounter; + sleep(sleepPerLoop); + } +} + +Json::Value FetchEndpoint(const string &in route) { + auto req = NadeoServices::Get("NadeoClubServices", route); + req.Start(); + while(!req.Finished()) { + yield(); + } + return Json::Parse(req.String()); +} + +class CotdApi { + string compUrl; + CTrackMania@ app; // = GetTmApp(); + CTrackManiaNetwork@ network; // = cast(app.Network); + CTrackManiaNetworkServerInfo@ server_info; // = cast(network.ServerInfo); + + CotdApi() { + NadeoServices::AddAudience("NadeoClubServices"); + compUrl = NadeoServices::BaseURLCompetition(); + + @app = GetTmApp(); + @network = cast(app.Network); + @server_info = cast(network.ServerInfo); } + + Json::Value CallApiPath(string path) { + if (path.Length <= 0 || !path.StartsWith("/")) { + warn("[CallApiPath] API Paths should start with '/'!"); + path = "/" + path; + } + trace("Requesting: " + compUrl + path); + return FetchEndpoint(compUrl + path); + } + + Json::Value GetCotdStatus() { + return CallApiPath("/api/daily-cup/current"); + } + + Json::Value GetCutoffForDiv(int challengeid, string mapid, int div) { + // the last position in the div + int offset = div * 64 - 1; + return CallApiPath("/api/challenges/" + challengeid + "/records/maps/" + mapid + "?length=1&offset=" + offset); + } + + Json::Value GetPlayersRank(int challengeid, string mapid, string userId) { + return CallApiPath("/api/challenges/" + challengeid + "/records/maps/" + mapid + "/players?players[]=" + userId); + } +} + +class GameInfo { + CTrackMania@ app; // = GetTmApp(); + CTrackManiaNetwork@ network; // = cast(app.Network); + CTrackManiaNetworkServerInfo@ server_info; // = cast(network.ServerInfo); + + GameInfo() { + @app = GetTmApp(); + @network = cast(app.Network); + @server_info = cast(network.ServerInfo); + } + + bool IsCotd() { + return network.ClientManiaAppPlayground !is null + && network.ClientManiaAppPlayground.Playground !is null + && server_info.CurGameModeStr == "TM_TimeAttackDaily_Online"; + } + + string MapId() { + auto rm = app.RootMap; +#if DEV + int now = Time::get_Now(); + if ((now % 1000) < 100) { + trace("[MapId()," + now + "] rm is null: " + (rm is null)); + if (rm !is null) { + trace("[MapId()," + now + "] rm.IdName: " + rm.IdName); + } + } #endif -} \ No newline at end of file + return (rm is null) ? "" : rm.IdName; + // if (rm is null) { + // return ""; + // } + // return rm.IdName; + } +} From b2a1e0320dc8c83fb420fed6be7e54ab63601841 Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Wed, 11 May 2022 12:39:14 +1000 Subject: [PATCH 2/7] fix window flag and add todo re references in GameInfo --- src/COTDStats.as | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/COTDStats.as b/src/COTDStats.as index deff258..d3702d4 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -73,7 +73,12 @@ void Render() { windowFlags |= UI::WindowFlags::NoInputs; } - UI::Begin("COTD Qualifying 2", windowFlags); +// We need a different window name to run multiple copies of the plugin (otherwise everything gets drawn in one window) +#if DEV + UI::Begin("COTD Qualifying (Dev)", windowFlags); +#else + UI::Begin("COTD Qualifying", windowFlags); +#endif UI::PushStyleVar(UI::StyleVar::ItemSpacing, vec2(0, 0)); UI::Dummy(vec2(0, 0)); @@ -361,6 +366,7 @@ class CotdApi { class GameInfo { CTrackMania@ app; // = GetTmApp(); + // todo: these references might change -- refactor to getter functions or something CTrackManiaNetwork@ network; // = cast(app.Network); CTrackManiaNetworkServerInfo@ server_info; // = cast(network.ServerInfo); From a0287e20d8db7aa96f879e6842e533950d38aba6 Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Wed, 11 May 2022 19:49:35 +1000 Subject: [PATCH 3/7] Fix bug WRT sorting divisions --- src/COTDStats.as | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/COTDStats.as b/src/COTDStats.as index d3702d4..44e7a9e 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -32,7 +32,7 @@ class DivTime { } string TimeString() { - return this.style + (((this.time > 0) && (this.time != 9999999)) ? Time::Format(this.time) : "-:--.---") + "\\$z"; + return this.style + (((this.time > 0) && (this.time <= 999999)) ? Time::Format(this.time) : "-:--.---") + "\\$z"; } int opCmp(DivTime@ other) { @@ -216,7 +216,7 @@ void SetDivCutoff(CotdApi@ api, DivTime@&in divObj, int cid, string mid, int div auto res = api.GetCutoffForDiv(cid, mid, div); divObj.time = (res.Length > 0) ? res[0]["time"] : 0; } else { // when the division isn't full - divObj.time = 0; + divObj.time = 9999999; } divObj.div = "" + div; divObj.hidden = false; From 9a84ea34db846db193a4f0990bac0848631e891d Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Wed, 11 May 2022 19:55:00 +1000 Subject: [PATCH 4/7] refactor handling of v large div times --- src/COTDStats.as | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/COTDStats.as b/src/COTDStats.as index 44e7a9e..9fecb88 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -13,6 +13,11 @@ bool showExtraDivs = false; [Setting category="Display Settings" name="Locator mode" description="Shows the window outside COTD so you can drag it around"] bool locatorMode = false; +const int MAX_DIV_TIME = 9999999; +// MAX_DIV_TIME of 9999999 is 10k seconds. +// We can safely `>> 3` is roughly division by 8 (which is still 1k+ seconds). +// Since COTD is at most ~120s, we can safely assume values for .time that are greater than (MAX_DIV_TIME >> 3) are dummy times. +// So we use `MAX_DIV_TIME >> 3` as a comparison value. class DivTime { string div; @@ -20,7 +25,7 @@ class DivTime { string style; bool hidden; - DivTime(string div = "--", int time = 9999999, string style = "\\$fff", bool hidden = true) { + DivTime(string div = "--", int time = MAX_DIV_TIME, string style = "\\$fff", bool hidden = true) { this.div = div; this.time = time; this.style = style; @@ -32,7 +37,7 @@ class DivTime { } string TimeString() { - return this.style + (((this.time > 0) && (this.time <= 999999)) ? Time::Format(this.time) : "-:--.---") + "\\$z"; + return this.style + (((this.time > 0) && (this.time < MAX_DIV_TIME >> 3)) ? Time::Format(this.time) : "-:--.---") + "\\$z"; } int opCmp(DivTime@ other) { @@ -47,15 +52,15 @@ int totalPlayers = 0; int curdiv = 0; DivTime@ div1 = DivTime("1", 0, "\\$fff", false); -DivTime@ nextdiv = DivTime("--", 9999999, "\\$fff"); -DivTime@ lowerbounddiv = DivTime("--", 9999999, "\\$fff", !showLowerBound); -DivTime@ pb = DivTime("--", 9999999, "\\$0ff", false); +DivTime@ nextdiv = DivTime("--", MAX_DIV_TIME, "\\$fff"); +DivTime@ lowerbounddiv = DivTime("--", MAX_DIV_TIME, "\\$fff", !showLowerBound); +DivTime@ pb = DivTime("--", MAX_DIV_TIME, "\\$0ff", false); -DivTime@ nextnextdiv = DivTime("--", 9999999, "\\$fff"); -DivTime@ belowdiv = DivTime("--", 9999999, "\\$fff"); +DivTime@ nextnextdiv = DivTime("--", MAX_DIV_TIME, "\\$fff"); +DivTime@ belowdiv = DivTime("--", MAX_DIV_TIME, "\\$fff"); // to track when pb changes -DivTime@ lastPb = DivTime("--", 9999999, "\\$0ff", false); +DivTime@ lastPb = DivTime("--", MAX_DIV_TIME, "\\$0ff", false); bool flag_api_haveNewPb = false; array divs = { pb, div1, nextnextdiv, nextdiv, lowerbounddiv, belowdiv }; @@ -125,7 +130,7 @@ void Render() { if (showDivDelta && !divs[i].hidden) { UI::TableNextColumn(); int deltaTime = pb.time - divs[i].time; - if (deltaTime != 0 && pb.time != 9999999 && divs[i].time != 9999999) { + if (deltaTime != 0 && pb.time < MAX_DIV_TIME >> 3 && divs[i].time < MAX_DIV_TIME >> 3) { UI::Text(((deltaTime >= 0) ? "\\$F70+" : "\\$26F-") + Time::Format(Math::Abs(deltaTime)) + "\\$z"); } } @@ -216,7 +221,7 @@ void SetDivCutoff(CotdApi@ api, DivTime@&in divObj, int cid, string mid, int div auto res = api.GetCutoffForDiv(cid, mid, div); divObj.time = (res.Length > 0) ? res[0]["time"] : 0; } else { // when the division isn't full - divObj.time = 9999999; + divObj.time = MAX_DIV_TIME; } divObj.div = "" + div; divObj.hidden = false; From af8e670b45de9f9a71ed6f639f4809205cd7cd67 Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Wed, 11 May 2022 20:09:52 +1000 Subject: [PATCH 5/7] refactor GameInfo and some chores. also fix bug wrt showing `belowdiv` when there aren't enough players --- src/COTDStats.as | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/COTDStats.as b/src/COTDStats.as index 9fecb88..197357b 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -219,7 +219,7 @@ void SetDivCutoff(CotdApi@ api, DivTime@&in divObj, int cid, string mid, int div // and don't request div cutoff times if the div isn't full if (div * 64 <= totalPlayers) { auto res = api.GetCutoffForDiv(cid, mid, div); - divObj.time = (res.Length > 0) ? res[0]["time"] : 0; + divObj.time = (res.Length > 0) ? res[0]["time"] : MAX_DIV_TIME; } else { // when the division isn't full divObj.time = MAX_DIV_TIME; } @@ -309,6 +309,9 @@ void UpdateFromAPI() { belowdiv.hidden = true; } + if (curdiv + 1 > Math::Ceil(totalPlayers / 64.)) { + belowdiv.hidden = true; + } } else { if (!canCallAPI) { // Reset challenge id once COTD ends @@ -371,17 +374,22 @@ class CotdApi { class GameInfo { CTrackMania@ app; // = GetTmApp(); - // todo: these references might change -- refactor to getter functions or something - CTrackManiaNetwork@ network; // = cast(app.Network); - CTrackManiaNetworkServerInfo@ server_info; // = cast(network.ServerInfo); GameInfo() { @app = GetTmApp(); - @network = cast(app.Network); - @server_info = cast(network.ServerInfo); + } + + CTrackManiaNetwork@ GetNetwork() { + return cast(app.Network); + } + + CTrackManiaNetworkServerInfo@ GetServerInfo() { + return cast(network.ServerInfo); } bool IsCotd() { + auto network = GetNetwork(); + auto server_info = GetServerInfo(); return network.ClientManiaAppPlayground !is null && network.ClientManiaAppPlayground.Playground !is null && server_info.CurGameModeStr == "TM_TimeAttackDaily_Online"; @@ -399,9 +407,5 @@ class GameInfo { } #endif return (rm is null) ? "" : rm.IdName; - // if (rm is null) { - // return ""; - // } - // return rm.IdName; } } From 061360b189ba17a7defe2ac04635ba3c075c8864 Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Wed, 18 May 2022 19:16:37 +1000 Subject: [PATCH 6/7] add time since update --- src/COTDStats.as | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/COTDStats.as b/src/COTDStats.as index 197357b..e628b15 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -19,6 +19,8 @@ const int MAX_DIV_TIME = 9999999; // Since COTD is at most ~120s, we can safely assume values for .time that are greater than (MAX_DIV_TIME >> 3) are dummy times. // So we use `MAX_DIV_TIME >> 3` as a comparison value. +int lastUpdated = 0; + class DivTime { string div; int time; @@ -97,6 +99,9 @@ void Render() { UI::Text(cotdName); UI::TableNextRow(); UI::TableNextColumn(); + UI::Text("Updated: " + ((Time::get_Now() - lastUpdated) / 1000.) + " s ago"); + UI::TableNextRow(); + UI::TableNextColumn(); UI::Text("\\$aaa" + totalPlayers + " players (" + Math::Ceil(totalPlayers/64.0) + " divs)\\$z"); UI::EndTable(); @@ -266,7 +271,7 @@ void UpdateFromAPI() { checkThisLoop = canCallAPI && mapid != "" && loopCounter == 0; if (checkThisLoop) { - + lastUpdated = Time::get_Now(); // trace("mapid:" + mapid); while (!NadeoServices::IsAuthenticated("NadeoClubServices")) { @@ -384,7 +389,7 @@ class GameInfo { } CTrackManiaNetworkServerInfo@ GetServerInfo() { - return cast(network.ServerInfo); + return cast(GetNetwork().ServerInfo); } bool IsCotd() { From a6e5d67c1f0d20f1e39825d786d1ecad08e2383b Mon Sep 17 00:00:00 2001 From: Max Kaye Date: Thu, 19 May 2022 11:59:37 +1000 Subject: [PATCH 7/7] format float to avoid excessive decimal places --- src/COTDStats.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/COTDStats.as b/src/COTDStats.as index e628b15..af32184 100644 --- a/src/COTDStats.as +++ b/src/COTDStats.as @@ -99,7 +99,7 @@ void Render() { UI::Text(cotdName); UI::TableNextRow(); UI::TableNextColumn(); - UI::Text("Updated: " + ((Time::get_Now() - lastUpdated) / 1000.) + " s ago"); + UI::Text("Updated: " + Text::Format("%.1f", (Time::get_Now() - lastUpdated) / 1000.) + " s ago"); UI::TableNextRow(); UI::TableNextColumn(); UI::Text("\\$aaa" + totalPlayers + " players (" + Math::Ceil(totalPlayers/64.0) + " divs)\\$z");